Skip to content

Commit fc40427

Browse files
committed
feat: Use tag name directly instead of ID
1 parent e8dfefe commit fc40427

File tree

5 files changed

+48
-48
lines changed

5 files changed

+48
-48
lines changed

src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import app.revanced.api.configuration.schema.ApiResponseAnnouncement
66
import app.revanced.api.configuration.schema.ApiResponseAnnouncementId
77
import kotlinx.coroutines.Dispatchers
88
import kotlinx.coroutines.runBlocking
9-
import kotlinx.datetime.*
9+
import kotlinx.datetime.toKotlinLocalDateTime
1010
import org.jetbrains.exposed.dao.IntEntity
1111
import org.jetbrains.exposed.dao.IntEntityClass
1212
import org.jetbrains.exposed.dao.id.EntityID
@@ -20,7 +20,7 @@ import java.time.LocalDateTime
2020
internal class AnnouncementRepository(private val database: Database) {
2121
// This is better than doing a maxByOrNull { it.id } on every request.
2222
private var latestAnnouncement: Announcement? = null
23-
private val latestAnnouncementByTag = mutableMapOf<Int, Announcement>()
23+
private val latestAnnouncementByTag = mutableMapOf<String, Announcement>()
2424

2525
init {
2626
runBlocking {
@@ -40,22 +40,23 @@ internal class AnnouncementRepository(private val database: Database) {
4040
private fun initializeLatestAnnouncements() {
4141
latestAnnouncement = Announcement.all().orderBy(Announcements.id to SortOrder.DESC).firstOrNull()
4242

43-
Tag.all().map { it.id.value }.forEach(::updateLatestAnnouncementForTag)
43+
Tag.all().map { it.name }.forEach(::updateLatestAnnouncementForTag)
4444
}
4545

4646
private fun updateLatestAnnouncement(new: Announcement) {
4747
if (latestAnnouncement == null || latestAnnouncement!!.id.value <= new.id.value) {
4848
latestAnnouncement = new
49-
new.tags.forEach { tag -> latestAnnouncementByTag[tag.id.value] = new }
49+
new.tags.forEach { tag -> latestAnnouncementByTag[tag.name] = new }
5050
}
5151
}
5252

53-
private fun updateLatestAnnouncementForTag(tag: Int) {
54-
val latestAnnouncementForTag = AnnouncementTags.select(AnnouncementTags.announcement)
55-
.where { AnnouncementTags.tag eq tag }
56-
.map { it[AnnouncementTags.announcement] }
57-
.mapNotNull { Announcement.findById(it) }
58-
.maxByOrNull { it.id }
53+
private fun updateLatestAnnouncementForTag(tag: String) {
54+
val latestAnnouncementForTag = Tags.innerJoin(AnnouncementTags)
55+
.select(AnnouncementTags.announcement)
56+
.where { Tags.name eq tag }
57+
.orderBy(AnnouncementTags.announcement to SortOrder.DESC)
58+
.limit(1)
59+
.firstNotNullOfOrNull { Announcement.findById(it[AnnouncementTags.announcement]) }
5960

6061
latestAnnouncementForTag?.let { latestAnnouncementByTag[tag] = it }
6162
}
@@ -64,16 +65,16 @@ internal class AnnouncementRepository(private val database: Database) {
6465
latestAnnouncement.toApiResponseAnnouncement()
6566
}
6667

67-
suspend fun latest(tags: Set<Int>) = transaction {
68+
suspend fun latest(tags: Set<String>) = transaction {
6869
tags.mapNotNull { tag -> latestAnnouncementByTag[tag] }.toApiAnnouncement()
6970
}
7071

7172
fun latestId() = latestAnnouncement?.id?.value.toApiResponseAnnouncementId()
7273

73-
fun latestId(tags: Set<Int>) =
74+
fun latestId(tags: Set<String>) =
7475
tags.map { tag -> latestAnnouncementByTag[tag]?.id?.value }.toApiResponseAnnouncementId()
7576

76-
suspend fun paged(cursor: Int, count: Int, tags: Set<Int>?, archived: Boolean) = transaction {
77+
suspend fun paged(cursor: Int, count: Int, tags: Set<String>?, archived: Boolean) = transaction {
7778
Announcement.find {
7879
fun idLessEq() = Announcements.id lessEq cursor
7980
fun archivedAtIsNull() = Announcements.archivedAt.isNull()
@@ -92,12 +93,12 @@ internal class AnnouncementRepository(private val database: Database) {
9293
archivedAtIsNull() or archivedAtGreaterNow()
9394
}
9495

95-
fun hasTags() = tags.mapNotNull { Tag.findById(it)?.id }.let { tags ->
96-
Announcements.id inSubQuery Announcements.leftJoin(AnnouncementTags)
96+
fun hasTags() = Announcements.id inSubQuery (
97+
Tags.innerJoin(AnnouncementTags)
9798
.select(AnnouncementTags.announcement)
98-
.where { AnnouncementTags.tag inList tags }
99+
.where { Tags.name inList tags }
99100
.withDistinct()
100-
}
101+
)
101102

102103
idLessEq() and archivedAtGreaterOrNullOrTrue() and hasTags()
103104
}
@@ -165,7 +166,7 @@ internal class AnnouncementRepository(private val database: Database) {
165166
// Delete the tag if no other announcements are referencing it.
166167
// One count means that the announcement is the only one referencing the tag.
167168
announcement.tags.filter { tag -> tag.announcements.count() == 1L }.forEach { tag ->
168-
latestAnnouncementByTag -= tag.id.value
169+
latestAnnouncementByTag -= tag.name
169170
tag.delete()
170171
}
171172

@@ -250,7 +251,7 @@ internal class AnnouncementRepository(private val database: Database) {
250251
title,
251252
content,
252253
attachments.map { it.url },
253-
tags.map { it.id.value },
254+
tags.map { it.name },
254255
createdAt,
255256
archivedAt,
256257
level,
@@ -259,7 +260,7 @@ internal class AnnouncementRepository(private val database: Database) {
259260

260261
private fun Iterable<Announcement>.toApiAnnouncement() = map { it.toApiResponseAnnouncement()!! }
261262

262-
private fun Iterable<Tag>.toApiTag() = map { ApiAnnouncementTag(it.id.value, it.name) }
263+
private fun Iterable<Tag>.toApiTag() = map { ApiAnnouncementTag(it.name) }
263264

264265
private fun Int?.toApiResponseAnnouncementId() = this?.let { ApiResponseAnnouncementId(this) }
265266

src/main/kotlin/app/revanced/api/configuration/routes/Announcements.kt

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import app.revanced.api.configuration.schema.ApiAnnouncement
88
import app.revanced.api.configuration.schema.ApiResponseAnnouncement
99
import app.revanced.api.configuration.schema.ApiResponseAnnouncementId
1010
import app.revanced.api.configuration.services.AnnouncementService
11-
import io.bkbn.kompendium.core.metadata.*
11+
import io.bkbn.kompendium.core.metadata.DeleteInfo
12+
import io.bkbn.kompendium.core.metadata.GetInfo
13+
import io.bkbn.kompendium.core.metadata.PatchInfo
14+
import io.bkbn.kompendium.core.metadata.PostInfo
1215
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
1316
import io.bkbn.kompendium.oas.payload.Parameter
1417
import io.ktor.http.*
@@ -35,7 +38,7 @@ internal fun Route.announcementsRoute() = route("announcements") {
3538
val tags = call.parameters.getAll("tag")
3639
val archived = call.parameters["archived"]?.toBoolean() ?: true
3740

38-
call.respond(announcementService.paged(cursor, count, tags?.map { it.toInt() }?.toSet(), archived))
41+
call.respond(announcementService.paged(cursor, count, tags?.toSet(), archived))
3942
}
4043
}
4144

@@ -55,7 +58,7 @@ internal fun Route.announcementsRoute() = route("announcements") {
5558
val tags = call.parameters.getAll("tag")
5659

5760
if (tags?.isNotEmpty() == true) {
58-
call.respond(announcementService.latest(tags.map { it.toInt() }.toSet()))
61+
call.respond(announcementService.latest(tags.toSet()))
5962
} else {
6063
call.respondOrNotFound(announcementService.latest())
6164
}
@@ -68,7 +71,7 @@ internal fun Route.announcementsRoute() = route("announcements") {
6871
val tags = call.parameters.getAll("tag")
6972

7073
if (tags?.isNotEmpty() == true) {
71-
call.respond(announcementService.latestId(tags.map { it.toInt() }.toSet()))
74+
call.respond(announcementService.latestId(tags.toSet()))
7275
} else {
7376
call.respondOrNotFound(announcementService.latestId())
7477
}
@@ -146,8 +149,8 @@ private fun Route.installAnnouncementsRouteDocumentation() = installNotarizedRou
146149
Parameter(
147150
name = "tag",
148151
`in` = Parameter.Location.query,
149-
schema = TypeDefinition.INT,
150-
description = "The tag IDs to filter the announcements by. Default is all tags",
152+
schema = TypeDefinition.STRING,
153+
description = "The tags to filter the announcements by. Default is all tags",
151154
required = false,
152155
),
153156
Parameter(
@@ -193,8 +196,8 @@ private fun Route.installAnnouncementsLatestRouteDocumentation() = installNotari
193196
Parameter(
194197
name = "tag",
195198
`in` = Parameter.Location.query,
196-
schema = TypeDefinition.INT,
197-
description = "The tag IDs to filter the latest announcements by",
199+
schema = TypeDefinition.STRING,
200+
description = "The tags to filter the latest announcements by",
198201
required = false,
199202
),
200203
)
@@ -228,8 +231,8 @@ private fun Route.installAnnouncementsLatestIdRouteDocumentation() = installNota
228231
Parameter(
229232
name = "tag",
230233
`in` = Parameter.Location.query,
231-
schema = TypeDefinition.INT,
232-
description = "The tag IDs to filter the latest announcements by",
234+
schema = TypeDefinition.STRING,
235+
description = "The tags to filter the latest announcements by",
233236
required = false,
234237
),
235238
)

src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class ApiResponseAnnouncement(
7676
// Using a list instead of a set because set semantics are unnecessary here.
7777
val attachments: List<String> = emptyList(),
7878
// Using a list instead of a set because set semantics are unnecessary here.
79-
val tags: List<Int> = emptyList(),
79+
val tags: List<String> = emptyList(),
8080
val createdAt: LocalDateTime,
8181
val archivedAt: LocalDateTime? = null,
8282
val level: Int = 0,
@@ -94,7 +94,6 @@ class ApiAnnouncementArchivedAt(
9494

9595
@Serializable
9696
class ApiAnnouncementTag(
97-
val id: Int,
9897
val name: String,
9998
)
10099

src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ import app.revanced.api.configuration.schema.ApiAnnouncement
66
internal class AnnouncementService(
77
private val announcementRepository: AnnouncementRepository,
88
) {
9-
suspend fun latest(tags: Set<Int>) = announcementRepository.latest(tags)
9+
suspend fun latest(tags: Set<String>) = announcementRepository.latest(tags)
1010

1111
suspend fun latest() = announcementRepository.latest()
1212

13-
fun latestId(tags: Set<Int>) = announcementRepository.latestId(tags)
13+
fun latestId(tags: Set<String>) = announcementRepository.latestId(tags)
1414

1515
fun latestId() = announcementRepository.latestId()
1616

17-
suspend fun paged(cursor: Int, limit: Int, tags: Set<Int>?, archived: Boolean) =
17+
suspend fun paged(cursor: Int, limit: Int, tags: Set<String>?, archived: Boolean) =
1818
announcementRepository.paged(cursor, limit, tags, archived)
1919

2020
suspend fun get(id: Int) = announcementRepository.get(id)

src/test/kotlin/app/revanced/api/configuration/services/AnnouncementServiceTest.kt

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import app.revanced.api.configuration.schema.ApiAnnouncement
55
import kotlinx.coroutines.runBlocking
66
import kotlinx.datetime.toKotlinLocalDateTime
77
import org.jetbrains.exposed.sql.Database
8-
import org.junit.jupiter.api.*
98
import org.junit.jupiter.api.Assertions.assertNull
9+
import org.junit.jupiter.api.BeforeAll
10+
import org.junit.jupiter.api.BeforeEach
11+
import org.junit.jupiter.api.Test
1012
import java.time.LocalDateTime
1113
import kotlin.test.assertEquals
1214
import kotlin.test.assertNotNull
@@ -84,27 +86,22 @@ private object AnnouncementServiceTest {
8486
announcementService.new(ApiAnnouncement(title = "2", tags = listOf("tag1", "tag3")))
8587
announcementService.new(ApiAnnouncement(title = "3", tags = listOf("tag1", "tag4")))
8688

87-
val tag2 = announcementService.tags().find { it.name == "tag2" }!!.id
88-
assert(announcementService.latest(setOf(tag2)).first().title == "1")
89+
assert(announcementService.latest(setOf("tag2")).first().title == "1")
90+
assert(announcementService.latest(setOf("tag3")).last().title == "2")
8991

90-
val tag3 = announcementService.tags().find { it.name == "tag3" }!!.id
91-
assert(announcementService.latest(setOf(tag3)).last().title == "2")
92-
93-
val tag1and3 =
94-
announcementService.tags().filter { it.name == "tag1" || it.name == "tag3" }.map { it.id }.toSet()
95-
val announcement2and3 = announcementService.latest(tag1and3)
92+
val announcement2and3 = announcementService.latest(setOf("tag1", "tag3"))
9693
assert(announcement2and3.size == 2)
9794
assert(announcement2and3.any { it.title == "2" })
9895
assert(announcement2and3.any { it.title == "3" })
9996

10097
announcementService.delete(announcementService.latestId()!!.id)
101-
assert(announcementService.latest(tag1and3).first().title == "2")
98+
assert(announcementService.latest(setOf("tag1", "tag3")).first().title == "2")
10299

103100
announcementService.delete(announcementService.latestId()!!.id)
104-
assert(announcementService.latest(tag1and3).first().title == "1")
101+
assert(announcementService.latest(setOf("tag1", "tag3")).first().title == "1")
105102

106103
announcementService.delete(announcementService.latestId()!!.id)
107-
assert(announcementService.latest(tag1and3).isEmpty())
104+
assert(announcementService.latest(setOf("tag1", "tag3")).isEmpty())
108105
assert(announcementService.tags().isEmpty())
109106
}
110107

@@ -183,7 +180,7 @@ private object AnnouncementServiceTest {
183180
val tags = announcementService.tags()
184181
assertEquals(5, tags.size, "Returns correct number of newly created tags")
185182

186-
val announcements3 = announcementService.paged(5, 5, setOf(tags[1].id), true)
183+
val announcements3 = announcementService.paged(5, 5, setOf(tags[1].name), true)
187184
assertEquals(4, announcements3.size, "Filters announcements by tag")
188185

189186
val announcements4 = announcementService.paged(Int.MAX_VALUE, 10, null, false)

0 commit comments

Comments
 (0)