feat: Add support for prereleases

This commit is contained in:
oSumAtrIX 2024-12-25 11:49:34 +01:00
parent 8a957cd797
commit c25bc8b4ba
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
7 changed files with 62 additions and 20 deletions

View File

@ -135,12 +135,14 @@ abstract class BackendRepository internal constructor(
* @property tag The tag of the release. * @property tag The tag of the release.
* @property assets The assets of the release. * @property assets The assets of the release.
* @property createdAt The date and time the release was created. * @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. * @property releaseNote The release note of the release.
*/ */
class BackendRelease( class BackendRelease(
val tag: String, val tag: String,
val releaseNote: String, val releaseNote: String,
val createdAt: LocalDateTime, val createdAt: LocalDateTime,
val prerelease: Boolean,
// Using a list instead of a set because set semantics are unnecessary here. // Using a list instead of a set because set semantics are unnecessary here.
val assets: List<BackendAsset>, val assets: List<BackendAsset>,
) { ) {
@ -180,13 +182,13 @@ abstract class BackendRepository internal constructor(
* *
* @param owner The owner of the repository. * @param owner The owner of the repository.
* @param repository The name 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. * @return The release.
*/ */
abstract suspend fun release( abstract suspend fun release(
owner: String, owner: String,
repository: String, repository: String,
tag: String? = null, prerelease: Boolean,
): BackendOrganization.BackendRepository.BackendRelease ): BackendOrganization.BackendRepository.BackendRelease
/** /**

View File

@ -24,10 +24,10 @@ class GitHubBackendRepository : BackendRepository("https://api.github.com", "htt
override suspend fun release( override suspend fun release(
owner: String, owner: String,
repository: String, repository: String,
tag: String?, prerelease: Boolean,
): BackendRelease { ): BackendRelease {
val release: GitHubRelease = if (tag != null) { val release: GitHubRelease = if (prerelease) {
client.get(Releases.Tag(owner, repository, tag)).body() client.get(Releases(owner, repository)).body<List<GitHubRelease>>().first { it.prerelease }
} else { } else {
client.get(Releases.Latest(owner, repository)).body() client.get(Releases.Latest(owner, repository)).body()
} }
@ -36,6 +36,7 @@ class GitHubBackendRepository : BackendRepository("https://api.github.com", "htt
tag = release.tagName, tag = release.tagName,
releaseNote = release.body, releaseNote = release.body,
createdAt = release.createdAt.toLocalDateTime(TimeZone.UTC), createdAt = release.createdAt.toLocalDateTime(TimeZone.UTC),
prerelease = release.prerelease,
assets = release.assets.map { assets = release.assets.map {
BackendAsset( BackendAsset(
name = it.name, name = it.name,
@ -163,6 +164,7 @@ class GitHubOrganization {
// Using a list instead of a set because set semantics are unnecessary here. // Using a list instead of a set because set semantics are unnecessary here.
val assets: List<GitHubAsset>, val assets: List<GitHubAsset>,
val createdAt: Instant, val createdAt: Instant,
val prerelease: Boolean,
val body: String, val body: String,
) { ) {
@Serializable @Serializable
@ -200,10 +202,8 @@ class Organization {
@Resource("/repos/{owner}/{repo}/contributors") @Resource("/repos/{owner}/{repo}/contributors")
class Contributors(val owner: String, val repo: String, @SerialName("per_page") val perPage: Int = 100) class Contributors(val owner: String, val repo: String, @SerialName("per_page") val perPage: Int = 100)
class Releases { @Resource("/repos/{owner}/{repo}/releases")
@Resource("/repos/{owner}/{repo}/releases/tags/{tag}") class Releases(val owner: String, val repo: String) {
class Tag(val owner: String, val repo: String, val tag: String)
@Resource("/repos/{owner}/{repo}/releases/latest") @Resource("/repos/{owner}/{repo}/releases/latest")
class Latest(val owner: String, val repo: String) class Latest(val owner: String, val repo: String)
} }

View File

@ -5,6 +5,8 @@ import app.revanced.api.configuration.ApiReleaseVersion
import app.revanced.api.configuration.installNotarizedRoute import app.revanced.api.configuration.installNotarizedRoute
import app.revanced.api.configuration.services.ManagerService import app.revanced.api.configuration.services.ManagerService
import io.bkbn.kompendium.core.metadata.GetInfo 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.http.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.plugins.ratelimit.* import io.ktor.server.plugins.ratelimit.*
@ -19,25 +21,38 @@ internal fun Route.managerRoute() = route("manager") {
rateLimit(RateLimitName("weak")) { rateLimit(RateLimitName("weak")) {
get { get {
call.respond(managerService.latestRelease()) val prerelease = call.parameters["prerelease"]?.toBoolean() ?: false
call.respond(managerService.latestRelease(prerelease))
} }
route("version") { route("version") {
installManagerVersionRouteDocumentation() installManagerVersionRouteDocumentation()
get { 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 { private fun Route.installManagerRouteDocumentation() = installNotarizedRoute {
tags = setOf("Manager") tags = setOf("Manager")
get = GetInfo.builder { get = GetInfo.builder {
description("Get the current manager release") description("Get the current manager release")
summary("Get current manager release") summary("Get current manager release")
parameters(prereleaseParameter)
response { response {
description("The latest manager release") description("The latest manager release")
mediaTypes("application/json") mediaTypes("application/json")
@ -53,6 +68,7 @@ private fun Route.installManagerVersionRouteDocumentation() = installNotarizedRo
get = GetInfo.builder { get = GetInfo.builder {
description("Get the current manager release version") description("Get the current manager release version")
summary("Get current manager release version") summary("Get current manager release version")
parameters(prereleaseParameter)
response { response {
description("The current manager release version") description("The current manager release version")
mediaTypes("application/json") mediaTypes("application/json")

View File

@ -7,6 +7,8 @@ import app.revanced.api.configuration.installCache
import app.revanced.api.configuration.installNotarizedRoute import app.revanced.api.configuration.installNotarizedRoute
import app.revanced.api.configuration.services.PatchesService import app.revanced.api.configuration.services.PatchesService
import io.bkbn.kompendium.core.metadata.GetInfo 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.http.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.plugins.ratelimit.* import io.ktor.server.plugins.ratelimit.*
@ -22,14 +24,18 @@ internal fun Route.patchesRoute() = route("patches") {
rateLimit(RateLimitName("weak")) { rateLimit(RateLimitName("weak")) {
get { get {
call.respond(patchesService.latestRelease()) val prerelease = call.parameters["prerelease"]?.toBoolean() ?: false
call.respond(patchesService.latestRelease(prerelease))
} }
route("version") { route("version") {
installPatchesVersionRouteDocumentation() installPatchesVersionRouteDocumentation()
get { 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() installPatchesListRouteDocumentation()
get { 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 { private fun Route.installPatchesRouteDocumentation() = installNotarizedRoute {
tags = setOf("Patches") tags = setOf("Patches")
get = GetInfo.builder { get = GetInfo.builder {
description("Get the current patches release") description("Get the current patches release")
summary("Get current patches release") summary("Get current patches release")
parameters(prereleaseParameter)
response { response {
description("The current patches release") description("The current patches release")
mediaTypes("application/json") mediaTypes("application/json")
@ -78,6 +95,7 @@ private fun Route.installPatchesVersionRouteDocumentation() = installNotarizedRo
get = GetInfo.builder { get = GetInfo.builder {
description("Get the current patches release version") description("Get the current patches release version")
summary("Get current patches release version") summary("Get current patches release version")
parameters(prereleaseParameter)
response { response {
description("The current patches release version") description("The current patches release version")
mediaTypes("application/json") mediaTypes("application/json")
@ -93,6 +111,7 @@ private fun Route.installPatchesListRouteDocumentation() = installNotarizedRoute
get = GetInfo.builder { get = GetInfo.builder {
description("Get the list of patches from the current patches release") description("Get the list of patches from the current patches release")
summary("Get list of patches from current patches release") summary("Get list of patches from current patches release")
parameters(prereleaseParameter)
response { response {
description("The list of patches") description("The list of patches")
mediaTypes("application/json") mediaTypes("application/json")

View File

@ -10,10 +10,11 @@ internal class ManagerService(
private val backendRepository: BackendRepository, private val backendRepository: BackendRepository,
private val configurationRepository: ConfigurationRepository, private val configurationRepository: ConfigurationRepository,
) { ) {
suspend fun latestRelease(): ApiRelease { suspend fun latestRelease(prerelease: Boolean): ApiRelease {
val managerRelease = backendRepository.release( val managerRelease = backendRepository.release(
configurationRepository.organization, configurationRepository.organization,
configurationRepository.manager.repository, configurationRepository.manager.repository,
prerelease,
) )
return ApiRelease( return ApiRelease(
@ -24,10 +25,11 @@ internal class ManagerService(
) )
} }
suspend fun latestVersion(): ApiReleaseVersion { suspend fun latestVersion(prerelease: Boolean): ApiReleaseVersion {
val managerRelease = backendRepository.release( val managerRelease = backendRepository.release(
configurationRepository.organization, configurationRepository.organization,
configurationRepository.manager.repository, configurationRepository.manager.repository,
prerelease,
) )
return ApiReleaseVersion(managerRelease.tag) return ApiReleaseVersion(managerRelease.tag)

View File

@ -19,10 +19,11 @@ internal class PatchesService(
private val backendRepository: BackendRepository, private val backendRepository: BackendRepository,
private val configurationRepository: ConfigurationRepository, private val configurationRepository: ConfigurationRepository,
) { ) {
suspend fun latestRelease(): ApiRelease { suspend fun latestRelease(prerelease: Boolean): ApiRelease {
val patchesRelease = backendRepository.release( val patchesRelease = backendRepository.release(
configurationRepository.organization, configurationRepository.organization,
configurationRepository.patches.repository, configurationRepository.patches.repository,
prerelease,
) )
return ApiRelease( return ApiRelease(
@ -34,10 +35,11 @@ internal class PatchesService(
) )
} }
suspend fun latestVersion(): ApiReleaseVersion { suspend fun latestVersion(prerelease: Boolean): ApiReleaseVersion {
val patchesRelease = backendRepository.release( val patchesRelease = backendRepository.release(
configurationRepository.organization, configurationRepository.organization,
configurationRepository.patches.repository, configurationRepository.patches.repository,
prerelease,
) )
return ApiReleaseVersion(patchesRelease.tag) return ApiReleaseVersion(patchesRelease.tag)
@ -48,10 +50,11 @@ internal class PatchesService(
.maximumSize(1) .maximumSize(1)
.build<String, ByteArray>() .build<String, ByteArray>()
suspend fun list(): ByteArray { suspend fun list(prerelease: Boolean): ByteArray {
val patchesRelease = backendRepository.release( val patchesRelease = backendRepository.release(
configurationRepository.organization, configurationRepository.organization,
configurationRepository.patches.repository, configurationRepository.patches.repository,
prerelease,
) )
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {

View File

@ -12,7 +12,7 @@ import java.security.MessageDigest
internal class SignatureService { internal class SignatureService {
private val signatureCache = Caffeine private val signatureCache = Caffeine
.newBuilder() .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. .build<ByteArray, Boolean>() // Hash -> Verified.
fun verify( fun verify(