mirror of
https://github.com/ReVanced/revanced-api.git
synced 2026-01-28 13:41:03 +00:00
chore: Move files to correct folders
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
package app.revanced.api.configuration
|
||||
|
||||
import app.revanced.api.repository.AnnouncementRepository
|
||||
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.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 app.revanced.api.configuration.repository.AnnouncementRepository
|
||||
import app.revanced.api.configuration.repository.ConfigurationRepository
|
||||
import app.revanced.api.configuration.repository.backend.BackendRepository
|
||||
import app.revanced.api.configuration.repository.backend.github.GitHubBackendRepository
|
||||
import app.revanced.api.configuration.services.AnnouncementService
|
||||
import app.revanced.api.configuration.services.ApiService
|
||||
import app.revanced.api.configuration.services.AuthService
|
||||
import app.revanced.api.configuration.services.OldApiService
|
||||
import app.revanced.api.configuration.services.PatchesService
|
||||
import com.akuleshov7.ktoml.Toml
|
||||
import com.akuleshov7.ktoml.source.decodeFromStream
|
||||
import io.github.cdimascio.dotenv.Dotenv
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package app.revanced.api.configuration
|
||||
|
||||
import app.revanced.api.services.AuthService
|
||||
import app.revanced.api.configuration.services.AuthService
|
||||
import io.ktor.server.application.*
|
||||
import org.koin.ktor.ext.get
|
||||
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
package app.revanced.api.configuration.repository
|
||||
|
||||
import app.revanced.api.configuration.repository.AnnouncementRepository.AttachmentTable.announcement
|
||||
import app.revanced.api.configuration.schema.APIAnnouncement
|
||||
import app.revanced.api.configuration.schema.APILatestAnnouncement
|
||||
import app.revanced.api.configuration.schema.APIResponseAnnouncement
|
||||
import kotlinx.datetime.*
|
||||
import org.jetbrains.exposed.dao.IntEntity
|
||||
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.kotlin.datetime.datetime
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
internal class AnnouncementRepository(private val database: Database) {
|
||||
init {
|
||||
transaction {
|
||||
SchemaUtils.create(AnnouncementTable, AttachmentTable)
|
||||
}
|
||||
}
|
||||
|
||||
fun all() = transaction {
|
||||
buildSet {
|
||||
AnnouncementEntity.all().forEach { announcement ->
|
||||
add(announcement.toApi())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun all(channel: String) = transaction {
|
||||
buildSet {
|
||||
AnnouncementEntity.find { AnnouncementTable.channel eq channel }.forEach { announcement ->
|
||||
add(announcement.toApi())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun delete(id: Int) = transaction {
|
||||
val announcement = AnnouncementEntity.findById(id) ?: return@transaction
|
||||
|
||||
announcement.delete()
|
||||
}
|
||||
|
||||
fun latest() = transaction {
|
||||
AnnouncementEntity.all().maxByOrNull { it.createdAt }?.toApi()
|
||||
}
|
||||
|
||||
fun latest(channel: String) = transaction {
|
||||
AnnouncementEntity.find { AnnouncementTable.channel eq channel }.maxByOrNull { it.createdAt }?.toApi()
|
||||
}
|
||||
|
||||
fun latestId() = transaction {
|
||||
AnnouncementEntity.all().maxByOrNull { it.createdAt }?.id?.value?.let {
|
||||
APILatestAnnouncement(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun latestId(channel: String) = transaction {
|
||||
AnnouncementEntity.find { AnnouncementTable.channel eq channel }.maxByOrNull { it.createdAt }?.id?.value?.let {
|
||||
APILatestAnnouncement(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun archive(
|
||||
id: Int,
|
||||
archivedAt: LocalDateTime?,
|
||||
) = transaction {
|
||||
AnnouncementEntity.findById(id)?.apply {
|
||||
this.archivedAt = archivedAt ?: java.time.LocalDateTime.now().toKotlinLocalDateTime()
|
||||
}
|
||||
}
|
||||
|
||||
fun unarchive(id: Int) = transaction {
|
||||
AnnouncementEntity.findById(id)?.apply {
|
||||
archivedAt = null
|
||||
}
|
||||
}
|
||||
|
||||
fun new(new: APIAnnouncement) = transaction {
|
||||
AnnouncementEntity.new announcement@{
|
||||
author = new.author
|
||||
title = new.title
|
||||
content = new.content
|
||||
channel = new.channel
|
||||
createdAt = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
|
||||
archivedAt = new.archivedAt
|
||||
level = new.level
|
||||
}.also { newAnnouncement ->
|
||||
new.attachmentUrls.map {
|
||||
AttachmentEntity.new {
|
||||
url = it
|
||||
announcement = newAnnouncement
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun update(id: Int, new: APIAnnouncement) = transaction {
|
||||
AnnouncementEntity.findById(id)?.apply {
|
||||
author = new.author
|
||||
title = new.title
|
||||
content = new.content
|
||||
channel = new.channel
|
||||
archivedAt = new.archivedAt
|
||||
level = new.level
|
||||
|
||||
attachments.forEach(AttachmentEntity::delete)
|
||||
new.attachmentUrls.map {
|
||||
AttachmentEntity.new {
|
||||
url = it
|
||||
announcement = this@apply
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> 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<Int>) : IntEntity(id) {
|
||||
companion object : IntEntityClass<AnnouncementEntity>(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<Int>) : IntEntity(id) {
|
||||
companion object : IntEntityClass<AttachmentEntity>(AttachmentTable)
|
||||
|
||||
var url by AttachmentTable.url
|
||||
var announcement by AnnouncementEntity referencedOn AttachmentTable.announcement
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package app.revanced.api.configuration.repository
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal class ConfigurationRepository(
|
||||
val organization: String,
|
||||
@SerialName("patches-repository")
|
||||
val patchesRepository: String,
|
||||
@SerialName("integrations-repositories")
|
||||
val integrationsRepositoryNames: Set<String>,
|
||||
@SerialName("contributors-repositories")
|
||||
val contributorsRepositoryNames: Set<String>,
|
||||
@SerialName("api-version")
|
||||
val apiVersion: Int = 1,
|
||||
)
|
||||
@@ -0,0 +1,136 @@
|
||||
package app.revanced.api.configuration.repository.backend
|
||||
|
||||
import io.ktor.client.*
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* The backend of the application used to get data for the API.
|
||||
*
|
||||
* @param client The HTTP client to use for requests.
|
||||
*/
|
||||
abstract class BackendRepository internal constructor(
|
||||
protected val client: HttpClient,
|
||||
) {
|
||||
/**
|
||||
* A user.
|
||||
*
|
||||
* @property name The name of the user.
|
||||
* @property avatarUrl The URL to the avatar of the user.
|
||||
* @property url The URL to the profile of the user.
|
||||
*/
|
||||
interface BackendUser {
|
||||
val name: String
|
||||
val avatarUrl: String
|
||||
val url: String
|
||||
}
|
||||
|
||||
/**
|
||||
* An organization.
|
||||
*
|
||||
* @property members The members of the organization.
|
||||
*/
|
||||
class BackendOrganization(
|
||||
val members: Set<BackendMember>,
|
||||
) {
|
||||
/**
|
||||
* A member of an organization.
|
||||
*
|
||||
* @property name The name of the member.
|
||||
* @property avatarUrl The URL to the avatar of the member.
|
||||
* @property url 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 BackendMember(
|
||||
override val name: String,
|
||||
override val avatarUrl: String,
|
||||
override val url: String,
|
||||
val bio: String?,
|
||||
val gpgKeysUrl: String,
|
||||
) : BackendUser
|
||||
|
||||
/**
|
||||
* A repository of an organization.
|
||||
*
|
||||
* @property contributors The contributors of the repository.
|
||||
*/
|
||||
class BackendRepository(
|
||||
val contributors: Set<BackendContributor>,
|
||||
) {
|
||||
/**
|
||||
* A contributor of a repository.
|
||||
*
|
||||
* @property name The name of the contributor.
|
||||
* @property avatarUrl The URL to the avatar of the contributor.
|
||||
* @property url The URL to the profile of the contributor.
|
||||
* @property contributions The number of contributions of the contributor.
|
||||
*/
|
||||
@Serializable
|
||||
class BackendContributor(
|
||||
override val name: String,
|
||||
override val avatarUrl: String,
|
||||
override val url: String,
|
||||
val contributions: Int,
|
||||
) : BackendUser
|
||||
|
||||
/**
|
||||
* 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 BackendRelease(
|
||||
val tag: String,
|
||||
val releaseNote: String,
|
||||
val createdAt: LocalDateTime,
|
||||
val assets: Set<BackendAsset>,
|
||||
) {
|
||||
/**
|
||||
* An asset of a release.
|
||||
*
|
||||
* @property downloadUrl The URL to download the asset.
|
||||
*/
|
||||
@Serializable
|
||||
class BackendAsset(
|
||||
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.
|
||||
* @return The release.
|
||||
*/
|
||||
abstract suspend fun release(
|
||||
owner: String,
|
||||
repository: String,
|
||||
tag: String? = null,
|
||||
): BackendOrganization.BackendRepository.BackendRelease
|
||||
|
||||
/**
|
||||
* 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 contributors(owner: String, repository: String): Set<BackendOrganization.BackendRepository.BackendContributor>
|
||||
|
||||
/**
|
||||
* Get the members of an organization.
|
||||
*
|
||||
* @param organization The name of the organization.
|
||||
* @return The members.
|
||||
*/
|
||||
abstract suspend fun members(organization: String): Set<BackendOrganization.BackendMember>
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package app.revanced.api.configuration.repository.backend.github
|
||||
|
||||
import app.revanced.api.configuration.repository.backend.BackendRepository
|
||||
import app.revanced.api.configuration.repository.backend.BackendRepository.BackendOrganization.BackendMember
|
||||
import app.revanced.api.configuration.repository.backend.BackendRepository.BackendOrganization.BackendRepository.BackendContributor
|
||||
import app.revanced.api.configuration.repository.backend.BackendRepository.BackendOrganization.BackendRepository.BackendRelease
|
||||
import app.revanced.api.configuration.repository.backend.BackendRepository.BackendOrganization.BackendRepository.BackendRelease.BackendAsset
|
||||
import app.revanced.api.configuration.repository.backend.github.api.Request
|
||||
import app.revanced.api.configuration.repository.backend.github.api.Request.Organization.Members
|
||||
import app.revanced.api.configuration.repository.backend.github.api.Request.Organization.Repository.Contributors
|
||||
import app.revanced.api.configuration.repository.backend.github.api.Request.Organization.Repository.Releases
|
||||
import app.revanced.api.configuration.repository.backend.github.api.Response
|
||||
import app.revanced.api.configuration.repository.backend.github.api.Response.GitHubOrganization.GitHubMember
|
||||
import app.revanced.api.configuration.repository.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubContributor
|
||||
import app.revanced.api.configuration.repository.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubRelease
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.plugins.resources.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
|
||||
class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) {
|
||||
override suspend fun release(
|
||||
owner: String,
|
||||
repository: String,
|
||||
tag: String?,
|
||||
): BackendRelease {
|
||||
val release: GitHubRelease = if (tag != null) {
|
||||
client.get(Releases.Tag(owner, repository, tag)).body()
|
||||
} else {
|
||||
client.get(Releases.Latest(owner, repository)).body()
|
||||
}
|
||||
|
||||
return BackendRelease(
|
||||
tag = release.tagName,
|
||||
releaseNote = release.body,
|
||||
createdAt = release.createdAt.toLocalDateTime(TimeZone.UTC),
|
||||
assets = release.assets.map {
|
||||
BackendAsset(downloadUrl = it.browserDownloadUrl)
|
||||
}.toSet(),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun contributors(
|
||||
owner: String,
|
||||
repository: String,
|
||||
): Set<BackendContributor> {
|
||||
val contributors: Set<GitHubContributor> = client.get(Contributors(owner, repository)).body()
|
||||
|
||||
return contributors.map {
|
||||
BackendContributor(
|
||||
name = it.login,
|
||||
avatarUrl = it.avatarUrl,
|
||||
url = it.url,
|
||||
contributions = it.contributions,
|
||||
)
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
override suspend fun members(organization: String): Set<BackendMember> {
|
||||
// Get the list of members of the organization.
|
||||
val members: Set<GitHubMember> = client.get(Members(organization)).body()
|
||||
|
||||
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.GitHubUser>()
|
||||
}
|
||||
}
|
||||
}.awaitAll().map { user ->
|
||||
// Map the user back to a member.
|
||||
BackendMember(
|
||||
name = user.login,
|
||||
avatarUrl = user.avatarUrl,
|
||||
url = user.url,
|
||||
bio = user.bio,
|
||||
gpgKeysUrl = "https://github.com/${user.login}.gpg",
|
||||
)
|
||||
}.toSet()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package app.revanced.api.configuration.repository.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package app.revanced.api.configuration.repository.backend.github.api
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
class Response {
|
||||
interface IGitHubUser {
|
||||
val login: String
|
||||
val avatarUrl: String
|
||||
val url: String
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class GitHubUser(
|
||||
override val login: String,
|
||||
override val avatarUrl: String,
|
||||
override val url: String,
|
||||
val bio: String?,
|
||||
) : IGitHubUser
|
||||
|
||||
class GitHubOrganization {
|
||||
@Serializable
|
||||
class GitHubMember(
|
||||
override val login: String,
|
||||
override val avatarUrl: String,
|
||||
override val url: String,
|
||||
) : IGitHubUser
|
||||
|
||||
class GitHubRepository {
|
||||
@Serializable
|
||||
class GitHubContributor(
|
||||
override val login: String,
|
||||
override val avatarUrl: String,
|
||||
override val url: String,
|
||||
val contributions: Int,
|
||||
) : IGitHubUser
|
||||
|
||||
@Serializable
|
||||
class GitHubRelease(
|
||||
val tagName: String,
|
||||
val assets: Set<GitHubAsset>,
|
||||
val createdAt: Instant,
|
||||
val body: String,
|
||||
) {
|
||||
@Serializable
|
||||
class GitHubAsset(
|
||||
val browserDownloadUrl: String,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package app.revanced.api.configuration.routing
|
||||
|
||||
import app.revanced.api.configuration.repository.ConfigurationRepository
|
||||
import app.revanced.api.configuration.routing.routes.announcementsRoute
|
||||
import app.revanced.api.configuration.routing.routes.oldApiRoute
|
||||
import app.revanced.api.configuration.routing.routes.patchesRoute
|
||||
import app.revanced.api.configuration.routing.routes.rootRoute
|
||||
import app.revanced.api.repository.ConfigurationRepository
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.routing.*
|
||||
import org.koin.ktor.ext.get
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
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 app.revanced.api.configuration.schema.APIAnnouncement
|
||||
import app.revanced.api.configuration.schema.APIAnnouncementArchivedAt
|
||||
import app.revanced.api.configuration.services.AnnouncementService
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.auth.*
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package app.revanced.api.configuration.routing.routes
|
||||
|
||||
import app.revanced.api.services.ApiService
|
||||
import app.revanced.api.services.AuthService
|
||||
import app.revanced.api.configuration.services.ApiService
|
||||
import app.revanced.api.configuration.services.AuthService
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.auth.*
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package app.revanced.api.configuration.routing.routes
|
||||
|
||||
import app.revanced.api.repository.OldApiService
|
||||
import app.revanced.api.configuration.services.OldApiService
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.routing.*
|
||||
import org.koin.ktor.ext.get
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package app.revanced.api.configuration.routing.routes
|
||||
|
||||
import app.revanced.api.services.PatchesService
|
||||
import app.revanced.api.configuration.services.PatchesService
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.response.*
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package app.revanced.api.configuration.schema
|
||||
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class APIRelease(
|
||||
val version: String,
|
||||
val createdAt: LocalDateTime,
|
||||
val changelog: String,
|
||||
val assets: Set<APIAsset>,
|
||||
)
|
||||
|
||||
interface APIUser {
|
||||
val name: String
|
||||
val avatarUrl: String
|
||||
val url: String
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class APIMember(
|
||||
override val name: String,
|
||||
override val avatarUrl: String,
|
||||
override val url: String,
|
||||
val gpgKeysUrl: String,
|
||||
) : APIUser
|
||||
|
||||
@Serializable
|
||||
class APIContributor(
|
||||
override val name: String,
|
||||
override val avatarUrl: String,
|
||||
override val url: String,
|
||||
val contributions: Int,
|
||||
) : APIUser
|
||||
|
||||
@Serializable
|
||||
class APIContributable(
|
||||
val name: String,
|
||||
val contributors: Set<APIContributor>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class APIAsset(
|
||||
val downloadUrl: String,
|
||||
) {
|
||||
val type = when {
|
||||
downloadUrl.endsWith(".jar") -> Type.PATCHES
|
||||
downloadUrl.endsWith(".apk") -> Type.INTEGRATIONS
|
||||
else -> Type.UNKNOWN
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
@SerialName("patches")
|
||||
PATCHES,
|
||||
|
||||
@SerialName("integrations")
|
||||
INTEGRATIONS,
|
||||
|
||||
@SerialName("unknown")
|
||||
UNKNOWN,
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class APIReleaseVersion(
|
||||
val version: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class APIAnnouncement(
|
||||
val author: String? = null,
|
||||
val title: String,
|
||||
val content: String? = null,
|
||||
val attachmentUrls: Set<String> = emptySet(),
|
||||
val channel: String? = null,
|
||||
val archivedAt: LocalDateTime? = null,
|
||||
val level: Int = 0,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class APIResponseAnnouncement(
|
||||
val id: Int,
|
||||
val author: String? = null,
|
||||
val title: String,
|
||||
val content: String? = null,
|
||||
val attachmentUrls: Set<String> = emptySet(),
|
||||
val channel: String? = null,
|
||||
val createdAt: LocalDateTime,
|
||||
val archivedAt: LocalDateTime? = null,
|
||||
val level: Int = 0,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class APILatestAnnouncement(
|
||||
val id: Int,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class APIAnnouncementArchivedAt(
|
||||
val archivedAt: LocalDateTime,
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
package app.revanced.api.configuration.services
|
||||
|
||||
import app.revanced.api.configuration.repository.AnnouncementRepository
|
||||
import app.revanced.api.configuration.schema.APIAnnouncement
|
||||
import app.revanced.api.configuration.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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package app.revanced.api.configuration.services
|
||||
|
||||
import app.revanced.api.configuration.repository.ConfigurationRepository
|
||||
import app.revanced.api.configuration.repository.backend.BackendRepository
|
||||
import app.revanced.api.configuration.schema.APIContributable
|
||||
import app.revanced.api.configuration.schema.APIContributor
|
||||
import app.revanced.api.configuration.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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package app.revanced.api.configuration.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 java.util.*
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
internal class AuthService(
|
||||
private val issuer: String,
|
||||
private val validityInMin: Int,
|
||||
private val jwtSecret: String,
|
||||
private val basicUsername: String,
|
||||
private val basicPassword: String,
|
||||
) {
|
||||
val configureSecurity: Application.() -> Unit = {
|
||||
install(Authentication) {
|
||||
jwt("jwt") {
|
||||
verifier(
|
||||
JWT.require(Algorithm.HMAC256(jwtSecret))
|
||||
.withIssuer(issuer)
|
||||
.build(),
|
||||
)
|
||||
validate { credential -> JWTPrincipal(credential.payload) }
|
||||
}
|
||||
|
||||
basic("basic") {
|
||||
validate { credentials ->
|
||||
if (credentials.name == basicUsername && credentials.password == basicPassword) {
|
||||
UserIdPrincipal(credentials.name)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun newToken(): String = JWT.create()
|
||||
.withIssuer(issuer)
|
||||
.withExpiresAt(Date(System.currentTimeMillis() + validityInMin.minutes.inWholeMilliseconds))
|
||||
.sign(Algorithm.HMAC256(jwtSecret))
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package app.revanced.api.configuration.services
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package app.revanced.api.configuration.services
|
||||
|
||||
import app.revanced.api.configuration.repository.ConfigurationRepository
|
||||
import app.revanced.api.configuration.repository.backend.BackendRepository
|
||||
import app.revanced.api.configuration.schema.APIAsset
|
||||
import app.revanced.api.configuration.schema.APIRelease
|
||||
import app.revanced.api.configuration.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<String, ByteArray>()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user