diff --git a/configuration.example.toml b/configuration.example.toml index f2b1795f..0ad3059d 100644 --- a/configuration.example.toml +++ b/configuration.example.toml @@ -1,6 +1,7 @@ organization = "revanced" patches = { repository = "revanced-patches", asset-regex = "jar$", signature-asset-regex = "asc$", public-key-file = "patches-public-key.asc" } integrations = { repository = "revanced-integrations", asset-regex = "apk$", signature-asset-regex = "asc$", public-key-file = "integrations-public-key.asc" } +manager = { repository = "revanced-manager", asset-regex = "apk$" } contributors-repositories = [ "revanced-patcher", "revanced-patches", diff --git a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt index 559ac143..a8ed717e 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt @@ -134,6 +134,7 @@ fun Application.configureDependencies( singleOf(::AnnouncementService) singleOf(::SignatureService) singleOf(::PatchesService) + singleOf(::ManagerService) singleOf(::ApiService) } diff --git a/src/main/kotlin/app/revanced/api/configuration/Routing.kt b/src/main/kotlin/app/revanced/api/configuration/Routing.kt index 074f9265..fe60ebc0 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Routing.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Routing.kt @@ -1,6 +1,7 @@ package app.revanced.api.configuration import app.revanced.api.configuration.repository.ConfigurationRepository +import app.revanced.api.configuration.routes.* import app.revanced.api.configuration.routes.announcementsRoute import app.revanced.api.configuration.routes.apiRoute import app.revanced.api.configuration.routes.oldApiRoute @@ -22,6 +23,7 @@ internal fun Application.configureRouting() = routing { route("/v${configuration.apiVersion}") { announcementsRoute() patchesRoute() + managerRoute() apiRoute() } diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt index bb567f3a..4e13cf64 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt @@ -1,5 +1,6 @@ package app.revanced.api.configuration.repository +import app.revanced.api.configuration.services.ManagerService import app.revanced.api.configuration.services.PatchesService import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName @@ -17,6 +18,7 @@ import java.io.File * @property organization The API backends organization name where the repositories for the patches and integrations are. * @property patches The source of the patches. * @property integrations The source of the integrations. + * @property manager The source of the manager. * @property contributorsRepositoryNames The names of the repositories to get contributors from. * @property apiVersion The version to use for the API. * @property corsAllowedHosts The hosts allowed to make requests to the API. @@ -26,8 +28,9 @@ import java.io.File @Serializable internal class ConfigurationRepository( val organization: String, - val patches: AssetConfiguration, - val integrations: AssetConfiguration, + val patches: SignedAssetConfiguration, + val integrations: SignedAssetConfiguration, + val manager: AssetConfiguration, @SerialName("contributors-repositories") val contributorsRepositoryNames: Set, @SerialName("api-version") @@ -39,9 +42,9 @@ internal class ConfigurationRepository( val oldApiEndpoint: String, ) { /** - * An asset configuration. + * Am asset configuration whose asset is signed. * - * [PatchesService] uses [BackendRepository] to get assets from its releases. + * [PatchesService] for example uses [BackendRepository] to get assets from its releases. * A release contains multiple assets. * * This configuration is used in [ConfigurationRepository] @@ -54,7 +57,7 @@ internal class ConfigurationRepository( * @property publicKeyId The ID of the public key to verify the signature of the asset. */ @Serializable - internal class AssetConfiguration( + internal class SignedAssetConfiguration( val repository: String, @Serializable(with = RegexSerializer::class) @SerialName("asset-regex") @@ -68,6 +71,26 @@ internal class ConfigurationRepository( @SerialName("public-key-id") val publicKeyId: Long, ) + + /** + * Am asset configuration. + * + * [ManagerService] for example uses [BackendRepository] to get assets from its releases. + * A release contains multiple assets. + * + * This configuration is used in [ConfigurationRepository] + * to determine which release assets from repositories to get and to verify them. + * + * @property repository The repository in which releases are made to get an asset. + * @property assetRegex The regex matching the asset name. + */ + @Serializable + internal class AssetConfiguration( + val repository: String, + @Serializable(with = RegexSerializer::class) + @SerialName("asset-regex") + val assetRegex: Regex, + ) } private object RegexSerializer : KSerializer { diff --git a/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt index 7cb5d822..5152d654 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt @@ -82,7 +82,7 @@ internal fun Route.apiRoute() { } } -fun Route.installRateLimitRouteDocumentation() = installNotarizedRoute { +private fun Route.installRateLimitRouteDocumentation() = installNotarizedRoute { tags = setOf("API") get = GetInfo.builder { @@ -97,7 +97,7 @@ fun Route.installRateLimitRouteDocumentation() = installNotarizedRoute { } } -fun Route.installPingRouteDocumentation() = installNotarizedRoute { +private fun Route.installPingRouteDocumentation() = installNotarizedRoute { tags = setOf("API") head = HeadInfo.builder { @@ -111,7 +111,7 @@ fun Route.installPingRouteDocumentation() = installNotarizedRoute { } } -fun Route.installTeamRouteDocumentation() = installNotarizedRoute { +private fun Route.installTeamRouteDocumentation() = installNotarizedRoute { tags = setOf("API") get = GetInfo.builder { @@ -126,7 +126,7 @@ fun Route.installTeamRouteDocumentation() = installNotarizedRoute { } } -fun Route.installContributorsRouteDocumentation() = installNotarizedRoute { +private fun Route.installContributorsRouteDocumentation() = installNotarizedRoute { tags = setOf("API") get = GetInfo.builder { @@ -141,7 +141,7 @@ fun Route.installContributorsRouteDocumentation() = installNotarizedRoute { } } -fun Route.installTokenRouteDocumentation() = installNotarizedRoute { +private fun Route.installTokenRouteDocumentation() = installNotarizedRoute { tags = setOf("API") get = GetInfo.builder { diff --git a/src/main/kotlin/app/revanced/api/configuration/routes/ManagerRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routes/ManagerRoute.kt new file mode 100644 index 00000000..e9b9cb85 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/routes/ManagerRoute.kt @@ -0,0 +1,66 @@ +package app.revanced.api.configuration.routes + +import app.revanced.api.configuration.installNotarizedRoute +import app.revanced.api.configuration.schema.APIManagerAsset +import app.revanced.api.configuration.schema.APIRelease +import app.revanced.api.configuration.schema.APIReleaseVersion +import app.revanced.api.configuration.services.ManagerService +import io.bkbn.kompendium.core.metadata.GetInfo +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.plugins.ratelimit.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import org.koin.ktor.ext.get as koinGet + +internal fun Route.managerRoute() = route("manager") { + val managerService = koinGet() + + route("latest") { + installLatestManagerRouteDocumentation() + + rateLimit(RateLimitName("weak")) { + get { + call.respond(managerService.latestRelease()) + } + + route("version") { + installLatestManagerVersionRouteDocumentation() + + get { + call.respond(managerService.latestVersion()) + } + } + } + } +} + +private fun Route.installLatestManagerRouteDocumentation() = installNotarizedRoute { + tags = setOf("Manager") + + get = GetInfo.builder { + description("Get the latest manager release") + summary("Get latest Manager release") + response { + description("The latest manager release") + mediaTypes("application/json") + responseCode(HttpStatusCode.OK) + responseType>() + } + } +} + +private fun Route.installLatestManagerVersionRouteDocumentation() = installNotarizedRoute { + tags = setOf("Manager") + + get = GetInfo.builder { + description("Get the latest manager release version") + summary("Get latest manager release version") + response { + description("The latest manager release version") + mediaTypes("application/json") + responseCode(HttpStatusCode.OK) + responseType() + } + } +} diff --git a/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt index 8419e6a5..b1ff8412 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt @@ -3,6 +3,7 @@ package app.revanced.api.configuration.routes import app.revanced.api.configuration.installCache import app.revanced.api.configuration.installNotarizedRoute import app.revanced.api.configuration.schema.APIAssetPublicKeys +import app.revanced.api.configuration.schema.APIPatchesAsset import app.revanced.api.configuration.schema.APIRelease import app.revanced.api.configuration.schema.APIReleaseVersion import app.revanced.api.configuration.services.PatchesService @@ -59,7 +60,7 @@ internal fun Route.patchesRoute() = route("patches") { } } -fun Route.installLatestPatchesRouteDocumentation() = installNotarizedRoute { +private fun Route.installLatestPatchesRouteDocumentation() = installNotarizedRoute { tags = setOf("Patches") get = GetInfo.builder { @@ -69,12 +70,12 @@ fun Route.installLatestPatchesRouteDocumentation() = installNotarizedRoute { description("The latest patches release") mediaTypes("application/json") responseCode(HttpStatusCode.OK) - responseType() + responseType>() } } } -fun Route.installLatestPatchesVersionRouteDocumentation() = installNotarizedRoute { +private fun Route.installLatestPatchesVersionRouteDocumentation() = installNotarizedRoute { tags = setOf("Patches") get = GetInfo.builder { @@ -89,7 +90,7 @@ fun Route.installLatestPatchesVersionRouteDocumentation() = installNotarizedRout } } -fun Route.installLatestPatchesListRouteDocumentation() = installNotarizedRoute { +private fun Route.installLatestPatchesListRouteDocumentation() = installNotarizedRoute { tags = setOf("Patches") get = GetInfo.builder { @@ -104,7 +105,7 @@ fun Route.installLatestPatchesListRouteDocumentation() = installNotarizedRoute { } } -fun Route.installPatchesPublicKeyRouteDocumentation() = installNotarizedRoute { +private fun Route.installPatchesPublicKeyRouteDocumentation() = installNotarizedRoute { tags = setOf("Patches") get = GetInfo.builder { diff --git a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt index 888f8118..a335bffd 100644 --- a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt +++ b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt @@ -3,15 +3,6 @@ package app.revanced.api.configuration.schema import kotlinx.datetime.LocalDateTime import kotlinx.serialization.Serializable -@Serializable -class APIRelease( - val version: String, - val createdAt: LocalDateTime, - val description: String, - // Using a list instead of a set because set semantics are unnecessary here. - val assets: List, -) - interface APIUser { val name: String val avatarUrl: String @@ -48,7 +39,21 @@ class APIContributable( ) @Serializable -class APIAsset( +class APIRelease( + val version: String, + val createdAt: LocalDateTime, + val description: String, + // Using a list instead of a set because set semantics are unnecessary here. + val assets: List, +) + +@Serializable +class APIManagerAsset( + val downloadUrl: String, +) + +@Serializable +class APIPatchesAsset( val downloadUrl: String, val signatureDownloadUrl: String, // TODO: Remove this eventually when integrations are merged into patches. diff --git a/src/main/kotlin/app/revanced/api/configuration/services/ManagerService.kt b/src/main/kotlin/app/revanced/api/configuration/services/ManagerService.kt new file mode 100644 index 00000000..17b06650 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/services/ManagerService.kt @@ -0,0 +1,38 @@ +package app.revanced.api.configuration.services + +import app.revanced.api.configuration.repository.BackendRepository +import app.revanced.api.configuration.repository.BackendRepository.BackendOrganization.BackendRepository.BackendRelease.Companion.first +import app.revanced.api.configuration.repository.ConfigurationRepository +import app.revanced.api.configuration.schema.* + +internal class ManagerService( + private val backendRepository: BackendRepository, + private val configurationRepository: ConfigurationRepository, +) { + suspend fun latestRelease(): APIRelease { + val managerRelease = backendRepository.release( + configurationRepository.organization, + configurationRepository.manager.repository, + ) + + val managerAsset = APIManagerAsset( + managerRelease.assets.first(configurationRepository.manager.assetRegex).downloadUrl, + ) + + return APIRelease( + managerRelease.tag, + managerRelease.createdAt, + managerRelease.releaseNote, + listOf(managerAsset), + ) + } + + suspend fun latestVersion(): APIReleaseVersion { + val managerRelease = backendRepository.release( + configurationRepository.organization, + configurationRepository.manager.repository, + ) + + return APIReleaseVersion(managerRelease.tag) + } +} diff --git a/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt b/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt index e24eb262..618fc335 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt @@ -18,7 +18,7 @@ internal class PatchesService( private val backendRepository: BackendRepository, private val configurationRepository: ConfigurationRepository, ) { - suspend fun latestRelease(): APIRelease { + suspend fun latestRelease(): APIRelease { val patchesRelease = backendRepository.release( configurationRepository.organization, configurationRepository.patches.repository, @@ -29,10 +29,10 @@ internal class PatchesService( configurationRepository.integrations.repository, ) - fun ConfigurationRepository.AssetConfiguration.asset( + fun ConfigurationRepository.SignedAssetConfiguration.asset( release: BackendRepository.BackendOrganization.BackendRepository.BackendRelease, assetName: APIAssetName, - ) = APIAsset( + ) = APIPatchesAsset( release.assets.first(assetRegex).downloadUrl, release.assets.first(signatureAssetRegex).downloadUrl, assetName, @@ -113,8 +113,8 @@ internal class PatchesService( } fun publicKeys(): APIAssetPublicKeys { - fun publicKeyBase64(getAssetConfiguration: ConfigurationRepository.() -> ConfigurationRepository.AssetConfiguration) = - configurationRepository.getAssetConfiguration().publicKeyFile.readBytes().encodeBase64() + fun publicKeyBase64(getSignedAssetConfiguration: ConfigurationRepository.() -> ConfigurationRepository.SignedAssetConfiguration) = + configurationRepository.getSignedAssetConfiguration().publicKeyFile.readBytes().encodeBase64() return APIAssetPublicKeys( publicKeyBase64 { patches },