mirror of
https://github.com/revanced/revanced-api.git
synced 2025-04-30 06:34:36 +02:00
perf: Make async db transactions and use List instead of Set
This commit is contained in:
parent
01780188b9
commit
a7d1892343
@ -27,6 +27,7 @@ import kotlinx.serialization.ExperimentalSerializationApi
|
|||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonNamingStrategy
|
import kotlinx.serialization.json.JsonNamingStrategy
|
||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.Database
|
||||||
|
import org.jetbrains.exposed.sql.transactions.TransactionManager
|
||||||
import org.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
import org.koin.core.parameter.parameterArrayOf
|
import org.koin.core.parameter.parameterArrayOf
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
@ -41,6 +42,7 @@ fun Application.configureDependencies(
|
|||||||
single {
|
single {
|
||||||
Dotenv.configure().load()
|
Dotenv.configure().load()
|
||||||
}
|
}
|
||||||
|
|
||||||
factory { params ->
|
factory { params ->
|
||||||
val defaultRequestUri: String = params.get<String>()
|
val defaultRequestUri: String = params.get<String>()
|
||||||
val configBlock = params.getOrNull<(HttpClientConfig<OkHttpConfig>.() -> Unit)>() ?: {}
|
val configBlock = params.getOrNull<(HttpClientConfig<OkHttpConfig>.() -> Unit)>() ?: {}
|
||||||
@ -54,17 +56,6 @@ fun Application.configureDependencies(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val repositoryModule = module {
|
val repositoryModule = module {
|
||||||
single {
|
|
||||||
val dotenv = get<Dotenv>()
|
|
||||||
|
|
||||||
Database.connect(
|
|
||||||
url = dotenv["DB_URL"],
|
|
||||||
user = dotenv["DB_USER"],
|
|
||||||
password = dotenv["DB_PASSWORD"],
|
|
||||||
driver = "org.h2.Driver",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
single<BackendRepository> {
|
single<BackendRepository> {
|
||||||
GitHubBackendRepository(
|
GitHubBackendRepository(
|
||||||
get {
|
get {
|
||||||
@ -106,7 +97,17 @@ fun Application.configureDependencies(
|
|||||||
Toml.decodeFromStream(configFile.inputStream())
|
Toml.decodeFromStream(configFile.inputStream())
|
||||||
}
|
}
|
||||||
|
|
||||||
singleOf(::AnnouncementRepository)
|
single {
|
||||||
|
val dotenv = get<Dotenv>()
|
||||||
|
|
||||||
|
TransactionManager.defaultDatabase = Database.connect(
|
||||||
|
url = dotenv["DB_URL"],
|
||||||
|
user = dotenv["DB_USER"],
|
||||||
|
password = dotenv["DB_PASSWORD"],
|
||||||
|
)
|
||||||
|
|
||||||
|
AnnouncementRepository()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val serviceModule = module {
|
val serviceModule = module {
|
||||||
|
@ -1,47 +1,41 @@
|
|||||||
package app.revanced.api.configuration.repository
|
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.APIAnnouncement
|
||||||
import app.revanced.api.configuration.schema.APIResponseAnnouncement
|
|
||||||
import app.revanced.api.configuration.schema.APIResponseAnnouncementId
|
import app.revanced.api.configuration.schema.APIResponseAnnouncementId
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.datetime.*
|
import kotlinx.datetime.*
|
||||||
import org.jetbrains.exposed.dao.IntEntity
|
import org.jetbrains.exposed.dao.IntEntity
|
||||||
import org.jetbrains.exposed.dao.IntEntityClass
|
import org.jetbrains.exposed.dao.IntEntityClass
|
||||||
import org.jetbrains.exposed.dao.id.EntityID
|
import org.jetbrains.exposed.dao.id.EntityID
|
||||||
import org.jetbrains.exposed.dao.id.IntIdTable
|
import org.jetbrains.exposed.dao.id.IntIdTable
|
||||||
|
import org.jetbrains.exposed.dao.load
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
|
import org.jetbrains.exposed.sql.kotlin.datetime.CurrentDateTime
|
||||||
import org.jetbrains.exposed.sql.kotlin.datetime.datetime
|
import org.jetbrains.exposed.sql.kotlin.datetime.datetime
|
||||||
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
|
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
|
||||||
|
import org.jetbrains.exposed.sql.transactions.experimental.suspendedTransactionAsync
|
||||||
|
|
||||||
internal class AnnouncementRepository(private val database: Database) {
|
internal class AnnouncementRepository {
|
||||||
init {
|
init {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
transaction {
|
transaction {
|
||||||
SchemaUtils.create(AnnouncementTable, AttachmentTable)
|
SchemaUtils.create(Announcements, Attachments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun all() = transaction {
|
suspend fun all() = transaction {
|
||||||
buildSet {
|
Announcement.all()
|
||||||
AnnouncementEntity.all().forEach { announcement ->
|
|
||||||
add(announcement.toApi())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun all(channel: String) = transaction {
|
suspend fun all(channel: String) = transaction {
|
||||||
buildSet {
|
Announcement.find { Announcements.channel eq channel }
|
||||||
AnnouncementEntity.find { AnnouncementTable.channel eq channel }.forEach { announcement ->
|
|
||||||
add(announcement.toApi())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun delete(id: Int) = transaction {
|
suspend fun delete(id: Int) = transaction {
|
||||||
val announcement = AnnouncementEntity.findById(id) ?: return@transaction
|
val announcement = Announcement.findById(id) ?: return@transaction
|
||||||
|
|
||||||
announcement.delete()
|
announcement.delete()
|
||||||
}
|
}
|
||||||
@ -49,21 +43,21 @@ internal class AnnouncementRepository(private val database: Database) {
|
|||||||
// TODO: These are inefficient, but I'm not sure how to make them more efficient.
|
// TODO: These are inefficient, but I'm not sure how to make them more efficient.
|
||||||
|
|
||||||
suspend fun latest() = transaction {
|
suspend fun latest() = transaction {
|
||||||
AnnouncementEntity.all().maxByOrNull { it.createdAt }?.toApi()
|
Announcement.all().maxByOrNull { it.id }?.load(Announcement::attachments)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun latest(channel: String) = transaction {
|
suspend fun latest(channel: String) = transaction {
|
||||||
AnnouncementEntity.find { AnnouncementTable.channel eq channel }.maxByOrNull { it.createdAt }?.toApi()
|
Announcement.find { Announcements.channel eq channel }.maxByOrNull { it.id }?.load(Announcement::attachments)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun latestId() = transaction {
|
suspend fun latestId() = transaction {
|
||||||
AnnouncementEntity.all().maxByOrNull { it.createdAt }?.id?.value?.let {
|
Announcement.all().maxByOrNull { it.id }?.id?.value?.let {
|
||||||
APIResponseAnnouncementId(it)
|
APIResponseAnnouncementId(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun latestId(channel: String) = transaction {
|
suspend fun latestId(channel: String) = transaction {
|
||||||
AnnouncementEntity.find { AnnouncementTable.channel eq channel }.maxByOrNull { it.createdAt }?.id?.value?.let {
|
Announcement.find { Announcements.channel eq channel }.maxByOrNull { it.id }?.id?.value?.let {
|
||||||
APIResponseAnnouncementId(it)
|
APIResponseAnnouncementId(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,106 +66,98 @@ internal class AnnouncementRepository(private val database: Database) {
|
|||||||
id: Int,
|
id: Int,
|
||||||
archivedAt: LocalDateTime?,
|
archivedAt: LocalDateTime?,
|
||||||
) = transaction {
|
) = transaction {
|
||||||
AnnouncementEntity.findById(id)?.apply {
|
Announcement.findByIdAndUpdate(id) {
|
||||||
this.archivedAt = archivedAt ?: java.time.LocalDateTime.now().toKotlinLocalDateTime()
|
it.archivedAt = archivedAt ?: java.time.LocalDateTime.now().toKotlinLocalDateTime()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun unarchive(id: Int) = transaction {
|
suspend fun unarchive(id: Int) = transaction {
|
||||||
AnnouncementEntity.findById(id)?.apply {
|
Announcement.findByIdAndUpdate(id) {
|
||||||
archivedAt = null
|
it.archivedAt = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun new(new: APIAnnouncement) = transaction {
|
suspend fun new(new: APIAnnouncement) = transaction {
|
||||||
AnnouncementEntity.new announcement@{
|
Announcement.new {
|
||||||
author = new.author
|
author = new.author
|
||||||
title = new.title
|
title = new.title
|
||||||
content = new.content
|
content = new.content
|
||||||
channel = new.channel
|
channel = new.channel
|
||||||
createdAt = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
|
|
||||||
archivedAt = new.archivedAt
|
archivedAt = new.archivedAt
|
||||||
level = new.level
|
level = new.level
|
||||||
}.also { newAnnouncement ->
|
}.also { newAnnouncement ->
|
||||||
new.attachmentUrls.map {
|
new.attachmentUrls.map { newUrl ->
|
||||||
AttachmentEntity.new {
|
suspendedTransactionAsync {
|
||||||
url = it
|
Attachment.new {
|
||||||
announcement = newAnnouncement
|
url = newUrl
|
||||||
|
announcement = newAnnouncement
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}.awaitAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun update(id: Int, new: APIAnnouncement) = transaction {
|
suspend fun update(id: Int, new: APIAnnouncement) = transaction {
|
||||||
AnnouncementEntity.findById(id)?.apply {
|
Announcement.findByIdAndUpdate(id) {
|
||||||
author = new.author
|
it.author = new.author
|
||||||
title = new.title
|
it.title = new.title
|
||||||
content = new.content
|
it.content = new.content
|
||||||
channel = new.channel
|
it.channel = new.channel
|
||||||
archivedAt = new.archivedAt
|
it.archivedAt = new.archivedAt
|
||||||
level = new.level
|
it.level = new.level
|
||||||
|
}?.also { newAnnouncement ->
|
||||||
attachments.forEach(AttachmentEntity::delete)
|
newAnnouncement.attachments.map {
|
||||||
new.attachmentUrls.map {
|
suspendedTransactionAsync {
|
||||||
AttachmentEntity.new {
|
it.delete()
|
||||||
url = it
|
|
||||||
announcement = this@apply
|
|
||||||
}
|
}
|
||||||
}
|
}.awaitAll()
|
||||||
|
|
||||||
|
new.attachmentUrls.map { newUrl ->
|
||||||
|
suspendedTransactionAsync {
|
||||||
|
Attachment.new {
|
||||||
|
url = newUrl
|
||||||
|
announcement = newAnnouncement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.awaitAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun <T> transaction(statement: Transaction.() -> T) =
|
private suspend fun <T> transaction(statement: suspend Transaction.() -> T) =
|
||||||
newSuspendedTransaction(Dispatchers.IO, database, statement = statement)
|
newSuspendedTransaction(Dispatchers.IO, statement = statement)
|
||||||
|
|
||||||
private object AnnouncementTable : IntIdTable() {
|
private object Announcements : IntIdTable() {
|
||||||
val author = varchar("author", 32).nullable()
|
val author = varchar("author", 32).nullable()
|
||||||
val title = varchar("title", 64)
|
val title = varchar("title", 64)
|
||||||
val content = text("content").nullable()
|
val content = text("content").nullable()
|
||||||
val channel = varchar("channel", 16).nullable()
|
val channel = varchar("channel", 16).nullable()
|
||||||
val createdAt = datetime("createdAt")
|
val createdAt = datetime("createdAt").defaultExpression(CurrentDateTime)
|
||||||
val archivedAt = datetime("archivedAt").nullable()
|
val archivedAt = datetime("archivedAt").nullable()
|
||||||
val level = integer("level")
|
val level = integer("level")
|
||||||
}
|
}
|
||||||
|
|
||||||
private object AttachmentTable : IntIdTable() {
|
private object Attachments : IntIdTable() {
|
||||||
val url = varchar("url", 256)
|
val url = varchar("url", 256)
|
||||||
val announcement = reference("announcement", AnnouncementTable, onDelete = ReferenceOption.CASCADE)
|
val announcement = reference("announcement", Announcements, onDelete = ReferenceOption.CASCADE)
|
||||||
}
|
}
|
||||||
|
|
||||||
class AnnouncementEntity(id: EntityID<Int>) : IntEntity(id) {
|
class Announcement(id: EntityID<Int>) : IntEntity(id) {
|
||||||
companion object : IntEntityClass<AnnouncementEntity>(AnnouncementTable)
|
companion object : IntEntityClass<Announcement>(Announcements)
|
||||||
|
|
||||||
var author by AnnouncementTable.author
|
var author by Announcements.author
|
||||||
var title by AnnouncementTable.title
|
var title by Announcements.title
|
||||||
var content by AnnouncementTable.content
|
var content by Announcements.content
|
||||||
val attachments by AttachmentEntity referrersOn announcement
|
val attachments by Attachment referrersOn Attachments.announcement
|
||||||
var channel by AnnouncementTable.channel
|
var channel by Announcements.channel
|
||||||
var createdAt by AnnouncementTable.createdAt
|
var createdAt by Announcements.createdAt
|
||||||
var archivedAt by AnnouncementTable.archivedAt
|
var archivedAt by Announcements.archivedAt
|
||||||
var level by AnnouncementTable.level
|
var level by Announcements.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) {
|
class Attachment(id: EntityID<Int>) : IntEntity(id) {
|
||||||
companion object : IntEntityClass<AttachmentEntity>(AttachmentTable)
|
companion object : IntEntityClass<Attachment>(Attachments)
|
||||||
|
|
||||||
var url by AttachmentTable.url
|
var url by Attachments.url
|
||||||
var announcement by AnnouncementEntity referencedOn AttachmentTable.announcement
|
var announcement by Announcement referencedOn Attachments.announcement
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,8 @@ abstract class BackendRepository internal constructor(
|
|||||||
* @property members The members of the organization.
|
* @property members The members of the organization.
|
||||||
*/
|
*/
|
||||||
class BackendOrganization(
|
class BackendOrganization(
|
||||||
val members: Set<BackendMember>,
|
// Using a list instead of a set because set semantics are unnecessary here.
|
||||||
|
val members: List<BackendMember>,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* A member of an organization.
|
* A member of an organization.
|
||||||
@ -55,7 +56,8 @@ abstract class BackendRepository internal constructor(
|
|||||||
* @property url The URL to the GPG master key.
|
* @property url The URL to the GPG master key.
|
||||||
*/
|
*/
|
||||||
class GpgKeys(
|
class GpgKeys(
|
||||||
val ids: Set<String>,
|
// Using a list instead of a set because set semantics are unnecessary here.
|
||||||
|
val ids: List<String>,
|
||||||
val url: String,
|
val url: String,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -66,7 +68,8 @@ abstract class BackendRepository internal constructor(
|
|||||||
* @property contributors The contributors of the repository.
|
* @property contributors The contributors of the repository.
|
||||||
*/
|
*/
|
||||||
class BackendRepository(
|
class BackendRepository(
|
||||||
val contributors: Set<BackendContributor>,
|
// Using a list instead of a set because set semantics are unnecessary here.
|
||||||
|
val contributors: List<BackendContributor>,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* A contributor of a repository.
|
* A contributor of a repository.
|
||||||
@ -95,10 +98,11 @@ abstract class BackendRepository internal constructor(
|
|||||||
val tag: String,
|
val tag: String,
|
||||||
val releaseNote: String,
|
val releaseNote: String,
|
||||||
val createdAt: LocalDateTime,
|
val createdAt: LocalDateTime,
|
||||||
val assets: Set<BackendAsset>,
|
// Using a list instead of a set because set semantics are unnecessary here.
|
||||||
|
val assets: List<BackendAsset>,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun Set<BackendAsset>.first(assetRegex: Regex) = first { assetRegex.containsMatchIn(it.name) }
|
fun List<BackendAsset>.first(assetRegex: Regex) = first { assetRegex.containsMatchIn(it.name) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -149,7 +153,7 @@ abstract class BackendRepository internal constructor(
|
|||||||
* @param repository The name of the repository.
|
* @param repository The name of the repository.
|
||||||
* @return The contributors.
|
* @return The contributors.
|
||||||
*/
|
*/
|
||||||
abstract suspend fun contributors(owner: String, repository: String): Set<BackendOrganization.BackendRepository.BackendContributor>
|
abstract suspend fun contributors(owner: String, repository: String): List<BackendOrganization.BackendRepository.BackendContributor>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the members of an organization.
|
* Get the members of an organization.
|
||||||
@ -157,7 +161,7 @@ abstract class BackendRepository internal constructor(
|
|||||||
* @param organization The name of the organization.
|
* @param organization The name of the organization.
|
||||||
* @return The members.
|
* @return The members.
|
||||||
*/
|
*/
|
||||||
abstract suspend fun members(organization: String): Set<BackendOrganization.BackendMember>
|
abstract suspend fun members(organization: String): List<BackendOrganization.BackendMember>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the rate limit of the backend.
|
* Get the rate limit of the backend.
|
||||||
|
@ -39,15 +39,15 @@ class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) {
|
|||||||
name = it.name,
|
name = it.name,
|
||||||
downloadUrl = it.browserDownloadUrl,
|
downloadUrl = it.browserDownloadUrl,
|
||||||
)
|
)
|
||||||
}.toSet(),
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun contributors(
|
override suspend fun contributors(
|
||||||
owner: String,
|
owner: String,
|
||||||
repository: String,
|
repository: String,
|
||||||
): Set<BackendContributor> {
|
): List<BackendContributor> {
|
||||||
val contributors: Set<GitHubContributor> = client.get(
|
val contributors: List<GitHubContributor> = client.get(
|
||||||
Contributors(
|
Contributors(
|
||||||
owner,
|
owner,
|
||||||
repository,
|
repository,
|
||||||
@ -61,12 +61,12 @@ class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) {
|
|||||||
url = it.htmlUrl,
|
url = it.htmlUrl,
|
||||||
contributions = it.contributions,
|
contributions = it.contributions,
|
||||||
)
|
)
|
||||||
}.toSet()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun members(organization: String): Set<BackendMember> {
|
override suspend fun members(organization: String): List<BackendMember> {
|
||||||
// Get the list of members of the organization.
|
// Get the list of members of the organization.
|
||||||
val members: Set<GitHubOrganization.GitHubMember> = client.get(Organization.Members(organization)).body()
|
val members: List<GitHubOrganization.GitHubMember> = client.get(Organization.Members(organization)).body()
|
||||||
|
|
||||||
return coroutineScope {
|
return coroutineScope {
|
||||||
members.map { member ->
|
members.map { member ->
|
||||||
@ -78,7 +78,7 @@ class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) {
|
|||||||
},
|
},
|
||||||
async {
|
async {
|
||||||
// Get the GPG key of the user.
|
// Get the GPG key of the user.
|
||||||
client.get(User.GpgKeys(member.login)).body<Set<GitHubUser.GitHubGpgKey>>()
|
client.get(User.GpgKeys(member.login)).body<List<GitHubUser.GitHubGpgKey>>()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -87,7 +87,7 @@ class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) {
|
|||||||
val user = responses[0] as GitHubUser
|
val user = responses[0] as GitHubUser
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val gpgKeys = responses[1] as Set<GitHubUser.GitHubGpgKey>
|
val gpgKeys = responses[1] as List<GitHubUser.GitHubGpgKey>
|
||||||
|
|
||||||
BackendMember(
|
BackendMember(
|
||||||
name = user.login,
|
name = user.login,
|
||||||
@ -96,11 +96,11 @@ class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) {
|
|||||||
bio = user.bio,
|
bio = user.bio,
|
||||||
gpgKeys =
|
gpgKeys =
|
||||||
BackendMember.GpgKeys(
|
BackendMember.GpgKeys(
|
||||||
ids = gpgKeys.map { it.keyId }.toSet(),
|
ids = gpgKeys.map { it.keyId },
|
||||||
url = "https://api.github.com/users/${user.login}.gpg",
|
url = "https://api.github.com/users/${user.login}.gpg",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}.toSet()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun rateLimit(): BackendRateLimit {
|
override suspend fun rateLimit(): BackendRateLimit {
|
||||||
@ -153,7 +153,8 @@ class GitHubOrganization {
|
|||||||
@Serializable
|
@Serializable
|
||||||
class GitHubRelease(
|
class GitHubRelease(
|
||||||
val tagName: String,
|
val tagName: String,
|
||||||
val assets: Set<GitHubAsset>,
|
// Using a list instead of a set because set semantics are unnecessary here.
|
||||||
|
val assets: List<GitHubAsset>,
|
||||||
val createdAt: Instant,
|
val createdAt: Instant,
|
||||||
val body: String,
|
val body: String,
|
||||||
) {
|
) {
|
||||||
|
@ -8,7 +8,8 @@ class APIRelease(
|
|||||||
val version: String,
|
val version: String,
|
||||||
val createdAt: LocalDateTime,
|
val createdAt: LocalDateTime,
|
||||||
val description: String,
|
val description: String,
|
||||||
val assets: Set<APIAsset>,
|
// Using a list instead of a set because set semantics are unnecessary here.
|
||||||
|
val assets: List<APIAsset>,
|
||||||
)
|
)
|
||||||
|
|
||||||
interface APIUser {
|
interface APIUser {
|
||||||
@ -42,7 +43,8 @@ class APIContributor(
|
|||||||
@Serializable
|
@Serializable
|
||||||
class APIContributable(
|
class APIContributable(
|
||||||
val name: String,
|
val name: String,
|
||||||
val contributors: Set<APIContributor>,
|
// Using a list instead of a set because set semantics are unnecessary here.
|
||||||
|
val contributors: List<APIContributor>,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -69,7 +71,8 @@ class APIAnnouncement(
|
|||||||
val author: String? = null,
|
val author: String? = null,
|
||||||
val title: String,
|
val title: String,
|
||||||
val content: String? = null,
|
val content: String? = null,
|
||||||
val attachmentUrls: Set<String> = emptySet(),
|
// Using a list instead of a set because set semantics are unnecessary here.
|
||||||
|
val attachmentUrls: List<String> = emptyList(),
|
||||||
val channel: String? = null,
|
val channel: String? = null,
|
||||||
val archivedAt: LocalDateTime? = null,
|
val archivedAt: LocalDateTime? = null,
|
||||||
val level: Int = 0,
|
val level: Int = 0,
|
||||||
@ -81,7 +84,8 @@ class APIResponseAnnouncement(
|
|||||||
val author: String? = null,
|
val author: String? = null,
|
||||||
val title: String,
|
val title: String,
|
||||||
val content: String? = null,
|
val content: String? = null,
|
||||||
val attachmentUrls: Set<String> = emptySet(),
|
// Using a list instead of a set because set semantics are unnecessary here.
|
||||||
|
val attachmentUrls: List<String> = emptyList(),
|
||||||
val channel: String? = null,
|
val channel: String? = null,
|
||||||
val createdAt: LocalDateTime,
|
val createdAt: LocalDateTime,
|
||||||
val archivedAt: LocalDateTime? = null,
|
val archivedAt: LocalDateTime? = null,
|
||||||
|
@ -2,6 +2,7 @@ package app.revanced.api.configuration.services
|
|||||||
|
|
||||||
import app.revanced.api.configuration.repository.AnnouncementRepository
|
import app.revanced.api.configuration.repository.AnnouncementRepository
|
||||||
import app.revanced.api.configuration.schema.APIAnnouncement
|
import app.revanced.api.configuration.schema.APIAnnouncement
|
||||||
|
import app.revanced.api.configuration.schema.APIResponseAnnouncement
|
||||||
import app.revanced.api.configuration.schema.APIResponseAnnouncementId
|
import app.revanced.api.configuration.schema.APIResponseAnnouncementId
|
||||||
import kotlinx.datetime.LocalDateTime
|
import kotlinx.datetime.LocalDateTime
|
||||||
|
|
||||||
@ -11,11 +12,11 @@ internal class AnnouncementService(
|
|||||||
suspend fun latestId(channel: String): APIResponseAnnouncementId? = announcementRepository.latestId(channel)
|
suspend fun latestId(channel: String): APIResponseAnnouncementId? = announcementRepository.latestId(channel)
|
||||||
suspend fun latestId(): APIResponseAnnouncementId? = announcementRepository.latestId()
|
suspend fun latestId(): APIResponseAnnouncementId? = announcementRepository.latestId()
|
||||||
|
|
||||||
suspend fun latest(channel: String) = announcementRepository.latest(channel)
|
suspend fun latest(channel: String) = announcementRepository.latest(channel)?.toApi()
|
||||||
suspend fun latest() = announcementRepository.latest()
|
suspend fun latest() = announcementRepository.latest()?.toApi()
|
||||||
|
|
||||||
suspend fun all(channel: String) = announcementRepository.all(channel)
|
suspend fun all(channel: String) = announcementRepository.all(channel).map { it.toApi() }
|
||||||
suspend fun all() = announcementRepository.all()
|
suspend fun all() = announcementRepository.all().map { it.toApi() }
|
||||||
|
|
||||||
suspend fun new(new: APIAnnouncement) {
|
suspend fun new(new: APIAnnouncement) {
|
||||||
announcementRepository.new(new)
|
announcementRepository.new(new)
|
||||||
@ -32,4 +33,16 @@ internal class AnnouncementService(
|
|||||||
suspend fun delete(id: Int) {
|
suspend fun delete(id: Int) {
|
||||||
announcementRepository.delete(id)
|
announcementRepository.delete(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun AnnouncementRepository.Announcement.toApi() = APIResponseAnnouncement(
|
||||||
|
id.value,
|
||||||
|
author,
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
attachments.map { it.url },
|
||||||
|
channel,
|
||||||
|
createdAt,
|
||||||
|
archivedAt,
|
||||||
|
level,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,11 @@ internal class ApiService(
|
|||||||
it,
|
it,
|
||||||
backendRepository.contributors(configurationRepository.organization, it).map {
|
backendRepository.contributors(configurationRepository.organization, it).map {
|
||||||
APIContributor(it.name, it.avatarUrl, it.url, it.contributions)
|
APIContributor(it.name, it.avatarUrl, it.url, it.contributions)
|
||||||
}.toSet(),
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.awaitAll().toSet()
|
}.awaitAll()
|
||||||
|
|
||||||
suspend fun team() = backendRepository.members(configurationRepository.organization).map { member ->
|
suspend fun team() = backendRepository.members(configurationRepository.organization).map { member ->
|
||||||
APIMember(
|
APIMember(
|
||||||
@ -41,7 +41,7 @@ internal class ApiService(
|
|||||||
},
|
},
|
||||||
|
|
||||||
)
|
)
|
||||||
}.toSet()
|
}
|
||||||
|
|
||||||
suspend fun rateLimit() = backendRepository.rateLimit()?.let {
|
suspend fun rateLimit() = backendRepository.rateLimit()?.let {
|
||||||
APIRateLimit(it.limit, it.remaining, it.reset)
|
APIRateLimit(it.limit, it.remaining, it.reset)
|
||||||
|
@ -51,7 +51,7 @@ internal class PatchesService(
|
|||||||
patchesRelease.tag,
|
patchesRelease.tag,
|
||||||
patchesRelease.createdAt,
|
patchesRelease.createdAt,
|
||||||
patchesRelease.releaseNote,
|
patchesRelease.releaseNote,
|
||||||
setOf(patchesAsset, integrationsAsset),
|
listOf(patchesAsset, integrationsAsset),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user