Compare commits

...

8 Commits

Author SHA1 Message Date
semantic-release-bot
e8dfefe6ae chore: Release v1.4.0-dev.3 [skip ci]
# [1.4.0-dev.3](https://github.com/ReVanced/revanced-api/compare/v1.4.0-dev.2...v1.4.0-dev.3) (2024-11-01)

### Bug Fixes

* Use new patches file extension ([d42a3a3](d42a3a3933))
2024-11-01 22:13:35 +00:00
oSumAtrIX
d42a3a3933 fix: Use new patches file extension 2024-11-01 23:11:37 +01:00
semantic-release-bot
bb7aa5b0b4 chore: Release v1.4.0-dev.2 [skip ci]
# [1.4.0-dev.2](https://github.com/ReVanced/revanced-api/compare/v1.4.0-dev.1...v1.4.0-dev.2) (2024-11-01)

### Bug Fixes

* Add missing logging level environment variable to .env.example ([3b62120](3b6212065a))

### Features

* Add URL and use friendly name for `APIContributable` ([a5498ab](a5498aba2b))
* Make backend configurable ([f91f3a6](f91f3a65c5))
* Remove ReVanced Integrations ([f1c1092](f1c10928ae))
2024-11-01 18:13:29 +00:00
oSumAtrIX
3b6212065a fix: Add missing logging level environment variable to .env.example 2024-11-01 19:11:33 +01:00
oSumAtrIX
3d3b7a7af8 chore: Use tables in configuration for readability 2024-11-01 19:09:01 +01:00
oSumAtrIX
f1c10928ae feat: Remove ReVanced Integrations
There is no need for them anymore in Patcher v20+
2024-11-01 19:04:22 +01:00
oSumAtrIX
a5498aba2b feat: Add URL and use friendly name for APIContributable 2024-11-01 18:43:39 +01:00
oSumAtrIX
f91f3a65c5 feat: Make backend configurable 2024-11-01 18:41:43 +01:00
21 changed files with 177 additions and 207 deletions

View File

@@ -13,3 +13,6 @@ AUTH_SHA256_DIGEST=
JWT_SECRET= JWT_SECRET=
JWT_ISSUER= JWT_ISSUER=
JWT_VALIDITY_IN_MIN= JWT_VALIDITY_IN_MIN=
# Logging level for the application
LOG_LEVEL=INFO

1
.gitignore vendored
View File

@@ -41,7 +41,6 @@ persistence/
configuration.toml configuration.toml
docker-compose.yml docker-compose.yml
patches-public-key.asc patches-public-key.asc
integrations-public-key.asc
node_modules/ node_modules/
static/ static/
about.json about.json

View File

@@ -1,3 +1,24 @@
# [1.4.0-dev.3](https://github.com/ReVanced/revanced-api/compare/v1.4.0-dev.2...v1.4.0-dev.3) (2024-11-01)
### Bug Fixes
* Use new patches file extension ([d42a3a3](https://github.com/ReVanced/revanced-api/commit/d42a3a393396a0f4e9085cda46e0af2c12b63cb1))
# [1.4.0-dev.2](https://github.com/ReVanced/revanced-api/compare/v1.4.0-dev.1...v1.4.0-dev.2) (2024-11-01)
### Bug Fixes
* Add missing logging level environment variable to .env.example ([3b62120](https://github.com/ReVanced/revanced-api/commit/3b6212065a5cfb95c303b6d0551747ba1eb317f6))
### Features
* Add URL and use friendly name for `APIContributable` ([a5498ab](https://github.com/ReVanced/revanced-api/commit/a5498aba2b99db89c28a65738cc58cc4c852c327))
* Make backend configurable ([f91f3a6](https://github.com/ReVanced/revanced-api/commit/f91f3a65c5e07b5b58ccbff1d4b0a5ba9b15fc50))
* Remove ReVanced Integrations ([f1c1092](https://github.com/ReVanced/revanced-api/commit/f1c10928ae3be1c6b1d675819755b3046fad70d8))
# [1.4.0-dev.1](https://github.com/ReVanced/revanced-api/compare/v1.3.0...v1.4.0-dev.1) (2024-11-01) # [1.4.0-dev.1](https://github.com/ReVanced/revanced-api/compare/v1.3.0...v1.4.0-dev.1) (2024-11-01)

View File

@@ -68,7 +68,8 @@ API server for ReVanced.
## ❓ About ## ❓ About
ReVanced API is a server that is used as the backend for ReVanced. ReVanced API is a server that is used as the backend for ReVanced.
ReVanced API acts as the data source for [ReVanced Website](https://github.com/ReVanced/revanced-website) and powers [ReVanced Manager](https://github.com/ReVanced/revanced-manager) ReVanced API acts as the data source for [ReVanced Website](https://github.com/ReVanced/revanced-website) and
powers [ReVanced Manager](https://github.com/ReVanced/revanced-manager)
with updates and ReVanced Patches. with updates and ReVanced Patches.
## 💪 Features ## 💪 Features
@@ -77,7 +78,7 @@ Some of the features ReVanced API include:
- 📢 **Announcements**: Post and get announcements - 📢 **Announcements**: Post and get announcements
- **About**: Get more information such as a description, ways to donate to, - **About**: Get more information such as a description, ways to donate to,
and links of the hoster of ReVanced API and links of the hoster of ReVanced API
- 🧩 **Patches**: Get the latest updates of ReVanced Patches, directly from ReVanced API - 🧩 **Patches**: Get the latest updates of ReVanced Patches, directly from ReVanced API
- 👥 **Contributors**: List all contributors involved in the project - 👥 **Contributors**: List all contributors involved in the project
- 🔄 **Backwards compatibility**: Proxy an old API for migration purposes and backwards compatibility - 🔄 **Backwards compatibility**: Proxy an old API for migration purposes and backwards compatibility
@@ -90,7 +91,8 @@ ReVanced API can be deployed as a Docker container or used standalone.
To deploy ReVanced API as a Docker container, you can use Docker Compose or Docker CLI. To deploy ReVanced API as a Docker container, you can use Docker Compose or Docker CLI.
The Docker image is published on GitHub Container registry, The Docker image is published on GitHub Container registry,
so before you can pull the image, you need to [authenticate to the Container registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-to-the-container-registry). so before you can pull the image, you need
to [authenticate to the Container registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-to-the-container-registry).
### 🗄️ Docker Compose ### 🗄️ Docker Compose
@@ -114,8 +116,6 @@ so before you can pull the image, you need to [authenticate to the Container reg
-v $(pwd)/configuration.toml:/app/configuration.toml \ -v $(pwd)/configuration.toml:/app/configuration.toml \
# Mount the patches public key # Mount the patches public key
-v $(pwd)/patches-public-key.asc:/app/patches-public-key.asc \ -v $(pwd)/patches-public-key.asc:/app/patches-public-key.asc \
# Mount the integrations public key
-v $(pwd)/integrations-public-key.asc:/app/integrations-public-key.asc \
# Mount the static folder # Mount the static folder
-v $(pwd)/static:/app/static \ -v $(pwd)/static:/app/static \
# Mount the about.json file # Mount the about.json file
@@ -141,7 +141,7 @@ A Java Runtime Environment (JRE) must be installed.
1. [Download](https://github.com/ReVanced/revanced-api/releases/latest) ReVanced API to a folder 1. [Download](https://github.com/ReVanced/revanced-api/releases/latest) ReVanced API to a folder
2. In the same folder, create an `.env` file using [.env.example](.env.example) as a template 2. In the same folder, create an `.env` file using [.env.example](.env.example) as a template
3. In the same folder, create a `configuration.toml` file 3. In the same folder, create a `configuration.toml` file
using [configuration.example.toml](configuration.example.toml) as a template using [configuration.example.toml](configuration.example.toml) as a template
4. In the same folder, create an `about.json` file using [about.example.json](about.example.json) as a template 4. In the same folder, create an `about.json` file using [about.example.json](about.example.json) as a template
5. Run `java -jar revanced-api.jar start` to start the server 5. Run `java -jar revanced-api.jar start` to start the server
@@ -159,7 +159,8 @@ A Java Development Kit (JDK) and Git must be installed.
### 📙 Contributing ### 📙 Contributing
Thank you for considering contributing to ReVanced API. You can find the contribution guidelines [here](CONTRIBUTING.md). Thank you for considering contributing to ReVanced API. You can find the contribution
guidelines [here](CONTRIBUTING.md).
### 🛠️ Building ### 🛠️ Building

View File

@@ -1,15 +1,3 @@
organization = "revanced"
patches = { repository = "revanced-patches", asset-regex = "jar$", signature-asset-regex = "asc$", public-key-file = "patches-public-key.asc", public-key-id = 0 }
integrations = { repository = "revanced-integrations", asset-regex = "apk$", signature-asset-regex = "asc$", public-key-file = "integrations-public-key.asc", public-key-id = 0 }
manager = { repository = "revanced-manager", asset-regex = "apk$" }
contributors-repositories = [
"revanced-patcher",
"revanced-patches",
"revanced-integrations",
"revanced-website",
"revanced-cli",
"revanced-manager",
]
api-version = 1 api-version = 1
cors-allowed-hosts = [ cors-allowed-hosts = [
"revanced.app", "revanced.app",
@@ -19,4 +7,24 @@ endpoint = "https://api.revanced.app"
old-api-endpoint = "https://old-api.revanced.app" old-api-endpoint = "https://old-api.revanced.app"
static-files-path = "static/root" static-files-path = "static/root"
versioned-static-files-path = "static/versioned" versioned-static-files-path = "static/versioned"
backend-service-name = "GitHub"
about-json-file-path = "about.json" about-json-file-path = "about.json"
organization = "revanced"
[patches]
repository = "revanced-patches"
asset-regex = "rvp$"
signature-asset-regex = "asc$"
public-key-file = "static/root/keys.asc"
public-key-id = 3897925568445097277
[manager]
repository = "revanced-manager"
asset-regex = "apk$"
[contributors-repositories]
revanced-patcher = "ReVanced Patcher"
revanced-patches = "ReVanced Patches"
revanced-website = "ReVanced Website"
revanced-cli = "ReVanced CLI"
revanced-manager = "ReVanced Manager"

View File

@@ -7,7 +7,6 @@ services:
- /data/revanced-api/.env:/app/.env - /data/revanced-api/.env:/app/.env
- /data/revanced-api/configuration.toml:/app/configuration.toml - /data/revanced-api/configuration.toml:/app/configuration.toml
- /data/revanced-api/patches-public-key.asc:/app/patches-public-key.asc - /data/revanced-api/patches-public-key.asc:/app/patches-public-key.asc
- /data/revanced-api/integrations-public-key.asc:/app/integrations-public-key.asc
- /data/revanced-api/static:/app/static - /data/revanced-api/static:/app/static
- /data/revanced-api/about.json:/app/about.json - /data/revanced-api/about.json:/app/about.json
environment: environment:

View File

@@ -1,4 +1,4 @@
org.gradle.parallel = true org.gradle.parallel = true
org.gradle.caching = true org.gradle.caching = true
kotlin.code.style = official kotlin.code.style = official
version = 1.4.0-dev.1 version = 1.4.0-dev.3

View File

@@ -5,101 +5,39 @@ import app.revanced.api.configuration.repository.BackendRepository
import app.revanced.api.configuration.repository.ConfigurationRepository import app.revanced.api.configuration.repository.ConfigurationRepository
import app.revanced.api.configuration.repository.GitHubBackendRepository import app.revanced.api.configuration.repository.GitHubBackendRepository
import app.revanced.api.configuration.services.* import app.revanced.api.configuration.services.*
import app.revanced.api.configuration.services.AnnouncementService
import app.revanced.api.configuration.services.ApiService
import app.revanced.api.configuration.services.AuthenticationService
import app.revanced.api.configuration.services.OldApiService
import app.revanced.api.configuration.services.PatchesService
import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.Toml
import com.akuleshov7.ktoml.source.decodeFromStream import com.akuleshov7.ktoml.source.decodeFromStream
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.auth.*
import io.ktor.client.plugins.auth.providers.*
import io.ktor.client.plugins.cache.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.resources.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.* import io.ktor.server.application.*
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.koin.core.module.dsl.singleOf import org.koin.core.module.dsl.singleOf
import org.koin.core.parameter.parameterArrayOf
import org.koin.dsl.module import org.koin.dsl.module
import org.koin.ktor.plugin.Koin import org.koin.ktor.plugin.Koin
import java.io.File import java.io.File
@OptIn(ExperimentalSerializationApi::class)
fun Application.configureDependencies( fun Application.configureDependencies(
configFile: File, configFile: File,
) { ) {
val miscModule = module {
factory { params ->
val defaultRequestUri: String = params.get<String>()
val configBlock = params.getOrNull<(HttpClientConfig<OkHttpConfig>.() -> Unit)>() ?: {}
HttpClient(OkHttp) {
defaultRequest { url(defaultRequestUri) }
configBlock()
}
}
}
val repositoryModule = module { val repositoryModule = module {
single<BackendRepository> { single<ConfigurationRepository> { Toml.decodeFromStream(configFile.inputStream()) }
GitHubBackendRepository(
get {
val defaultRequestUri = "https://api.github.com"
val configBlock: HttpClientConfig<OkHttpConfig>.() -> Unit = {
install(HttpCache)
install(Resources)
install(ContentNegotiation) {
json(
Json {
ignoreUnknownKeys = true
namingStrategy = JsonNamingStrategy.SnakeCase
},
)
}
System.getProperty("BACKEND_API_TOKEN")?.let {
install(Auth) {
bearer {
loadTokens {
BearerTokens(
accessToken = it,
refreshToken = "", // Required dummy value
)
}
sendWithoutRequest { true }
}
}
}
}
parameterArrayOf(defaultRequestUri, configBlock)
},
)
}
single<ConfigurationRepository> {
Toml.decodeFromStream(configFile.inputStream())
}
single { single {
TransactionManager.defaultDatabase = Database.connect( Database.connect(
url = System.getProperty("DB_URL"), url = System.getProperty("DB_URL"),
user = System.getProperty("DB_USER"), user = System.getProperty("DB_USER"),
password = System.getProperty("DB_PASSWORD"), password = System.getProperty("DB_PASSWORD"),
) )
}
singleOf(::AnnouncementRepository)
singleOf(::GitHubBackendRepository)
single<BackendRepository> {
val backendServices = mapOf(
GitHubBackendRepository.SERVICE_NAME to { get<GitHubBackendRepository>() },
// Implement more backend services here.
)
AnnouncementRepository() val configuration = get<ConfigurationRepository>()
val backendFactory = backendServices[configuration.backendServiceName]!!
backendFactory()
} }
} }
@@ -113,15 +51,7 @@ fun Application.configureDependencies(
AuthenticationService(issuer, validityInMin, jwtSecret, authSHA256DigestString) AuthenticationService(issuer, validityInMin, jwtSecret, authSHA256DigestString)
} }
single { singleOf(::OldApiService)
val configuration = get<ConfigurationRepository>()
OldApiService(
get {
parameterArrayOf(configuration.oldApiEndpoint)
},
)
}
singleOf(::AnnouncementService) singleOf(::AnnouncementService)
singleOf(::SignatureService) singleOf(::SignatureService)
singleOf(::PatchesService) singleOf(::PatchesService)
@@ -131,7 +61,6 @@ fun Application.configureDependencies(
install(Koin) { install(Koin) {
modules( modules(
miscModule,
repositoryModule, repositoryModule,
serviceModule, serviceModule,
) )

View File

@@ -17,7 +17,7 @@ import org.jetbrains.exposed.sql.kotlin.datetime.datetime
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import java.time.LocalDateTime import java.time.LocalDateTime
internal class AnnouncementRepository { internal class AnnouncementRepository(private val database: Database) {
// This is better than doing a maxByOrNull { it.id } on every request. // This is better than doing a maxByOrNull { it.id } on every request.
private var latestAnnouncement: Announcement? = null private var latestAnnouncement: Announcement? = null
private val latestAnnouncementByTag = mutableMapOf<Int, Announcement>() private val latestAnnouncementByTag = mutableMapOf<Int, Announcement>()
@@ -187,7 +187,7 @@ internal class AnnouncementRepository {
} }
private suspend fun <T> transaction(statement: suspend Transaction.() -> T) = private suspend fun <T> transaction(statement: suspend Transaction.() -> T) =
newSuspendedTransaction(Dispatchers.IO, statement = statement) newSuspendedTransaction(Dispatchers.IO, database, statement = statement)
private object Announcements : IntIdTable() { private object Announcements : IntIdTable() {
val author = varchar("author", 32).nullable() val author = varchar("author", 32).nullable()

View File

@@ -1,16 +1,59 @@
package app.revanced.api.configuration.repository package app.revanced.api.configuration.repository
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.auth.*
import io.ktor.client.plugins.auth.providers.*
import io.ktor.client.plugins.cache.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.resources.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
/** /**
* The backend of the API used to get data. * The backend of the API used to get data.
* *
* @param client The HTTP client to use for requests. * @param defaultRequestUri The URI to use for requests.
* @param website The site of the backend users can visit.
*/ */
abstract class BackendRepository internal constructor( abstract class BackendRepository internal constructor(
protected val client: HttpClient, defaultRequestUri: String,
internal val website: String,
) { ) {
protected val client: HttpClient = HttpClient(OkHttp) {
defaultRequest { url(defaultRequestUri) }
install(HttpCache)
install(Resources)
install(ContentNegotiation) {
json(
Json {
ignoreUnknownKeys = true
@Suppress("OPT_IN_USAGE")
namingStrategy = JsonNamingStrategy.SnakeCase
},
)
}
System.getProperty("BACKEND_API_TOKEN")?.let {
install(Auth) {
bearer {
loadTokens {
BearerTokens(
accessToken = it,
refreshToken = "", // Required dummy value
)
}
sendWithoutRequest { true }
}
}
}
}
/** /**
* A user. * A user.
* *
@@ -153,7 +196,10 @@ abstract class BackendRepository internal constructor(
* @param repository The name of the repository. * @param repository The name of the repository.
* @return The contributors. * @return The contributors.
*/ */
abstract suspend fun contributors(owner: String, repository: String): List<BackendOrganization.BackendRepository.BackendContributor> abstract suspend fun contributors(
owner: String,
repository: String,
): List<BackendOrganization.BackendRepository.BackendContributor>
/** /**
* Get the members of an organization. * Get the members of an organization.

View File

@@ -22,11 +22,11 @@ import kotlin.io.path.createDirectories
/** /**
* The repository storing the configuration for the API. * The repository storing the configuration for the API.
* *
* @property organization The API backends organization name where the repositories for the patches and integrations are. * @property organization The API backends organization name where the repositories are.
* @property patches The source of the patches. * @property patches The source of the patches.
* @property integrations The source of the integrations.
* @property manager The source of the manager. * @property manager The source of the manager.
* @property contributorsRepositoryNames The names of the repositories to get contributors from. * @property contributorsRepositoryNames The friendly name of repos mapped to the repository names to get contributors from.
* @property backendServiceName The name of the backend service to use for the repositories, contributors, etc.
* @property apiVersion The version to use for the API. * @property apiVersion The version to use for the API.
* @property corsAllowedHosts The hosts allowed to make requests to the API. * @property corsAllowedHosts The hosts allowed to make requests to the API.
* @property endpoint The endpoint of the API. * @property endpoint The endpoint of the API.
@@ -40,10 +40,11 @@ import kotlin.io.path.createDirectories
internal class ConfigurationRepository( internal class ConfigurationRepository(
val organization: String, val organization: String,
val patches: SignedAssetConfiguration, val patches: SignedAssetConfiguration,
val integrations: SignedAssetConfiguration,
val manager: AssetConfiguration, val manager: AssetConfiguration,
@SerialName("contributors-repositories") @SerialName("contributors-repositories")
val contributorsRepositoryNames: Set<String>, val contributorsRepositoryNames: Map<String, String>,
@SerialName("backend-service-name")
val backendServiceName: String,
@SerialName("api-version") @SerialName("api-version")
val apiVersion: Int = 1, val apiVersion: Int = 1,
@SerialName("cors-allowed-hosts") @SerialName("cors-allowed-hosts")

View File

@@ -8,18 +8,19 @@ import app.revanced.api.configuration.repository.GitHubOrganization.GitHubReposi
import app.revanced.api.configuration.repository.GitHubOrganization.GitHubRepository.GitHubRelease import app.revanced.api.configuration.repository.GitHubOrganization.GitHubRepository.GitHubRelease
import app.revanced.api.configuration.repository.Organization.Repository.Contributors import app.revanced.api.configuration.repository.Organization.Repository.Contributors
import app.revanced.api.configuration.repository.Organization.Repository.Releases import app.revanced.api.configuration.repository.Organization.Repository.Releases
import io.ktor.client.*
import io.ktor.client.call.* import io.ktor.client.call.*
import io.ktor.client.plugins.resources.* import io.ktor.client.plugins.resources.*
import io.ktor.resources.* import io.ktor.resources.*
import kotlinx.coroutines.* import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime import kotlinx.datetime.toLocalDateTime
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) { class GitHubBackendRepository : BackendRepository("https://api.github.com", "https://github.com") {
override suspend fun release( override suspend fun release(
owner: String, owner: String,
repository: String, repository: String,
@@ -67,7 +68,8 @@ class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) {
override suspend fun members(organization: String): List<BackendMember> { override suspend fun members(organization: String): List<BackendMember> {
// Get the list of members of the organization. // Get the list of members of the organization.
val publicMembers: List<GitHubOrganization.GitHubMember> = client.get(Organization.PublicMembers(organization)).body() val publicMembers: List<GitHubOrganization.GitHubMember> =
client.get(Organization.PublicMembers(organization)).body()
return coroutineScope { return coroutineScope {
publicMembers.map { member -> publicMembers.map { member ->
@@ -113,6 +115,10 @@ class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) {
reset = Instant.fromEpochSeconds(rateLimit.rate.reset).toLocalDateTime(TimeZone.UTC), reset = Instant.fromEpochSeconds(rateLimit.rate.reset).toLocalDateTime(TimeZone.UTC),
) )
} }
companion object {
const val SERVICE_NAME = "GitHub"
}
} }
interface IGitHubUser { interface IGitHubUser {

View File

@@ -1,7 +1,6 @@
package app.revanced.api.configuration.routes package app.revanced.api.configuration.routes
import app.revanced.api.configuration.installNotarizedRoute 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.ApiRelease
import app.revanced.api.configuration.schema.ApiReleaseVersion import app.revanced.api.configuration.schema.ApiReleaseVersion
import app.revanced.api.configuration.services.ManagerService import app.revanced.api.configuration.services.ManagerService
@@ -53,7 +52,7 @@ private fun Route.installManagerRouteDocumentation(deprecated: Boolean) = instal
description("The latest manager release") description("The latest manager release")
mediaTypes("application/json") mediaTypes("application/json")
responseCode(HttpStatusCode.OK) responseCode(HttpStatusCode.OK)
responseType<ApiRelease<ApiManagerAsset>>() responseType<ApiRelease>()
} }
} }
} }

View File

@@ -2,8 +2,7 @@ package app.revanced.api.configuration.routes
import app.revanced.api.configuration.installCache import app.revanced.api.configuration.installCache
import app.revanced.api.configuration.installNotarizedRoute import app.revanced.api.configuration.installNotarizedRoute
import app.revanced.api.configuration.schema.ApiAssetPublicKeys import app.revanced.api.configuration.schema.ApiAssetPublicKey
import app.revanced.api.configuration.schema.ApiPatchesAsset
import app.revanced.api.configuration.schema.ApiRelease import app.revanced.api.configuration.schema.ApiRelease
import app.revanced.api.configuration.schema.ApiReleaseVersion import app.revanced.api.configuration.schema.ApiReleaseVersion
import app.revanced.api.configuration.services.PatchesService import app.revanced.api.configuration.services.PatchesService
@@ -61,7 +60,7 @@ private fun Route.configure(deprecated: Boolean = false) {
installPatchesPublicKeyRouteDocumentation(deprecated) installPatchesPublicKeyRouteDocumentation(deprecated)
get { get {
call.respond(patchesService.publicKeys()) call.respond(patchesService.publicKey())
} }
} }
} }
@@ -78,7 +77,7 @@ private fun Route.installPatchesRouteDocumentation(deprecated: Boolean) = instal
description("The current patches release") description("The current patches release")
mediaTypes("application/json") mediaTypes("application/json")
responseCode(HttpStatusCode.OK) responseCode(HttpStatusCode.OK)
responseType<ApiRelease<ApiPatchesAsset>>() responseType<ApiRelease>()
} }
} }
} }
@@ -120,13 +119,13 @@ private fun Route.installPatchesPublicKeyRouteDocumentation(deprecated: Boolean)
get = GetInfo.builder { get = GetInfo.builder {
if (deprecated) isDeprecated() if (deprecated) isDeprecated()
description("Get the public keys for verifying patches and integrations assets") description("Get the public keys for verifying patches assets")
summary("Get patches and integrations public keys") summary("Get patches public keys")
response { response {
description("The public keys") description("The public keys")
mediaTypes("application/json") mediaTypes("application/json")
responseCode(HttpStatusCode.OK) responseCode(HttpStatusCode.OK)
responseType<ApiAssetPublicKeys>() responseType<ApiAssetPublicKey>()
} }
} }
} }

View File

@@ -35,38 +35,20 @@ class ApiContributor(
@Serializable @Serializable
class APIContributable( class APIContributable(
val name: String, val name: String,
val url: String,
// 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 contributors: List<ApiContributor>, val contributors: List<ApiContributor>,
) )
@Serializable @Serializable
class ApiRelease<T>( class ApiRelease(
val version: String, val version: String,
val createdAt: LocalDateTime, val createdAt: LocalDateTime,
val description: String, val description: String,
// Using a list instead of a set because set semantics are unnecessary here.
val assets: List<T>,
)
@Serializable
class ApiManagerAsset(
val downloadUrl: String, val downloadUrl: String,
val signatureDownloadUrl: String? = null,
) )
@Serializable
class ApiPatchesAsset(
val downloadUrl: String,
val signatureDownloadUrl: String,
// TODO: Remove this eventually when integrations are merged into patches.
val name: ApiAssetName,
)
@Serializable
enum class ApiAssetName {
PATCHES,
INTEGRATION,
}
@Serializable @Serializable
class ApiReleaseVersion( class ApiReleaseVersion(
val version: String, val version: String,
@@ -124,9 +106,8 @@ class ApiRateLimit(
) )
@Serializable @Serializable
class ApiAssetPublicKeys( class ApiAssetPublicKey(
val patchesPublicKey: String, val patchesPublicKey: String,
val integrationsPublicKey: String,
) )
@Serializable @Serializable

View File

@@ -3,6 +3,7 @@ package app.revanced.api.configuration.services
import app.revanced.api.configuration.repository.BackendRepository import app.revanced.api.configuration.repository.BackendRepository
import app.revanced.api.configuration.repository.ConfigurationRepository import app.revanced.api.configuration.repository.ConfigurationRepository
import app.revanced.api.configuration.schema.* import app.revanced.api.configuration.schema.*
import io.ktor.http.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
@@ -16,11 +17,15 @@ internal class ApiService(
val about = configurationRepository.about val about = configurationRepository.about
suspend fun contributors() = withContext(Dispatchers.IO) { suspend fun contributors() = withContext(Dispatchers.IO) {
configurationRepository.contributorsRepositoryNames.map { configurationRepository.contributorsRepositoryNames.map { (repository, name) ->
async { async {
APIContributable( APIContributable(
it, name,
backendRepository.contributors(configurationRepository.organization, it).map { URLBuilder().apply {
takeFrom(backendRepository.website)
path(configurationRepository.organization, repository)
}.buildString(),
backendRepository.contributors(configurationRepository.organization, repository).map {
ApiContributor(it.name, it.avatarUrl, it.url, it.contributions) ApiContributor(it.name, it.avatarUrl, it.url, it.contributions)
}, },
) )

View File

@@ -3,27 +3,24 @@ package app.revanced.api.configuration.services
import app.revanced.api.configuration.repository.BackendRepository 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.BackendRepository.BackendOrganization.BackendRepository.BackendRelease.Companion.first
import app.revanced.api.configuration.repository.ConfigurationRepository import app.revanced.api.configuration.repository.ConfigurationRepository
import app.revanced.api.configuration.schema.* import app.revanced.api.configuration.schema.ApiRelease
import app.revanced.api.configuration.schema.ApiReleaseVersion
internal class ManagerService( internal class ManagerService(
private val backendRepository: BackendRepository, private val backendRepository: BackendRepository,
private val configurationRepository: ConfigurationRepository, private val configurationRepository: ConfigurationRepository,
) { ) {
suspend fun latestRelease(): ApiRelease<ApiManagerAsset> { suspend fun latestRelease(): ApiRelease {
val managerRelease = backendRepository.release( val managerRelease = backendRepository.release(
configurationRepository.organization, configurationRepository.organization,
configurationRepository.manager.repository, configurationRepository.manager.repository,
) )
val managerAsset = ApiManagerAsset(
managerRelease.assets.first(configurationRepository.manager.assetRegex).downloadUrl,
)
return ApiRelease( return ApiRelease(
managerRelease.tag, managerRelease.tag,
managerRelease.createdAt, managerRelease.createdAt,
managerRelease.releaseNote, managerRelease.releaseNote,
listOf(managerAsset), managerRelease.assets.first(configurationRepository.manager.assetRegex).downloadUrl,
) )
} }

View File

@@ -1,6 +1,9 @@
package app.revanced.api.configuration.services package app.revanced.api.configuration.services
import app.revanced.api.configuration.repository.ConfigurationRepository
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.*
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.statement.* import io.ktor.client.statement.*
import io.ktor.http.* import io.ktor.http.*
@@ -11,7 +14,11 @@ import io.ktor.server.response.*
import io.ktor.util.* import io.ktor.util.*
import io.ktor.utils.io.* import io.ktor.utils.io.*
internal class OldApiService(private val client: HttpClient) { internal class OldApiService(configurationRepository: ConfigurationRepository) {
private val client = HttpClient(OkHttp) {
defaultRequest { url(configurationRepository.oldApiEndpoint) }
}
@OptIn(InternalAPI::class) @OptIn(InternalAPI::class)
suspend fun proxy(call: ApplicationCall) { suspend fun proxy(call: ApplicationCall) {
val channel = call.request.receiveChannel() val channel = call.request.receiveChannel()

View File

@@ -3,7 +3,9 @@ package app.revanced.api.configuration.services
import app.revanced.api.configuration.repository.BackendRepository 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.BackendRepository.BackendOrganization.BackendRepository.BackendRelease.Companion.first
import app.revanced.api.configuration.repository.ConfigurationRepository import app.revanced.api.configuration.repository.ConfigurationRepository
import app.revanced.api.configuration.schema.* import app.revanced.api.configuration.schema.ApiAssetPublicKey
import app.revanced.api.configuration.schema.ApiRelease
import app.revanced.api.configuration.schema.ApiReleaseVersion
import app.revanced.library.serializeTo import app.revanced.library.serializeTo
import app.revanced.patcher.patch.loadPatchesFromJar import app.revanced.patcher.patch.loadPatchesFromJar
import com.github.benmanes.caffeine.cache.Caffeine import com.github.benmanes.caffeine.cache.Caffeine
@@ -17,40 +19,18 @@ internal class PatchesService(
private val backendRepository: BackendRepository, private val backendRepository: BackendRepository,
private val configurationRepository: ConfigurationRepository, private val configurationRepository: ConfigurationRepository,
) { ) {
suspend fun latestRelease(): ApiRelease<ApiPatchesAsset> { suspend fun latestRelease(): ApiRelease {
val patchesRelease = backendRepository.release( val patchesRelease = backendRepository.release(
configurationRepository.organization, configurationRepository.organization,
configurationRepository.patches.repository, configurationRepository.patches.repository,
) )
val integrationsRelease = backendRepository.release(
configurationRepository.organization,
configurationRepository.integrations.repository,
)
fun ConfigurationRepository.SignedAssetConfiguration.asset(
release: BackendRepository.BackendOrganization.BackendRepository.BackendRelease,
assetName: ApiAssetName,
) = ApiPatchesAsset(
release.assets.first(assetRegex).downloadUrl,
release.assets.first(signatureAssetRegex).downloadUrl,
assetName,
)
val patchesAsset = configurationRepository.patches.asset(
patchesRelease,
ApiAssetName.PATCHES,
)
val integrationsAsset = configurationRepository.integrations.asset(
integrationsRelease,
ApiAssetName.INTEGRATION,
)
return ApiRelease( return ApiRelease(
patchesRelease.tag, patchesRelease.tag,
patchesRelease.createdAt, patchesRelease.createdAt,
patchesRelease.releaseNote, patchesRelease.releaseNote,
listOf(patchesAsset, integrationsAsset), patchesRelease.assets.first(configurationRepository.patches.assetRegex).downloadUrl,
patchesRelease.assets.first(configurationRepository.patches.signatureAssetRegex).downloadUrl,
) )
} }
@@ -111,14 +91,5 @@ internal class PatchesService(
} }
} }
fun publicKeys(): ApiAssetPublicKeys { fun publicKey() = ApiAssetPublicKey(configurationRepository.patches.publicKeyFile.readText())
fun readPublicKey(
getSignedAssetConfiguration: ConfigurationRepository.() -> ConfigurationRepository.SignedAssetConfiguration,
) = configurationRepository.getSignedAssetConfiguration().publicKeyFile.readText()
return ApiAssetPublicKeys(
readPublicKey { patches },
readPublicKey { integrations },
)
}
} }

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(2) // Assuming this is enough for patches and integrations. .maximumSize(1) // 1 because currently only the latest patches is needed.
.build<ByteArray, Boolean>() // Hash -> Verified. .build<ByteArray, Boolean>() // Hash -> Verified.
fun verify( fun verify(

View File

@@ -5,7 +5,6 @@ import app.revanced.api.configuration.schema.ApiAnnouncement
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.datetime.toKotlinLocalDateTime import kotlinx.datetime.toKotlinLocalDateTime
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.junit.jupiter.api.* import org.junit.jupiter.api.*
import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertNull
import java.time.LocalDateTime import java.time.LocalDateTime
@@ -18,10 +17,9 @@ private object AnnouncementServiceTest {
@JvmStatic @JvmStatic
@BeforeAll @BeforeAll
fun setUp() { fun setUp() {
TransactionManager.defaultDatabase = val database = Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false")
Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false")
announcementService = AnnouncementService(AnnouncementRepository()) announcementService = AnnouncementService(AnnouncementRepository(database))
} }
@BeforeEach @BeforeEach