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.transaction 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().start(apiServerPort) println("Javalin web server started") app.get("/") { ctx -> run { transaction(database) { addLogger(StdOutSqlLogger) val dataPoints = Statistics.slice(Statistics.playerId.count()).selectAll().limit(1).map { it[Statistics.playerId.count()] }[0] val playerCount = Statistics.slice(Statistics.playerId.countDistinct()) .selectAll() .limit(1) .map { it[Statistics.playerId.countDistinct()] }[0] ctx.result("$dataPoints data points from $playerCount players!") } } } app.get("/statistics") { ctx -> run { if (statisticsCache.isEmpty() or (Duration.between(Instant.now(), statisticsCacheTimestamp) > Duration.ofHours(1))) { transaction(database) { addLogger(StdOutSqlLogger) val statistics = Statistics.slice(Statistics.type, Statistics.name) .selectAll() .groupBy(Statistics.type, Statistics.name) .map { Statistic(it[Statistics.type], it[Statistics.name]) } statisticsCache = Json.encodeToString(statistics) } } ctx.result(statisticsCache) } } app.get("/statistics/:type/:name") { ctx -> run { transaction(database) { addLogger(StdOutSqlLogger) val maxExpr = Statistics.value.max() val statistics = Statistics.slice( Statistics.type, Statistics.name, Statistics.playerId, maxExpr) .select { Statistics.type.eq(ctx.pathParam("type")) and Statistics.name.eq(ctx.pathParam("name")) } .groupBy(Statistics.playerId) .orderBy(maxExpr, SortOrder.DESC) .map { StatisticValue( it[Statistics.playerId], it[Statistics.type], it[Statistics.name], it[maxExpr]!!) } ctx.result(Json { prettyPrint = true }.encodeToString(statistics)) } } } app.get("/players") { ctx -> run { if (playersCache.isEmpty() or (Duration.between(Instant.now(), playersCacheTimestamp) > Duration.ofHours(1))) { transaction(database) { addLogger(StdOutSqlLogger) val players = Statistics.slice(Statistics.playerId) .selectAll() .groupBy(Statistics.playerId) .map { it[Statistics.playerId] } playersCache = Json.encodeToString(players) } } ctx.result(playersCache) } } app.get("/players/:playerId") { ctx -> run { transaction(database) { addLogger(StdOutSqlLogger) val maxExpr = Statistics.value.max() val statistics = Statistics.slice( Statistics.type, Statistics.name, Statistics.playerId, maxExpr) .select { Statistics.playerId.eq(ctx.pathParam("playerId")) } .groupBy(Statistics.type, Statistics.name) .map { StatisticValue( it[Statistics.playerId], it[Statistics.type], it[Statistics.name], it[maxExpr]!!) } ctx.result(Json { prettyPrint = true }.encodeToString(statistics)) } } } app.get("/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)) } } } } @Serializable data class Statistic(val type: String, val name: String) @Serializable data class StatisticValue( val playerId: String, val type: String, val name: String, val value: Long ) @Serializable data class HistoricalStatisticValue( val playerId: String, val type: String, val name: String, val timestamp: String, val value: Long )