From fa2f8b2f86d2eb5b11196ba868fb95b2b6335a94 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 5 Jun 2024 03:07:28 +0200 Subject: [PATCH] refactor: Refactor into services and repositories --- build.gradle.kts | 29 +-- gradle/libs.versions.toml | 12 +- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 5 +- gradlew | 41 +++- gradlew.bat | 35 +-- .../revanced/api/command/StartAPICommand.kt | 5 +- .../api/configuration/Dependencies.kt | 83 +++++++ .../api/{modules => configuration}/HTTP.kt | 8 +- .../revanced/api/configuration/Security.kt | 9 + .../api/configuration/Serialization.kt | 19 ++ .../api/configuration/routing/Routing.kt | 19 ++ .../routing/routes/Announcements.kt | 86 +++++++ .../configuration/routing/routes/ApiRoute.kt | 41 ++++ .../routing/routes/PatchesRoute.kt | 26 ++ .../app/revanced/api/modules/Dependencies.kt | 75 ------ .../app/revanced/api/modules/Routing.kt | 232 ------------------ .../app/revanced/api/modules/Serialization.kt | 11 - .../AnnouncementRepository.kt} | 151 ++++++------ .../ConfigurationRepository.kt} | 4 +- .../backend/BackendRepository.kt} | 10 +- .../github/GitHubBackendRepository.kt} | 36 +-- .../backend/github/api/Request.kt} | 2 +- .../backend/github/api/Response.kt} | 2 +- .../api/services/AnnouncementService.kt | 35 +++ .../app/revanced/api/services/ApiService.kt | 33 +++ .../Security.kt => services/AuthService.kt} | 10 +- .../revanced/api/services/PatchesService.kt | 87 +++++++ src/main/resources/static/api/about.json | 2 +- .../kotlin/app/revanced/ApplicationTest.kt | 57 ----- 30 files changed, 623 insertions(+), 542 deletions(-) create mode 100644 src/main/kotlin/app/revanced/api/configuration/Dependencies.kt rename src/main/kotlin/app/revanced/api/{modules => configuration}/HTTP.kt (81%) create mode 100644 src/main/kotlin/app/revanced/api/configuration/Security.kt create mode 100644 src/main/kotlin/app/revanced/api/configuration/Serialization.kt create mode 100644 src/main/kotlin/app/revanced/api/configuration/routing/Routing.kt create mode 100644 src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt create mode 100644 src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt create mode 100644 src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt delete mode 100644 src/main/kotlin/app/revanced/api/modules/Dependencies.kt delete mode 100644 src/main/kotlin/app/revanced/api/modules/Routing.kt delete mode 100644 src/main/kotlin/app/revanced/api/modules/Serialization.kt rename src/main/kotlin/app/revanced/api/{modules/Database.kt => repository/AnnouncementRepository.kt} (52%) rename src/main/kotlin/app/revanced/api/{schema/ConfigurationSchema.kt => repository/ConfigurationRepository.kt} (85%) rename src/main/kotlin/app/revanced/api/{backend/Backend.kt => repository/backend/BackendRepository.kt} (91%) rename src/main/kotlin/app/revanced/api/{backend/github/GitHubBackend.kt => repository/backend/github/GitHubBackendRepository.kt} (67%) rename src/main/kotlin/app/revanced/api/{backend/github/api/RequestResource.kt => repository/backend/github/api/Request.kt} (93%) rename src/main/kotlin/app/revanced/api/{backend/github/api/ResponseSchema.kt => repository/backend/github/api/Response.kt} (96%) create mode 100644 src/main/kotlin/app/revanced/api/services/AnnouncementService.kt create mode 100644 src/main/kotlin/app/revanced/api/services/ApiService.kt rename src/main/kotlin/app/revanced/api/{modules/Security.kt => services/AuthService.kt} (87%) create mode 100644 src/main/kotlin/app/revanced/api/services/PatchesService.kt delete mode 100644 src/test/kotlin/app/revanced/ApplicationTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index c9b481b..a62319d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,21 +11,11 @@ tasks { expand("projectVersion" to project.version) } - /* - Dummy task to hack gradle-semantic-release-plugin to release this project. - - Explanation: - SemVer is a standard for versioning libraries. - For that reason the semantic-release plugin uses the "publish" task to publish libraries. - However, this subproject is not a library, and the "publish" task is not available for this subproject. - Because semantic-release is not designed to handle this case, we need to hack it. - - RE: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435 - */ + // Needed by gradle-semantic-release-plugin. + // Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435 register("publish") { group = "publishing" - description = "Dummy task to hack gradle-semantic-release-plugin to release ReVanced API" - dependsOn(startShadowScripts) + dependsOn(shadowJar) } } @@ -42,8 +32,15 @@ ktor { repositories { mavenCentral() google() - maven { url = uri("https://jitpack.io") } mavenLocal() + maven { + // A repository must be specified for some reason. "registry" is a dummy. + url = uri("https://maven.pkg.github.com/revanced/registry") + credentials { + username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR") + password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN") + } + } } dependencies { @@ -78,8 +75,4 @@ dependencies { implementation(libs.revanced.patcher) implementation(libs.revanced.library) implementation(libs.caffeine) - - testImplementation(libs.mockk) - testImplementation(libs.ktor.server.tests) - testImplementation(libs.kotlin.test.junit) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f00cb0f..66cbed3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "1.9.22" +kotlin = "2.0.0" logback = "1.4.14" exposed = "0.41.1" h2 = "2.2.224" @@ -7,11 +7,10 @@ koin = "3.5.3" dotenv = "6.4.1" ktor = "2.3.7" ktoml = "0.5.1" -picocli = "4.7.3" +picocli = "4.7.5" datetime = "0.5.0" -mockk = "1.13.9" -revanced-patcher = "19.2.0" -revanced-library = "1.5.0" +revanced-patcher = "19.3.1" +revanced-library = "2.3.0" caffeine = "3.1.8" [libraries] @@ -39,13 +38,10 @@ exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "e exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" } exposed-kotlin-datetime = { module = "org.jetbrains.exposed:exposed-kotlin-datetime", version.ref = "exposed" } dotenv-kotlin = { module = "io.github.cdimascio:dotenv-kotlin", version.ref = "dotenv" } -ktor-server-tests = { module = "io.ktor:ktor-server-tests" } -kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } ktoml-core = { module = "com.akuleshov7:ktoml-core", version.ref = "ktoml" } ktoml-file = { module = "com.akuleshov7:ktoml-file", version.ref = "ktoml" } picocli = { module = "info.picocli:picocli", version.ref = "picocli" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "datetime" } -mockk = { module = "io.mockk:mockk", version.ref = "mockk" } revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" } revanced-library = { module = "app.revanced:revanced-library", version.ref = "revanced-library" } caffeine = { module = "com.github.ben-manes.caffeine:caffeine", version.ref = "caffeine" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch literal 43453 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 Vo0ScuU6DbNL5P=uVd@+Z4*=P9Hgf;~ literal 59536 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�cCgb#m`a|c{XyiMM^|2OP(X?bxKl6T=|ov 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 JgTXZr4*< /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +131,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ @@ -205,6 +214,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index 107acd3..25da30d 100755 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt index b1898ef..585730a 100644 --- a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt +++ b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt @@ -1,6 +1,7 @@ package app.revanced.api.command -import app.revanced.api.modules.* +import app.revanced.api.configuration.* +import app.revanced.api.configuration.routing.configureRouting import io.ktor.server.engine.* import io.ktor.server.netty.* import picocli.CommandLine @@ -27,7 +28,7 @@ internal object StartAPICommand : Runnable { override fun run() { embeddedServer(Netty, port, host) { configureDependencies() - configureHTTP() + configureHTTP(allowedHost = host) configureSerialization() configureSecurity() configureRouting() diff --git a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt new file mode 100644 index 0000000..dc8f620 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt @@ -0,0 +1,83 @@ +package app.revanced.api.configuration + +import app.revanced.api.repository.AnnouncementRepository +import app.revanced.api.repository.ConfigurationRepository +import app.revanced.api.repository.backend.BackendRepository +import app.revanced.api.repository.backend.github.GitHubBackendRepository +import app.revanced.api.services.AnnouncementService +import app.revanced.api.services.ApiService +import app.revanced.api.services.AuthService +import app.revanced.api.services.PatchesService +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.source.decodeFromStream +import io.github.cdimascio.dotenv.Dotenv +import io.ktor.server.application.* +import org.jetbrains.exposed.sql.Database +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.bind +import org.koin.dsl.module +import org.koin.ktor.plugin.Koin +import java.io.File + +fun Application.configureDependencies() { + val globalModule = module { + single { + Dotenv.configure() + .systemProperties() + .load() + } + } + + val repositoryModule = module { + single { + val dotenv = get() + + Database.connect( + url = dotenv["DB_URL"], + user = dotenv["DB_USER"], + password = dotenv["DB_PASSWORD"], + driver = "org.h2.Driver", + ) + } + + single { + val configFilePath = get()["CONFIG_FILE_PATH"] + val configFile = File(configFilePath).inputStream() + + Toml.decodeFromStream(configFile) + } + + singleOf(::AnnouncementRepository) + } + + val serviceModule = module { + single { + val dotenv = get() + + val jwtSecret = dotenv["JWT_SECRET"] + val issuer = dotenv["JWT_ISSUER"] + val validityInMin = dotenv["JWT_VALIDITY_IN_MIN"].toInt() + + val basicUsername = dotenv["BASIC_USERNAME"] + val basicPassword = dotenv["BASIC_PASSWORD"] + + AuthService(issuer, validityInMin, jwtSecret, basicUsername, basicPassword) + } + single { + val token = get()["GITHUB_TOKEN"] + + GitHubBackendRepository(token) + } bind BackendRepository::class + singleOf(::AnnouncementService) + singleOf(::PatchesService) + singleOf(::ApiService) + } + + install(Koin) { + modules( + globalModule, + repositoryModule, + serviceModule, + ) + } +} diff --git a/src/main/kotlin/app/revanced/api/modules/HTTP.kt b/src/main/kotlin/app/revanced/api/configuration/HTTP.kt similarity index 81% rename from src/main/kotlin/app/revanced/api/modules/HTTP.kt rename to src/main/kotlin/app/revanced/api/configuration/HTTP.kt index 1f3248f..336b100 100644 --- a/src/main/kotlin/app/revanced/api/modules/HTTP.kt +++ b/src/main/kotlin/app/revanced/api/configuration/HTTP.kt @@ -1,4 +1,4 @@ -package app.revanced.api.modules +package app.revanced.api.configuration import io.ktor.http.* import io.ktor.http.content.* @@ -8,7 +8,9 @@ import io.ktor.server.plugins.conditionalheaders.* import io.ktor.server.plugins.cors.routing.* import kotlin.time.Duration.Companion.minutes -fun Application.configureHTTP() { +fun Application.configureHTTP( + allowedHost: String, +) { install(ConditionalHeaders) install(CORS) { allowMethod(HttpMethod.Options) @@ -16,7 +18,7 @@ fun Application.configureHTTP() { allowMethod(HttpMethod.Delete) allowMethod(HttpMethod.Patch) allowHeader(HttpHeaders.Authorization) - anyHost() // @TODO: Don't do this in production if possible. Try to limit it. + allowHost(allowedHost) } install(CachingHeaders) { options { _, _ -> CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 5.minutes.inWholeSeconds.toInt())) } diff --git a/src/main/kotlin/app/revanced/api/configuration/Security.kt b/src/main/kotlin/app/revanced/api/configuration/Security.kt new file mode 100644 index 0000000..2543fb1 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/Security.kt @@ -0,0 +1,9 @@ +package app.revanced.api.configuration + +import app.revanced.api.services.AuthService +import io.ktor.server.application.* +import org.koin.ktor.ext.get + +fun Application.configureSecurity() { + get().configureSecurity(this) +} diff --git a/src/main/kotlin/app/revanced/api/configuration/Serialization.kt b/src/main/kotlin/app/revanced/api/configuration/Serialization.kt new file mode 100644 index 0000000..4e9f7ed --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/Serialization.kt @@ -0,0 +1,19 @@ +package app.revanced.api.configuration + +import io.ktor.serialization.kotlinx.json.* +import io.ktor.server.application.* +import io.ktor.server.plugins.contentnegotiation.* +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonNamingStrategy + +@OptIn(ExperimentalSerializationApi::class) +fun Application.configureSerialization() { + install(ContentNegotiation) { + json( + Json { + namingStrategy = JsonNamingStrategy.SnakeCase + }, + ) + } +} diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/Routing.kt b/src/main/kotlin/app/revanced/api/configuration/routing/Routing.kt new file mode 100644 index 0000000..18420b6 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/routing/Routing.kt @@ -0,0 +1,19 @@ +package app.revanced.api.configuration.routing + +import app.revanced.api.configuration.routing.routes.configureAnnouncementsRoute +import app.revanced.api.configuration.routing.routes.configurePatchesRoute +import app.revanced.api.configuration.routing.routes.configureRootRoute +import app.revanced.api.repository.ConfigurationRepository +import io.ktor.server.application.* +import io.ktor.server.routing.* +import org.koin.ktor.ext.get + +internal fun Application.configureRouting() = routing { + val configuration = get() + + route("/v${configuration.apiVersion}") { + configureRootRoute() + configurePatchesRoute() + configureAnnouncementsRoute() + } +} diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt b/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt new file mode 100644 index 0000000..4f9f038 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt @@ -0,0 +1,86 @@ +package app.revanced.api.configuration.routing.routes + +import app.revanced.api.schema.APIAnnouncement +import app.revanced.api.schema.APIAnnouncementArchivedAt +import app.revanced.api.services.AnnouncementService +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import io.ktor.server.util.* +import org.koin.ktor.ext.get as koinGet + +internal fun Route.configureAnnouncementsRoute() = route("/announcements") { + val announcementService = koinGet() + + route("/{channel}/latest") { + get("/id") { + val channel: String by call.parameters + + call.respond( + announcementService.latestId(channel) ?: return@get call.respond(HttpStatusCode.NotFound), + ) + } + + get { + val channel: String by call.parameters + + call.respond( + announcementService.latest(channel) ?: return@get call.respond(HttpStatusCode.NotFound), + ) + } + } + + get("/{channel}") { + val channel: String by call.parameters + + call.respond(announcementService.all(channel)) + } + + route("/latest") { + get("/id") { + call.respond(announcementService.latestId() ?: return@get call.respond(HttpStatusCode.NotFound)) + } + + get { + call.respond(announcementService.latest() ?: return@get call.respond(HttpStatusCode.NotFound)) + } + } + + get { + call.respond(announcementService.all()) + } + + authenticate("jwt") { + post { + announcementService.new(call.receive()) + } + + post("/{id}/archive") { + val id: Int by call.parameters + val archivedAt = call.receiveNullable()?.archivedAt + + announcementService.archive(id, archivedAt) + } + + post("/{id}/unarchive") { + val id: Int by call.parameters + + announcementService.unarchive(id) + } + + patch("/{id}") { + val id: Int by call.parameters + + announcementService.update(id, call.receive()) + } + + delete("/{id}") { + val id: Int by call.parameters + + announcementService.delete(id) + } + } +} diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt new file mode 100644 index 0000000..b502ac8 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt @@ -0,0 +1,41 @@ +package app.revanced.api.configuration.routing.routes + +import app.revanced.api.services.ApiService +import app.revanced.api.services.AuthService +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.http.content.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import org.koin.ktor.ext.get + +internal fun Route.configureRootRoute() { + val apiService = get() + val authService = get() + + get("/contributors") { + call.respond(apiService.contributors()) + } + + get("/team") { + call.respond(apiService.team()) + } + + route("/ping") { + handle { + call.respond(HttpStatusCode.NoContent) + } + } + + authenticate("basic") { + get("/token") { + call.respond(authService.newToken()) + } + } + + staticResources("/", "/static/api") { + contentType { ContentType.Application.Json } + extensions("json") + } +} diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt new file mode 100644 index 0000000..21e811e --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt @@ -0,0 +1,26 @@ +package app.revanced.api.configuration.routing.routes + +import app.revanced.api.services.PatchesService +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import org.koin.ktor.ext.get as koinGet + +internal fun Route.configurePatchesRoute() = route("/patches") { + val patchesService = koinGet() + + route("latest") { + get { + call.respond(patchesService.latestRelease()) + } + + get("/version") { + call.respond(patchesService.latestVersion()) + } + + get("/list") { + call.respondBytes(ContentType.Application.Json) { patchesService.list() } + } + } +} diff --git a/src/main/kotlin/app/revanced/api/modules/Dependencies.kt b/src/main/kotlin/app/revanced/api/modules/Dependencies.kt deleted file mode 100644 index cf8afe0..0000000 --- a/src/main/kotlin/app/revanced/api/modules/Dependencies.kt +++ /dev/null @@ -1,75 +0,0 @@ -package app.revanced.api.modules - -import app.revanced.api.backend.Backend -import app.revanced.api.backend.github.GitHubBackend -import app.revanced.api.schema.APIConfiguration -import com.akuleshov7.ktoml.Toml -import com.akuleshov7.ktoml.source.decodeFromStream -import io.github.cdimascio.dotenv.Dotenv -import io.ktor.server.application.* -import org.jetbrains.exposed.sql.Database -import org.koin.dsl.bind -import org.koin.dsl.module -import org.koin.ktor.plugin.Koin -import java.io.File - -fun Application.configureDependencies() { - install(Koin) { - modules( - globalModule, - gitHubBackendModule, - databaseModule, - authModule, - ) - } -} - -val globalModule = module { - single { - Dotenv.configure() - .systemProperties() - .load() - } - single { - val configFilePath = get()["CONFIG_FILE_PATH"] - Toml.decodeFromStream(File(configFilePath).inputStream()) - } -} - -val gitHubBackendModule = module { - single { - val token = get()["GITHUB_TOKEN"] - GitHubBackend(token) - } bind Backend::class -} - -val databaseModule = module { - single { - val dotenv = get() - - Database.connect( - url = dotenv["DB_URL"], - user = dotenv["DB_USER"], - password = dotenv["DB_PASSWORD"], - driver = "org.h2.Driver", - ) - } - factory { - AnnouncementService(get()) - } -} - -val authModule = module { - single { - val dotenv = get() - - val jwtSecret = dotenv["JWT_SECRET"] - val issuer = dotenv["JWT_ISSUER"] - val validityInMin = dotenv["JWT_VALIDITY_IN_MIN"].toInt() - - val basicUsername = dotenv["BASIC_USERNAME"] - val basicPassword = dotenv["BASIC_PASSWORD"] - - AuthService(issuer, validityInMin, jwtSecret, basicUsername, basicPassword) - } -} diff --git a/src/main/kotlin/app/revanced/api/modules/Routing.kt b/src/main/kotlin/app/revanced/api/modules/Routing.kt deleted file mode 100644 index 616e2e5..0000000 --- a/src/main/kotlin/app/revanced/api/modules/Routing.kt +++ /dev/null @@ -1,232 +0,0 @@ -package app.revanced.api.modules - -import app.revanced.api.backend.Backend -import app.revanced.api.schema.* -import app.revanced.library.PatchUtils -import app.revanced.patcher.PatchBundleLoader -import com.github.benmanes.caffeine.cache.Caffeine -import io.ktor.http.* -import io.ktor.server.application.* -import io.ktor.server.auth.* -import io.ktor.server.http.content.* -import io.ktor.server.request.* -import io.ktor.server.response.* -import io.ktor.server.routing.* -import io.ktor.util.pipeline.* -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import java.io.ByteArrayOutputStream -import java.net.URL -import org.koin.ktor.ext.get as koinGet - -fun Application.configureRouting() { - val backend: Backend = koinGet() - val configuration: APIConfiguration = koinGet() - val announcementService: AnnouncementService = koinGet() - val authService: AuthService = koinGet() - - routing { - route("/v${configuration.apiVersion}") { - route("/announcements") { - suspend fun PipelineContext<*, ApplicationCall>.announcement(block: AnnouncementService.() -> APIResponseAnnouncement?) = - announcementService.block()?.let { call.respond(it) } - ?: call.respond(HttpStatusCode.NotFound) - - suspend fun PipelineContext<*, ApplicationCall>.announcementId(block: AnnouncementService.() -> APILatestAnnouncement?) = - announcementService.block()?.let { call.respond(it) } - ?: call.respond(HttpStatusCode.NotFound) - - suspend fun PipelineContext<*, ApplicationCall>.channel(block: suspend (String) -> Unit) = - block(call.parameters["channel"]!!) - - route("/{channel}/latest") { - get("/id") { - channel { - announcementId { - latestId(it) - } - } - } - - get { - channel { - announcement { - latest(it) - } - } - } - } - - get("/{channel}") { - channel { - call.respond(announcementService.read(it)) - } - } - - route("/latest") { - get("/id") { - announcementId { - latestId() - } - } - - get { - announcement { - latest() - } - } - } - - get { - call.respond(announcementService.read()) - } - - authenticate("jwt") { - suspend fun PipelineContext<*, ApplicationCall>.id(block: suspend (Int) -> Unit) = - call.parameters["id"]!!.toIntOrNull()?.let { - block(it) - } ?: call.respond(HttpStatusCode.BadRequest) - - post { - announcementService.new(call.receive()) - } - - post("/{id}/archive") { - id { - val archivedAt = call.receiveNullable()?.archivedAt - announcementService.archive(it, archivedAt) - } - } - - post("/{id}/unarchive") { - id { - announcementService.unarchive(it) - } - } - - patch("/{id}") { - id { - announcementService.update(it, call.receive()) - } - } - - delete("/{id}") { - id { - announcementService.delete(it) - } - } - } - } - - route("/patches") { - route("latest") { - get { - val patchesRelease = - backend.getRelease(configuration.organization, configuration.patchesRepository) - val integrationsReleases = configuration.integrationsRepositoryNames.map { - async { backend.getRelease(configuration.organization, it) } - }.awaitAll() - - val assets = (patchesRelease.assets + integrationsReleases.flatMap { it.assets }) - .map { APIAsset(it.downloadUrl) } - .filter { it.type != APIAsset.Type.UNKNOWN } - .toSet() - - val apiRelease = APIRelease( - patchesRelease.tag, - patchesRelease.createdAt, - patchesRelease.releaseNote, - assets, - ) - - call.respond(apiRelease) - } - - get("/version") { - val patchesRelease = - backend.getRelease(configuration.organization, configuration.patchesRepository) - - val apiPatchesRelease = APIReleaseVersion(patchesRelease.tag) - - call.respond(apiPatchesRelease) - } - - val patchesListCache = Caffeine - .newBuilder() - .maximumSize(1) - .build() - - get("/list") { - val patchesRelease = - backend.getRelease(configuration.organization, configuration.patchesRepository) - - val patchesListByteArray = patchesListCache.getIfPresent(patchesRelease.tag) ?: run { - val downloadUrl = patchesRelease.assets - .map { APIAsset(it.downloadUrl) } - .find { it.type == APIAsset.Type.PATCHES } - ?.downloadUrl - - val patches = kotlin.io.path.createTempFile().toFile().apply { - outputStream().use { URL(downloadUrl).openStream().copyTo(it) } - }.let { file -> - PatchBundleLoader.Jar(file).also { file.delete() } - } - - ByteArrayOutputStream().use { stream -> - PatchUtils.Json.serialize(patches, outputStream = stream) - - stream.toByteArray() - }.also { - patchesListCache.put(patchesRelease.tag, it) - } - } - - call.respondBytes(ContentType.Application.Json) { patchesListByteArray } - } - } - } - - staticResources("/", "/static/api") { - contentType { ContentType.Application.Json } - extensions("json") - } - - get("/contributors") { - val contributors = - configuration.contributorsRepositoryNames.map { - async { - APIContributable( - it, - backend.getContributors(configuration.organization, it).map { - APIContributor(it.name, it.avatarUrl, it.url, it.contributions) - }.toSet(), - ) - } - }.awaitAll() - - call.respond(contributors) - } - - get("/team") { - val team = - backend.getMembers(configuration.organization).map { - APIMember(it.name, it.avatarUrl, it.url, it.gpgKeysUrl) - } - - call.respond(team) - } - - route("/ping") { - handle { - call.respond(HttpStatusCode.NoContent) - } - } - - authenticate("basic") { - get("/token") { - call.respond(authService.newToken()) - } - } - } - } -} diff --git a/src/main/kotlin/app/revanced/api/modules/Serialization.kt b/src/main/kotlin/app/revanced/api/modules/Serialization.kt deleted file mode 100644 index ef38558..0000000 --- a/src/main/kotlin/app/revanced/api/modules/Serialization.kt +++ /dev/null @@ -1,11 +0,0 @@ -package app.revanced.api.modules - -import io.ktor.serialization.kotlinx.json.* -import io.ktor.server.application.* -import io.ktor.server.plugins.contentnegotiation.* - -fun Application.configureSerialization() { - install(ContentNegotiation) { - json() - } -} diff --git a/src/main/kotlin/app/revanced/api/modules/Database.kt b/src/main/kotlin/app/revanced/api/repository/AnnouncementRepository.kt similarity index 52% rename from src/main/kotlin/app/revanced/api/modules/Database.kt rename to src/main/kotlin/app/revanced/api/repository/AnnouncementRepository.kt index 4727a0b..a6c4b63 100644 --- a/src/main/kotlin/app/revanced/api/modules/Database.kt +++ b/src/main/kotlin/app/revanced/api/repository/AnnouncementRepository.kt @@ -1,6 +1,6 @@ -package app.revanced.api.modules +package app.revanced.api.repository -import app.revanced.api.modules.AnnouncementService.Attachments.announcement +import app.revanced.api.repository.AnnouncementRepository.AttachmentTable.announcement import app.revanced.api.schema.APIAnnouncement import app.revanced.api.schema.APILatestAnnouncement import app.revanced.api.schema.APIResponseAnnouncement @@ -10,96 +10,54 @@ import org.jetbrains.exposed.dao.IntEntityClass import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.kotlin.datetime.datetime import org.jetbrains.exposed.sql.transactions.transaction -class AnnouncementService(private val database: Database) { - private object Announcements : IntIdTable() { - val author = varchar("author", 32).nullable() - val title = varchar("title", 64) - val content = text("content").nullable() - val channel = varchar("channel", 16).nullable() - val createdAt = datetime("createdAt") - val archivedAt = datetime("archivedAt").nullable() - val level = integer("level") - } - - private object Attachments : IntIdTable() { - val url = varchar("url", 256) - val announcement = reference("announcement", Announcements, onDelete = ReferenceOption.CASCADE) - } - - class Announcement(id: EntityID) : IntEntity(id) { - companion object : IntEntityClass(Announcements) - - var author by Announcements.author - var title by Announcements.title - var content by Announcements.content - val attachments by Attachment referrersOn announcement - var channel by Announcements.channel - var createdAt by Announcements.createdAt - var archivedAt by Announcements.archivedAt - var level by Announcements.level - - fun api() = APIResponseAnnouncement( - id.value, - author, - title, - content, - attachments.map(Attachment::url).toSet(), - channel, - createdAt, - archivedAt, - level, - ) - } - - class Attachment(id: EntityID) : IntEntity(id) { - companion object : IntEntityClass(Attachments) - - var url by Attachments.url - var announcement by Announcement referencedOn Attachments.announcement - } - +internal class AnnouncementRepository(private val database: Database) { init { transaction { - SchemaUtils.create(Announcements, Attachments) + SchemaUtils.create(AnnouncementTable, AttachmentTable) } } - private fun transaction(block: Transaction.() -> T) = transaction(database, block) - - fun read() = transaction { - Announcement.all().map { it.api() }.toSet() + fun all() = transaction { + buildSet { + AnnouncementEntity.all().forEach { announcement -> + add(announcement.toApi()) + } + } } - fun read(channel: String) = transaction { - Announcement.find { Announcements.channel eq channel }.map { it.api() }.toSet() + fun all(channel: String) = transaction { + buildSet { + AnnouncementEntity.find { AnnouncementTable.channel eq channel }.forEach { announcement -> + add(announcement.toApi()) + } + } } fun delete(id: Int) = transaction { - val announcement = Announcement.findById(id) ?: return@transaction + val announcement = AnnouncementEntity.findById(id) ?: return@transaction announcement.delete() } fun latest() = transaction { - Announcement.all().maxByOrNull { it.createdAt }?.api() + AnnouncementEntity.all().maxByOrNull { it.createdAt }?.toApi() } fun latest(channel: String) = transaction { - Announcement.find { Announcements.channel eq channel }.maxByOrNull { it.createdAt }?.api() + AnnouncementEntity.find { AnnouncementTable.channel eq channel }.maxByOrNull { it.createdAt }?.toApi() } fun latestId() = transaction { - Announcement.all().maxByOrNull { it.createdAt }?.id?.value?.let { + AnnouncementEntity.all().maxByOrNull { it.createdAt }?.id?.value?.let { APILatestAnnouncement(it) } } fun latestId(channel: String) = transaction { - Announcement.find { Announcements.channel eq channel }.maxByOrNull { it.createdAt }?.id?.value?.let { + AnnouncementEntity.find { AnnouncementTable.channel eq channel }.maxByOrNull { it.createdAt }?.id?.value?.let { APILatestAnnouncement(it) } } @@ -108,19 +66,19 @@ class AnnouncementService(private val database: Database) { id: Int, archivedAt: LocalDateTime?, ) = transaction { - Announcement.findById(id)?.apply { + AnnouncementEntity.findById(id)?.apply { this.archivedAt = archivedAt ?: java.time.LocalDateTime.now().toKotlinLocalDateTime() } } fun unarchive(id: Int) = transaction { - Announcement.findById(id)?.apply { + AnnouncementEntity.findById(id)?.apply { archivedAt = null } } fun new(new: APIAnnouncement) = transaction { - Announcement.new announcement@{ + AnnouncementEntity.new announcement@{ author = new.author title = new.title content = new.content @@ -130,7 +88,7 @@ class AnnouncementService(private val database: Database) { level = new.level }.also { newAnnouncement -> new.attachmentUrls.map { - Attachment.new { + AttachmentEntity.new { url = it announcement = newAnnouncement } @@ -139,7 +97,7 @@ class AnnouncementService(private val database: Database) { } fun update(id: Int, new: APIAnnouncement) = transaction { - Announcement.findById(id)?.apply { + AnnouncementEntity.findById(id)?.apply { author = new.author title = new.title content = new.content @@ -147,13 +105,66 @@ class AnnouncementService(private val database: Database) { archivedAt = new.archivedAt level = new.level - attachments.forEach(Attachment::delete) + attachments.forEach(AttachmentEntity::delete) new.attachmentUrls.map { - Attachment.new { + AttachmentEntity.new { url = it announcement = this@apply } } } } + + private fun transaction(block: Transaction.() -> T) = transaction(database, block) + + private object AnnouncementTable : IntIdTable() { + val author = varchar("author", 32).nullable() + val title = varchar("title", 64) + val content = text("content").nullable() + val channel = varchar("channel", 16).nullable() + val createdAt = datetime("createdAt") + val archivedAt = datetime("archivedAt").nullable() + val level = integer("level") + } + + private object AttachmentTable : IntIdTable() { + val url = varchar("url", 256) + val announcement = reference("announcement", AnnouncementTable, onDelete = ReferenceOption.CASCADE) + } + + class AnnouncementEntity(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(AnnouncementTable) + + var author by AnnouncementTable.author + var title by AnnouncementTable.title + var content by AnnouncementTable.content + val attachments by AttachmentEntity referrersOn announcement + var channel by AnnouncementTable.channel + var createdAt by AnnouncementTable.createdAt + var archivedAt by AnnouncementTable.archivedAt + var level by AnnouncementTable.level + + fun toApi() = APIResponseAnnouncement( + id.value, + author, + title, + content, + attachmentUrls = buildSet { + attachments.forEach { + add(it.url) + } + }, + channel, + createdAt, + archivedAt, + level, + ) + } + + class AttachmentEntity(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(AttachmentTable) + + var url by AttachmentTable.url + var announcement by AnnouncementEntity referencedOn AttachmentTable.announcement + } } diff --git a/src/main/kotlin/app/revanced/api/schema/ConfigurationSchema.kt b/src/main/kotlin/app/revanced/api/repository/ConfigurationRepository.kt similarity index 85% rename from src/main/kotlin/app/revanced/api/schema/ConfigurationSchema.kt rename to src/main/kotlin/app/revanced/api/repository/ConfigurationRepository.kt index 5eff0b6..531de56 100644 --- a/src/main/kotlin/app/revanced/api/schema/ConfigurationSchema.kt +++ b/src/main/kotlin/app/revanced/api/repository/ConfigurationRepository.kt @@ -1,10 +1,10 @@ -package app.revanced.api.schema +package app.revanced.api.repository import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -class APIConfiguration( +internal class ConfigurationRepository( val organization: String, @SerialName("patches-repository") val patchesRepository: String, diff --git a/src/main/kotlin/app/revanced/api/backend/Backend.kt b/src/main/kotlin/app/revanced/api/repository/backend/BackendRepository.kt similarity index 91% rename from src/main/kotlin/app/revanced/api/backend/Backend.kt rename to src/main/kotlin/app/revanced/api/repository/backend/BackendRepository.kt index c3e53c1..47ec9d8 100644 --- a/src/main/kotlin/app/revanced/api/backend/Backend.kt +++ b/src/main/kotlin/app/revanced/api/repository/backend/BackendRepository.kt @@ -1,4 +1,4 @@ -package app.revanced.api.backend +package app.revanced.api.repository.backend import io.ktor.client.* import io.ktor.client.engine.okhttp.* @@ -10,7 +10,7 @@ import kotlinx.serialization.Serializable * * @param httpClientConfig The configuration of the HTTP client. */ -abstract class Backend( +abstract class BackendRepository internal constructor( httpClientConfig: HttpClientConfig.() -> Unit = {}, ) { protected val client: HttpClient = HttpClient(OkHttp, httpClientConfig) @@ -114,7 +114,7 @@ abstract class Backend( * @param tag The tag of the release. If null, the latest release is returned. * @return The release. */ - abstract suspend fun getRelease( + abstract suspend fun release( owner: String, repository: String, tag: String? = null, @@ -127,7 +127,7 @@ abstract class Backend( * @param repository The name of the repository. * @return The contributors. */ - abstract suspend fun getContributors(owner: String, repository: String): Set + abstract suspend fun contributors(owner: String, repository: String): Set /** * Get the members of an organization. @@ -135,5 +135,5 @@ abstract class Backend( * @param organization The name of the organization. * @return The members. */ - abstract suspend fun getMembers(organization: String): Set + abstract suspend fun members(organization: String): Set } diff --git a/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt b/src/main/kotlin/app/revanced/api/repository/backend/github/GitHubBackendRepository.kt similarity index 67% rename from src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt rename to src/main/kotlin/app/revanced/api/repository/backend/github/GitHubBackendRepository.kt index be0db97..5687f72 100644 --- a/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt +++ b/src/main/kotlin/app/revanced/api/repository/backend/github/GitHubBackendRepository.kt @@ -1,18 +1,18 @@ -package app.revanced.api.backend.github +package app.revanced.api.repository.backend.github -import app.revanced.api.backend.Backend -import app.revanced.api.backend.Backend.BackendOrganization.BackendMember -import app.revanced.api.backend.Backend.BackendOrganization.BackendRepository.BackendContributor -import app.revanced.api.backend.Backend.BackendOrganization.BackendRepository.BackendRelease -import app.revanced.api.backend.Backend.BackendOrganization.BackendRepository.BackendRelease.BackendAsset -import app.revanced.api.backend.github.api.Request -import app.revanced.api.backend.github.api.Request.Organization.Members -import app.revanced.api.backend.github.api.Request.Organization.Repository.Contributors -import app.revanced.api.backend.github.api.Request.Organization.Repository.Releases -import app.revanced.api.backend.github.api.Response -import app.revanced.api.backend.github.api.Response.GitHubOrganization.GitHubMember -import app.revanced.api.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubContributor -import app.revanced.api.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubRelease +import app.revanced.api.repository.backend.BackendRepository +import app.revanced.api.repository.backend.BackendRepository.BackendOrganization.BackendMember +import app.revanced.api.repository.backend.BackendRepository.BackendOrganization.BackendRepository.BackendContributor +import app.revanced.api.repository.backend.BackendRepository.BackendOrganization.BackendRepository.BackendRelease +import app.revanced.api.repository.backend.BackendRepository.BackendOrganization.BackendRepository.BackendRelease.BackendAsset +import app.revanced.api.repository.backend.github.api.Request +import app.revanced.api.repository.backend.github.api.Request.Organization.Members +import app.revanced.api.repository.backend.github.api.Request.Organization.Repository.Contributors +import app.revanced.api.repository.backend.github.api.Request.Organization.Repository.Releases +import app.revanced.api.repository.backend.github.api.Response +import app.revanced.api.repository.backend.github.api.Response.GitHubOrganization.GitHubMember +import app.revanced.api.repository.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubContributor +import app.revanced.api.repository.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubRelease import io.ktor.client.call.* import io.ktor.client.plugins.* import io.ktor.client.plugins.auth.* @@ -30,7 +30,7 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonNamingStrategy @OptIn(ExperimentalSerializationApi::class) -class GitHubBackend(token: String? = null) : Backend({ +class GitHubBackendRepository(token: String? = null) : BackendRepository({ install(HttpCache) install(Resources) install(ContentNegotiation) { @@ -59,7 +59,7 @@ class GitHubBackend(token: String? = null) : Backend({ } } }) { - override suspend fun getRelease( + override suspend fun release( owner: String, repository: String, tag: String?, @@ -80,7 +80,7 @@ class GitHubBackend(token: String? = null) : Backend({ ) } - override suspend fun getContributors( + override suspend fun contributors( owner: String, repository: String, ): Set { @@ -96,7 +96,7 @@ class GitHubBackend(token: String? = null) : Backend({ }.toSet() } - override suspend fun getMembers(organization: String): Set { + override suspend fun members(organization: String): Set { // Get the list of members of the organization. val members: Set = client.get(Members(organization)).body() diff --git a/src/main/kotlin/app/revanced/api/backend/github/api/RequestResource.kt b/src/main/kotlin/app/revanced/api/repository/backend/github/api/Request.kt similarity index 93% rename from src/main/kotlin/app/revanced/api/backend/github/api/RequestResource.kt rename to src/main/kotlin/app/revanced/api/repository/backend/github/api/Request.kt index dc480ba..557f9e2 100644 --- a/src/main/kotlin/app/revanced/api/backend/github/api/RequestResource.kt +++ b/src/main/kotlin/app/revanced/api/repository/backend/github/api/Request.kt @@ -1,4 +1,4 @@ -package app.revanced.api.backend.github.api +package app.revanced.api.repository.backend.github.api import io.ktor.resources.* diff --git a/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt b/src/main/kotlin/app/revanced/api/repository/backend/github/api/Response.kt similarity index 96% rename from src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt rename to src/main/kotlin/app/revanced/api/repository/backend/github/api/Response.kt index 693a897..2ddc8f1 100644 --- a/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt +++ b/src/main/kotlin/app/revanced/api/repository/backend/github/api/Response.kt @@ -1,4 +1,4 @@ -package app.revanced.api.backend.github.api +package app.revanced.api.repository.backend.github.api import kotlinx.datetime.Instant import kotlinx.serialization.Serializable diff --git a/src/main/kotlin/app/revanced/api/services/AnnouncementService.kt b/src/main/kotlin/app/revanced/api/services/AnnouncementService.kt new file mode 100644 index 0000000..63418da --- /dev/null +++ b/src/main/kotlin/app/revanced/api/services/AnnouncementService.kt @@ -0,0 +1,35 @@ +package app.revanced.api.services + +import app.revanced.api.repository.AnnouncementRepository +import app.revanced.api.schema.APIAnnouncement +import app.revanced.api.schema.APILatestAnnouncement +import kotlinx.datetime.LocalDateTime + +internal class AnnouncementService( + private val announcementRepository: AnnouncementRepository, +) { + fun latestId(channel: String): APILatestAnnouncement? = announcementRepository.latestId(channel) + fun latestId(): APILatestAnnouncement? = announcementRepository.latestId() + + fun latest(channel: String) = announcementRepository.latest(channel) + fun latest() = announcementRepository.latest() + + fun all(channel: String) = announcementRepository.all(channel) + fun all() = announcementRepository.all() + + fun new(new: APIAnnouncement) { + announcementRepository.new(new) + } + fun archive(id: Int, archivedAt: LocalDateTime?) { + announcementRepository.archive(id, archivedAt) + } + fun unarchive(id: Int) { + announcementRepository.unarchive(id) + } + fun update(id: Int, new: APIAnnouncement) { + announcementRepository.update(id, new) + } + fun delete(id: Int) { + announcementRepository.delete(id) + } +} diff --git a/src/main/kotlin/app/revanced/api/services/ApiService.kt b/src/main/kotlin/app/revanced/api/services/ApiService.kt new file mode 100644 index 0000000..d36e64b --- /dev/null +++ b/src/main/kotlin/app/revanced/api/services/ApiService.kt @@ -0,0 +1,33 @@ +package app.revanced.api.services + +import app.revanced.api.repository.ConfigurationRepository +import app.revanced.api.repository.backend.BackendRepository +import app.revanced.api.schema.APIContributable +import app.revanced.api.schema.APIContributor +import app.revanced.api.schema.APIMember +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.withContext + +internal class ApiService( + private val backendRepository: BackendRepository, + private val configurationRepository: ConfigurationRepository, +) { + suspend fun contributors() = withContext(Dispatchers.IO) { + configurationRepository.contributorsRepositoryNames.map { + async { + APIContributable( + it, + backendRepository.contributors(configurationRepository.organization, it).map { + APIContributor(it.name, it.avatarUrl, it.url, it.contributions) + }.toSet(), + ) + } + } + }.awaitAll() + + suspend fun team() = backendRepository.members(configurationRepository.organization).map { + APIMember(it.name, it.avatarUrl, it.url, it.gpgKeysUrl) + } +} diff --git a/src/main/kotlin/app/revanced/api/modules/Security.kt b/src/main/kotlin/app/revanced/api/services/AuthService.kt similarity index 87% rename from src/main/kotlin/app/revanced/api/modules/Security.kt rename to src/main/kotlin/app/revanced/api/services/AuthService.kt index 02cc5f3..af824a0 100644 --- a/src/main/kotlin/app/revanced/api/modules/Security.kt +++ b/src/main/kotlin/app/revanced/api/services/AuthService.kt @@ -1,15 +1,14 @@ -package app.revanced.api.modules +package app.revanced.api.services import com.auth0.jwt.JWT import com.auth0.jwt.algorithms.Algorithm import io.ktor.server.application.* import io.ktor.server.auth.* import io.ktor.server.auth.jwt.* -import org.koin.ktor.ext.get import java.util.* import kotlin.time.Duration.Companion.minutes -class AuthService( +internal class AuthService( private val issuer: String, private val validityInMin: Int, private val jwtSecret: String, @@ -46,8 +45,3 @@ class AuthService( .sign(Algorithm.HMAC256(jwtSecret)) } } - -fun Application.configureSecurity() { - val configureSecurity = get().configureSecurity - configureSecurity() -} diff --git a/src/main/kotlin/app/revanced/api/services/PatchesService.kt b/src/main/kotlin/app/revanced/api/services/PatchesService.kt new file mode 100644 index 0000000..22afd70 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/services/PatchesService.kt @@ -0,0 +1,87 @@ +package app.revanced.api.services + +import app.revanced.api.repository.ConfigurationRepository +import app.revanced.api.repository.backend.BackendRepository +import app.revanced.api.schema.APIAsset +import app.revanced.api.schema.APIRelease +import app.revanced.api.schema.APIReleaseVersion +import app.revanced.library.PatchUtils +import app.revanced.patcher.PatchBundleLoader +import com.github.benmanes.caffeine.cache.Caffeine +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.withContext +import java.io.ByteArrayOutputStream +import java.net.URL + +internal class PatchesService( + private val backendRepository: BackendRepository, + private val configurationRepository: ConfigurationRepository, +) { + private val patchesListCache = Caffeine + .newBuilder() + .maximumSize(1) + .build() + + suspend fun latestRelease(): APIRelease { + val patchesRelease = backendRepository.release( + configurationRepository.organization, + configurationRepository.patchesRepository, + ) + val integrationsReleases = withContext(Dispatchers.Default) { + configurationRepository.integrationsRepositoryNames.map { + async { backendRepository.release(configurationRepository.organization, it) } + } + }.awaitAll() + + val assets = (patchesRelease.assets + integrationsReleases.flatMap { it.assets }) + .map { APIAsset(it.downloadUrl) } + .filter { it.type != APIAsset.Type.UNKNOWN } + .toSet() + + return APIRelease( + patchesRelease.tag, + patchesRelease.createdAt, + patchesRelease.releaseNote, + assets, + ) + } + + suspend fun latestVersion(): APIReleaseVersion { + val patchesRelease = backendRepository.release( + configurationRepository.organization, + configurationRepository.patchesRepository, + ) + + return APIReleaseVersion(patchesRelease.tag) + } + + suspend fun list(): ByteArray { + val patchesRelease = backendRepository.release( + configurationRepository.organization, + configurationRepository.patchesRepository, + ) + + return patchesListCache.getIfPresent(patchesRelease.tag) ?: run { + val downloadUrl = patchesRelease.assets + .map { APIAsset(it.downloadUrl) } + .find { it.type == APIAsset.Type.PATCHES } + ?.downloadUrl + + val patches = kotlin.io.path.createTempFile().toFile().apply { + outputStream().use { URL(downloadUrl).openStream().copyTo(it) } + }.let { file -> + PatchBundleLoader.Jar(file).also { file.delete() } + } + + ByteArrayOutputStream().use { stream -> + PatchUtils.Json.serialize(patches, outputStream = stream) + + stream.toByteArray() + }.also { + patchesListCache.put(patchesRelease.tag, it) + } + } + } +} diff --git a/src/main/resources/static/api/about.json b/src/main/resources/static/api/about.json index 1a0d1cb..a947c48 100644 --- a/src/main/resources/static/api/about.json +++ b/src/main/resources/static/api/about.json @@ -80,4 +80,4 @@ } ] } -} \ No newline at end of file +} diff --git a/src/test/kotlin/app/revanced/ApplicationTest.kt b/src/test/kotlin/app/revanced/ApplicationTest.kt deleted file mode 100644 index 5f1ce2e..0000000 --- a/src/test/kotlin/app/revanced/ApplicationTest.kt +++ /dev/null @@ -1,57 +0,0 @@ -package app.revanced - -import app.revanced.api.modules.* -import app.revanced.api.schema.APIConfiguration -import com.akuleshov7.ktoml.Toml -import io.github.cdimascio.dotenv.Dotenv -import io.ktor.client.request.* -import io.ktor.client.statement.* -import io.ktor.http.* -import io.ktor.server.testing.* -import io.ktor.util.* -import io.mockk.every -import io.mockk.mockk -import kotlinx.serialization.encodeToString -import kotlin.test.* - -class ApplicationTest { - @Test - fun `successfully create a token`() = testApplication { - val apiConfigurationFile = kotlin.io.path.createTempFile().toFile().apply { - Toml.encodeToString( - APIConfiguration( - organization = "ReVanced", - patchesRepository = "", - integrationsRepositoryNames = setOf(), - contributorsRepositoryNames = setOf(), - ), - ).let(::writeText) - - deleteOnExit() - } - - val dotenv = mockk() - every { dotenv[any()] } returns "ReVanced" - every { dotenv["JWT_VALIDITY_IN_MIN"] } returns "5" - every { dotenv["CONFIG_FILE_PATH"] } returns apiConfigurationFile.absolutePath - - application { - configureDependencies() - configureHTTP() - configureSerialization() - configureSecurity() - configureRouting() - } - - val token = client.get("/v1/token") { - headers { - append( - HttpHeaders.Authorization, - "Basic ${"${dotenv["BASIC_USERNAME"]}:${dotenv["BASIC_PASSWORD"]}".encodeBase64()}", - ) - } - }.bodyAsText() - - assert(token.isNotEmpty()) - } -}