Compare commits
4 commits
13487b5234
...
2c8016e77f
Author | SHA1 | Date | |
---|---|---|---|
Ecorous | 2c8016e77f | ||
TheKodeToad | 8fa8fbf8e8 | ||
TheKodeToad | d3b64c6401 | ||
TheKodeToad | 1d9b12b8a8 |
|
@ -37,6 +37,7 @@ dependencies {
|
||||||
implementation("com.h2database:h2:$h2_version")
|
implementation("com.h2database:h2:$h2_version")
|
||||||
implementation("org.jetbrains.exposed:exposed-core:$exposed_version")
|
implementation("org.jetbrains.exposed:exposed-core:$exposed_version")
|
||||||
implementation("org.jetbrains.exposed:exposed-jdbc:$exposed_version")
|
implementation("org.jetbrains.exposed:exposed-jdbc:$exposed_version")
|
||||||
|
implementation("org.jetbrains.exposed:exposed-json:$exposed_version")
|
||||||
implementation("org.jetbrains.exposed:exposed-kotlin-datetime:$exposed_version")
|
implementation("org.jetbrains.exposed:exposed-kotlin-datetime:$exposed_version")
|
||||||
implementation("io.ktor:ktor-server-netty-jvm")
|
implementation("io.ktor:ktor-server-netty-jvm")
|
||||||
implementation("ch.qos.logback:logback-classic:$logback_version")
|
implementation("ch.qos.logback:logback-classic:$logback_version")
|
||||||
|
|
|
@ -4,4 +4,4 @@ logback_version=1.4.14
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
postgres_version=42.7.3
|
postgres_version=42.7.3
|
||||||
h2_version=2.1.214
|
h2_version=2.1.214
|
||||||
exposed_version=0.41.1
|
exposed_version=0.51.1
|
14
src/main/kotlin/dev/frogmc/Config.kt
Normal file
14
src/main/kotlin/dev/frogmc/Config.kt
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package dev.frogmc
|
||||||
|
|
||||||
|
object Config {
|
||||||
|
val POSTGRES_DATABASE = getEnv("DATABASE", "frogmc")
|
||||||
|
val POSTGRES_USER = getEnv("USER", "postgres")
|
||||||
|
val POSTGRES_PASSWORD = getEnv("PASSWORD", "example")
|
||||||
|
val POSTGRES_HOST = getEnv("HOST", "localhost")
|
||||||
|
val POSTGRES_PORT = getEnv("PORT", "5432")
|
||||||
|
val UPLOAD_SECRET = getEnv("UPLOAD_SECRET", "").toByteArray()
|
||||||
|
|
||||||
|
private fun getEnv(key: String, default: String): String {
|
||||||
|
return System.getenv("FROGMC_META_$key") ?: default
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,32 +7,22 @@ import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
object DB {
|
object DB {
|
||||||
var db: Database? = null
|
var db: Database? = null
|
||||||
|
|
||||||
private fun getEnv(name: String, default: String): String {
|
|
||||||
return System.getenv(name) ?: default
|
|
||||||
}
|
|
||||||
|
|
||||||
fun init(): Boolean {
|
fun init(): Boolean {
|
||||||
// use postgresql
|
// use postgresql
|
||||||
try {
|
try {
|
||||||
val database = getEnv("FROGMC_META_DATABASE", "frogmc")
|
|
||||||
val user = getEnv("FROGMC_META_USER", "postgres")
|
|
||||||
val password = getEnv("FROGMC_META_PASSWORD", "example")
|
|
||||||
val host = getEnv("FROGMC_META_HOST", "localhost")
|
|
||||||
val port = getEnv("FROGMC_META_PORT", "5432")
|
|
||||||
|
|
||||||
logger.info("Connecting to the database...")
|
logger.info("Connecting to the database...")
|
||||||
val d = Database.connect(
|
val d = Database.connect(
|
||||||
url = "jdbc:postgresql://$host:$port/$database",
|
url = "jdbc:postgresql://${Config.POSTGRES_HOST}:${Config.POSTGRES_PORT}/${Config.POSTGRES_DATABASE}",
|
||||||
driver = "org.postgresql.Driver",
|
driver = "org.postgresql.Driver",
|
||||||
user,
|
Config.POSTGRES_USER,
|
||||||
password
|
Config.POSTGRES_PASSWORD
|
||||||
)
|
)
|
||||||
db = d
|
db = d
|
||||||
transaction(db) {
|
transaction(db) {
|
||||||
logger.info("what is going on")
|
|
||||||
SchemaUtils.create(
|
SchemaUtils.create(
|
||||||
LoaderVersions,
|
LoaderVersions,
|
||||||
LibraryVersions
|
LibraryVersions,
|
||||||
|
File
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (db == null) {
|
if (db == null) {
|
||||||
|
@ -45,32 +35,10 @@ object DB {
|
||||||
logger.error(e.stackTraceToString())
|
logger.error(e.stackTraceToString())
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (db == null) {
|
|
||||||
logger.error("Failed to connect to the database.")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
logger.info(db!!.name)
|
logger.info(db!!.name)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLoaderVersions(): List<LoaderVersion> {
|
|
||||||
logger.info("Getting loader versions...")
|
|
||||||
if (db == null) {
|
|
||||||
logger.error("Database is null.")
|
|
||||||
init()
|
|
||||||
}
|
|
||||||
logger.info(db!!.name)
|
|
||||||
return transaction(db) {
|
|
||||||
return@transaction LoaderVersions.selectAll().map {
|
|
||||||
LoaderVersion(
|
|
||||||
it[LoaderVersions.version],
|
|
||||||
it[LoaderVersions.releaseDate],
|
|
||||||
it[LoaderVersions.downloadUrl]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}.sortedBy { it.releaseDate }.reversed()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getLibraryVersions(): List<LibraryVersion> {
|
fun getLibraryVersions(): List<LibraryVersion> {
|
||||||
return transaction(db) {
|
return transaction(db) {
|
||||||
return@transaction LibraryVersions.selectAll().map {
|
return@transaction LibraryVersions.selectAll().map {
|
||||||
|
|
16
src/main/kotlin/dev/frogmc/plugins/Auth.kt
Normal file
16
src/main/kotlin/dev/frogmc/plugins/Auth.kt
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package dev.frogmc.plugins
|
||||||
|
|
||||||
|
import dev.frogmc.Config
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.security.MessageDigest
|
||||||
|
|
||||||
|
val authPlugin = createRouteScopedPlugin("auth") {
|
||||||
|
onCall {
|
||||||
|
val authorization = it.request.headers["Authorization"]
|
||||||
|
if (authorization.isNullOrEmpty() || !MessageDigest.isEqual(authorization.toByteArray(), Config.UPLOAD_SECRET))
|
||||||
|
it.respond(HttpStatusCode.Unauthorized);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
package dev.frogmc.plugins
|
package dev.frogmc.plugins
|
||||||
|
|
||||||
import dev.frogmc.DB
|
import dev.frogmc.DB
|
||||||
|
import dev.frogmc.types.LoaderVersion
|
||||||
|
import dev.frogmc.types.PartialLoaderVersion
|
||||||
|
import dev.frogmc.types.LoaderVersions
|
||||||
import dev.frogmc.types.ModrinthVersion
|
import dev.frogmc.types.ModrinthVersion
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.*
|
||||||
|
@ -9,108 +12,113 @@ import io.ktor.http.*
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.html.*
|
import io.ktor.server.html.*
|
||||||
import io.ktor.server.http.content.*
|
import io.ktor.server.http.content.*
|
||||||
|
import io.ktor.server.request.*
|
||||||
import io.ktor.server.response.*
|
import io.ktor.server.response.*
|
||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import org.jetbrains.exposed.exceptions.ExposedSQLException
|
||||||
fun BODY.route(block: DIV.() -> Unit) =
|
import org.jetbrains.exposed.sql.*
|
||||||
div(classes = "route", block)
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
fun Application.configureRouting() {
|
fun Application.configureRouting() {
|
||||||
routing {
|
routing {
|
||||||
staticResources("/static", "static")
|
staticResources("/", "static")
|
||||||
get("/") {
|
|
||||||
call.respondHtml {
|
|
||||||
head {
|
|
||||||
title { +"FrogMC API" }
|
|
||||||
link { rel = "stylesheet"; type = "text/css"; href = "/static/style.css" }
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
h1 { +"FrogMC API" }
|
|
||||||
p { +"This is the FrogMC Meta API." }
|
|
||||||
p { +"Use this to fetch versions of FrogMC library and loader." }
|
|
||||||
br()
|
|
||||||
p { +"Current API version: v1" }
|
|
||||||
br()
|
|
||||||
div(classes = "routes") {
|
|
||||||
div(classes = "route") {
|
|
||||||
code { +"GET /v1/loader/versions" }
|
|
||||||
p { +"Fetches all versions of FrogMC loader." }
|
|
||||||
}
|
|
||||||
br()
|
|
||||||
div(classes = "route") {
|
|
||||||
code { +"GET /v1/loader/versions/latest" }
|
|
||||||
p { +"Fetches the latest version of FrogMC loader." }
|
|
||||||
}
|
|
||||||
br()
|
|
||||||
div(classes = "route") {
|
|
||||||
code { +"GET /v1/loader/versions/{version}" }
|
|
||||||
p { +"Fetches a specific version of FrogMC loader." }
|
|
||||||
}
|
|
||||||
br()
|
|
||||||
div(classes = "route") {
|
|
||||||
code { +"GET /v1/loader/versions/{version}/download" }
|
|
||||||
p { +"Fetches the download URL of a specific version of FrogMC loader." }
|
|
||||||
}
|
|
||||||
br()
|
|
||||||
div(classes = "route") {
|
|
||||||
code { +"GET /v1/library/versions" }
|
|
||||||
p { +"Fetches all versions of FrogMC library." }
|
|
||||||
}
|
|
||||||
br()
|
|
||||||
div(classes = "route") {
|
|
||||||
code { +"GET /v1/library/versions/latest" }
|
|
||||||
p { +"Fetches the latest version of FrogMC library." }
|
|
||||||
}
|
|
||||||
br()
|
|
||||||
div(classes = "route") {
|
|
||||||
code { +"GET /v1/library/versions/{version}" }
|
|
||||||
p { +"Fetches a specific version of FrogMC library." }
|
|
||||||
}
|
|
||||||
br()
|
|
||||||
div(classes = "route") {
|
|
||||||
code { +"GET /v1/library/versions/{version}/download" }
|
|
||||||
p { +"Fetches the download URL of a specific version of FrogMC library." }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
route("/v1") {
|
route("/v1") {
|
||||||
route("/loader") {
|
route("/loader") {
|
||||||
get("/versions") {
|
get("/versions") {
|
||||||
val versions = DB.getLoaderVersions()
|
call.respond(transaction(DB.db) {
|
||||||
call.respond(versions)
|
LoaderVersions
|
||||||
|
.select(LoaderVersions.version, LoaderVersions.releaseDate)
|
||||||
|
.orderBy(LoaderVersions.releaseDate to SortOrder.ASC)
|
||||||
|
.map {
|
||||||
|
PartialLoaderVersion(
|
||||||
|
it[LoaderVersions.version],
|
||||||
|
it[LoaderVersions.releaseDate],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
get("/versions/latest") {
|
get("/versions/latest") {
|
||||||
val versions = DB.getLoaderVersions()
|
val row = transaction(DB.db) {
|
||||||
call.respond(versions.first())
|
LoaderVersions
|
||||||
|
.selectAll()
|
||||||
|
.orderBy(LoaderVersions.releaseDate to SortOrder.DESC)
|
||||||
|
.firstOrNull()
|
||||||
}
|
}
|
||||||
get("/versions/{version}/download") {
|
|
||||||
val version = call.parameters["version"] ?: return@get call.respond(
|
if (row == null) {
|
||||||
HttpStatusCode.BadRequest,
|
call.respond(HttpStatusCode.NotFound)
|
||||||
"message" to "Invalid version."
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
call.respond(
|
||||||
|
LoaderVersion(
|
||||||
|
row[LoaderVersions.version],
|
||||||
|
row[LoaderVersions.releaseDate],
|
||||||
|
row[LoaderVersions.libraries]
|
||||||
)
|
)
|
||||||
val versions = DB.getLoaderVersions()
|
|
||||||
val versionObj = versions.find { it.version == version } ?: return@get call.respond(
|
|
||||||
HttpStatusCode.NotFound,
|
|
||||||
"message" to "Version not found."
|
|
||||||
)
|
)
|
||||||
call.respond("url" to versionObj.downloadUrl)
|
|
||||||
}
|
}
|
||||||
get("/versions/{version}") {
|
get("/versions/{version}") {
|
||||||
val version = call.parameters["version"] ?: return@get call.respond(
|
val version = call.parameters["version"] ?: return@get call.respond(HttpStatusCode.BadRequest)
|
||||||
HttpStatusCode.BadRequest,
|
val row = transaction {
|
||||||
"message" to "Invalid version."
|
LoaderVersions
|
||||||
|
.selectAll()
|
||||||
|
.where { LoaderVersions.version eq version }
|
||||||
|
.firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row == null) {
|
||||||
|
call.respond(HttpStatusCode.NotFound)
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
call.respond(
|
||||||
|
LoaderVersion(
|
||||||
|
row[LoaderVersions.version],
|
||||||
|
row[LoaderVersions.releaseDate],
|
||||||
|
row[LoaderVersions.libraries]
|
||||||
)
|
)
|
||||||
val versions = DB.getLoaderVersions()
|
|
||||||
val versionObj = versions.find { it.version == version } ?: return@get call.respond(
|
|
||||||
HttpStatusCode.NotFound,
|
|
||||||
"message" to "Version not found."
|
|
||||||
)
|
)
|
||||||
call.respond(versionObj)
|
}
|
||||||
|
route("/versions/upload") {
|
||||||
|
install(authPlugin)
|
||||||
|
post {
|
||||||
|
val versionObj = call.receive<LoaderVersion>()
|
||||||
|
|
||||||
|
try {
|
||||||
|
transaction {
|
||||||
|
LoaderVersions.insert {
|
||||||
|
it[version] = versionObj.version
|
||||||
|
it[releaseDate] = versionObj.releaseDate
|
||||||
|
it[libraries] = versionObj.libraries
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: ExposedSQLException) { // TODO: this catches EVERYTHING
|
||||||
|
call.respond(HttpStatusCode.Conflict)
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
call.respond(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
route("/versions/delete/{version}") {
|
||||||
|
install(authPlugin)
|
||||||
|
delete {
|
||||||
|
val version =
|
||||||
|
call.parameters["version"] ?: return@delete call.respond(HttpStatusCode.BadRequest)
|
||||||
|
val rows = transaction {
|
||||||
|
LoaderVersions.deleteWhere { LoaderVersions.version eq version }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rows == 0) {
|
||||||
|
call.respond(HttpStatusCode.NotFound)
|
||||||
|
return@delete
|
||||||
|
}
|
||||||
|
|
||||||
|
call.respond(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
route("/library") {
|
route("/library") {
|
||||||
|
|
|
@ -4,11 +4,25 @@ import kotlinx.datetime.LocalDateTime
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PartialLoaderVersion(
|
||||||
|
val version: String,
|
||||||
|
val releaseDate: LocalDateTime,
|
||||||
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class LoaderVersion(
|
data class LoaderVersion(
|
||||||
val version: String,
|
val version: String,
|
||||||
val releaseDate: LocalDateTime,
|
val releaseDate: LocalDateTime,
|
||||||
val downloadUrl: String
|
val libraries: List<LoaderLibrary>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class LoaderLibrary(
|
||||||
|
val name: String,
|
||||||
|
val url: String,
|
||||||
|
val sha1: String,
|
||||||
|
val size: Int,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
package dev.frogmc.types
|
package dev.frogmc.types
|
||||||
|
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import org.jetbrains.exposed.sql.Table
|
import org.jetbrains.exposed.sql.Table
|
||||||
|
import org.jetbrains.exposed.sql.json.jsonb
|
||||||
import org.jetbrains.exposed.sql.kotlin.datetime.datetime
|
import org.jetbrains.exposed.sql.kotlin.datetime.datetime
|
||||||
|
|
||||||
object LoaderVersions : Table() {
|
object LoaderVersions : Table() {
|
||||||
val version = text("version")
|
val version = text("version")
|
||||||
val releaseDate = datetime("release_date")
|
val releaseDate = datetime("release_date")
|
||||||
val downloadUrl = varchar("download_url", 255)
|
val libraries = jsonb<List<LoaderLibrary>>("libraries", Json)
|
||||||
override val primaryKey = PrimaryKey(version)
|
override val primaryKey = PrimaryKey(version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,3 +18,10 @@ object LibraryVersions : Table() {
|
||||||
val modrinthVersion = text("mr_version")
|
val modrinthVersion = text("mr_version")
|
||||||
override val primaryKey = PrimaryKey(version)
|
override val primaryKey = PrimaryKey(version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object File : Table() {
|
||||||
|
val version = text("version")
|
||||||
|
val sha1 = text("sha1")
|
||||||
|
val size = text("size")
|
||||||
|
val url = text("url")
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,68 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
</head>
|
<title>FrogMC API</title>
|
||||||
<body>
|
<link rel="stylesheet" type="text/css" href="/style.css">
|
||||||
<h1>Hello Ktor!</h1>
|
</head>
|
||||||
</body>
|
<body>
|
||||||
|
<h1>FrogMC API</h1>
|
||||||
|
<p>This is the FrogMC Meta API.</p>
|
||||||
|
<p>Use this to fetch versions of FrogMC library and loader.</p>
|
||||||
|
<br>
|
||||||
|
<p>Current API version: v1</p>
|
||||||
|
<br>
|
||||||
|
<div class="routes">
|
||||||
|
<div class="route">
|
||||||
|
<code>GET /v1/loader/versions</code>
|
||||||
|
<p>Fetches all versions of FrogMC loader.</p>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="route">
|
||||||
|
<code>GET /v1/loader/versions/latest</code>
|
||||||
|
<p>Fetches the latest version of FrogMC loader.</p>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="route">
|
||||||
|
<code>GET /v1/loader/versions/{version}</code>
|
||||||
|
<p>Fetches a specific version of FrogMC loader.</p>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="route">
|
||||||
|
<code>GET /v1/loader/versions/{version}/download</code>
|
||||||
|
<p>Fetches the download URL of a specific version of FrogMC loader.</p>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="route">
|
||||||
|
<code>POST /v1/loader/versions/upload</code>
|
||||||
|
<p>Uploads a version of the loader.</p>
|
||||||
|
<p><strong>This endpoint requires authorization.</strong></p>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="route">
|
||||||
|
<code>DELETE /v1/loader/versions/delete/{version}</code>
|
||||||
|
<p>Deletes a version of the loader.</p>
|
||||||
|
<p><strong>This endpoint requires authorization.</strong></p>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="route">
|
||||||
|
<code>GET /v1/library/versions</code>
|
||||||
|
<p>Fetches all versions of FrogMC library.</p>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="route">
|
||||||
|
<code>GET /v1/library/versions/latest</code>
|
||||||
|
<p>Fetches the latest version of FrogMC library.</p>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="route">
|
||||||
|
<code>GET /v1/library/versions/{version}</code>
|
||||||
|
<p>Fetches a specific version of FrogMC library.</p>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="route">
|
||||||
|
<code>GET /v1/library/versions/{version}/download</code>
|
||||||
|
<p>Fetches the download URL of a specific version of FrogMC library.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -18,7 +18,6 @@ div.routes {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
background-color: var(--ctp-mocha-surface0);
|
background-color: var(--ctp-mocha-surface0);
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
margin-left: 38.5%;
|
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue