refactor: Refactor into services and repositories

This commit is contained in:
oSumAtrIX
2024-06-05 03:07:28 +02:00
parent 7a1957d013
commit fa2f8b2f86
30 changed files with 623 additions and 542 deletions

View File

@@ -0,0 +1,83 @@
package app.revanced.api.configuration
import app.revanced.api.repository.AnnouncementRepository
import app.revanced.api.repository.ConfigurationRepository
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 com.akuleshov7.ktoml.Toml
import com.akuleshov7.ktoml.source.decodeFromStream
import io.github.cdimascio.dotenv.Dotenv
import io.ktor.server.application.*
import org.jetbrains.exposed.sql.Database
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.bind
import org.koin.dsl.module
import org.koin.ktor.plugin.Koin
import java.io.File
fun Application.configureDependencies() {
val globalModule = module {
single {
Dotenv.configure()
.systemProperties()
.load()
}
}
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 {
val configFilePath = get<Dotenv>()["CONFIG_FILE_PATH"]
val configFile = File(configFilePath).inputStream()
Toml.decodeFromStream<ConfigurationRepository>(configFile)
}
singleOf(::AnnouncementRepository)
}
val serviceModule = module {
single {
val dotenv = get<Dotenv>()
val jwtSecret = dotenv["JWT_SECRET"]
val issuer = dotenv["JWT_ISSUER"]
val validityInMin = dotenv["JWT_VALIDITY_IN_MIN"].toInt()
val basicUsername = dotenv["BASIC_USERNAME"]
val basicPassword = dotenv["BASIC_PASSWORD"]
AuthService(issuer, validityInMin, jwtSecret, basicUsername, basicPassword)
}
single {
val token = get<Dotenv>()["GITHUB_TOKEN"]
GitHubBackendRepository(token)
} bind BackendRepository::class
singleOf(::AnnouncementService)
singleOf(::PatchesService)
singleOf(::ApiService)
}
install(Koin) {
modules(
globalModule,
repositoryModule,
serviceModule,
)
}
}

View File

@@ -0,0 +1,26 @@
package app.revanced.api.configuration
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.server.application.*
import io.ktor.server.plugins.cachingheaders.*
import io.ktor.server.plugins.conditionalheaders.*
import io.ktor.server.plugins.cors.routing.*
import kotlin.time.Duration.Companion.minutes
fun Application.configureHTTP(
allowedHost: String,
) {
install(ConditionalHeaders)
install(CORS) {
allowMethod(HttpMethod.Options)
allowMethod(HttpMethod.Put)
allowMethod(HttpMethod.Delete)
allowMethod(HttpMethod.Patch)
allowHeader(HttpHeaders.Authorization)
allowHost(allowedHost)
}
install(CachingHeaders) {
options { _, _ -> CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 5.minutes.inWholeSeconds.toInt())) }
}
}

View File

@@ -0,0 +1,9 @@
package app.revanced.api.configuration
import app.revanced.api.services.AuthService
import io.ktor.server.application.*
import org.koin.ktor.ext.get
fun Application.configureSecurity() {
get<AuthService>().configureSecurity(this)
}

View File

@@ -0,0 +1,19 @@
package app.revanced.api.configuration
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
@OptIn(ExperimentalSerializationApi::class)
fun Application.configureSerialization() {
install(ContentNegotiation) {
json(
Json {
namingStrategy = JsonNamingStrategy.SnakeCase
},
)
}
}

View File

@@ -0,0 +1,19 @@
package app.revanced.api.configuration.routing
import app.revanced.api.configuration.routing.routes.configureAnnouncementsRoute
import app.revanced.api.configuration.routing.routes.configurePatchesRoute
import app.revanced.api.configuration.routing.routes.configureRootRoute
import app.revanced.api.repository.ConfigurationRepository
import io.ktor.server.application.*
import io.ktor.server.routing.*
import org.koin.ktor.ext.get
internal fun Application.configureRouting() = routing {
val configuration = get<ConfigurationRepository>()
route("/v${configuration.apiVersion}") {
configureRootRoute()
configurePatchesRoute()
configureAnnouncementsRoute()
}
}

View File

@@ -0,0 +1,86 @@
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 io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.util.*
import org.koin.ktor.ext.get as koinGet
internal fun Route.configureAnnouncementsRoute() = route("/announcements") {
val announcementService = koinGet<AnnouncementService>()
route("/{channel}/latest") {
get("/id") {
val channel: String by call.parameters
call.respond(
announcementService.latestId(channel) ?: return@get call.respond(HttpStatusCode.NotFound),
)
}
get {
val channel: String by call.parameters
call.respond(
announcementService.latest(channel) ?: return@get call.respond(HttpStatusCode.NotFound),
)
}
}
get("/{channel}") {
val channel: String by call.parameters
call.respond(announcementService.all(channel))
}
route("/latest") {
get("/id") {
call.respond(announcementService.latestId() ?: return@get call.respond(HttpStatusCode.NotFound))
}
get {
call.respond(announcementService.latest() ?: return@get call.respond(HttpStatusCode.NotFound))
}
}
get {
call.respond(announcementService.all())
}
authenticate("jwt") {
post {
announcementService.new(call.receive<APIAnnouncement>())
}
post("/{id}/archive") {
val id: Int by call.parameters
val archivedAt = call.receiveNullable<APIAnnouncementArchivedAt>()?.archivedAt
announcementService.archive(id, archivedAt)
}
post("/{id}/unarchive") {
val id: Int by call.parameters
announcementService.unarchive(id)
}
patch("/{id}") {
val id: Int by call.parameters
announcementService.update(id, call.receive<APIAnnouncement>())
}
delete("/{id}") {
val id: Int by call.parameters
announcementService.delete(id)
}
}
}

View File

@@ -0,0 +1,41 @@
package app.revanced.api.configuration.routing.routes
import app.revanced.api.services.ApiService
import app.revanced.api.services.AuthService
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.http.content.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import org.koin.ktor.ext.get
internal fun Route.configureRootRoute() {
val apiService = get<ApiService>()
val authService = get<AuthService>()
get("/contributors") {
call.respond(apiService.contributors())
}
get("/team") {
call.respond(apiService.team())
}
route("/ping") {
handle {
call.respond(HttpStatusCode.NoContent)
}
}
authenticate("basic") {
get("/token") {
call.respond(authService.newToken())
}
}
staticResources("/", "/static/api") {
contentType { ContentType.Application.Json }
extensions("json")
}
}

View File

@@ -0,0 +1,26 @@
package app.revanced.api.configuration.routing.routes
import app.revanced.api.services.PatchesService
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import org.koin.ktor.ext.get as koinGet
internal fun Route.configurePatchesRoute() = route("/patches") {
val patchesService = koinGet<PatchesService>()
route("latest") {
get {
call.respond(patchesService.latestRelease())
}
get("/version") {
call.respond(patchesService.latestVersion())
}
get("/list") {
call.respondBytes(ContentType.Application.Json) { patchesService.list() }
}
}
}