chore: Move files to correct folders

This commit is contained in:
oSumAtrIX
2024-06-05 17:27:25 +02:00
parent 2430be75d8
commit 659cce3e03
19 changed files with 60 additions and 60 deletions

View File

@@ -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
}
}

View File

@@ -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,
)

View File

@@ -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>
}

View File

@@ -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()
}
}

View File

@@ -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)
}
}
}
}

View File

@@ -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,
)
}
}
}
}