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.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/*") { 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 )