mirror of
https://github.com/revanced/revanced-api.git
synced 2025-04-30 06:34:36 +02:00
feat: Add proxy for old API
This commit is contained in:
parent
fa2f8b2f86
commit
39f54bbb32
@ -11,3 +11,5 @@ JWT_VALIDITY_IN_MIN=
|
|||||||
|
|
||||||
BASIC_USERNAME=
|
BASIC_USERNAME=
|
||||||
BASIC_PASSWORD=
|
BASIC_PASSWORD=
|
||||||
|
|
||||||
|
OLD_API_URL=
|
@ -2,8 +2,11 @@ package app.revanced.api.command
|
|||||||
|
|
||||||
import app.revanced.api.configuration.*
|
import app.revanced.api.configuration.*
|
||||||
import app.revanced.api.configuration.routing.configureRouting
|
import app.revanced.api.configuration.routing.configureRouting
|
||||||
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.engine.*
|
import io.ktor.server.engine.*
|
||||||
import io.ktor.server.netty.*
|
import io.ktor.server.netty.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
|
|
||||||
@CommandLine.Command(
|
@CommandLine.Command(
|
||||||
|
@ -2,6 +2,7 @@ package app.revanced.api.configuration
|
|||||||
|
|
||||||
import app.revanced.api.repository.AnnouncementRepository
|
import app.revanced.api.repository.AnnouncementRepository
|
||||||
import app.revanced.api.repository.ConfigurationRepository
|
import app.revanced.api.repository.ConfigurationRepository
|
||||||
|
import app.revanced.api.repository.OldApiService
|
||||||
import app.revanced.api.repository.backend.BackendRepository
|
import app.revanced.api.repository.backend.BackendRepository
|
||||||
import app.revanced.api.repository.backend.github.GitHubBackendRepository
|
import app.revanced.api.repository.backend.github.GitHubBackendRepository
|
||||||
import app.revanced.api.services.AnnouncementService
|
import app.revanced.api.services.AnnouncementService
|
||||||
@ -11,14 +12,27 @@ import app.revanced.api.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.github.cdimascio.dotenv.Dotenv
|
import io.github.cdimascio.dotenv.Dotenv
|
||||||
|
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.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
import org.koin.dsl.bind
|
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() {
|
||||||
val globalModule = module {
|
val globalModule = module {
|
||||||
single {
|
single {
|
||||||
@ -26,6 +40,16 @@ fun Application.configureDependencies() {
|
|||||||
.systemProperties()
|
.systemProperties()
|
||||||
.load()
|
.load()
|
||||||
}
|
}
|
||||||
|
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 {
|
||||||
@ -40,6 +64,43 @@ fun Application.configureDependencies() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
single<BackendRepository> {
|
||||||
|
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
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
get<Dotenv>()["GITHUB_TOKEN"]?.let {
|
||||||
|
install(Auth) {
|
||||||
|
bearer {
|
||||||
|
loadTokens {
|
||||||
|
BearerTokens(
|
||||||
|
accessToken = it,
|
||||||
|
refreshToken = "", // Required dummy value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
sendWithoutRequest { true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parameterArrayOf(defaultRequestUri, configBlock)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
single {
|
single {
|
||||||
val configFilePath = get<Dotenv>()["CONFIG_FILE_PATH"]
|
val configFilePath = get<Dotenv>()["CONFIG_FILE_PATH"]
|
||||||
val configFile = File(configFilePath).inputStream()
|
val configFile = File(configFilePath).inputStream()
|
||||||
@ -64,10 +125,13 @@ fun Application.configureDependencies() {
|
|||||||
AuthService(issuer, validityInMin, jwtSecret, basicUsername, basicPassword)
|
AuthService(issuer, validityInMin, jwtSecret, basicUsername, basicPassword)
|
||||||
}
|
}
|
||||||
single {
|
single {
|
||||||
val token = get<Dotenv>()["GITHUB_TOKEN"]
|
OldApiService(
|
||||||
|
get {
|
||||||
GitHubBackendRepository(token)
|
val defaultRequestUri = get<Dotenv>()["OLD_API_URL"]
|
||||||
} bind BackendRepository::class
|
parameterArrayOf(defaultRequestUri)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
singleOf(::AnnouncementService)
|
singleOf(::AnnouncementService)
|
||||||
singleOf(::PatchesService)
|
singleOf(::PatchesService)
|
||||||
singleOf(::ApiService)
|
singleOf(::ApiService)
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
package app.revanced.api.configuration.routing
|
package app.revanced.api.configuration.routing
|
||||||
|
|
||||||
import app.revanced.api.configuration.routing.routes.configureAnnouncementsRoute
|
import app.revanced.api.configuration.routing.routes.announcementsRoute
|
||||||
import app.revanced.api.configuration.routing.routes.configurePatchesRoute
|
import app.revanced.api.configuration.routing.routes.oldApiRoute
|
||||||
import app.revanced.api.configuration.routing.routes.configureRootRoute
|
import app.revanced.api.configuration.routing.routes.patchesRoute
|
||||||
|
import app.revanced.api.configuration.routing.routes.rootRoute
|
||||||
import app.revanced.api.repository.ConfigurationRepository
|
import app.revanced.api.repository.ConfigurationRepository
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
@ -12,8 +13,11 @@ internal fun Application.configureRouting() = routing {
|
|||||||
val configuration = get<ConfigurationRepository>()
|
val configuration = get<ConfigurationRepository>()
|
||||||
|
|
||||||
route("/v${configuration.apiVersion}") {
|
route("/v${configuration.apiVersion}") {
|
||||||
configureRootRoute()
|
rootRoute()
|
||||||
configurePatchesRoute()
|
patchesRoute()
|
||||||
configureAnnouncementsRoute()
|
announcementsRoute()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Remove, once migration period from v2 API is over (In 1-2 years).
|
||||||
|
oldApiRoute()
|
||||||
}
|
}
|
||||||
|
@ -12,11 +12,11 @@ import io.ktor.server.routing.*
|
|||||||
import io.ktor.server.util.*
|
import io.ktor.server.util.*
|
||||||
import org.koin.ktor.ext.get as koinGet
|
import org.koin.ktor.ext.get as koinGet
|
||||||
|
|
||||||
internal fun Route.configureAnnouncementsRoute() = route("/announcements") {
|
internal fun Route.announcementsRoute() = route("announcements") {
|
||||||
val announcementService = koinGet<AnnouncementService>()
|
val announcementService = koinGet<AnnouncementService>()
|
||||||
|
|
||||||
route("/{channel}/latest") {
|
route("{channel}/latest") {
|
||||||
get("/id") {
|
get("id") {
|
||||||
val channel: String by call.parameters
|
val channel: String by call.parameters
|
||||||
|
|
||||||
call.respond(
|
call.respond(
|
||||||
@ -33,14 +33,14 @@ internal fun Route.configureAnnouncementsRoute() = route("/announcements") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get("/{channel}") {
|
get("{channel}") {
|
||||||
val channel: String by call.parameters
|
val channel: String by call.parameters
|
||||||
|
|
||||||
call.respond(announcementService.all(channel))
|
call.respond(announcementService.all(channel))
|
||||||
}
|
}
|
||||||
|
|
||||||
route("/latest") {
|
route("latest") {
|
||||||
get("/id") {
|
get("id") {
|
||||||
call.respond(announcementService.latestId() ?: return@get call.respond(HttpStatusCode.NotFound))
|
call.respond(announcementService.latestId() ?: return@get call.respond(HttpStatusCode.NotFound))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,26 +58,26 @@ internal fun Route.configureAnnouncementsRoute() = route("/announcements") {
|
|||||||
announcementService.new(call.receive<APIAnnouncement>())
|
announcementService.new(call.receive<APIAnnouncement>())
|
||||||
}
|
}
|
||||||
|
|
||||||
post("/{id}/archive") {
|
post("{id}/archive") {
|
||||||
val id: Int by call.parameters
|
val id: Int by call.parameters
|
||||||
val archivedAt = call.receiveNullable<APIAnnouncementArchivedAt>()?.archivedAt
|
val archivedAt = call.receiveNullable<APIAnnouncementArchivedAt>()?.archivedAt
|
||||||
|
|
||||||
announcementService.archive(id, archivedAt)
|
announcementService.archive(id, archivedAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
post("/{id}/unarchive") {
|
post("{id}/unarchive") {
|
||||||
val id: Int by call.parameters
|
val id: Int by call.parameters
|
||||||
|
|
||||||
announcementService.unarchive(id)
|
announcementService.unarchive(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
patch("/{id}") {
|
patch("{id}") {
|
||||||
val id: Int by call.parameters
|
val id: Int by call.parameters
|
||||||
|
|
||||||
announcementService.update(id, call.receive<APIAnnouncement>())
|
announcementService.update(id, call.receive<APIAnnouncement>())
|
||||||
}
|
}
|
||||||
|
|
||||||
delete("/{id}") {
|
delete("{id}") {
|
||||||
val id: Int by call.parameters
|
val id: Int by call.parameters
|
||||||
|
|
||||||
announcementService.delete(id)
|
announcementService.delete(id)
|
||||||
|
@ -10,31 +10,31 @@ import io.ktor.server.response.*
|
|||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
import org.koin.ktor.ext.get
|
import org.koin.ktor.ext.get
|
||||||
|
|
||||||
internal fun Route.configureRootRoute() {
|
internal fun Route.rootRoute() {
|
||||||
val apiService = get<ApiService>()
|
val apiService = get<ApiService>()
|
||||||
val authService = get<AuthService>()
|
val authService = get<AuthService>()
|
||||||
|
|
||||||
get("/contributors") {
|
get("contributors") {
|
||||||
call.respond(apiService.contributors())
|
call.respond(apiService.contributors())
|
||||||
}
|
}
|
||||||
|
|
||||||
get("/team") {
|
get("team") {
|
||||||
call.respond(apiService.team())
|
call.respond(apiService.team())
|
||||||
}
|
}
|
||||||
|
|
||||||
route("/ping") {
|
route("ping") {
|
||||||
handle {
|
handle {
|
||||||
call.respond(HttpStatusCode.NoContent)
|
call.respond(HttpStatusCode.NoContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticate("basic") {
|
authenticate("basic") {
|
||||||
get("/token") {
|
get("token") {
|
||||||
call.respond(authService.newToken())
|
call.respond(authService.newToken())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
staticResources("/", "/static/api") {
|
staticResources("/", "/app/revanced/api/static") {
|
||||||
contentType { ContentType.Application.Json }
|
contentType { ContentType.Application.Json }
|
||||||
extensions("json")
|
extensions("json")
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
package app.revanced.api.configuration.routing.routes
|
||||||
|
|
||||||
|
import app.revanced.api.repository.OldApiService
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
import org.koin.ktor.ext.get
|
||||||
|
|
||||||
|
internal fun Route.oldApiRoute() {
|
||||||
|
val oldApiService = get<OldApiService>()
|
||||||
|
|
||||||
|
route(Regex("(v2|tools|contributor).*")) {
|
||||||
|
handle {
|
||||||
|
oldApiService.proxy(call)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ import io.ktor.server.response.*
|
|||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
import org.koin.ktor.ext.get as koinGet
|
import org.koin.ktor.ext.get as koinGet
|
||||||
|
|
||||||
internal fun Route.configurePatchesRoute() = route("/patches") {
|
internal fun Route.patchesRoute() = route("patches") {
|
||||||
val patchesService = koinGet<PatchesService>()
|
val patchesService = koinGet<PatchesService>()
|
||||||
|
|
||||||
route("latest") {
|
route("latest") {
|
||||||
@ -15,11 +15,11 @@ internal fun Route.configurePatchesRoute() = route("/patches") {
|
|||||||
call.respond(patchesService.latestRelease())
|
call.respond(patchesService.latestRelease())
|
||||||
}
|
}
|
||||||
|
|
||||||
get("/version") {
|
get("version") {
|
||||||
call.respond(patchesService.latestVersion())
|
call.respond(patchesService.latestVersion())
|
||||||
}
|
}
|
||||||
|
|
||||||
get("/list") {
|
get("list") {
|
||||||
call.respondBytes(ContentType.Application.Json) { patchesService.list() }
|
call.respondBytes(ContentType.Application.Json) { patchesService.list() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
65
src/main/kotlin/app/revanced/api/repository/OldApiService.kt
Normal file
65
src/main/kotlin/app/revanced/api/repository/OldApiService.kt
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package app.revanced.api.repository
|
||||||
|
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.client.statement.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.http.content.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.request.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.util.*
|
||||||
|
import io.ktor.utils.io.*
|
||||||
|
|
||||||
|
internal class OldApiService(private val client: HttpClient) {
|
||||||
|
@OptIn(InternalAPI::class)
|
||||||
|
suspend fun proxy(call: ApplicationCall) {
|
||||||
|
val channel = call.request.receiveChannel()
|
||||||
|
val size = channel.availableForRead
|
||||||
|
val byteArray = ByteArray(size)
|
||||||
|
channel.readFully(byteArray)
|
||||||
|
|
||||||
|
val response: HttpResponse = client.request(call.request.uri) {
|
||||||
|
method = call.request.httpMethod
|
||||||
|
|
||||||
|
headers {
|
||||||
|
appendAll(
|
||||||
|
call.request.headers.filter { key, _ ->
|
||||||
|
!key.equals(
|
||||||
|
HttpHeaders.ContentType,
|
||||||
|
ignoreCase = true,
|
||||||
|
) && !key.equals(
|
||||||
|
HttpHeaders.ContentLength,
|
||||||
|
ignoreCase = true,
|
||||||
|
) && !key.equals(HttpHeaders.Host, ignoreCase = true)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (call.request.httpMethod == HttpMethod.Post) {
|
||||||
|
body = ByteArrayContent(byteArray, call.request.contentType())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val headers = response.headers
|
||||||
|
|
||||||
|
call.respond(object : OutgoingContent.WriteChannelContent() {
|
||||||
|
override val contentLength: Long? = headers[HttpHeaders.ContentLength]?.toLong()
|
||||||
|
override val contentType = headers[HttpHeaders.ContentType]?.let { ContentType.parse(it) }
|
||||||
|
override val headers: Headers = Headers.build {
|
||||||
|
appendAll(
|
||||||
|
headers.filter { key, _ ->
|
||||||
|
!key.equals(
|
||||||
|
HttpHeaders.ContentType,
|
||||||
|
ignoreCase = true,
|
||||||
|
) && !key.equals(HttpHeaders.ContentLength, ignoreCase = true)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override val status = response.status
|
||||||
|
|
||||||
|
override suspend fun writeTo(channel: ByteWriteChannel) {
|
||||||
|
response.content.copyAndClose(channel)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +1,17 @@
|
|||||||
package app.revanced.api.repository.backend
|
package app.revanced.api.repository.backend
|
||||||
|
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.engine.okhttp.*
|
|
||||||
import kotlinx.datetime.LocalDateTime
|
import kotlinx.datetime.LocalDateTime
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The backend of the application used to get data for the API.
|
* The backend of the application used to get data for the API.
|
||||||
*
|
*
|
||||||
* @param httpClientConfig The configuration of the HTTP client.
|
* @param client The HTTP client to use for requests.
|
||||||
*/
|
*/
|
||||||
abstract class BackendRepository internal constructor(
|
abstract class BackendRepository internal constructor(
|
||||||
httpClientConfig: HttpClientConfig<OkHttpConfig>.() -> Unit = {},
|
protected val client: HttpClient,
|
||||||
) {
|
) {
|
||||||
protected val client: HttpClient = HttpClient(OkHttp, httpClientConfig)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A user.
|
* A user.
|
||||||
*
|
*
|
||||||
|
@ -13,52 +13,14 @@ 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.GitHubMember
|
||||||
import app.revanced.api.repository.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubContributor
|
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 app.revanced.api.repository.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubRelease
|
||||||
|
import io.ktor.client.*
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.*
|
||||||
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.client.plugins.resources.*
|
||||||
import io.ktor.client.plugins.resources.Resources
|
|
||||||
import io.ktor.serialization.kotlinx.json.*
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.datetime.TimeZone
|
import kotlinx.datetime.TimeZone
|
||||||
import kotlinx.datetime.toLocalDateTime
|
import kotlinx.datetime.toLocalDateTime
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.JsonNamingStrategy
|
|
||||||
|
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) {
|
||||||
class GitHubBackendRepository(token: String? = null) : BackendRepository({
|
|
||||||
install(HttpCache)
|
|
||||||
install(Resources)
|
|
||||||
install(ContentNegotiation) {
|
|
||||||
json(
|
|
||||||
Json {
|
|
||||||
ignoreUnknownKeys = true
|
|
||||||
namingStrategy = JsonNamingStrategy.SnakeCase
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultRequest { url("https://api.github.com") }
|
|
||||||
|
|
||||||
token?.let {
|
|
||||||
install(Auth) {
|
|
||||||
bearer {
|
|
||||||
loadTokens {
|
|
||||||
BearerTokens(
|
|
||||||
accessToken = it,
|
|
||||||
refreshToken = "", // Required dummy value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
sendWithoutRequest { true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
override suspend fun release(
|
override suspend fun release(
|
||||||
owner: String,
|
owner: String,
|
||||||
repository: String,
|
repository: String,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user