feat: Initialize project

This commit is contained in:
oSumAtrIX
2024-02-01 04:12:05 +01:00
parent bf5eaa8940
commit 8ae50b543e
81 changed files with 4005 additions and 6302 deletions

View File

@@ -0,0 +1,140 @@
package app.revanced.api.backend
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import kotlinx.serialization.Serializable
/**
* The backend of the application used to get data for the API.
*
* @param httpClientConfig The configuration of the HTTP client.
*/
abstract class Backend(
httpClientConfig: HttpClientConfig<OkHttpConfig>.() -> Unit = {}
) {
protected val client: HttpClient = HttpClient(OkHttp, httpClientConfig)
/**
* A user.
*
* @property name The name of the user.
* @property avatarUrl The URL to the avatar of the user.
* @property profileUrl The URL to the profile of the user.
*/
interface User {
val name: String
val avatarUrl: String
val profileUrl: String
}
/**
* An organization.
*
* @property members The members of the organization.
*/
class Organization(
val members: Set<Member>
) {
/**
* A member of an organization.
*
* @property name The name of the member.
* @property avatarUrl The URL to the avatar of the member.
* @property profileUrl The URL to the profile of the member.
* @property bio The bio of the member.
* @property gpgKeysUrl The URL to the GPG keys of the member.
*/
@Serializable
class Member (
override val name: String,
override val avatarUrl: String,
override val profileUrl: String,
val bio: String?,
val gpgKeysUrl: String?
) : User
/**
* A repository of an organization.
*
* @property contributors The contributors of the repository.
*/
class Repository(
val contributors: Set<Contributor>
) {
/**
* A contributor of a repository.
*
* @property name The name of the contributor.
* @property avatarUrl The URL to the avatar of the contributor.
* @property profileUrl The URL to the profile of the contributor.
*/
@Serializable
class Contributor(
override val name: String,
override val avatarUrl: String,
override val profileUrl: String
) : User
/**
* A release of a repository.
*
* @property tag The tag of the release.
* @property assets The assets of the release.
* @property createdAt The date and time the release was created.
* @property releaseNote The release note of the release.
*/
@Serializable
class Release(
val tag: String,
val releaseNote: String,
val createdAt: String,
val assets: Set<Asset>
) {
/**
* An asset of a release.
*
* @property downloadUrl The URL to download the asset.
*/
@Serializable
class Asset(
val downloadUrl: String
)
}
}
}
/**
* Get a release of a repository.
*
* @param owner The owner of the repository.
* @param repository The name of the repository.
* @param tag The tag of the release. If null, the latest release is returned.
* @param preRelease Whether to return a pre-release.
* If no pre-release exists, the latest release is returned.
* If tag is not null, this parameter is ignored.
* @return The release.
*/
abstract suspend fun getRelease(
owner: String,
repository: String,
tag: String? = null,
preRelease: Boolean = false
): Organization.Repository.Release
/**
* Get the contributors of a repository.
*
* @param owner The owner of the repository.
* @param repository The name of the repository.
* @return The contributors.
*/
abstract suspend fun getContributors(owner: String, repository: String): Set<Organization.Repository.Contributor>
/**
* Get the members of an organization.
*
* @param organization The name of the organization.
* @return The members.
*/
abstract suspend fun getMembers(organization: String): Set<Organization.Member>
}

View File

@@ -0,0 +1,116 @@
package app.revanced.api.backend.github
import app.revanced.api.backend.Backend
import app.revanced.api.backend.github.api.Request
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 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.Organization.Repository.Release
import app.revanced.api.backend.github.api.Response.Organization.Repository.Contributor
import app.revanced.api.backend.github.api.Response.Organization.Member
import io.ktor.client.plugins.resources.Resources
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.*
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
@OptIn(ExperimentalSerializationApi::class)
class GitHubBackend(token: String? = null) : Backend({
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 getRelease(
owner: String,
repository: String,
tag: String?,
preRelease: Boolean
): Organization.Repository.Release {
val release = if (preRelease) {
val releases: Set<Release> = client.get(Releases(owner, repository)).body()
releases.firstOrNull { it.preReleases } ?: releases.first() // Latest pre-release or latest release
} else {
client.get(
tag?.let { Releases.Tag(owner, repository, it) }
?: Releases.Latest(owner, repository)
).body()
}
return Organization.Repository.Release(
tag = release.tagName,
releaseNote = release.body,
createdAt = release.createdAt,
assets = release.assets.map {
Organization.Repository.Release.Asset(
downloadUrl = it.browserDownloadUrl
)
}.toSet()
)
}
override suspend fun getContributors(owner: String, repository: String): Set<Organization.Repository.Contributor> {
val contributors: Set<Contributor> = client.get(Contributors(owner, repository)).body()
return contributors.map {
Organization.Repository.Contributor(
name = it.login,
avatarUrl = it.avatarUrl,
profileUrl = it.url
)
}.toSet()
}
override suspend fun getMembers(organization: String): Set<Organization.Member> {
// Get the list of members of the organization.
val members: Set<Member> = client.get(Members(organization)).body<Set<Member>>()
return runBlocking(Dispatchers.Default) {
members.map { member ->
// Map the member to a user in order to get the bio.
async {
client.get(Request.User(member.login)).body<Response.User>()
}
}
}.awaitAll().map { user ->
// Map the user back to a member.
Organization.Member(
name = user.login,
avatarUrl = user.avatarUrl,
profileUrl = user.url,
bio = user.bio,
gpgKeysUrl = "https://github.com/${user.login}.gpg",
)
}.toSet()
}
}

View File

@@ -0,0 +1,26 @@
package app.revanced.api.backend.github.api
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)
class Repository {
@Resource("/repos/{owner}/{repo}/contributors")
class Contributors(val owner: String, val repo: String)
@Resource("/repos/{owner}/{repo}/releases")
class Releases(val owner: String, val repo: String) {
@Resource("/repos/{owner}/{repo}/releases/tags/{tag}")
class Tag(val owner: String, val repo: String, val tag: String)
@Resource("/repos/{owner}/{repo}/releases/latest")
class Latest(val owner: String, val repo: String)
}
}
}
}

View File

@@ -0,0 +1,52 @@
package app.revanced.api.backend.github.api
import kotlinx.serialization.Serializable
class Response {
interface IUser {
val login: String
val avatarUrl: String
val url: String
}
@Serializable
class User (
override val login: String,
override val avatarUrl: String,
override val url: String,
val bio: String?,
) : IUser
class Organization {
@Serializable
class Member(
override val login: String,
override val avatarUrl: String,
override val url: String,
) : IUser
class Repository {
@Serializable
class Contributor(
override val login: String,
override val avatarUrl: String,
override val url: String,
) : IUser
@Serializable
class Release(
val tagName: String,
val assets: Set<Asset>,
val preReleases: Boolean,
val createdAt: String,
val body: String
) {
@Serializable
class Asset(
val browserDownloadUrl: String
)
}
}
}
}