Skip to content

Commit

Permalink
feat: Add OpenAPI docs and cache to routes
Browse files Browse the repository at this point in the history
  • Loading branch information
oSumAtrIX committed Jun 8, 2024
1 parent accfa3d commit c94ca48
Show file tree
Hide file tree
Showing 14 changed files with 572 additions and 104 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ dependencies {
implementation(libs.ktor.server.jetty)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.koin.ktor)
implementation("io.bkbn:kompendium-core:latest.release")
implementation(libs.h2)
implementation(libs.logback.classic)
implementation(libs.exposed.core)
Expand Down
18 changes: 9 additions & 9 deletions src/main/kotlin/app/revanced/api/command/MainCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@ package app.revanced.api.command
import picocli.CommandLine
import java.util.*

internal val applicationVersion = MainCommand::class.java.getResourceAsStream(
"/app/revanced/api/version.properties",
)?.use { stream ->
Properties().apply {
load(stream)
}.getProperty("version")
} ?: "v0.0.0"

fun main(args: Array<String>) {
CommandLine(MainCommand).execute(*args).let(System::exit)
}

private object CLIVersionProvider : CommandLine.IVersionProvider {
override fun getVersion() =
arrayOf(
MainCommand::class.java.getResourceAsStream(
"/app/revanced/api/version.properties",
)?.use { stream ->
Properties().apply {
load(stream)
}.let {
"ReVanced API v${it.getProperty("version")}"
}
} ?: "ReVanced API",
"ReVanced API $applicationVersion",
)
}

Expand Down
7 changes: 2 additions & 5 deletions src/main/kotlin/app/revanced/api/command/StartAPICommand.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
package app.revanced.api.command

import app.revanced.api.configuration.configureDependencies
import app.revanced.api.configuration.configureHTTP
import app.revanced.api.configuration.configureRouting
import app.revanced.api.configuration.configureSecurity
import app.revanced.api.configuration.configureSerialization
import app.revanced.api.configuration.*
import io.ktor.server.engine.*
import io.ktor.server.jetty.*
import picocli.CommandLine
Expand Down Expand Up @@ -42,6 +38,7 @@ internal object StartAPICommand : Runnable {
configureHTTP()
configureSerialization()
configureSecurity()
configureOpenAPI()
configureRouting()
}.start(wait = true)
}
Expand Down
22 changes: 21 additions & 1 deletion src/main/kotlin/app/revanced/api/configuration/Extensions.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
package app.revanced.api.configuration

import io.bkbn.kompendium.core.plugin.NotarizedRoute
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.server.application.*
import io.ktor.server.plugins.cachingheaders.*
import io.ktor.server.response.*
import kotlin.time.Duration

suspend fun ApplicationCall.respondOrNotFound(value: Any?) = respond(value ?: HttpStatusCode.NotFound)
internal suspend fun ApplicationCall.respondOrNotFound(value: Any?) = respond(value ?: HttpStatusCode.NotFound)

internal fun ApplicationCallPipeline.installCache(maxAge: Duration) =
installCache(CacheControl.MaxAge(maxAgeSeconds = maxAge.inWholeSeconds.toInt()))

internal fun ApplicationCallPipeline.installNoCache() =
installCache(CacheControl.NoCache(null))

internal fun ApplicationCallPipeline.installCache(cacheControl: CacheControl) =
install(CachingHeaders) {
options { _, _ ->
CachingOptions(cacheControl)
}
}

internal fun ApplicationCallPipeline.installNotarizedRoute(configure: NotarizedRoute.Config.() -> Unit = {}) =
install(NotarizedRoute(), configure)
52 changes: 52 additions & 0 deletions src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package app.revanced.api.configuration

import app.revanced.api.command.applicationVersion
import io.bkbn.kompendium.core.plugin.NotarizedApplication
import io.bkbn.kompendium.json.schema.KotlinXSchemaConfigurator
import io.bkbn.kompendium.oas.OpenApiSpec
import io.bkbn.kompendium.oas.component.Components
import io.bkbn.kompendium.oas.info.Contact
import io.bkbn.kompendium.oas.info.Info
import io.bkbn.kompendium.oas.info.License
import io.bkbn.kompendium.oas.security.BasicAuth
import io.bkbn.kompendium.oas.security.BearerAuth
import io.bkbn.kompendium.oas.server.Server
import io.ktor.server.application.*
import java.net.URI

internal fun Application.configureOpenAPI() {
install(NotarizedApplication()) {
spec = {
OpenApiSpec(
info = Info(
title = "Revanced API",
version = applicationVersion,
description = "API server for ReVanced.",
contact = Contact(
name = "ReVanced",
url = URI("https://revanced.app"),
email = "[email protected]",
),
license = License(
name = "AGPLv3",
url = URI("https://github.com/ReVanced/revanced-api/blob/main/LICENSE"),
),
),
components = Components(
securitySchemes = mutableMapOf(
"bearer" to BearerAuth(),
"basic" to BasicAuth(),
),
),

).apply {
servers += Server(
url = URI("https://api.revanced.app"),
description = "ReVanced API server.",
)
}
}

schemaConfigurator = KotlinXSchemaConfigurator()
}
}
11 changes: 1 addition & 10 deletions src/main/kotlin/app/revanced/api/configuration/Routing.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,15 @@ import app.revanced.api.configuration.routes.announcementsRoute
import app.revanced.api.configuration.routes.oldApiRoute
import app.revanced.api.configuration.routes.patchesRoute
import app.revanced.api.configuration.routes.rootRoute
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.server.application.*
import io.ktor.server.plugins.cachingheaders.*
import io.ktor.server.routing.*
import org.koin.ktor.ext.get
import kotlin.time.Duration.Companion.minutes

internal fun Application.configureRouting() = routing {
val configuration = get<ConfigurationRepository>()

install(CachingHeaders) {
options { _, _ ->
CachingOptions(
CacheControl.MaxAge(maxAgeSeconds = 5.minutes.inWholeSeconds.toInt()),
)
}
}
installCache(5.minutes)

route("/v${configuration.apiVersion}") {
rootRoute()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package app.revanced.api.configuration

import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
Expand All @@ -12,6 +13,7 @@ fun Application.configureSerialization() {
install(ContentNegotiation) {
json(
Json {
serializersModule = KompendiumSerializersModule.module
namingStrategy = JsonNamingStrategy.SnakeCase
explicitNulls = false
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ 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 app.revanced.api.configuration.schema.APIResponseAnnouncementId
import kotlinx.datetime.*
import org.jetbrains.exposed.dao.IntEntity
import org.jetbrains.exposed.dao.IntEntityClass
Expand Down Expand Up @@ -42,6 +42,8 @@ internal class AnnouncementRepository(private val database: Database) {
announcement.delete()
}

// TODO: These are inefficient, but I'm not sure how to make them more efficient.

fun latest() = transaction {
AnnouncementEntity.all().maxByOrNull { it.createdAt }?.toApi()
}
Expand All @@ -52,13 +54,13 @@ internal class AnnouncementRepository(private val database: Database) {

fun latestId() = transaction {
AnnouncementEntity.all().maxByOrNull { it.createdAt }?.id?.value?.let {
APILatestAnnouncement(it)
APIResponseAnnouncementId(it)
}
}

fun latestId(channel: String) = transaction {
AnnouncementEntity.find { AnnouncementTable.channel eq channel }.maxByOrNull { it.createdAt }?.id?.value?.let {
APILatestAnnouncement(it)
APIResponseAnnouncementId(it)
}
}

Expand Down
Loading

0 comments on commit c94ca48

Please sign in to comment.