mirror of
https://github.com/revanced/revanced-api.git
synced 2025-04-29 14:14:29 +02:00
style: Lint code
This commit is contained in:
parent
6b34aaf5db
commit
e798a4c070
3
.editorconfig
Normal file
3
.editorconfig
Normal file
@ -0,0 +1,3 @@
|
||||
[*.{kt,kts}]
|
||||
ktlint_code_style = intellij_idea
|
||||
ktlint_standard_no-wildcard-imports = disabled
|
@ -11,7 +11,7 @@ import kotlinx.serialization.Serializable
|
||||
* @param httpClientConfig The configuration of the HTTP client.
|
||||
*/
|
||||
abstract class Backend(
|
||||
httpClientConfig: HttpClientConfig<OkHttpConfig>.() -> Unit = {}
|
||||
httpClientConfig: HttpClientConfig<OkHttpConfig>.() -> Unit = {},
|
||||
) {
|
||||
protected val client: HttpClient = HttpClient(OkHttp, httpClientConfig)
|
||||
|
||||
@ -34,7 +34,7 @@ abstract class Backend(
|
||||
* @property members The members of the organization.
|
||||
*/
|
||||
class BackendOrganization(
|
||||
val members: Set<BackendMember>
|
||||
val members: Set<BackendMember>,
|
||||
) {
|
||||
/**
|
||||
* A member of an organization.
|
||||
@ -46,12 +46,12 @@ abstract class Backend(
|
||||
* @property gpgKeysUrl The URL to the GPG keys of the member.
|
||||
*/
|
||||
@Serializable
|
||||
class BackendMember (
|
||||
class BackendMember(
|
||||
override val name: String,
|
||||
override val avatarUrl: String,
|
||||
override val url: String,
|
||||
val bio: String?,
|
||||
val gpgKeysUrl: String
|
||||
val gpgKeysUrl: String,
|
||||
) : BackendUser
|
||||
|
||||
/**
|
||||
@ -60,7 +60,7 @@ abstract class Backend(
|
||||
* @property contributors The contributors of the repository.
|
||||
*/
|
||||
class BackendRepository(
|
||||
val contributors: Set<BackendContributor>
|
||||
val contributors: Set<BackendContributor>,
|
||||
) {
|
||||
/**
|
||||
* A contributor of a repository.
|
||||
@ -75,7 +75,7 @@ abstract class Backend(
|
||||
override val name: String,
|
||||
override val avatarUrl: String,
|
||||
override val url: String,
|
||||
val contributions: Int
|
||||
val contributions: Int,
|
||||
) : BackendUser
|
||||
|
||||
/**
|
||||
@ -91,7 +91,7 @@ abstract class Backend(
|
||||
val tag: String,
|
||||
val releaseNote: String,
|
||||
val createdAt: LocalDateTime,
|
||||
val assets: Set<BackendAsset>
|
||||
val assets: Set<BackendAsset>,
|
||||
) {
|
||||
/**
|
||||
* An asset of a release.
|
||||
@ -100,7 +100,7 @@ abstract class Backend(
|
||||
*/
|
||||
@Serializable
|
||||
class BackendAsset(
|
||||
val downloadUrl: String
|
||||
val downloadUrl: String,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,18 @@
|
||||
package app.revanced.api.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 io.ktor.client.call.*
|
||||
import io.ktor.client.plugins.*
|
||||
import io.ktor.client.plugins.auth.*
|
||||
@ -9,17 +20,6 @@ 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 app.revanced.api.backend.Backend.BackendOrganization.BackendMember
|
||||
import app.revanced.api.backend.Backend.BackendOrganization.BackendRepository.BackendRelease
|
||||
import app.revanced.api.backend.Backend.BackendOrganization.BackendRepository.BackendContributor
|
||||
import app.revanced.api.backend.Backend.BackendOrganization.BackendRepository.BackendRelease.BackendAsset
|
||||
import app.revanced.api.backend.github.api.Request.Organization.Repository.Releases
|
||||
import app.revanced.api.backend.github.api.Request.Organization.Repository.Contributors
|
||||
import app.revanced.api.backend.github.api.Request.Organization.Members
|
||||
import app.revanced.api.backend.github.api.Response
|
||||
import app.revanced.api.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubRelease
|
||||
import app.revanced.api.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubContributor
|
||||
import app.revanced.api.backend.github.api.Response.GitHubOrganization.GitHubMember
|
||||
import io.ktor.client.plugins.resources.Resources
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.coroutines.*
|
||||
@ -28,18 +28,18 @@ import kotlinx.datetime.toLocalDateTime
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonNamingStrategy
|
||||
import org.koin.dsl.bind
|
||||
import org.koin.dsl.module
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
class GitHubBackend(token: String? = null) : Backend({
|
||||
install(HttpCache)
|
||||
install(Resources)
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
ignoreUnknownKeys = true
|
||||
namingStrategy = JsonNamingStrategy.SnakeCase
|
||||
})
|
||||
json(
|
||||
Json {
|
||||
ignoreUnknownKeys = true
|
||||
namingStrategy = JsonNamingStrategy.SnakeCase
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
defaultRequest { url("https://api.github.com") }
|
||||
@ -50,7 +50,7 @@ class GitHubBackend(token: String? = null) : Backend({
|
||||
loadTokens {
|
||||
BearerTokens(
|
||||
accessToken = it,
|
||||
refreshToken = "" // Required dummy value
|
||||
refreshToken = "", // Required dummy value
|
||||
)
|
||||
}
|
||||
|
||||
@ -64,11 +64,11 @@ class GitHubBackend(token: String? = null) : Backend({
|
||||
repository: String,
|
||||
tag: String?,
|
||||
): BackendRelease {
|
||||
val release: GitHubRelease = if (tag != null)
|
||||
val release: GitHubRelease = if (tag != null) {
|
||||
client.get(Releases.Tag(owner, repository, tag)).body()
|
||||
else
|
||||
} else {
|
||||
client.get(Releases.Latest(owner, repository)).body()
|
||||
|
||||
}
|
||||
|
||||
return BackendRelease(
|
||||
tag = release.tagName,
|
||||
@ -76,13 +76,13 @@ class GitHubBackend(token: String? = null) : Backend({
|
||||
createdAt = release.createdAt.toLocalDateTime(TimeZone.UTC),
|
||||
assets = release.assets.map {
|
||||
BackendAsset(downloadUrl = it.browserDownloadUrl)
|
||||
}.toSet()
|
||||
}.toSet(),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getContributors(
|
||||
owner: String,
|
||||
repository: String
|
||||
repository: String,
|
||||
): Set<BackendContributor> {
|
||||
val contributors: Set<GitHubContributor> = client.get(Contributors(owner, repository)).body()
|
||||
|
||||
@ -91,7 +91,7 @@ class GitHubBackend(token: String? = null) : Backend({
|
||||
name = it.login,
|
||||
avatarUrl = it.avatarUrl,
|
||||
url = it.url,
|
||||
contributions = it.contributions
|
||||
contributions = it.contributions,
|
||||
)
|
||||
}.toSet()
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import io.ktor.resources.*
|
||||
class Request {
|
||||
@Resource("/users/{username}")
|
||||
class User(val username: String)
|
||||
|
||||
class Organization {
|
||||
@Resource("/orgs/{org}/members")
|
||||
class Members(val org: String)
|
||||
|
@ -3,7 +3,6 @@ package app.revanced.api.backend.github.api
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
class Response {
|
||||
interface IGitHubUser {
|
||||
val login: String
|
||||
@ -12,7 +11,7 @@ class Response {
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class GitHubUser (
|
||||
class GitHubUser(
|
||||
override val login: String,
|
||||
override val avatarUrl: String,
|
||||
override val url: String,
|
||||
@ -41,11 +40,11 @@ class Response {
|
||||
val tagName: String,
|
||||
val assets: Set<GitHubAsset>,
|
||||
val createdAt: Instant,
|
||||
val body: String
|
||||
val body: String,
|
||||
) {
|
||||
@Serializable
|
||||
class GitHubAsset(
|
||||
val browserDownloadUrl: String
|
||||
val browserDownloadUrl: String,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
package app.revanced.api.modules
|
||||
|
||||
import app.revanced.api.modules.AnnouncementService.Attachments.announcement
|
||||
import app.revanced.api.schema.APIResponseAnnouncement
|
||||
import app.revanced.api.schema.APIAnnouncement
|
||||
import app.revanced.api.schema.APILatestAnnouncement
|
||||
import app.revanced.api.schema.APIResponseAnnouncement
|
||||
import kotlinx.datetime.*
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.jetbrains.exposed.dao.IntEntity
|
||||
import org.jetbrains.exposed.dao.IntEntityClass
|
||||
import org.jetbrains.exposed.dao.id.EntityID
|
||||
@ -13,7 +12,7 @@ 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() {
|
||||
@ -52,7 +51,7 @@ class AnnouncementService(private val database: Database) {
|
||||
channel,
|
||||
createdAt,
|
||||
archivedAt,
|
||||
level
|
||||
level,
|
||||
)
|
||||
}
|
||||
|
||||
@ -107,7 +106,7 @@ class AnnouncementService(private val database: Database) {
|
||||
|
||||
fun archive(
|
||||
id: Int,
|
||||
archivedAt: LocalDateTime?
|
||||
archivedAt: LocalDateTime?,
|
||||
) = transaction {
|
||||
Announcement.findById(id)?.apply {
|
||||
this.archivedAt = archivedAt ?: java.time.LocalDateTime.now().toKotlinLocalDateTime()
|
||||
|
@ -19,7 +19,7 @@ fun Application.configureDependencies() {
|
||||
globalModule,
|
||||
gitHubBackendModule,
|
||||
databaseModule,
|
||||
authModule
|
||||
authModule,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -51,7 +51,7 @@ val databaseModule = module {
|
||||
url = dotenv["DB_URL"],
|
||||
user = dotenv["DB_USER"],
|
||||
password = dotenv["DB_PASSWORD"],
|
||||
driver = "org.h2.Driver"
|
||||
driver = "org.h2.Driver",
|
||||
)
|
||||
}
|
||||
factory<AnnouncementService> {
|
||||
|
@ -23,41 +23,68 @@ fun Application.configureRouting() {
|
||||
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>.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>.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("/id") {
|
||||
channel {
|
||||
announcementId {
|
||||
latestId(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get { channel { announcement { latest(it) } } }
|
||||
get {
|
||||
channel {
|
||||
announcement {
|
||||
latest(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get("/{channel}") { channel { call.respond(announcementService.read(it)) } }
|
||||
get("/{channel}") {
|
||||
channel {
|
||||
call.respond(announcementService.read(it))
|
||||
}
|
||||
}
|
||||
|
||||
route("/latest") {
|
||||
get("/id") { announcementId { latestId() } }
|
||||
get("/id") {
|
||||
announcementId {
|
||||
latestId()
|
||||
}
|
||||
}
|
||||
|
||||
get { announcement { latest() } }
|
||||
get {
|
||||
announcement {
|
||||
latest()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get { call.respond(announcementService.read()) }
|
||||
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)
|
||||
call.parameters["id"]!!.toIntOrNull()?.let {
|
||||
block(it)
|
||||
} ?: call.respond(HttpStatusCode.BadRequest)
|
||||
|
||||
post { announcementService.new(call.receive<APIAnnouncement>()) }
|
||||
post {
|
||||
announcementService.new(call.receive<APIAnnouncement>())
|
||||
}
|
||||
|
||||
post("/{id}/archive") {
|
||||
id {
|
||||
@ -66,11 +93,23 @@ fun Application.configureRouting() {
|
||||
}
|
||||
}
|
||||
|
||||
post("/{id}/unarchive") { id { announcementService.unarchive(it) } }
|
||||
post("/{id}/unarchive") {
|
||||
id {
|
||||
announcementService.unarchive(it)
|
||||
}
|
||||
}
|
||||
|
||||
patch("/{id}") { id { announcementService.update(it, call.receive<APIAnnouncement>()) } }
|
||||
patch("/{id}") {
|
||||
id {
|
||||
announcementService.update(it, call.receive<APIAnnouncement>())
|
||||
}
|
||||
}
|
||||
|
||||
delete("/{id}") { id { announcementService.delete(it) } }
|
||||
delete("/{id}") {
|
||||
id {
|
||||
announcementService.delete(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,20 +117,23 @@ fun Application.configureRouting() {
|
||||
route("latest") {
|
||||
get {
|
||||
val patches = backend.getRelease(configuration.organization, configuration.patchesRepository)
|
||||
val integrations = configuration.integrationsRepositoryNames.map {
|
||||
async { backend.getRelease(configuration.organization, it) }
|
||||
}.awaitAll()
|
||||
val integrations =
|
||||
configuration.integrationsRepositoryNames.map {
|
||||
async { backend.getRelease(configuration.organization, it) }
|
||||
}.awaitAll()
|
||||
|
||||
val assets = (patches.assets + integrations.flatMap { it.assets }).filter {
|
||||
it.downloadUrl.endsWith(".apk") || it.downloadUrl.endsWith(".jar")
|
||||
}.map { APIAsset(it.downloadUrl) }.toSet()
|
||||
val assets =
|
||||
(patches.assets + integrations.flatMap { it.assets }).filter {
|
||||
it.downloadUrl.endsWith(".apk") || it.downloadUrl.endsWith(".jar")
|
||||
}.map { APIAsset(it.downloadUrl) }.toSet()
|
||||
|
||||
val release = APIRelease(
|
||||
patches.tag,
|
||||
patches.createdAt,
|
||||
patches.releaseNote,
|
||||
assets
|
||||
)
|
||||
val release =
|
||||
APIRelease(
|
||||
patches.tag,
|
||||
patches.createdAt,
|
||||
patches.releaseNote,
|
||||
assets,
|
||||
)
|
||||
|
||||
call.respond(release)
|
||||
}
|
||||
@ -112,24 +154,26 @@ fun Application.configureRouting() {
|
||||
}
|
||||
|
||||
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()
|
||||
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)
|
||||
}
|
||||
val team =
|
||||
backend.getMembers(configuration.organization).map {
|
||||
APIMember(it.name, it.avatarUrl, it.url, it.gpgKeysUrl)
|
||||
}
|
||||
|
||||
call.respond(team)
|
||||
}
|
||||
@ -140,7 +184,11 @@ fun Application.configureRouting() {
|
||||
}
|
||||
}
|
||||
|
||||
authenticate("basic") { get("/token") { call.respond(authService.newToken()) } }
|
||||
authenticate("basic") {
|
||||
get("/token") {
|
||||
call.respond(authService.newToken())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ class AuthService(
|
||||
verifier(
|
||||
JWT.require(Algorithm.HMAC256(jwtSecret))
|
||||
.withIssuer(issuer)
|
||||
.build()
|
||||
.build(),
|
||||
)
|
||||
validate { credential -> JWTPrincipal(credential.payload) }
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ class APIRelease(
|
||||
val version: String,
|
||||
val createdAt: LocalDateTime,
|
||||
val changelog: String,
|
||||
val assets: Set<APIAsset>
|
||||
val assets: Set<APIAsset>,
|
||||
)
|
||||
|
||||
interface APIUser {
|
||||
@ -22,7 +22,7 @@ class APIMember(
|
||||
override val name: String,
|
||||
override val avatarUrl: String,
|
||||
override val url: String,
|
||||
val gpgKeysUrl: String
|
||||
val gpgKeysUrl: String,
|
||||
) : APIUser
|
||||
|
||||
@Serializable
|
||||
@ -36,7 +36,7 @@ class APIContributor(
|
||||
@Serializable
|
||||
class APIContributable(
|
||||
val name: String,
|
||||
val contributors: Set<APIContributor>
|
||||
val contributors: Set<APIContributor>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@ -52,7 +52,7 @@ class APIAsset(
|
||||
|
||||
@Serializable
|
||||
class APIReleaseVersion(
|
||||
val version: String
|
||||
val version: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@ -63,7 +63,7 @@ class APIAnnouncement(
|
||||
val attachmentUrls: Set<String> = emptySet(),
|
||||
val channel: String? = null,
|
||||
val archivedAt: LocalDateTime? = null,
|
||||
val level: Int = 0
|
||||
val level: Int = 0,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@ -76,15 +76,15 @@ class APIResponseAnnouncement(
|
||||
val channel: String? = null,
|
||||
val createdAt: LocalDateTime,
|
||||
val archivedAt: LocalDateTime? = null,
|
||||
val level: Int = 0
|
||||
val level: Int = 0,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class APILatestAnnouncement(
|
||||
val id: Int
|
||||
val id: Int,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class APIAnnouncementArchivedAt(
|
||||
val archivedAt: LocalDateTime
|
||||
val archivedAt: LocalDateTime,
|
||||
)
|
@ -13,5 +13,5 @@ class APIConfiguration(
|
||||
@SerialName("contributors-repositories")
|
||||
val contributorsRepositoryNames: Set<String>,
|
||||
@SerialName("api-version")
|
||||
val apiVersion: Int = 1
|
||||
val apiVersion: Int = 1,
|
||||
)
|
@ -23,8 +23,8 @@ class ApplicationTest {
|
||||
organization = "ReVanced",
|
||||
patchesRepository = "",
|
||||
integrationsRepositoryNames = setOf(),
|
||||
contributorsRepositoryNames = setOf()
|
||||
)
|
||||
contributorsRepositoryNames = setOf(),
|
||||
),
|
||||
).let(::writeText)
|
||||
|
||||
deleteOnExit()
|
||||
@ -47,7 +47,7 @@ class ApplicationTest {
|
||||
headers {
|
||||
append(
|
||||
HttpHeaders.Authorization,
|
||||
"Basic ${"${dotenv["BASIC_USERNAME"]}:${dotenv["BASIC_PASSWORD"]}".encodeBase64()}"
|
||||
"Basic ${"${dotenv["BASIC_USERNAME"]}:${dotenv["BASIC_PASSWORD"]}".encodeBase64()}",
|
||||
)
|
||||
}
|
||||
}.bodyAsText()
|
||||
|
Loading…
x
Reference in New Issue
Block a user