From f91f3a65c5e07b5b58ccbff1d4b0a5ba9b15fc50 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Fri, 1 Nov 2024 03:09:11 +0100 Subject: [PATCH] feat: Make backend configurable --- configuration.example.toml | 1 + .../api/configuration/Dependencies.kt | 101 +++--------------- .../repository/AnnouncementRepository.kt | 4 +- .../repository/BackendRepository.kt | 52 ++++++++- .../repository/ConfigurationRepository.kt | 3 + .../repository/GitHubBackendRepository.kt | 14 ++- .../configuration/services/OldApiService.kt | 9 +- .../services/AnnouncementServiceTest.kt | 6 +- 8 files changed, 90 insertions(+), 100 deletions(-) diff --git a/configuration.example.toml b/configuration.example.toml index fec352c..3bd92cd 100644 --- a/configuration.example.toml +++ b/configuration.example.toml @@ -20,3 +20,4 @@ old-api-endpoint = "https://old-api.revanced.app" static-files-path = "static/root" versioned-static-files-path = "static/versioned" about-json-file-path = "about.json" +backend-service-name = "GitHub" diff --git a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt index 451ae73..71a0d21 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt @@ -5,101 +5,39 @@ import app.revanced.api.configuration.repository.BackendRepository import app.revanced.api.configuration.repository.ConfigurationRepository import app.revanced.api.configuration.repository.GitHubBackendRepository 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.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 kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonNamingStrategy import org.jetbrains.exposed.sql.Database -import org.jetbrains.exposed.sql.transactions.TransactionManager import org.koin.core.module.dsl.singleOf -import org.koin.core.parameter.parameterArrayOf import org.koin.dsl.module import org.koin.ktor.plugin.Koin import java.io.File -@OptIn(ExperimentalSerializationApi::class) fun Application.configureDependencies( configFile: File, ) { - val miscModule = module { - factory { params -> - val defaultRequestUri: String = params.get() - val configBlock = params.getOrNull<(HttpClientConfig.() -> Unit)>() ?: {} - - HttpClient(OkHttp) { - defaultRequest { url(defaultRequestUri) } - - configBlock() - } - } - } - val repositoryModule = module { - single { - GitHubBackendRepository( - get { - val defaultRequestUri = "https://api.github.com" - val configBlock: HttpClientConfig.() -> 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 { - Toml.decodeFromStream(configFile.inputStream()) - } - + single { Toml.decodeFromStream(configFile.inputStream()) } single { - TransactionManager.defaultDatabase = Database.connect( + Database.connect( url = System.getProperty("DB_URL"), user = System.getProperty("DB_USER"), password = System.getProperty("DB_PASSWORD"), ) + } + singleOf(::AnnouncementRepository) + singleOf(::GitHubBackendRepository) + single { + val backendServices = mapOf( + GitHubBackendRepository.SERVICE_NAME to { get() }, + // Implement more backend services here. + ) - AnnouncementRepository() + val configuration = get() + val backendFactory = backendServices[configuration.backendServiceName]!! + + backendFactory() } } @@ -113,15 +51,7 @@ fun Application.configureDependencies( AuthenticationService(issuer, validityInMin, jwtSecret, authSHA256DigestString) } - single { - val configuration = get() - - OldApiService( - get { - parameterArrayOf(configuration.oldApiEndpoint) - }, - ) - } + singleOf(::OldApiService) singleOf(::AnnouncementService) singleOf(::SignatureService) singleOf(::PatchesService) @@ -131,7 +61,6 @@ fun Application.configureDependencies( install(Koin) { modules( - miscModule, repositoryModule, serviceModule, ) diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt index 3f6ff6f..d7ce97b 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt @@ -17,7 +17,7 @@ import org.jetbrains.exposed.sql.kotlin.datetime.datetime import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction 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. private var latestAnnouncement: Announcement? = null private val latestAnnouncementByTag = mutableMapOf() @@ -187,7 +187,7 @@ internal class AnnouncementRepository { } private suspend fun transaction(statement: suspend Transaction.() -> T) = - newSuspendedTransaction(Dispatchers.IO, statement = statement) + newSuspendedTransaction(Dispatchers.IO, database, statement = statement) private object Announcements : IntIdTable() { val author = varchar("author", 32).nullable() diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt index 69429ca..85d10bb 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt @@ -1,16 +1,59 @@ package app.revanced.api.configuration.repository 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.serialization.json.Json +import kotlinx.serialization.json.JsonNamingStrategy /** * 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( - 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. * @@ -153,7 +196,10 @@ abstract class BackendRepository internal constructor( * @param repository The name of the repository. * @return The contributors. */ - abstract suspend fun contributors(owner: String, repository: String): List + abstract suspend fun contributors( + owner: String, + repository: String, + ): List /** * Get the members of an organization. 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 65b82f6..9aebfa5 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt @@ -27,6 +27,7 @@ import kotlin.io.path.createDirectories * @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 backendServiceName The name of the backend service to use for the repositories, contributors, etc. * @property apiVersion The version to use for the API. * @property corsAllowedHosts The hosts allowed to make requests to the API. * @property endpoint The endpoint of the API. @@ -44,6 +45,8 @@ internal class ConfigurationRepository( val manager: AssetConfiguration, @SerialName("contributors-repositories") val contributorsRepositoryNames: Set, + @SerialName("backend-service-name") + val backendServiceName: String, @SerialName("api-version") val apiVersion: Int = 1, @SerialName("cors-allowed-hosts") diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt index 39d0236..420ccaf 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt @@ -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.Organization.Repository.Contributors import app.revanced.api.configuration.repository.Organization.Repository.Releases -import io.ktor.client.* import io.ktor.client.call.* import io.ktor.client.plugins.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.TimeZone import kotlinx.datetime.toLocalDateTime import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) { +class GitHubBackendRepository : BackendRepository("https://api.github.com", "https://github.com") { override suspend fun release( owner: String, repository: String, @@ -67,7 +68,8 @@ class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) { override suspend fun members(organization: String): List { // Get the list of members of the organization. - val publicMembers: List = client.get(Organization.PublicMembers(organization)).body() + val publicMembers: List = + client.get(Organization.PublicMembers(organization)).body() return coroutineScope { publicMembers.map { member -> @@ -113,6 +115,10 @@ class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) { reset = Instant.fromEpochSeconds(rateLimit.rate.reset).toLocalDateTime(TimeZone.UTC), ) } + + companion object { + const val SERVICE_NAME = "GitHub" + } } interface IGitHubUser { diff --git a/src/main/kotlin/app/revanced/api/configuration/services/OldApiService.kt b/src/main/kotlin/app/revanced/api/configuration/services/OldApiService.kt index 2c464aa..271f6f5 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/OldApiService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/OldApiService.kt @@ -1,6 +1,9 @@ package app.revanced.api.configuration.services +import app.revanced.api.configuration.repository.ConfigurationRepository import io.ktor.client.* +import io.ktor.client.engine.okhttp.* +import io.ktor.client.plugins.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* @@ -11,7 +14,11 @@ import io.ktor.server.response.* import io.ktor.util.* 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) suspend fun proxy(call: ApplicationCall) { val channel = call.request.receiveChannel() diff --git a/src/test/kotlin/app/revanced/api/configuration/services/AnnouncementServiceTest.kt b/src/test/kotlin/app/revanced/api/configuration/services/AnnouncementServiceTest.kt index 4b6e058..3fdf4d6 100644 --- a/src/test/kotlin/app/revanced/api/configuration/services/AnnouncementServiceTest.kt +++ b/src/test/kotlin/app/revanced/api/configuration/services/AnnouncementServiceTest.kt @@ -5,7 +5,6 @@ import app.revanced.api.configuration.schema.ApiAnnouncement import kotlinx.coroutines.runBlocking import kotlinx.datetime.toKotlinLocalDateTime import org.jetbrains.exposed.sql.Database -import org.jetbrains.exposed.sql.transactions.TransactionManager import org.junit.jupiter.api.* import org.junit.jupiter.api.Assertions.assertNull import java.time.LocalDateTime @@ -18,10 +17,9 @@ private object AnnouncementServiceTest { @JvmStatic @BeforeAll fun setUp() { - TransactionManager.defaultDatabase = - Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false") + val database = Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false") - announcementService = AnnouncementService(AnnouncementRepository()) + announcementService = AnnouncementService(AnnouncementRepository(database)) } @BeforeEach