mirror of
https://github.com/revanced/revanced-api.git
synced 2025-04-29 14:14:29 +02:00
chore: Merge branch dev
to main
(#196)
This pull request will Merge branch `dev` to `main`.
This commit is contained in:
commit
989094309f
22
CHANGELOG.md
22
CHANGELOG.md
@ -1,3 +1,25 @@
|
||||
# [1.6.0-dev.3](https://github.com/ReVanced/revanced-api/compare/v1.6.0-dev.2...v1.6.0-dev.3) (2024-12-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add status page link to about ([8a957cd](https://github.com/ReVanced/revanced-api/commit/8a957cd797e7e42f43670baaed60ac0d3543342f))
|
||||
* Add support for prereleases ([c25bc8b](https://github.com/ReVanced/revanced-api/commit/c25bc8b4ba2bd4bf1708f19dc8bc228a7f54d548))
|
||||
|
||||
# [1.6.0-dev.2](https://github.com/ReVanced/revanced-api/compare/v1.6.0-dev.1...v1.6.0-dev.2) (2024-12-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Make some announcements schema fields nullable ([db22874](https://github.com/ReVanced/revanced-api/commit/db22874f063bae0c9e7f0c99a20cdf1b16addd89))
|
||||
|
||||
# [1.6.0-dev.1](https://github.com/ReVanced/revanced-api/compare/v1.5.0...v1.6.0-dev.1) (2024-11-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Allow setting `Announcement.createdAt` when creating an announcement ([7f6e29d](https://github.com/ReVanced/revanced-api/commit/7f6e29de5205f63ac4aaea490c844b58e14000c8))
|
||||
|
||||
# [1.5.0](https://github.com/ReVanced/revanced-api/compare/v1.4.0...v1.5.0) (2024-11-06)
|
||||
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
"branding": {
|
||||
"logo": "https://raw.githubusercontent.com/ReVanced/revanced-branding/main/assets/revanced-logo/revanced-logo.svg"
|
||||
},
|
||||
"status": "https://status.revanced.app",
|
||||
"contact": {
|
||||
"email": "contact@revanced.app"
|
||||
},
|
||||
|
@ -1,4 +1,4 @@
|
||||
org.gradle.parallel = true
|
||||
org.gradle.caching = true
|
||||
kotlin.code.style = official
|
||||
version = 1.5.0
|
||||
version = 1.6.0-dev.3
|
||||
|
@ -1,6 +1,9 @@
|
||||
package app.revanced.api.configuration
|
||||
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
interface ApiUser {
|
||||
@ -60,10 +63,10 @@ class ApiAnnouncement(
|
||||
val title: String,
|
||||
val content: String? = null,
|
||||
// Using a list instead of a set because set semantics are unnecessary here.
|
||||
val attachments: List<String> = emptyList(),
|
||||
val attachments: List<String>? = null,
|
||||
// Using a list instead of a set because set semantics are unnecessary here.
|
||||
val tags: List<String> = emptyList(),
|
||||
val createdAt: LocalDateTime,
|
||||
val tags: List<String>? = null,
|
||||
val createdAt: LocalDateTime = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()),
|
||||
val archivedAt: LocalDateTime? = null,
|
||||
val level: Int = 0,
|
||||
)
|
||||
@ -75,9 +78,9 @@ class ApiResponseAnnouncement(
|
||||
val title: String,
|
||||
val content: String? = null,
|
||||
// Using a list instead of a set because set semantics are unnecessary here.
|
||||
val attachments: List<String> = emptyList(),
|
||||
val attachments: List<String>? = null,
|
||||
// Using a list instead of a set because set semantics are unnecessary here.
|
||||
val tags: List<String> = emptyList(),
|
||||
val tags: List<String>? = null,
|
||||
val createdAt: LocalDateTime,
|
||||
val archivedAt: LocalDateTime? = null,
|
||||
val level: Int = 0,
|
||||
@ -120,6 +123,7 @@ class APIAbout(
|
||||
// Using a list instead of a set because set semantics are unnecessary here.
|
||||
val socials: List<Social>?,
|
||||
val donations: Donations?,
|
||||
val status: String,
|
||||
) {
|
||||
@Serializable
|
||||
class Branding(
|
||||
|
@ -69,8 +69,7 @@ internal class AnnouncementRepository(private val database: Database) {
|
||||
|
||||
fun latestId() = latestAnnouncement?.id?.value.toApiResponseAnnouncementId()
|
||||
|
||||
fun latestId(tags: Set<String>) =
|
||||
tags.map { tag -> latestAnnouncementByTag[tag]?.id?.value }.toApiResponseAnnouncementId()
|
||||
fun latestId(tags: Set<String>) = tags.map { tag -> latestAnnouncementByTag[tag]?.id?.value }.toApiResponseAnnouncementId()
|
||||
|
||||
suspend fun paged(cursor: Int, count: Int, tags: Set<String>?) = transaction {
|
||||
Announcement.find {
|
||||
@ -100,13 +99,16 @@ internal class AnnouncementRepository(private val database: Database) {
|
||||
author = new.author
|
||||
title = new.title
|
||||
content = new.content
|
||||
createdAt = new.createdAt
|
||||
archivedAt = new.archivedAt
|
||||
level = new.level
|
||||
tags = SizedCollection(
|
||||
new.tags.map { tag -> Tag.find { Tags.name eq tag }.firstOrNull() ?: Tag.new { name = tag } },
|
||||
)
|
||||
if (new.tags != null) {
|
||||
tags = SizedCollection(
|
||||
new.tags.map { tag -> Tag.find { Tags.name eq tag }.firstOrNull() ?: Tag.new { name = tag } },
|
||||
)
|
||||
}
|
||||
}.apply {
|
||||
new.attachments.map { attachmentUrl ->
|
||||
new.attachments?.map { attachmentUrl ->
|
||||
Attachment.new {
|
||||
url = attachmentUrl
|
||||
announcement = this@apply
|
||||
@ -124,24 +126,28 @@ internal class AnnouncementRepository(private val database: Database) {
|
||||
it.archivedAt = new.archivedAt
|
||||
it.level = new.level
|
||||
|
||||
// Get the old tags, create new tags if they don't exist,
|
||||
// and delete tags that are not in the new tags, after updating the announcement.
|
||||
val oldTags = it.tags.toList()
|
||||
val updatedTags = new.tags.map { name ->
|
||||
Tag.find { Tags.name eq name }.firstOrNull() ?: Tag.new { this.name = name }
|
||||
}
|
||||
it.tags = SizedCollection(updatedTags)
|
||||
oldTags.forEach { tag ->
|
||||
if (tag in updatedTags || !tag.announcements.empty()) return@forEach
|
||||
tag.delete()
|
||||
if (new.tags != null) {
|
||||
// Get the old tags, create new tags if they don't exist,
|
||||
// and delete tags that are not in the new tags, after updating the announcement.
|
||||
val oldTags = it.tags.toList()
|
||||
val updatedTags = new.tags.map { name ->
|
||||
Tag.find { Tags.name eq name }.firstOrNull() ?: Tag.new { this.name = name }
|
||||
}
|
||||
it.tags = SizedCollection(updatedTags)
|
||||
oldTags.forEach { tag ->
|
||||
if (tag in updatedTags || !tag.announcements.empty()) return@forEach
|
||||
tag.delete()
|
||||
}
|
||||
}
|
||||
|
||||
// Delete old attachments and create new attachments.
|
||||
it.attachments.forEach { attachment -> attachment.delete() }
|
||||
new.attachments.map { attachment ->
|
||||
Attachment.new {
|
||||
url = attachment
|
||||
announcement = it
|
||||
if (new.attachments != null) {
|
||||
it.attachments.forEach { attachment -> attachment.delete() }
|
||||
new.attachments.map { attachment ->
|
||||
Attachment.new {
|
||||
url = attachment
|
||||
announcement = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}?.let(::updateLatestAnnouncement) ?: Unit
|
||||
@ -174,8 +180,7 @@ internal class AnnouncementRepository(private val database: Database) {
|
||||
Tag.all().toList().toApiTag()
|
||||
}
|
||||
|
||||
private suspend fun <T> transaction(statement: suspend Transaction.() -> T) =
|
||||
newSuspendedTransaction(Dispatchers.IO, database, statement = statement)
|
||||
private suspend fun <T> transaction(statement: suspend Transaction.() -> T) = newSuspendedTransaction(Dispatchers.IO, database, statement = statement)
|
||||
|
||||
private object Announcements : IntIdTable() {
|
||||
val author = varchar("author", 32).nullable()
|
||||
|
@ -135,12 +135,14 @@ abstract class BackendRepository internal constructor(
|
||||
* @property tag The tag of the release.
|
||||
* @property assets The assets of the release.
|
||||
* @property createdAt The date and time the release was created.
|
||||
* @property prerelease Whether the release is a prerelease.
|
||||
* @property releaseNote The release note of the release.
|
||||
*/
|
||||
class BackendRelease(
|
||||
val tag: String,
|
||||
val releaseNote: String,
|
||||
val createdAt: LocalDateTime,
|
||||
val prerelease: Boolean,
|
||||
// Using a list instead of a set because set semantics are unnecessary here.
|
||||
val assets: List<BackendAsset>,
|
||||
) {
|
||||
@ -180,13 +182,13 @@ abstract class BackendRepository internal constructor(
|
||||
*
|
||||
* @param owner The owner of the repository.
|
||||
* @param repository The name of the repository.
|
||||
* @param tag The tag of the release. If null, the latest release is returned.
|
||||
* @param prerelease Whether to get a prerelease.
|
||||
* @return The release.
|
||||
*/
|
||||
abstract suspend fun release(
|
||||
owner: String,
|
||||
repository: String,
|
||||
tag: String? = null,
|
||||
prerelease: Boolean,
|
||||
): BackendOrganization.BackendRepository.BackendRelease
|
||||
|
||||
/**
|
||||
|
@ -24,10 +24,10 @@ class GitHubBackendRepository : BackendRepository("https://api.github.com", "htt
|
||||
override suspend fun release(
|
||||
owner: String,
|
||||
repository: String,
|
||||
tag: String?,
|
||||
prerelease: Boolean,
|
||||
): BackendRelease {
|
||||
val release: GitHubRelease = if (tag != null) {
|
||||
client.get(Releases.Tag(owner, repository, tag)).body()
|
||||
val release: GitHubRelease = if (prerelease) {
|
||||
client.get(Releases(owner, repository)).body<List<GitHubRelease>>().first { it.prerelease }
|
||||
} else {
|
||||
client.get(Releases.Latest(owner, repository)).body()
|
||||
}
|
||||
@ -36,6 +36,7 @@ class GitHubBackendRepository : BackendRepository("https://api.github.com", "htt
|
||||
tag = release.tagName,
|
||||
releaseNote = release.body,
|
||||
createdAt = release.createdAt.toLocalDateTime(TimeZone.UTC),
|
||||
prerelease = release.prerelease,
|
||||
assets = release.assets.map {
|
||||
BackendAsset(
|
||||
name = it.name,
|
||||
@ -163,6 +164,7 @@ class GitHubOrganization {
|
||||
// Using a list instead of a set because set semantics are unnecessary here.
|
||||
val assets: List<GitHubAsset>,
|
||||
val createdAt: Instant,
|
||||
val prerelease: Boolean,
|
||||
val body: String,
|
||||
) {
|
||||
@Serializable
|
||||
@ -200,10 +202,8 @@ class Organization {
|
||||
@Resource("/repos/{owner}/{repo}/contributors")
|
||||
class Contributors(val owner: String, val repo: String, @SerialName("per_page") val perPage: Int = 100)
|
||||
|
||||
class Releases {
|
||||
@Resource("/repos/{owner}/{repo}/releases/tags/{tag}")
|
||||
class Tag(val owner: String, val repo: String, val tag: String)
|
||||
|
||||
@Resource("/repos/{owner}/{repo}/releases")
|
||||
class Releases(val owner: String, val repo: String) {
|
||||
@Resource("/repos/{owner}/{repo}/releases/latest")
|
||||
class Latest(val owner: String, val repo: String)
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import app.revanced.api.configuration.ApiReleaseVersion
|
||||
import app.revanced.api.configuration.installNotarizedRoute
|
||||
import app.revanced.api.configuration.services.ManagerService
|
||||
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||
import io.bkbn.kompendium.oas.payload.Parameter
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.plugins.ratelimit.*
|
||||
@ -19,25 +21,38 @@ internal fun Route.managerRoute() = route("manager") {
|
||||
|
||||
rateLimit(RateLimitName("weak")) {
|
||||
get {
|
||||
call.respond(managerService.latestRelease())
|
||||
val prerelease = call.parameters["prerelease"]?.toBoolean() ?: false
|
||||
|
||||
call.respond(managerService.latestRelease(prerelease))
|
||||
}
|
||||
|
||||
route("version") {
|
||||
installManagerVersionRouteDocumentation()
|
||||
|
||||
get {
|
||||
call.respond(managerService.latestVersion())
|
||||
val prerelease = call.parameters["prerelease"]?.toBoolean() ?: false
|
||||
|
||||
call.respond(managerService.latestVersion(prerelease))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val prereleaseParameter = Parameter(
|
||||
name = "prerelease",
|
||||
`in` = Parameter.Location.query,
|
||||
schema = TypeDefinition.STRING,
|
||||
description = "Whether to get the current manager prerelease",
|
||||
required = false,
|
||||
)
|
||||
|
||||
private fun Route.installManagerRouteDocumentation() = installNotarizedRoute {
|
||||
tags = setOf("Manager")
|
||||
|
||||
get = GetInfo.builder {
|
||||
description("Get the current manager release")
|
||||
summary("Get current manager release")
|
||||
parameters(prereleaseParameter)
|
||||
response {
|
||||
description("The latest manager release")
|
||||
mediaTypes("application/json")
|
||||
@ -53,6 +68,7 @@ private fun Route.installManagerVersionRouteDocumentation() = installNotarizedRo
|
||||
get = GetInfo.builder {
|
||||
description("Get the current manager release version")
|
||||
summary("Get current manager release version")
|
||||
parameters(prereleaseParameter)
|
||||
response {
|
||||
description("The current manager release version")
|
||||
mediaTypes("application/json")
|
||||
|
@ -7,6 +7,8 @@ import app.revanced.api.configuration.installCache
|
||||
import app.revanced.api.configuration.installNotarizedRoute
|
||||
import app.revanced.api.configuration.services.PatchesService
|
||||
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||
import io.bkbn.kompendium.oas.payload.Parameter
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.plugins.ratelimit.*
|
||||
@ -22,14 +24,18 @@ internal fun Route.patchesRoute() = route("patches") {
|
||||
|
||||
rateLimit(RateLimitName("weak")) {
|
||||
get {
|
||||
call.respond(patchesService.latestRelease())
|
||||
val prerelease = call.parameters["prerelease"]?.toBoolean() ?: false
|
||||
|
||||
call.respond(patchesService.latestRelease(prerelease))
|
||||
}
|
||||
|
||||
route("version") {
|
||||
installPatchesVersionRouteDocumentation()
|
||||
|
||||
get {
|
||||
call.respond(patchesService.latestVersion())
|
||||
val prerelease = call.parameters["prerelease"]?.toBoolean() ?: false
|
||||
|
||||
call.respond(patchesService.latestVersion(prerelease))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -39,7 +45,9 @@ internal fun Route.patchesRoute() = route("patches") {
|
||||
installPatchesListRouteDocumentation()
|
||||
|
||||
get {
|
||||
call.respondBytes(ContentType.Application.Json) { patchesService.list() }
|
||||
val prerelease = call.parameters["prerelease"]?.toBoolean() ?: false
|
||||
|
||||
call.respondBytes(ContentType.Application.Json) { patchesService.list(prerelease) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -57,12 +65,21 @@ internal fun Route.patchesRoute() = route("patches") {
|
||||
}
|
||||
}
|
||||
|
||||
private val prereleaseParameter = Parameter(
|
||||
name = "prerelease",
|
||||
`in` = Parameter.Location.query,
|
||||
schema = TypeDefinition.STRING,
|
||||
description = "Whether to get the current patches prerelease",
|
||||
required = false,
|
||||
)
|
||||
|
||||
private fun Route.installPatchesRouteDocumentation() = installNotarizedRoute {
|
||||
tags = setOf("Patches")
|
||||
|
||||
get = GetInfo.builder {
|
||||
description("Get the current patches release")
|
||||
summary("Get current patches release")
|
||||
parameters(prereleaseParameter)
|
||||
response {
|
||||
description("The current patches release")
|
||||
mediaTypes("application/json")
|
||||
@ -78,6 +95,7 @@ private fun Route.installPatchesVersionRouteDocumentation() = installNotarizedRo
|
||||
get = GetInfo.builder {
|
||||
description("Get the current patches release version")
|
||||
summary("Get current patches release version")
|
||||
parameters(prereleaseParameter)
|
||||
response {
|
||||
description("The current patches release version")
|
||||
mediaTypes("application/json")
|
||||
@ -93,6 +111,7 @@ private fun Route.installPatchesListRouteDocumentation() = installNotarizedRoute
|
||||
get = GetInfo.builder {
|
||||
description("Get the list of patches from the current patches release")
|
||||
summary("Get list of patches from current patches release")
|
||||
parameters(prereleaseParameter)
|
||||
response {
|
||||
description("The list of patches")
|
||||
mediaTypes("application/json")
|
||||
|
@ -10,10 +10,11 @@ internal class ManagerService(
|
||||
private val backendRepository: BackendRepository,
|
||||
private val configurationRepository: ConfigurationRepository,
|
||||
) {
|
||||
suspend fun latestRelease(): ApiRelease {
|
||||
suspend fun latestRelease(prerelease: Boolean): ApiRelease {
|
||||
val managerRelease = backendRepository.release(
|
||||
configurationRepository.organization,
|
||||
configurationRepository.manager.repository,
|
||||
prerelease,
|
||||
)
|
||||
|
||||
return ApiRelease(
|
||||
@ -24,10 +25,11 @@ internal class ManagerService(
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun latestVersion(): ApiReleaseVersion {
|
||||
suspend fun latestVersion(prerelease: Boolean): ApiReleaseVersion {
|
||||
val managerRelease = backendRepository.release(
|
||||
configurationRepository.organization,
|
||||
configurationRepository.manager.repository,
|
||||
prerelease,
|
||||
)
|
||||
|
||||
return ApiReleaseVersion(managerRelease.tag)
|
||||
|
@ -19,10 +19,11 @@ internal class PatchesService(
|
||||
private val backendRepository: BackendRepository,
|
||||
private val configurationRepository: ConfigurationRepository,
|
||||
) {
|
||||
suspend fun latestRelease(): ApiRelease {
|
||||
suspend fun latestRelease(prerelease: Boolean): ApiRelease {
|
||||
val patchesRelease = backendRepository.release(
|
||||
configurationRepository.organization,
|
||||
configurationRepository.patches.repository,
|
||||
prerelease,
|
||||
)
|
||||
|
||||
return ApiRelease(
|
||||
@ -34,10 +35,11 @@ internal class PatchesService(
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun latestVersion(): ApiReleaseVersion {
|
||||
suspend fun latestVersion(prerelease: Boolean): ApiReleaseVersion {
|
||||
val patchesRelease = backendRepository.release(
|
||||
configurationRepository.organization,
|
||||
configurationRepository.patches.repository,
|
||||
prerelease,
|
||||
)
|
||||
|
||||
return ApiReleaseVersion(patchesRelease.tag)
|
||||
@ -48,10 +50,11 @@ internal class PatchesService(
|
||||
.maximumSize(1)
|
||||
.build<String, ByteArray>()
|
||||
|
||||
suspend fun list(): ByteArray {
|
||||
suspend fun list(prerelease: Boolean): ByteArray {
|
||||
val patchesRelease = backendRepository.release(
|
||||
configurationRepository.organization,
|
||||
configurationRepository.patches.repository,
|
||||
prerelease,
|
||||
)
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
|
@ -12,7 +12,7 @@ import java.security.MessageDigest
|
||||
internal class SignatureService {
|
||||
private val signatureCache = Caffeine
|
||||
.newBuilder()
|
||||
.maximumSize(1) // 1 because currently only the latest patches is needed.
|
||||
.maximumSize(2) // 2 because currently only the latest release and prerelease patches are needed.
|
||||
.build<ByteArray, Boolean>() // Hash -> Verified.
|
||||
|
||||
fun verify(
|
||||
|
@ -135,7 +135,7 @@ private object AnnouncementServiceTest {
|
||||
val latestAnnouncement = announcementService.latest()!!
|
||||
val latestId = latestAnnouncement.id
|
||||
|
||||
val attachments = latestAnnouncement.attachments
|
||||
val attachments = latestAnnouncement.attachments!!
|
||||
assertEquals(2, attachments.size)
|
||||
assert(attachments.any { it == "attachment1" })
|
||||
assert(attachments.any { it == "attachment2" })
|
||||
@ -144,7 +144,7 @@ private object AnnouncementServiceTest {
|
||||
latestId,
|
||||
ApiAnnouncement(title = "title", attachments = listOf("attachment1", "attachment3")),
|
||||
)
|
||||
assert(announcementService.get(latestId)!!.attachments.any { it == "attachment3" })
|
||||
assert(announcementService.get(latestId)!!.attachments!!.any { it == "attachment3" })
|
||||
}
|
||||
|
||||
@Test
|
||||
|
Loading…
x
Reference in New Issue
Block a user