mirror of
https://github.com/ReVanced/revanced-api.git
synced 2026-01-18 08:53:57 +00:00
Compare commits
14 Commits
v1.4.0-dev
...
v1.6.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
712ab3be8c | ||
|
|
db22874f06 | ||
|
|
5d5533a920 | ||
|
|
7f6e29de52 | ||
|
|
48469d32c2 | ||
|
|
7f9159fef1 | ||
|
|
b063b4daf2 | ||
|
|
58ba4cb11c | ||
|
|
55e3774f07 | ||
|
|
d5d9e04325 | ||
|
|
6c8153ba98 | ||
|
|
e871b23210 | ||
|
|
e22ec16e40 | ||
|
|
440fbbc6c2 |
66
CHANGELOG.md
66
CHANGELOG.md
@@ -1,3 +1,69 @@
|
|||||||
|
# [1.6.0-dev.2](https://github.com/ReVanced/revanced-api/compare/v1.6.0-dev.1...v1.6.0-dev.2) (2024-12-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Make some announcements schema fields nullable ([db22874](https://github.com/ReVanced/revanced-api/commit/db22874f063bae0c9e7f0c99a20cdf1b16addd89))
|
||||||
|
|
||||||
|
# [1.6.0-dev.1](https://github.com/ReVanced/revanced-api/compare/v1.5.0...v1.6.0-dev.1) (2024-11-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Allow setting `Announcement.createdAt` when creating an announcement ([7f6e29d](https://github.com/ReVanced/revanced-api/commit/7f6e29de5205f63ac4aaea490c844b58e14000c8))
|
||||||
|
|
||||||
|
# [1.5.0](https://github.com/ReVanced/revanced-api/compare/v1.4.0...v1.5.0) (2024-11-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Allow updating `createdAt` field for announcements ([58ba4cb](https://github.com/ReVanced/revanced-api/commit/58ba4cb11c789507826cd70ac548943a94da4223))
|
||||||
|
* Move spec url to versioned path ([e871b23](https://github.com/ReVanced/revanced-api/commit/e871b23210798723c34bce93c7567d8fbcf4e060))
|
||||||
|
* Simplify log pattern ([d5d9e04](https://github.com/ReVanced/revanced-api/commit/d5d9e04325fa93540be0438e7b51243e2aeeab3d))
|
||||||
|
|
||||||
|
# [1.5.0-dev.2](https://github.com/ReVanced/revanced-api/compare/v1.5.0-dev.1...v1.5.0-dev.2) (2024-11-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Allow updating `createdAt` field for announcements ([58ba4cb](https://github.com/ReVanced/revanced-api/commit/58ba4cb11c789507826cd70ac548943a94da4223))
|
||||||
|
* Simplify log pattern ([d5d9e04](https://github.com/ReVanced/revanced-api/commit/d5d9e04325fa93540be0438e7b51243e2aeeab3d))
|
||||||
|
|
||||||
|
# [1.5.0-dev.1](https://github.com/ReVanced/revanced-api/compare/v1.4.0...v1.5.0-dev.1) (2024-11-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Move spec url to versioned path ([e871b23](https://github.com/ReVanced/revanced-api/commit/e871b23210798723c34bce93c7567d8fbcf4e060))
|
||||||
|
* Simplify log pattern ([d5d9e04](https://github.com/ReVanced/revanced-api/commit/d5d9e04325fa93540be0438e7b51243e2aeeab3d))
|
||||||
|
|
||||||
|
# [1.5.0-dev.1](https://github.com/ReVanced/revanced-api/compare/v1.4.0...v1.5.0-dev.1) (2024-11-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Move spec url to versioned path ([e871b23](https://github.com/ReVanced/revanced-api/commit/e871b23210798723c34bce93c7567d8fbcf4e060))
|
||||||
|
|
||||||
|
# [1.4.0](https://github.com/ReVanced/revanced-api/compare/v1.3.0...v1.4.0) (2024-11-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Add missing logging level environment variable to .env.example ([3b62120](https://github.com/ReVanced/revanced-api/commit/3b6212065a5cfb95c303b6d0551747ba1eb317f6))
|
||||||
|
* Use new patches file extension ([d42a3a3](https://github.com/ReVanced/revanced-api/commit/d42a3a393396a0f4e9085cda46e0af2c12b63cb1))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add URL and use friendly name for `APIContributable` ([a5498ab](https://github.com/ReVanced/revanced-api/commit/a5498aba2b99db89c28a65738cc58cc4c852c327))
|
||||||
|
* Allow versioning by arbitrary path string ([814d3c9](https://github.com/ReVanced/revanced-api/commit/814d3c946e31068e12e3886aa8beb3238ef126ae))
|
||||||
|
* Improve announcements API ([#192](https://github.com/ReVanced/revanced-api/issues/192)) ([56a00dd](https://github.com/ReVanced/revanced-api/commit/56a00ddb85f302d441f0b222a9902ea2c1c18897))
|
||||||
|
* Make backend configurable ([f91f3a6](https://github.com/ReVanced/revanced-api/commit/f91f3a65c5e07b5b58ccbff1d4b0a5ba9b15fc50))
|
||||||
|
* Remove "archived" query parameter ([8ad614e](https://github.com/ReVanced/revanced-api/commit/8ad614ef4fdaf45af87a3316ef4db7e7236fd64a))
|
||||||
|
* Remove deprecated routes and old API ([eca40a6](https://github.com/ReVanced/revanced-api/commit/eca40a69799240f7803aa8851eb3ee961937e4d6))
|
||||||
|
* Remove ReVanced Integrations ([f1c1092](https://github.com/ReVanced/revanced-api/commit/f1c10928ae3be1c6b1d675819755b3046fad70d8))
|
||||||
|
* Use tag name directly instead of ID ([fc40427](https://github.com/ReVanced/revanced-api/commit/fc40427fbaafb523045eb6f5285d90949b206b8b))
|
||||||
|
|
||||||
# [1.4.0-dev.6](https://github.com/ReVanced/revanced-api/compare/v1.4.0-dev.5...v1.4.0-dev.6) (2024-11-06)
|
# [1.4.0-dev.6](https://github.com/ReVanced/revanced-api/compare/v1.4.0-dev.5...v1.4.0-dev.6) (2024-11-06)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
org.gradle.parallel = true
|
org.gradle.parallel = true
|
||||||
org.gradle.caching = true
|
org.gradle.caching = true
|
||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 1.4.0-dev.6
|
version = 1.6.0-dev.2
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package app.revanced.api.configuration
|
package app.revanced.api.configuration
|
||||||
|
|
||||||
|
import kotlinx.datetime.Clock
|
||||||
import kotlinx.datetime.LocalDateTime
|
import kotlinx.datetime.LocalDateTime
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.toLocalDateTime
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
interface ApiUser {
|
interface ApiUser {
|
||||||
@@ -60,9 +63,10 @@ class ApiAnnouncement(
|
|||||||
val title: String,
|
val title: String,
|
||||||
val content: String? = null,
|
val content: String? = null,
|
||||||
// Using a list instead of a set because set semantics are unnecessary here.
|
// Using a list instead of a set because set semantics are unnecessary here.
|
||||||
val attachments: List<String> = emptyList(),
|
val attachments: List<String>? = null,
|
||||||
// Using a list instead of a set because set semantics are unnecessary here.
|
// Using a list instead of a set because set semantics are unnecessary here.
|
||||||
val tags: List<String> = emptyList(),
|
val tags: List<String>? = null,
|
||||||
|
val createdAt: LocalDateTime = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()),
|
||||||
val archivedAt: LocalDateTime? = null,
|
val archivedAt: LocalDateTime? = null,
|
||||||
val level: Int = 0,
|
val level: Int = 0,
|
||||||
)
|
)
|
||||||
@@ -74,9 +78,9 @@ class ApiResponseAnnouncement(
|
|||||||
val title: String,
|
val title: String,
|
||||||
val content: String? = null,
|
val content: String? = null,
|
||||||
// Using a list instead of a set because set semantics are unnecessary here.
|
// Using a list instead of a set because set semantics are unnecessary here.
|
||||||
val attachments: List<String> = emptyList(),
|
val attachments: List<String>? = null,
|
||||||
// Using a list instead of a set because set semantics are unnecessary here.
|
// Using a list instead of a set because set semantics are unnecessary here.
|
||||||
val tags: List<String> = emptyList(),
|
val tags: List<String>? = null,
|
||||||
val createdAt: LocalDateTime,
|
val createdAt: LocalDateTime,
|
||||||
val archivedAt: LocalDateTime? = null,
|
val archivedAt: LocalDateTime? = null,
|
||||||
val level: Int = 0,
|
val level: Int = 0,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import org.koin.ktor.ext.get
|
|||||||
import kotlin.time.Duration.Companion.minutes
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
|
||||||
fun Application.configureHTTP() {
|
fun Application.configureHTTP() {
|
||||||
val configurationRepository = get<ConfigurationRepository>()
|
val configuration = get<ConfigurationRepository>()
|
||||||
|
|
||||||
install(CORS) {
|
install(CORS) {
|
||||||
HttpMethod.DefaultMethods.minus(HttpMethod.Options).forEach(::allowMethod)
|
HttpMethod.DefaultMethods.minus(HttpMethod.Options).forEach(::allowMethod)
|
||||||
@@ -22,7 +22,7 @@ fun Application.configureHTTP() {
|
|||||||
|
|
||||||
allowCredentials = true
|
allowCredentials = true
|
||||||
|
|
||||||
configurationRepository.corsAllowedHosts.forEach { host ->
|
configuration.corsAllowedHosts.forEach { host ->
|
||||||
allowHost(host = host, schemes = listOf("https"))
|
allowHost(host = host, schemes = listOf("https"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package app.revanced.api.configuration
|
|||||||
|
|
||||||
import app.revanced.api.command.applicationVersion
|
import app.revanced.api.command.applicationVersion
|
||||||
import app.revanced.api.configuration.repository.ConfigurationRepository
|
import app.revanced.api.configuration.repository.ConfigurationRepository
|
||||||
|
import io.bkbn.kompendium.core.attribute.KompendiumAttributes
|
||||||
import io.bkbn.kompendium.core.plugin.NotarizedApplication
|
import io.bkbn.kompendium.core.plugin.NotarizedApplication
|
||||||
import io.bkbn.kompendium.json.schema.KotlinXSchemaConfigurator
|
import io.bkbn.kompendium.json.schema.KotlinXSchemaConfigurator
|
||||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||||
@@ -12,13 +13,22 @@ import io.bkbn.kompendium.oas.info.License
|
|||||||
import io.bkbn.kompendium.oas.security.BearerAuth
|
import io.bkbn.kompendium.oas.security.BearerAuth
|
||||||
import io.bkbn.kompendium.oas.server.Server
|
import io.bkbn.kompendium.oas.server.Server
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import org.koin.ktor.ext.get
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
import org.koin.ktor.ext.get as koinGet
|
||||||
|
|
||||||
internal fun Application.configureOpenAPI() {
|
internal fun Application.configureOpenAPI() {
|
||||||
val configurationRepository = get<ConfigurationRepository>()
|
val configuration = koinGet<ConfigurationRepository>()
|
||||||
|
|
||||||
install(NotarizedApplication()) {
|
install(NotarizedApplication()) {
|
||||||
|
openApiJson = {
|
||||||
|
route("/${configuration.apiVersion}/openapi.json") {
|
||||||
|
get {
|
||||||
|
call.respond(application.attributes[KompendiumAttributes.openApiSpec])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
spec = OpenApiSpec(
|
spec = OpenApiSpec(
|
||||||
info = Info(
|
info = Info(
|
||||||
title = "ReVanced API",
|
title = "ReVanced API",
|
||||||
@@ -41,7 +51,7 @@ internal fun Application.configureOpenAPI() {
|
|||||||
),
|
),
|
||||||
).apply {
|
).apply {
|
||||||
servers += Server(
|
servers += Server(
|
||||||
url = URI(configurationRepository.endpoint),
|
url = URI(configuration.endpoint),
|
||||||
description = "ReVanced API server",
|
description = "ReVanced API server",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ internal fun Application.configureRouting() = routing {
|
|||||||
extensions("json", "asc")
|
extensions("json", "asc")
|
||||||
}
|
}
|
||||||
|
|
||||||
swagger(pageTitle = "ReVanced API", path = "/")
|
val specUrl = "/${configuration.apiVersion}/openapi.json"
|
||||||
redoc(pageTitle = "ReVanced API", path = "/redoc")
|
swagger(pageTitle = "ReVanced API", path = "/", specUrl = specUrl)
|
||||||
|
redoc(pageTitle = "ReVanced API", path = "/redoc", specUrl = specUrl)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,8 +69,7 @@ internal class AnnouncementRepository(private val database: Database) {
|
|||||||
|
|
||||||
fun latestId() = latestAnnouncement?.id?.value.toApiResponseAnnouncementId()
|
fun latestId() = latestAnnouncement?.id?.value.toApiResponseAnnouncementId()
|
||||||
|
|
||||||
fun latestId(tags: Set<String>) =
|
fun latestId(tags: Set<String>) = tags.map { tag -> latestAnnouncementByTag[tag]?.id?.value }.toApiResponseAnnouncementId()
|
||||||
tags.map { tag -> latestAnnouncementByTag[tag]?.id?.value }.toApiResponseAnnouncementId()
|
|
||||||
|
|
||||||
suspend fun paged(cursor: Int, count: Int, tags: Set<String>?) = transaction {
|
suspend fun paged(cursor: Int, count: Int, tags: Set<String>?) = transaction {
|
||||||
Announcement.find {
|
Announcement.find {
|
||||||
@@ -100,13 +99,16 @@ internal class AnnouncementRepository(private val database: Database) {
|
|||||||
author = new.author
|
author = new.author
|
||||||
title = new.title
|
title = new.title
|
||||||
content = new.content
|
content = new.content
|
||||||
|
createdAt = new.createdAt
|
||||||
archivedAt = new.archivedAt
|
archivedAt = new.archivedAt
|
||||||
level = new.level
|
level = new.level
|
||||||
|
if (new.tags != null) {
|
||||||
tags = SizedCollection(
|
tags = SizedCollection(
|
||||||
new.tags.map { tag -> Tag.find { Tags.name eq tag }.firstOrNull() ?: Tag.new { name = tag } },
|
new.tags.map { tag -> Tag.find { Tags.name eq tag }.firstOrNull() ?: Tag.new { name = tag } },
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}.apply {
|
}.apply {
|
||||||
new.attachments.map { attachmentUrl ->
|
new.attachments?.map { attachmentUrl ->
|
||||||
Attachment.new {
|
Attachment.new {
|
||||||
url = attachmentUrl
|
url = attachmentUrl
|
||||||
announcement = this@apply
|
announcement = this@apply
|
||||||
@@ -120,9 +122,11 @@ internal class AnnouncementRepository(private val database: Database) {
|
|||||||
it.author = new.author
|
it.author = new.author
|
||||||
it.title = new.title
|
it.title = new.title
|
||||||
it.content = new.content
|
it.content = new.content
|
||||||
|
it.createdAt = new.createdAt
|
||||||
it.archivedAt = new.archivedAt
|
it.archivedAt = new.archivedAt
|
||||||
it.level = new.level
|
it.level = new.level
|
||||||
|
|
||||||
|
if (new.tags != null) {
|
||||||
// Get the old tags, create new tags if they don't exist,
|
// Get the old tags, create new tags if they don't exist,
|
||||||
// and delete tags that are not in the new tags, after updating the announcement.
|
// and delete tags that are not in the new tags, after updating the announcement.
|
||||||
val oldTags = it.tags.toList()
|
val oldTags = it.tags.toList()
|
||||||
@@ -134,8 +138,10 @@ internal class AnnouncementRepository(private val database: Database) {
|
|||||||
if (tag in updatedTags || !tag.announcements.empty()) return@forEach
|
if (tag in updatedTags || !tag.announcements.empty()) return@forEach
|
||||||
tag.delete()
|
tag.delete()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Delete old attachments and create new attachments.
|
// Delete old attachments and create new attachments.
|
||||||
|
if (new.attachments != null) {
|
||||||
it.attachments.forEach { attachment -> attachment.delete() }
|
it.attachments.forEach { attachment -> attachment.delete() }
|
||||||
new.attachments.map { attachment ->
|
new.attachments.map { attachment ->
|
||||||
Attachment.new {
|
Attachment.new {
|
||||||
@@ -143,6 +149,7 @@ internal class AnnouncementRepository(private val database: Database) {
|
|||||||
announcement = it
|
announcement = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}?.let(::updateLatestAnnouncement) ?: Unit
|
}?.let(::updateLatestAnnouncement) ?: Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,8 +180,7 @@ internal class AnnouncementRepository(private val database: Database) {
|
|||||||
Tag.all().toList().toApiTag()
|
Tag.all().toList().toApiTag()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun <T> transaction(statement: suspend Transaction.() -> T) =
|
private suspend fun <T> transaction(statement: suspend Transaction.() -> T) = newSuspendedTransaction(Dispatchers.IO, database, statement = statement)
|
||||||
newSuspendedTransaction(Dispatchers.IO, database, statement = statement)
|
|
||||||
|
|
||||||
private object Announcements : IntIdTable() {
|
private object Announcements : IntIdTable() {
|
||||||
val author = varchar("author", 32).nullable()
|
val author = varchar("author", 32).nullable()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
<encoder>
|
<encoder>
|
||||||
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} %-5level %msg%n</pattern>
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
<root level="\${LOG_LEVEL:-INFO}">
|
<root level="\${LOG_LEVEL:-INFO}">
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ private object AnnouncementServiceTest {
|
|||||||
val latestAnnouncement = announcementService.latest()!!
|
val latestAnnouncement = announcementService.latest()!!
|
||||||
val latestId = latestAnnouncement.id
|
val latestId = latestAnnouncement.id
|
||||||
|
|
||||||
val attachments = latestAnnouncement.attachments
|
val attachments = latestAnnouncement.attachments!!
|
||||||
assertEquals(2, attachments.size)
|
assertEquals(2, attachments.size)
|
||||||
assert(attachments.any { it == "attachment1" })
|
assert(attachments.any { it == "attachment1" })
|
||||||
assert(attachments.any { it == "attachment2" })
|
assert(attachments.any { it == "attachment2" })
|
||||||
@@ -144,7 +144,7 @@ private object AnnouncementServiceTest {
|
|||||||
latestId,
|
latestId,
|
||||||
ApiAnnouncement(title = "title", attachments = listOf("attachment1", "attachment3")),
|
ApiAnnouncement(title = "title", attachments = listOf("attachment1", "attachment3")),
|
||||||
)
|
)
|
||||||
assert(announcementService.get(latestId)!!.attachments.any { it == "attachment3" })
|
assert(announcementService.get(latestId)!!.attachments!!.any { it == "attachment3" })
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user