Stonks/app/src/main/kotlin/xyz/etztech/stonks/Api.kt

241 lines
9.1 KiB
Kotlin

package xyz.etztech.stonks.api
import io.javalin.Javalin
import java.time.Duration
import java.time.Instant
import kotlinx.coroutines.*
import kotlinx.serialization.*
import kotlinx.serialization.json.Json
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.*
import xyz.etztech.stonks.dsl.AggregateStatistics
import xyz.etztech.stonks.dsl.LiveStatistics
import xyz.etztech.stonks.dsl.Players
import xyz.etztech.stonks.dsl.Statistics
private var statisticsCache = ""
private var statisticsCacheTimestamp = Instant.now()
private var playersCache = ""
private var playersCacheTimestamp = Instant.now()
fun initApiServer(apiServerPort: Int, database: Database) {
val app =
Javalin.create { config ->
config.enableCorsForAllOrigins()
config.addStaticFiles("spa")
config.addSinglePageRoot("", "spa/index.html")
config.enforceSsl = false
config.ignoreTrailingSlashes = true
}
.start(apiServerPort)
app.get("api/statistics") { ctx ->
run {
if (statisticsCache.isEmpty() or
(Duration.between(Instant.now(), statisticsCacheTimestamp) >
Duration.ofHours(1))
) {
transaction(database) {
addLogger(StdOutSqlLogger)
val statistics =
LiveStatistics.slice(LiveStatistics.type, LiveStatistics.name)
.selectAll()
.groupBy(LiveStatistics.type, LiveStatistics.name)
.map {
Statistic(it[LiveStatistics.type], it[LiveStatistics.name])
}
statisticsCache = Json.encodeToString(statistics)
}
}
ctx.result(statisticsCache)
}
}
app.get("api/statistics/:type/:name") { ctx ->
run {
transaction(database) {
addLogger(StdOutSqlLogger)
val statistics =
LiveStatistics.slice(
LiveStatistics.type,
LiveStatistics.name,
LiveStatistics.playerId,
LiveStatistics.value,
LiveStatistics.rank
)
.select {
LiveStatistics.type.eq(ctx.pathParam("type")) and
LiveStatistics.name.eq(ctx.pathParam("name"))
}
.orderBy(LiveStatistics.rank, SortOrder.ASC)
.map {
StatisticValue(
it[LiveStatistics.playerId],
it[LiveStatistics.type],
it[LiveStatistics.name],
it[LiveStatistics.value],
it[LiveStatistics.rank]
)
}
ctx.result(Json { prettyPrint = true }.encodeToString(statistics))
}
}
}
app.get("api/players") { ctx ->
run {
if (playersCache.isEmpty() or
(Duration.between(Instant.now(), playersCacheTimestamp) >
Duration.ofHours(1))
) {
transaction(database) {
addLogger(StdOutSqlLogger)
val players =
LiveStatistics.leftJoin(Players, { playerId }, { id })
.slice(LiveStatistics.playerId, Players.name)
.selectAll()
.groupBy(LiveStatistics.playerId)
.map { Player(it[LiveStatistics.playerId], it[Players.name]) }
playersCache = Json.encodeToString(players)
}
}
ctx.result(playersCache)
}
}
app.get("api/players/:playerId") { ctx ->
run {
transaction(database) {
addLogger(StdOutSqlLogger)
val statistics =
LiveStatistics.slice(
LiveStatistics.type,
LiveStatistics.name,
LiveStatistics.playerId,
LiveStatistics.value,
LiveStatistics.rank
)
.select { LiveStatistics.playerId.eq(ctx.pathParam("playerId")) }
.map {
StatisticValue(
it[LiveStatistics.playerId],
it[LiveStatistics.type],
it[LiveStatistics.name],
it[LiveStatistics.value],
it[LiveStatistics.rank]
)
}
ctx.result(Json { prettyPrint = true }.encodeToString(statistics))
}
}
}
app.get("api/players/:playerId/:type/:name") { ctx ->
run {
transaction(database) {
addLogger(StdOutSqlLogger)
val statistics =
Statistics.slice(
Statistics.type,
Statistics.name,
Statistics.playerId,
Statistics.timestamp,
Statistics.value
)
.select {
Statistics.playerId.eq(ctx.pathParam("playerId")) and
Statistics.type.eq(ctx.pathParam("type")) and
Statistics.name.eq(ctx.pathParam("name"))
}
.orderBy(Statistics.timestamp, SortOrder.DESC)
.map {
HistoricalStatisticValue(
it[Statistics.playerId],
it[Statistics.type],
it[Statistics.name],
it[Statistics.timestamp].toString(),
it[Statistics.value]
)
}
ctx.result(Json { prettyPrint = true }.encodeToString(statistics))
}
}
}
app.get("api/aggregates") { ctx ->
run {
transaction(database) {
addLogger(StdOutSqlLogger)
val maxValueExpr = AggregateStatistics.value.max()
val aggregates =
AggregateStatistics.slice(
AggregateStatistics.type,
AggregateStatistics.name,
maxValueExpr
)
.selectAll()
.groupBy(AggregateStatistics.type, AggregateStatistics.name)
.map {
AggregateValue(
it[AggregateStatistics.type],
it[AggregateStatistics.name],
it[maxValueExpr]!!
)
}
ctx.result(Json { prettyPrint = true }.encodeToString(aggregates))
}
}
}
app.get("api/*") { ctx -> ctx.status(404) }
println("Javalin web server started")
}
@Serializable data class Statistic(val type: String, val name: String)
@Serializable data class Player(val id: String, val name: String?)
@Serializable
data class StatisticValue(
val playerId: String,
val type: String,
val name: String,
val value: Long,
val rank: Int
)
@Serializable
data class HistoricalStatisticValue(
val playerId: String,
val type: String,
val name: String,
val timestamp: String,
val value: Long
)
@Serializable data class AggregateValue(val type: String, val name: String, val value: Long)
@Serializable
data class HistoricalAggregateValue(
val type: String,
val name: String,
val timestamp: String,
val value: Long
)