Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/pregame lobby page #2

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,17 @@ project/plugins/project/
*.log
*.metals
*.bsp
<<<<<<< Updated upstream
*.bloop
*.idea

.vscode
# End of https://www.gitignore.io/api/sbt,scala,intellij

project/metals.sbt
project/project

*.idea
.sbt/
.ivy2/
.cache/
Expand Down
18 changes: 10 additions & 8 deletions backend/src/main/scala/org/kys/athena/Server.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package org.kys.athena

import org.kys.athena.modules.{CacheModule, ConfigModule, CurrentGameModule, GroupModule, LoggerModule, RiotApiModule}
import org.kys.athena.modules.{
CacheModule, ConfigModule, CurrentGameModule, GroupModule, LoggerModule,
PregameModule, RiotApiModule
}
import org.kys.athena.http.routes.LogicEndpoints
import org.kys.athena.meraki.api.MerakiApiClient
import sttp.client3.httpclient.zio.HttpClientZioBackend
Expand All @@ -9,7 +12,6 @@ import org.http4s.implicits._
import org.http4s.server.Router
import org.http4s.server.blaze.BlazeServerBuilder
import org.http4s.server.middleware.CORS
import org.kys.athena.modules.ConfigModule.ConfigModule
import org.kys.athena.http.middleware.ApacheLogging
import org.kys.athena.modules.ratelimiter.RateLimiter
import zio.clock.Clock
Expand Down Expand Up @@ -40,16 +42,16 @@ object Server extends App {
val riotClient = (config ++ zioClient ++ cache ++ rrl ++ Clock.live) >>> RiotApiModule.live
val merakiClient = zioClient >>> MerakiApiClient.live
val gc = riotClient >>> GroupModule.live
val cgc = (riotClient ++ merakiClient) >>> CurrentGameModule.live
val cgc = (riotClient ++ merakiClient ++ gc) >>> CurrentGameModule.live
val pgc = (riotClient ++ gc) >>> PregameModule.live

allocateHttpServer.provideCustomLayer(cgc ++ gc ++ config).exitCode
allocateHttpServer.provideCustomLayer(cgc ++ gc ++ pgc ++ config).exitCode
}

def allocateHttpServer: ZIO[AppRuntime with ConfigModule, Throwable, Unit] = {
ZIO.runtime[AppRuntime with ConfigModule]
def allocateHttpServer: ZIO[AppRuntime with Has[ConfigModule], Throwable, Unit] = {
ZIO.runtime[AppRuntime with Has[ConfigModule]]
.flatMap { implicit runtime =>

val config = runtime.environment.get[ConfigModule.Service].loaded
val config = runtime.environment.get[ConfigModule].loaded

val routes: HttpRoutes[AppTask] = Router(config.http.prefix -> LogicEndpoints.publicRoutes,
"/" -> LogicEndpoints.internalRoutes)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package org.kys.athena.http.routes

import org.kys.athena.modules.{CurrentGameModule, GroupModule}
import org.kys.athena.modules.CurrentGameModule.CurrentGameController
import org.kys.athena.modules.GroupModule.GroupController
import org.kys.athena.http.models.pregame.PregameResponse
import org.kys.athena.modules.{CurrentGameModule, PregameModule}
import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter
import sttp.tapir.ztapir._
import zio._
Expand All @@ -12,49 +11,76 @@ import java.net.URLDecoder
import java.util.UUID


object LogicEndpoints extends Endpoints {
object LogicEndpoints {
// TODO: do something about this mess

type Env = CurrentGameController with GroupController
val currentGameByNameImpl = this.currentGameByName.zServerLogic { case (platform, name, fetchGroups, requestId) =>
val fetchGroupsDefault = fetchGroups.getOrElse(false)
val decodedName = URLDecoder.decode(name, "UTF-8")
implicit val getReqId: String = requestId.fold(UUID.randomUUID().toString)(identity)
type Env = Has[CurrentGameModule] with Has[PregameModule]
val currentGameByNameImpl = Endpoints.currentGameByName
.zServerLogic { case (platform, name, fetchGroups, requestId) =>
val fetchGroupsDefault = fetchGroups.getOrElse(false)
val decodedName = URLDecoder.decode(name, "UTF-8")
implicit val getReqId: String = requestId.fold(UUID.randomUUID().toString)(identity)

(for {
game <- CurrentGameModule.getCurrentGame(platform, decodedName)
uuidAdded <-
if (fetchGroupsDefault)
GroupModule.getGroupsForGameAsync(platform, game).map(u => game.copy(groupUuid = Some(u)))
else IO.succeed(game)
(for {
game <- CurrentGameModule.getCurrentGame(platform, decodedName)
uuidAdded <-
if (fetchGroupsDefault)
CurrentGameModule.getGroupsForGameAsync(platform, game).map(u => game.copy(groupUuid = Some(u)))
else IO.succeed(game)
} yield uuidAdded).resurrect.flatMapError(ErrorHandler.defaultErrorHandler)
}

val groupsByNameImpl = this.groupsByName.zServerLogic { case (platform, name, requestId) =>
val currentGameGroupsByNameImpl = Endpoints.currentGameGroupsByName.zServerLogic { case (platform, name, requestId) =>
val decodedName = URLDecoder.decode(name, "UTF-8")
implicit val getReqId: String = requestId.fold(UUID.randomUUID().toString)(identity)

(for {
game <- CurrentGameModule.getCurrentGame(platform, decodedName)
groups <- GroupModule.getGroupsForGame(platform, game)
groups <- CurrentGameModule.getGroupsForGame(platform, game)
} yield groups).resurrect.flatMapError(ErrorHandler.defaultErrorHandler)
}

val currentGameGroupsByUUIDImpl = Endpoints.currentGameGroupsByUUID.zServerLogic { case (uuid, requestId) =>
implicit val getReqId: String = requestId.fold(UUID.randomUUID().toString)(identity)

(for {
gg <- CurrentGameModule.getGroupsByUUID(uuid)
} yield gg).resurrect.flatMapError(ErrorHandler.defaultErrorHandler)
}

val pregameByNameImpl = Endpoints.pregameByName.zServerLogic { case (platform, names, fetchGroups, requestId) =>
implicit val getReqId: String = requestId.fold(UUID.randomUUID().toString)(identity)
(for {
pg <- PregameModule.getPregameLobby(platform, names)
} yield PregameResponse(pg, None)).resurrect.flatMapError(ErrorHandler.defaultErrorHandler)
}

val pregameGroupsByNameImpl = Endpoints.pregameGroupsByName.zServerLogic { case (platform, names, requestId) =>
implicit val getReqId: String = requestId.fold(UUID.randomUUID().toString)(identity)
(for {
pg <- PregameModule.getPregameLobby(platform, names)
groups <- PregameModule.getGroupsForPregame(platform, pg)
} yield groups).resurrect.flatMapError(ErrorHandler.defaultErrorHandler)
}

val groupsByUUIDImpl = this.groupsByUUID.zServerLogic { case (uuid, requestId) =>
val pregameGroupsByUUIDImpl = Endpoints.pregameGameGroupsByUUID.zServerLogic { case (uuid, requestId) =>
implicit val getReqId: String = requestId.fold(UUID.randomUUID().toString)(identity)

(for {
gg <- GroupModule.getGroupsByUUID(uuid)
gg <- PregameModule.getGroupsByUUID(uuid)
} yield gg).resurrect.flatMapError(ErrorHandler.defaultErrorHandler)
}

val healthzImpl = this.healthz.zServerLogic { _ =>
val healthzImpl = Endpoints.healthz.zServerLogic { _ =>
UIO.succeed("Ok")
}

val publicRoutes = ZHttp4sServerInterpreter.from(List(currentGameByNameImpl.widen[Env],
groupsByNameImpl.widen[Env],
groupsByUUIDImpl.widen[Env])).toRoutes
currentGameGroupsByNameImpl.widen[Env],
currentGameGroupsByUUIDImpl.widen[Env],
pregameByNameImpl.widen[Env],
pregameGroupsByNameImpl.widen[Env],
pregameGroupsByUUIDImpl.widen[Env])).toRoutes

val internalRoutes = ZHttp4sServerInterpreter.from(healthzImpl.widen[Env]).toRoutes
}
60 changes: 34 additions & 26 deletions backend/src/main/scala/org/kys/athena/modules/CacheModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,49 @@ package org.kys.athena.modules
import com.github.blemale.scaffeine.Scaffeine
import org.kys.athena.util.errors.{CacheError, CastError, UnknownError}
import zio._
import zio.macros.accessible

import java.util.concurrent.TimeUnit
import scala.concurrent.duration.FiniteDuration
import scala.reflect.ClassTag

@accessible
object CacheModule {
type CacheModule = Has[Service]

trait Service {
def put[T](key: String, v: T): IO[CacheError, Unit]
def get[T](key: String)(implicit evT: ClassTag[T]): IO[CacheError, Option[T]]
}
trait CacheModule {
def put[T](key: String, v: T): IO[CacheError, Unit]

def get[T](key: String)(implicit evT: ClassTag[T]): IO[CacheError, Option[T]]
}


object CacheModule {

@SuppressWarnings(Array("org.wartremover.warts.AsInstanceOf", "org.wartremover.warts.Serializable"))
val live = (for {
config <- ConfigModule.loaded
cache <- Task.effect {
Scaffeine().recordStats()
.expireAfterWrite(FiniteDuration.apply(config.cacheRiotRequestsFor, TimeUnit.SECONDS))
.maximumSize(config.cacheRiotRequestsMaxCount)
.build[String, Serializable]()
}.orDie
} yield new Service {
override def put[T](key: String, v: T): IO[CacheError, Unit] = {
IO.effect(cache.put(key, v.asInstanceOf[Serializable])).mapError(e => UnknownError(e))
}
config <- ConfigModule.loaded
cache <- Task.effect {
Scaffeine().recordStats()
.expireAfterWrite(FiniteDuration.apply(config.cacheRiotRequestsFor, TimeUnit.SECONDS))
.maximumSize(config.cacheRiotRequestsMaxCount)
.build[String, Serializable]()
}.orDie
} yield new CacheModule {
override def put[T](key: String, v: T): IO[CacheError, Unit] = {
IO.effect(cache.put(key, v.asInstanceOf[Serializable])).mapError(e => UnknownError(e))
}

override def get[T](key: String)(implicit ev: ClassTag[T]): IO[CacheError, Option[T]] = {
ZIO.effect(cache.getIfPresent(key)).flatMap(r => Task.effect(r.map(_.asInstanceOf[T]))).mapError {
case _: ClassCastException =>
CastError(s"Failed to cast to ${implicitly[ClassTag[T]].runtimeClass.getSimpleName}")
case e => UnknownError(e)
}
override def get[T](key: String)(implicit ev: ClassTag[T]): IO[CacheError, Option[T]] = {
ZIO.effect(cache.getIfPresent(key)).flatMap(r => Task.effect(r.map(_.asInstanceOf[T]))).mapError {
case _: ClassCastException =>
CastError(s"Failed to cast to ${implicitly[ClassTag[T]].runtimeClass.getSimpleName}")
case e => UnknownError(e)
}
}).toLayer
}
}).toLayer

def get[T](key: String)(implicit evT: ClassTag[T]): ZIO[Has[CacheModule], CacheError, Option[T]] = {
ZIO.accessM[Has[CacheModule]](_.get.get(key)(evT))
}

def put[T](key: String, v: T): ZIO[Has[CacheModule], CacheError, Unit] = {
ZIO.accessM[Has[CacheModule]](_.get.put(key, v))
}
}
19 changes: 9 additions & 10 deletions backend/src/main/scala/org/kys/athena/modules/ConfigModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,23 @@ package org.kys.athena.modules

import org.kys.athena.config.Config
import pureconfig.ConfigSource
import zio.macros.accessible
import pureconfig.generic.auto._
import zio.{Has, Task}
import zio.{Has, Task, ZIO, ZLayer}


@accessible
object ConfigModule {
type ConfigModule = Has[Service]
trait ConfigModule {
val loaded: Config
}

trait Service {
val loaded: Config
}
object ConfigModule {

val live = {
val live: ZLayer[Any, Throwable, Has[ConfigModule]] = {
Task.effect(ConfigSource.default.loadOrThrow[Config]).map(c => {
new Service {
new ConfigModule {
override val loaded: Config = c
}
}).toLayer
}

def loaded: ZIO[Has[ConfigModule], Nothing, Config] = ZIO.access[Has[ConfigModule]](_.get.loaded)
}
Loading