diff --git a/.gitignore b/.gitignore index 9319aae..b3f02dd 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/backend/src/main/scala/org/kys/athena/Server.scala b/backend/src/main/scala/org/kys/athena/Server.scala index 992faf2..b218cfd 100644 --- a/backend/src/main/scala/org/kys/athena/Server.scala +++ b/backend/src/main/scala/org/kys/athena/Server.scala @@ -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 @@ -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 @@ -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) diff --git a/backend/src/main/scala/org/kys/athena/data/SummonerMatchHistory.scala b/backend/src/main/scala/org/kys/athena/data/SummonerMatchHistory.scala deleted file mode 100644 index 36b154a..0000000 --- a/backend/src/main/scala/org/kys/athena/data/SummonerMatchHistory.scala +++ /dev/null @@ -1,7 +0,0 @@ -package org.kys.athena.data - -import org.kys.athena.http.models.current.InGameSummoner -import org.kys.athena.riot.api.dto.`match`.Match - - -final case class SummonerMatchHistory(inGameSummoner: InGameSummoner, history: List[Match]) diff --git a/backend/src/main/scala/org/kys/athena/http/routes/LogicEndpoints.scala b/backend/src/main/scala/org/kys/athena/http/routes/LogicEndpoints.scala index 37034d2..2e4bc3e 100644 --- a/backend/src/main/scala/org/kys/athena/http/routes/LogicEndpoints.scala +++ b/backend/src/main/scala/org/kys/athena/http/routes/LogicEndpoints.scala @@ -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._ @@ -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 } diff --git a/backend/src/main/scala/org/kys/athena/modules/CacheModule.scala b/backend/src/main/scala/org/kys/athena/modules/CacheModule.scala index c4f0963..38e137b 100644 --- a/backend/src/main/scala/org/kys/athena/modules/CacheModule.scala +++ b/backend/src/main/scala/org/kys/athena/modules/CacheModule.scala @@ -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)) + } } diff --git a/backend/src/main/scala/org/kys/athena/modules/ConfigModule.scala b/backend/src/main/scala/org/kys/athena/modules/ConfigModule.scala index b488ef5..375f1e8 100644 --- a/backend/src/main/scala/org/kys/athena/modules/ConfigModule.scala +++ b/backend/src/main/scala/org/kys/athena/modules/ConfigModule.scala @@ -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) } diff --git a/backend/src/main/scala/org/kys/athena/modules/CurrentGameModule.scala b/backend/src/main/scala/org/kys/athena/modules/CurrentGameModule.scala index 989afc9..908f49d 100644 --- a/backend/src/main/scala/org/kys/athena/modules/CurrentGameModule.scala +++ b/backend/src/main/scala/org/kys/athena/modules/CurrentGameModule.scala @@ -1,29 +1,44 @@ package org.kys.athena.modules +import com.github.blemale.scaffeine.{Cache, Scaffeine} import org.kys.athena.http import org.kys.athena.http.models.current.{InGameSummoner, OngoingGameResponse, OngoingGameTeam, PositionEnum} +import org.kys.athena.http.models.premade.PremadeResponse import org.kys.athena.meraki.api.MerakiApiClient import org.kys.athena.meraki.api.errors.MerakiApiError import org.kys.athena.riot.api.dto.common.{GameQueueTypeEnum, Platform, SummonerSpellsEnum} import org.kys.athena.riot.api.dto.currentgameinfo.{BannedChampion, CurrentGameInfo, CurrentGameParticipant} import zio._ -import zio.macros.accessible import org.kys.athena.riot +import java.util.UUID import scala.collection.MapView +import scala.concurrent.duration.DurationInt -@accessible -object CurrentGameModule { +trait CurrentGameModule { + def getCurrentGame(platform: Platform, name: String)(implicit reqId: String): IO[Throwable, OngoingGameResponse] - type CurrentGameController = Has[CurrentGameModule.Service] - trait Service { - def getCurrentGame(platform: Platform, name: String)(implicit reqId: String): IO[Throwable, OngoingGameResponse] - } + def getGroupsForGame(platform: Platform, + ongoingGameInfo: OngoingGameResponse, + gamesQueryCount: Int = 5) + (implicit reqId: String): IO[Throwable, PremadeResponse] + + def getGroupsForGameAsync(platform: Platform, + ongoingGameInfo: OngoingGameResponse, + gamesQueryCount: Int = 5) + (implicit reqId: String): IO[Throwable, UUID] + + def getGroupsByUUID(uuid: UUID): IO[Throwable, PremadeResponse] +} - val live = ZLayer.fromServices[RiotApiModule.Service, MerakiApiClient.Service, - CurrentGameModule.Service] { (riotApiClient, merakiApiClient) => - new Service { +object CurrentGameModule { + + val live = ZLayer.fromServices[ + RiotApiModule.Service, MerakiApiClient.Service, GroupModule, CurrentGameModule] { (riotApiClient, + merakiApiClient, + groupModule) => + new CurrentGameModule { case class ParticipantTuple(blue: List[CurrentGameParticipant], red: List[CurrentGameParticipant]) case class BansTuple(blue: Option[Set[BannedChampion]], red: Option[Set[BannedChampion]]) @@ -84,16 +99,16 @@ object CurrentGameModule { } val processed = filtered - .map { perm => - val score = perm.map { - case (posn, sum) => - playRate.data.get(sum.championId.toInt).flatMap(_.get(posn)) match { - case Some(p) => p.playRate - case None => 0D - } - }.sum - (perm.toMap.view.mapValues(_.summonerId), score) - } + .map { perm => + val score = perm.map { + case (posn, sum) => + playRate.data.get(sum.championId.toInt).flatMap(_.get(posn)) match { + case Some(p) => p.playRate + case None => 0D + } + }.sum + (perm.toMap.view.mapValues(_.summonerId), score) + } val maxByRate = processed.foldRight((MapView[PositionEnum, String](), 0D)) { (cur, maxSoFar) => if (cur._2 > maxSoFar._2) cur else maxSoFar @@ -105,7 +120,6 @@ object CurrentGameModule { } } - def getCurrentGame(platform: Platform, name: String) (implicit reqId: String): IO[Throwable, OngoingGameResponse] = { for { @@ -128,7 +142,80 @@ object CurrentGameModule { OngoingGameTeam(blueSummoners, bluePositions, bans.blue), OngoingGameTeam(redSummoners, redPositions, bans.red)) } + + + val uuidCache: Cache[UUID, Promise[Throwable, PremadeResponse]] = Scaffeine() + .recordStats() + .expireAfterWrite(5.minutes) + .build[UUID, Promise[Throwable, PremadeResponse]]() + + def getGroupsForGame(platform: Platform, + ongoingGameInfo: OngoingGameResponse, + gamesQueryCount: Int = 5)(implicit reqId: String): IO[Throwable, PremadeResponse] = { + for { + blueGroups <- groupModule.groupsForTeam(ongoingGameInfo.blueTeam.summoners.map(_.summonerId), + gamesQueryCount, + platform) + redGroups <- groupModule.groupsForTeam(ongoingGameInfo.redTeam.summoners.map(_.summonerId), + gamesQueryCount, + platform) + } yield PremadeResponse(blueGroups, redGroups) + } + + def getGroupsForGameAsync(platform: Platform, + ongoingGameInfo: OngoingGameResponse, + gamesQueryCount: Int = 5)(implicit reqId: String): IO[Throwable, UUID] = { + val promise = Promise.make[Throwable, PremadeResponse] + for { + uuid <- Task.effect(UUID.randomUUID()) + p <- promise + _ <- Task.effect(uuidCache.put(uuid, p)) + _ <- p + .complete(getGroupsForGame(platform, ongoingGameInfo, gamesQueryCount)) + .forkDaemon + } yield uuid + } + + def getGroupsByUUID(uuid: UUID): IO[Throwable, PremadeResponse] = { + for { + entry <- IO.effect(uuidCache.getIfPresent(uuid)) + res <- entry match { + case Some(v) => { + v.await.either.flatMap { + case Right(re) => IO.succeed(re) + case Left(ex) => + scribe.error(s"Get groups by UUID failed uuid=$uuid", ex) + IO.fail(ex) + } + } + case None => IO.fail(http.errors.NotFoundError("UUID not found")) + } + } yield res + } + } - } + } + + def getCurrentGame(platform: Platform, name: String) + (implicit reqId: String): ZIO[Has[CurrentGameModule], Throwable, OngoingGameResponse] = { + ZIO.accessM[Has[CurrentGameModule]](_.get.getCurrentGame(platform, name)(reqId)) + } + + def getGroupsForGame(platform: Platform, + ongoingGameInfo: OngoingGameResponse, + gamesQueryCount: Int = 5) + (implicit reqId: String): ZIO[Has[CurrentGameModule], Throwable, PremadeResponse] = { + ZIO.accessM[Has[CurrentGameModule]](_.get.getGroupsForGame(platform, ongoingGameInfo, gamesQueryCount)(reqId)) + } + + def getGroupsForGameAsync(platform: Platform, + ongoingGameInfo: OngoingGameResponse, + gamesQueryCount: Int = 5) + (implicit reqId: String): ZIO[Has[CurrentGameModule], Throwable, UUID] = { + ZIO.accessM[Has[CurrentGameModule]](_.get.getGroupsForGameAsync(platform, ongoingGameInfo, gamesQueryCount)(reqId)) + } + def getGroupsByUUID(uuid: UUID): ZIO[Has[CurrentGameModule], Throwable, PremadeResponse] = { + ZIO.accessM[Has[CurrentGameModule]](_.get.getGroupsByUUID(uuid)) + } } diff --git a/backend/src/main/scala/org/kys/athena/modules/GroupModule.scala b/backend/src/main/scala/org/kys/athena/modules/GroupModule.scala index 0f3995f..8442d8d 100644 --- a/backend/src/main/scala/org/kys/athena/modules/GroupModule.scala +++ b/backend/src/main/scala/org/kys/athena/modules/GroupModule.scala @@ -1,46 +1,25 @@ package org.kys.athena.modules -import com.github.blemale.scaffeine.{Cache, Scaffeine} -import org.kys.athena.data.SummonerMatchHistory -import org.kys.athena.http.models.current.OngoingGameResponse -import org.kys.athena.http.models.premade.{PlayerGroup, PremadeResponse} +import org.kys.athena.http.models.premade.PlayerGroup import org.kys.athena.riot.api.dto.common.{GameQueueTypeEnum, Platform} -import org.kys.athena.http -import zio.macros.accessible +import org.kys.athena.riot.api.errors import zio._ -import java.util.UUID -import scala.concurrent.duration.DurationInt +trait GroupModule { + def groupsForTeam(summoners: Set[String], gameQueryCount: Int, platform: Platform) + (implicit reqId: String): ZIO[Any, errors.RiotApiError, Set[PlayerGroup]] +} -@accessible object GroupModule { - type GroupController = Has[GroupModule.Service] - - trait Service { - def getGroupsForGame(platform: Platform, - ongoingGameInfo: OngoingGameResponse, - gamesQueryCount: Int = 5) - (implicit reqId: String): IO[Throwable, PremadeResponse] - - def getGroupsForGameAsync(platform: Platform, - ongoingGameInfo: OngoingGameResponse, - gamesQueryCount: Int = 5) - (implicit reqId: String): IO[Throwable, UUID] - - def getGroupsByUUID(uuid: UUID): IO[Throwable, PremadeResponse] - } - - val live = ZLayer.fromService[RiotApiModule.Service, GroupModule.Service] { riotApiClient => - - new Service { - case class TeamTupleWithHistory(blueTeam: Set[SummonerMatchHistory], redTeam: Set[SummonerMatchHistory]) - val uuidCache: Cache[UUID, Promise[Throwable, PremadeResponse]] = Scaffeine() - .recordStats() - .expireAfterWrite(5.minutes) - .build[UUID, Promise[Throwable, PremadeResponse]]() + val live = ZLayer.fromService[RiotApiModule.Service, GroupModule] { riotApiClient => + new GroupModule { + case class Game(summoners: Set[String]) { + def intersect(other: Game): Set[String] = summoners.intersect(other.summoners) + } + // Supported match history queues val queues: Set[GameQueueTypeEnum] = Set(GameQueueTypeEnum.SummonersRiftBlind, GameQueueTypeEnum.SummonersRiftDraft, GameQueueTypeEnum.SummonersRiftSoloRanked, @@ -48,25 +27,27 @@ object GroupModule { GameQueueTypeEnum.HowlingAbyss, GameQueueTypeEnum.SummonersRiftClash) - def getTeamHistory(ongoingGameInfo: OngoingGameResponse, gameQueryCount: Int, platform: Platform) - (implicit reqId: String): Task[TeamTupleWithHistory] = { - for { - blueGames <- riotApiClient.matchHistoryByInGameSummonerSet(ongoingGameInfo.blueTeam.summoners, - gameQueryCount, - queues, - platform) - redGames <- riotApiClient.matchHistoryByInGameSummonerSet(ongoingGameInfo.redTeam.summoners, - gameQueryCount, - queues, - platform) - } yield TeamTupleWithHistory(blueGames, redGames) - } - def determineGroups(teamTupleWithHistory: TeamTupleWithHistory): PremadeResponse = { - def determineGroupsImpl(searchSet: Set[SummonerMatchHistory]): Set[PlayerGroup] = { - val curTeam = searchSet.map(_.inGameSummoner) + def groupsFromGames(searchFor: Game, history: List[Game]): Set[PlayerGroup] = { + // Iterate over all games in history + history.foldRight(Set[PlayerGroup]()) { (curGame, acc) => + // Compare each with the current, find intersections + val intersect = curGame.intersect(searchFor) + // If length of intersect is > 1, increment playedWith, or create a new PlayerGroup + acc.find(_.summoners == intersect) match { + case Some(lobby) => acc.excl(lobby).incl(lobby.copy(gamesPlayed = lobby.gamesPlayed + 1)) + case None if intersect.size > 1 => acc.incl(PlayerGroup(intersect, 1)) + case _ => acc + } + } + } - val gameParticipants = searchSet.flatMap(_.history).map(_.participantsFused) + def groupsForTeam(summoners: Set[String], gameQueryCount: Int, platform: Platform) + (implicit reqId: String): ZIO[Any, errors.RiotApiError, Set[PlayerGroup]] = { + ZIO.foreachPar(summoners) { id => + riotApiClient.matchHistoryBySummonerId(id, gameQueryCount, queues, platform) + }.map { resp => + val gameParticipants = resp.flatten.toList.map(_.participantsFused) // Log bad API response gameParticipants.count(_.isEmpty) match { @@ -74,75 +55,23 @@ object GroupModule { case c => { scribe.error("Got bad response from API: " + "Some of the participants don't have corresponding player. " + - "Flattening and proceeding. Please investigate " + curTeam) + "Flattening and proceeding. Please investigate " + summoners) } } // Split participants into separate teams - val uniqueGames = gameParticipants.flatten + val games = gameParticipants.flatten .flatMap(_.groupBy(_.teamId).values) - .map(_.map(_.player.summonerId)) - - uniqueGames.foldRight(Set[PlayerGroup]())((curGame, acc) => { + .map(pFused => Game(pFused.map(_.player.summonerId).toSet)) - val cursorGamePlayersFromCurrentGame = curTeam.filter { curPlayer => - curGame.contains(curPlayer.summonerId) - } - - acc.find(_.summoners == cursorGamePlayersFromCurrentGame.map(_.summonerId)) match { - case Some(lobby) => acc.excl(lobby).incl(lobby.copy(gamesPlayed = lobby.gamesPlayed + 1)) - case None if cursorGamePlayersFromCurrentGame.size > 1 => { - acc.incl(PlayerGroup(cursorGamePlayersFromCurrentGame.map(_.summonerId), 1)) - } - case _ => acc - } - }) + groupsFromGames(Game(summoners), games) } - val blueGroups = determineGroupsImpl(teamTupleWithHistory.blueTeam) - val redGroups = determineGroupsImpl(teamTupleWithHistory.redTeam) - - PremadeResponse(blueGroups, redGroups) - } - - def getGroupsForGame(platform: Platform, - ongoingGameInfo: OngoingGameResponse, - gamesQueryCount: Int = 5)(implicit reqId: String): IO[Throwable, PremadeResponse] = { - for { - teamHistory <- getTeamHistory(ongoingGameInfo, gamesQueryCount, platform) - groupsTuple <- Task.succeed(determineGroups(teamHistory)) - } yield groupsTuple - } - - def getGroupsForGameAsync(platform: Platform, - ongoingGameInfo: OngoingGameResponse, - gamesQueryCount: Int = 5)(implicit reqId: String): IO[Throwable, UUID] = { - val promise = Promise.make[Throwable, PremadeResponse] - for { - uuid <- Task.effect(UUID.randomUUID()) - p <- promise - _ <- Task.effect(uuidCache.put(uuid, p)) - _ <- p - .complete(getGroupsForGame(platform, ongoingGameInfo, gamesQueryCount)) - .forkDaemon - } yield uuid - } - - def getGroupsByUUID(uuid: UUID): IO[Throwable, PremadeResponse] = { - for { - entry <- IO.effect(uuidCache.getIfPresent(uuid)) - res <- entry match { - case Some(v) => { - v.await.either.flatMap { - case Right(re) => IO.succeed(re) - case Left(ex) => - scribe.error(s"Get groups by UUID failed uuid=$uuid", ex) - IO.fail(ex) - } - } - case None => IO.fail(http.errors.NotFoundError("UUID not found")) - } - } yield res } } } + + def groupsForTeam(summoners: Set[String], gameQueryCount: Int, platform: Platform) + (implicit reqId: String): ZIO[Has[GroupModule], errors.RiotApiError, Set[PlayerGroup]] = { + ZIO.accessM[Has[GroupModule]](_.get.groupsForTeam(summoners, gameQueryCount, platform)) + } } diff --git a/backend/src/main/scala/org/kys/athena/modules/PregameModule.scala b/backend/src/main/scala/org/kys/athena/modules/PregameModule.scala new file mode 100644 index 0000000..2a37d80 --- /dev/null +++ b/backend/src/main/scala/org/kys/athena/modules/PregameModule.scala @@ -0,0 +1,116 @@ +package org.kys.athena.modules + +import com.github.blemale.scaffeine.{Cache, Scaffeine} +import org.kys.athena.http +import org.kys.athena.http.models.common.RankedLeague +import org.kys.athena.http.models.pregame.PregameSummoner +import org.kys.athena.http.models.premade.PlayerGroup +import org.kys.athena.riot.api.dto.common.Platform +import zio._ + +import java.util.UUID +import scala.concurrent.duration.DurationInt + + +trait PregameModule { + def getPregameLobby(platform: Platform, names: Set[String]) + (implicit reqId: String): IO[Throwable, Set[PregameSummoner]] + + def getGroupsForPregame(platform: Platform, + summoners: Set[PregameSummoner], + gamesQueryCount: Int = 5) + (implicit reqId: String): IO[Throwable, Set[PlayerGroup]] + + def getGroupsForPregameAsync(platform: Platform, + summoners: Set[PregameSummoner], + gamesQueryCount: Int = 5) + (implicit reqId: String): IO[Throwable, UUID] + + def getGroupsByUUID(uuid: UUID): IO[Throwable, Set[PlayerGroup]] +} + +object PregameModule { + val live = ZLayer.fromServices[RiotApiModule.Service, GroupModule, PregameModule] { (riotApiClient, groupModule) => + new PregameModule { + + val uuidCache: Cache[UUID, Promise[Throwable, Set[PlayerGroup]]] = Scaffeine() + .recordStats() + .expireAfterWrite(5.minutes) + .build[UUID, Promise[Throwable, Set[PlayerGroup]]]() + + override def getPregameLobby(platform: Platform, + names: Set[String]) + (implicit reqId: String): IO[Throwable, Set[PregameSummoner]] = { + for { + summoners <- ZIO.foreachPar(names)(name => riotApiClient.summonerByName(name, platform)) + leagues <- ZIO.foreachPar(summoners)(s => riotApiClient.leaguesBySummonerId(s.id, platform).map(l => (s, l))) + } yield leagues.map(t => { + PregameSummoner(t._1.name, t._1.id, t._1.summonerLevel, + t._2.map(RankedLeague(_))) + }) + } + + override def getGroupsForPregame(platform: Platform, + summoners: Set[PregameSummoner], + gamesQueryCount: Int) + (implicit reqId: String): IO[Throwable, Set[PlayerGroup]] = { + groupModule.groupsForTeam(summoners.map(_.summonerId), gamesQueryCount, platform) + } + + override def getGroupsForPregameAsync(platform: Platform, + summoners: Set[PregameSummoner], + gamesQueryCount: Int) + (implicit reqId: String): IO[Throwable, UUID] = { + val promise = Promise.make[Throwable, Set[PlayerGroup]] + for { + uuid <- Task.effect(UUID.randomUUID()) + p <- promise + _ <- Task.effect(uuidCache.put(uuid, p)) + _ <- p + .complete(getGroupsForPregame(platform, summoners, gamesQueryCount)) + .forkDaemon + } yield uuid + } + + override def getGroupsByUUID(uuid: UUID): IO[Throwable, Set[PlayerGroup]] = { + for { + entry <- IO.effect(uuidCache.getIfPresent(uuid)) + res <- entry match { + case Some(v) => { + v.await.either.flatMap { + case Right(re) => IO.succeed(re) + case Left(ex) => // TODO: change log text + scribe.error(s"Get groups by UUID failed uuid=$uuid", ex) + IO.fail(ex) + } + } + case None => IO.fail(http.errors.NotFoundError("UUID not found")) + } + } yield res + } + } + } + + def getPregameLobby(platform: Platform, names: Set[String]) + (implicit reqId: String): ZIO[Has[PregameModule], Throwable, Set[PregameSummoner]] = { + ZIO.accessM[Has[PregameModule]](_.get.getPregameLobby(platform, names)(reqId)) + } + + def getGroupsForPregame(platform: Platform, + summoners: Set[PregameSummoner], + gamesQueryCount: Int = 5) + (implicit reqId: String): ZIO[Has[PregameModule], Throwable, Set[PlayerGroup]] = { + ZIO.accessM[Has[PregameModule]](_.get.getGroupsForPregame(platform, summoners, gamesQueryCount)(reqId)) + } + + def getGroupsForPregameAsync(platform: Platform, + summoners: Set[PregameSummoner], + gamesQueryCount: Int = 5) + (implicit reqId: String): ZIO[Has[PregameModule], Throwable, UUID] = { + ZIO.accessM[Has[PregameModule]](_.get.getGroupsForPregameAsync(platform, summoners, gamesQueryCount)) + } + + def getGroupsByUUID(uuid: UUID): ZIO[Has[PregameModule], Throwable, Set[PlayerGroup]] = { + ZIO.accessM[Has[PregameModule]](_.get.getGroupsByUUID(uuid)) + } +} \ No newline at end of file diff --git a/backend/src/main/scala/org/kys/athena/modules/RiotApiModule.scala b/backend/src/main/scala/org/kys/athena/modules/RiotApiModule.scala index 74bf9c5..1dbe386 100644 --- a/backend/src/main/scala/org/kys/athena/modules/RiotApiModule.scala +++ b/backend/src/main/scala/org/kys/athena/modules/RiotApiModule.scala @@ -1,7 +1,6 @@ package org.kys.athena.modules import io.circe.parser.parse -import org.kys.athena.data.SummonerMatchHistory import org.kys.athena.http.models.current.InGameSummoner import org.kys.athena.riot.api.dto.`match`.Match import org.kys.athena.riot.api.dto.common.{GameQueueTypeEnum, Platform} @@ -24,6 +23,8 @@ import zio.duration._ import scala.reflect.ClassTag +// Using the macro here because there are a lot of methods and therefore a lot of boilerplate. +// The end result of the macro is the same as in other modules. @accessible object RiotApiModule { type RiotApiClient = Has[RiotApiModule.Service] @@ -47,18 +48,13 @@ object RiotApiModule { platform: Platform) (implicit reqId: String): IO[RiotApiError, List[Match]] - def matchHistoryByInGameSummonerSet(inGameSummonerSet: Set[InGameSummoner], - gamesQueryCount: Int, queues: Set[GameQueueTypeEnum] = Set(), - platform: Platform) - (implicit reqId: String): IO[RiotApiError, Set[SummonerMatchHistory]] - def inGameSummonerByParticipant(participant: CurrentGameParticipant, platform: Platform) (implicit reqId: String): IO[RiotApiError, InGameSummoner] } val live = { - ZLayer.fromServices[ConfigModule.Service, SttpClient.Service, CacheModule.Service, RateLimiter, + ZLayer.fromServices[ConfigModule, SttpClient.Service, CacheModule, RateLimiter, Clock.Service, Service] { (config, backend, cacheController, regionalRateLimiter, clock) => new Service { @@ -205,19 +201,6 @@ object RiotApiModule { } } - // Returns hydrated match history for each summoner (last `gamesQueryCount` games) - def matchHistoryByInGameSummonerSet(inGameSummonerSet: Set[InGameSummoner], - gamesQueryCount: Int, - queues: Set[GameQueueTypeEnum] = Set(), - platform: Platform)(implicit reqId: String) - : IO[RiotApiError, Set[SummonerMatchHistory]] = { - ZIO.foreachPar(inGameSummonerSet.toList) { inGameSummoner => - matchHistoryBySummonerId(inGameSummoner.summonerId, gamesQueryCount, queues, platform).map { history => - SummonerMatchHistory(inGameSummoner, history) - } - }.map(_.toSet) - } - // Groups `Summoner`, `League`, and `CurrentGameParticipant` def inGameSummonerByParticipant(participant: CurrentGameParticipant, platform: Platform) diff --git a/build.sbt b/build.sbt index 924b157..b59fb0d 100644 --- a/build.sbt +++ b/build.sbt @@ -8,15 +8,18 @@ scalaVersion in ThisBuild := "2.13.4" // Projects lazy val global = project .in(file(".")) - .settings(settings) + .settings(settings, bloopGenerate in Test := None) .disablePlugins(AssemblyPlugin) - .aggregate(common.jvm, common.js, backend, frontend) + .aggregate(common.jvm, common.js, backend, frontend, macroSub) lazy val common = crossProject(JSPlatform, JVMPlatform) .crossType(CrossType.Pure) .settings(name := "common", settings, libraryDependencies ++= dependencies.common.value) + .jsSettings( + bloopGenerate in Test := None + ) .disablePlugins(AssemblyPlugin) lazy val backend = project @@ -27,14 +30,29 @@ lazy val backend = project javaOptions in Compile ++= Seq("-Xss8M")) .dependsOn(common.jvm) + lazy val frontend = project .settings(name := "frontend", settings, libraryDependencies ++= dependencies.js.value, - scalaJSUseMainModuleInitializer := true) + scalaJSUseMainModuleInitializer := true, + // include the macro classes and resources in the main js + Compile / packageBin / mappings ++= (macroSub / Compile / packageBin / mappings).value, + // include the macro sources in the main source js + Compile / packageSrc / mappings ++= (macroSub / Compile / packageSrc / mappings).value, + bloopGenerate in Test := None) .disablePlugins(AssemblyPlugin) .enablePlugins(ScalaJSPlugin) .dependsOn(common.js) + .dependsOn(macroSub) + +lazy val macroSub = (project in file("macro")) + .settings(name := "macro", + commonSettings, + libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value, + bloopGenerate in Test := None) + .disablePlugins(AssemblyPlugin) + .enablePlugins(ScalaJSPlugin) // Settings lazy val compilerOptions = Seq("-unchecked", "-feature", "-deprecation", "-Wunused:imports", "-Ymacro-annotations", @@ -43,7 +61,10 @@ lazy val compilerOptions = Seq("-unchecked", "-feature", "-deprecation", "-Wunus lazy val commonSettings = Seq(scalacOptions ++= compilerOptions, resolvers += "jitpack" at "https://jitpack.io") lazy val wartremoverSettings = Seq(wartremoverWarnings in(Compile, compile) ++= Warts.unsafe.filterNot { w => - w == Wart.Any || w == Wart.Nothing || w == Wart.DefaultArguments || w == Wart.StringPlusAny || + w == Wart.Any || + w == Wart.Nothing || + w == Wart.DefaultArguments || + w == Wart.StringPlusAny || w == Wart.NonUnitStatements }) @@ -76,7 +97,7 @@ lazy val dependencies = new { val laminarVersion = "0.11.0" val airstreamVersion = "0.11.1" - val zioVersion = "1.0.4" + val zioVersion = "1.0.4-2" val zioCatsVersion = "2.2.0.1" val tapirVersion = "0.17.9" @@ -117,6 +138,7 @@ lazy val dependencies = new { val js = Def.setting(common.value ++ Seq( "com.raquo" %%% "laminar" % laminarVersion, "com.raquo" %%% "airstream" % airstreamVersion, - "com.raquo" %%% "waypoint" % "0.2.0" + "com.raquo" %%% "waypoint" % "0.2.0", + "com.softwaremill.sttp.tapir" %%% "tapir-sttp-client" % tapirVersion )) } diff --git a/common/src/main/scala/org/kys/athena/http/models/current/RankedLeague.scala b/common/src/main/scala/org/kys/athena/http/models/common/RankedLeague.scala similarity index 95% rename from common/src/main/scala/org/kys/athena/http/models/current/RankedLeague.scala rename to common/src/main/scala/org/kys/athena/http/models/common/RankedLeague.scala index 983f004..08d1f94 100644 --- a/common/src/main/scala/org/kys/athena/http/models/current/RankedLeague.scala +++ b/common/src/main/scala/org/kys/athena/http/models/common/RankedLeague.scala @@ -1,4 +1,4 @@ -package org.kys.athena.http.models.current +package org.kys.athena.http.models.common import org.kys.athena.riot.api.dto.league.{League, MiniSeries, RankedQueueTypeEnum, TierEnum} diff --git a/common/src/main/scala/org/kys/athena/http/models/current/InGameSummoner.scala b/common/src/main/scala/org/kys/athena/http/models/current/InGameSummoner.scala index bbd196d..d751c42 100644 --- a/common/src/main/scala/org/kys/athena/http/models/current/InGameSummoner.scala +++ b/common/src/main/scala/org/kys/athena/http/models/current/InGameSummoner.scala @@ -1,5 +1,6 @@ package org.kys.athena.http.models.current +import org.kys.athena.http.models.common.RankedLeague import org.kys.athena.http.models.current import org.kys.athena.riot.api.dto.currentgameinfo.CurrentGameParticipant import org.kys.athena.riot.api.dto.league.League diff --git a/common/src/main/scala/org/kys/athena/http/models/pregame/PregameResponse.scala b/common/src/main/scala/org/kys/athena/http/models/pregame/PregameResponse.scala new file mode 100644 index 0000000..421bfc9 --- /dev/null +++ b/common/src/main/scala/org/kys/athena/http/models/pregame/PregameResponse.scala @@ -0,0 +1,7 @@ +package org.kys.athena.http.models.pregame + +import java.util.UUID + + +final case class PregameResponse(summoners: Set[PregameSummoner], groupUuid: Option[UUID] = None) + diff --git a/common/src/main/scala/org/kys/athena/http/models/pregame/PregameSummoner.scala b/common/src/main/scala/org/kys/athena/http/models/pregame/PregameSummoner.scala new file mode 100644 index 0000000..cb9579b --- /dev/null +++ b/common/src/main/scala/org/kys/athena/http/models/pregame/PregameSummoner.scala @@ -0,0 +1,9 @@ +package org.kys.athena.http.models.pregame + +import org.kys.athena.http.models.common.RankedLeague + + +final case class PregameSummoner(name: String, + summonerId: String, + summonerLevel: Long, + rankedLeagues: List[RankedLeague]) \ No newline at end of file diff --git a/common/src/main/scala/org/kys/athena/http/routes/Endpoints.scala b/common/src/main/scala/org/kys/athena/http/routes/Endpoints.scala index 02f6bb0..ea0ab1c 100644 --- a/common/src/main/scala/org/kys/athena/http/routes/Endpoints.scala +++ b/common/src/main/scala/org/kys/athena/http/routes/Endpoints.scala @@ -6,7 +6,8 @@ import sttp.tapir._ import sttp.tapir.codec.enumeratum._ import io.circe.generic.auto._ import org.kys.athena.http.errors.{BackendApiError, BadRequestError, InternalServerError, NotFoundError} -import org.kys.athena.http.models.premade.PremadeResponse +import org.kys.athena.http.models.pregame.PregameResponse +import org.kys.athena.http.models.premade.{PlayerGroup, PremadeResponse} import org.kys.athena.riot.api.dto.league.{RankedQueueTypeEnum, TierEnum} import sttp.tapir.json.circe._ import sttp.tapir.generic.auto._ @@ -14,7 +15,7 @@ import sttp.model.StatusCode import java.util.UUID -trait Endpoints { +object Endpoints { private implicit val schemaForPlatform: Schema[Platform] = Schema.string private implicit val schemaForGQTE : Schema[GameQueueTypeEnum] = Schema.string private implicit val schemaForPE : Schema[PositionEnum] = Schema.string @@ -41,7 +42,7 @@ trait Endpoints { .errorOut(defaultErrorCodes) .summary("Endpoint to get current game info for a player") - val groupsByName: Endpoint[(Platform, String, Option[String]), BackendApiError, PremadeResponse, Any] = + val currentGameGroupsByName: Endpoint[(Platform, String, Option[String]), BackendApiError, PremadeResponse, Any] = endpoint.get .in("current" / "by-summoner-name") .in(path[Platform]("platformId") / path[String]("summonerName")) @@ -51,7 +52,7 @@ trait Endpoints { .errorOut(defaultErrorCodes) .summary("Endpoint to get premades in a game for a player") - val groupsByUUID: Endpoint[(UUID, Option[String]), BackendApiError, PremadeResponse, Any] = + val currentGameGroupsByUUID: Endpoint[(UUID, Option[String]), BackendApiError, PremadeResponse, Any] = endpoint.get .in("current" / "by-uuid") .in(path[UUID]("uuid")) @@ -60,4 +61,38 @@ trait Endpoints { .out(jsonBody[PremadeResponse]) .errorOut(defaultErrorCodes) .summary("Endpoint to get premades in a game for a player by UUID") + + val pregameByName: Endpoint[ + (Platform, Set[String], Option[Boolean], Option[String]), + BackendApiError, PregameResponse, Any] = + endpoint.get + .in("pregame" / "by-summoner-name") + .in(path[Platform]("platformId")) + .in(query[Set[String]]("summoner")) + .in(query[Option[Boolean]]("fetchGroups")) + .in(header[Option[String]]("X-Request-ID")) + .out(jsonBody[PregameResponse]) + .errorOut(defaultErrorCodes) + .summary("Endpoint to get pregame info for a set of players") + + val pregameGroupsByName: Endpoint[(Platform, Set[String], Option[String]), BackendApiError, Set[PlayerGroup], Any] = + endpoint.get + .in("pregame" / "by-summoner-name") + .in(path[Platform]("platformId")) + .in("groups") + .in(query[Set[String]]("summoner")) + .in(header[Option[String]]("X-Request-ID")) + .out(jsonBody[Set[PlayerGroup]]) + .errorOut(defaultErrorCodes) + .summary("Endpoint to get premades in a lobby of players") + + val pregameGameGroupsByUUID: Endpoint[(UUID, Option[String]), BackendApiError, Set[PlayerGroup], Any] = + endpoint.get + .in("pregame" / "by-uuid") + .in(path[UUID]("uuid")) + .in("groups") + .in(header[Option[String]]("X-Request-ID")) + .out(jsonBody[Set[PlayerGroup]]) + .errorOut(defaultErrorCodes) + .summary("Endpoint to get premades in a lobby by UUID") } diff --git a/common/src/main/scala/org/kys/athena/util/exceptions/BadRequestException.scala b/common/src/main/scala/org/kys/athena/util/exceptions/BadRequestException.scala deleted file mode 100644 index 63f4eff..0000000 --- a/common/src/main/scala/org/kys/athena/util/exceptions/BadRequestException.scala +++ /dev/null @@ -1,6 +0,0 @@ -package org.kys.athena.util.exceptions - -import scala.util.control.NoStackTrace - - -final case class BadRequestException(reason: String) extends Throwable with NoStackTrace diff --git a/common/src/main/scala/org/kys/athena/util/exceptions/InconsistentAPIException.scala b/common/src/main/scala/org/kys/athena/util/exceptions/InconsistentAPIException.scala deleted file mode 100644 index 4ee480f..0000000 --- a/common/src/main/scala/org/kys/athena/util/exceptions/InconsistentAPIException.scala +++ /dev/null @@ -1,6 +0,0 @@ -package org.kys.athena.util.exceptions - -import scala.util.control.NoStackTrace - - -final case class InconsistentAPIException(dtoName: String, errorDesc: String) extends Throwable with NoStackTrace diff --git a/common/src/main/scala/org/kys/athena/util/exceptions/NotFoundException.scala b/common/src/main/scala/org/kys/athena/util/exceptions/NotFoundException.scala deleted file mode 100644 index 55f1ca5..0000000 --- a/common/src/main/scala/org/kys/athena/util/exceptions/NotFoundException.scala +++ /dev/null @@ -1,6 +0,0 @@ -package org.kys.athena.util.exceptions - -import scala.util.control.NoStackTrace - - -final case class NotFoundException(reason: String) extends Throwable with NoStackTrace diff --git a/common/src/main/scala/org/kys/athena/util/exceptions/RiotException.scala b/common/src/main/scala/org/kys/athena/util/exceptions/RiotException.scala deleted file mode 100644 index f403a5c..0000000 --- a/common/src/main/scala/org/kys/athena/util/exceptions/RiotException.scala +++ /dev/null @@ -1,6 +0,0 @@ -package org.kys.athena.util.exceptions - -import scala.util.control.NoStackTrace - - -final case class RiotException(statusCode: Int, errorMessage: Option[String]) extends Throwable with NoStackTrace diff --git a/frontend/.env b/frontend/.env index 07037e5..d638455 100644 --- a/frontend/.env +++ b/frontend/.env @@ -7,4 +7,5 @@ IMGPROXY_BASE_URL= FRONTEND_URL= USE_FAKE_DATA=false ANALYTICS_SCRIPT_URL= -ANALYTICS_WEBSITE_ID= \ No newline at end of file +ANALYTICS_WEBSITE_ID= +DISCORD_INVITE_URL=https://discord.gg/VMTW7MfnRN \ No newline at end of file diff --git a/frontend/src/main/resources/public/icons/academic-cap.svg b/frontend/src/main/resources/icons/academic-cap.svg similarity index 100% rename from frontend/src/main/resources/public/icons/academic-cap.svg rename to frontend/src/main/resources/icons/academic-cap.svg diff --git a/frontend/src/main/resources/public/icons/adjustments.svg b/frontend/src/main/resources/icons/adjustments.svg similarity index 100% rename from frontend/src/main/resources/public/icons/adjustments.svg rename to frontend/src/main/resources/icons/adjustments.svg diff --git a/frontend/src/main/resources/public/icons/annotation.svg b/frontend/src/main/resources/icons/annotation.svg similarity index 100% rename from frontend/src/main/resources/public/icons/annotation.svg rename to frontend/src/main/resources/icons/annotation.svg diff --git a/frontend/src/main/resources/public/icons/archive.svg b/frontend/src/main/resources/icons/archive.svg similarity index 100% rename from frontend/src/main/resources/public/icons/archive.svg rename to frontend/src/main/resources/icons/archive.svg diff --git a/frontend/src/main/resources/public/icons/arrow-circle-down.svg b/frontend/src/main/resources/icons/arrow-circle-down.svg similarity index 100% rename from frontend/src/main/resources/public/icons/arrow-circle-down.svg rename to frontend/src/main/resources/icons/arrow-circle-down.svg diff --git a/frontend/src/main/resources/public/icons/arrow-circle-left.svg b/frontend/src/main/resources/icons/arrow-circle-left.svg similarity index 100% rename from frontend/src/main/resources/public/icons/arrow-circle-left.svg rename to frontend/src/main/resources/icons/arrow-circle-left.svg diff --git a/frontend/src/main/resources/public/icons/arrow-circle-right.svg b/frontend/src/main/resources/icons/arrow-circle-right.svg similarity index 100% rename from frontend/src/main/resources/public/icons/arrow-circle-right.svg rename to frontend/src/main/resources/icons/arrow-circle-right.svg diff --git a/frontend/src/main/resources/public/icons/arrow-circle-up.svg b/frontend/src/main/resources/icons/arrow-circle-up.svg similarity index 100% rename from frontend/src/main/resources/public/icons/arrow-circle-up.svg rename to frontend/src/main/resources/icons/arrow-circle-up.svg diff --git a/frontend/src/main/resources/public/icons/arrow-down.svg b/frontend/src/main/resources/icons/arrow-down.svg similarity index 100% rename from frontend/src/main/resources/public/icons/arrow-down.svg rename to frontend/src/main/resources/icons/arrow-down.svg diff --git a/frontend/src/main/resources/public/icons/arrow-left.svg b/frontend/src/main/resources/icons/arrow-left.svg similarity index 100% rename from frontend/src/main/resources/public/icons/arrow-left.svg rename to frontend/src/main/resources/icons/arrow-left.svg diff --git a/frontend/src/main/resources/public/icons/arrow-narrow-down.svg b/frontend/src/main/resources/icons/arrow-narrow-down.svg similarity index 100% rename from frontend/src/main/resources/public/icons/arrow-narrow-down.svg rename to frontend/src/main/resources/icons/arrow-narrow-down.svg diff --git a/frontend/src/main/resources/public/icons/arrow-narrow-left.svg b/frontend/src/main/resources/icons/arrow-narrow-left.svg similarity index 100% rename from frontend/src/main/resources/public/icons/arrow-narrow-left.svg rename to frontend/src/main/resources/icons/arrow-narrow-left.svg diff --git a/frontend/src/main/resources/public/icons/arrow-narrow-right.svg b/frontend/src/main/resources/icons/arrow-narrow-right.svg similarity index 100% rename from frontend/src/main/resources/public/icons/arrow-narrow-right.svg rename to frontend/src/main/resources/icons/arrow-narrow-right.svg diff --git a/frontend/src/main/resources/public/icons/arrow-narrow-up.svg b/frontend/src/main/resources/icons/arrow-narrow-up.svg similarity index 100% rename from frontend/src/main/resources/public/icons/arrow-narrow-up.svg rename to frontend/src/main/resources/icons/arrow-narrow-up.svg diff --git a/frontend/src/main/resources/public/icons/arrow-right.svg b/frontend/src/main/resources/icons/arrow-right.svg similarity index 100% rename from frontend/src/main/resources/public/icons/arrow-right.svg rename to frontend/src/main/resources/icons/arrow-right.svg diff --git a/frontend/src/main/resources/public/icons/arrow-up.svg b/frontend/src/main/resources/icons/arrow-up.svg similarity index 100% rename from frontend/src/main/resources/public/icons/arrow-up.svg rename to frontend/src/main/resources/icons/arrow-up.svg diff --git a/frontend/src/main/resources/public/icons/arrows-expand.svg b/frontend/src/main/resources/icons/arrows-expand.svg similarity index 100% rename from frontend/src/main/resources/public/icons/arrows-expand.svg rename to frontend/src/main/resources/icons/arrows-expand.svg diff --git a/frontend/src/main/resources/public/icons/at-symbol.svg b/frontend/src/main/resources/icons/at-symbol.svg similarity index 100% rename from frontend/src/main/resources/public/icons/at-symbol.svg rename to frontend/src/main/resources/icons/at-symbol.svg diff --git a/frontend/src/main/resources/public/icons/backspace.svg b/frontend/src/main/resources/icons/backspace.svg similarity index 100% rename from frontend/src/main/resources/public/icons/backspace.svg rename to frontend/src/main/resources/icons/backspace.svg diff --git a/frontend/src/main/resources/public/icons/badge-check.svg b/frontend/src/main/resources/icons/badge-check.svg similarity index 100% rename from frontend/src/main/resources/public/icons/badge-check.svg rename to frontend/src/main/resources/icons/badge-check.svg diff --git a/frontend/src/main/resources/public/icons/ban.svg b/frontend/src/main/resources/icons/ban.svg similarity index 100% rename from frontend/src/main/resources/public/icons/ban.svg rename to frontend/src/main/resources/icons/ban.svg diff --git a/frontend/src/main/resources/public/icons/beaker.svg b/frontend/src/main/resources/icons/beaker.svg similarity index 100% rename from frontend/src/main/resources/public/icons/beaker.svg rename to frontend/src/main/resources/icons/beaker.svg diff --git a/frontend/src/main/resources/public/icons/bell.svg b/frontend/src/main/resources/icons/bell.svg similarity index 100% rename from frontend/src/main/resources/public/icons/bell.svg rename to frontend/src/main/resources/icons/bell.svg diff --git a/frontend/src/main/resources/public/icons/book-open.svg b/frontend/src/main/resources/icons/book-open.svg similarity index 100% rename from frontend/src/main/resources/public/icons/book-open.svg rename to frontend/src/main/resources/icons/book-open.svg diff --git a/frontend/src/main/resources/public/icons/bookmark-alt.svg b/frontend/src/main/resources/icons/bookmark-alt.svg similarity index 100% rename from frontend/src/main/resources/public/icons/bookmark-alt.svg rename to frontend/src/main/resources/icons/bookmark-alt.svg diff --git a/frontend/src/main/resources/public/icons/bookmark.svg b/frontend/src/main/resources/icons/bookmark.svg similarity index 100% rename from frontend/src/main/resources/public/icons/bookmark.svg rename to frontend/src/main/resources/icons/bookmark.svg diff --git a/frontend/src/main/resources/public/icons/briefcase.svg b/frontend/src/main/resources/icons/briefcase.svg similarity index 100% rename from frontend/src/main/resources/public/icons/briefcase.svg rename to frontend/src/main/resources/icons/briefcase.svg diff --git a/frontend/src/main/resources/public/icons/cake.svg b/frontend/src/main/resources/icons/cake.svg similarity index 100% rename from frontend/src/main/resources/public/icons/cake.svg rename to frontend/src/main/resources/icons/cake.svg diff --git a/frontend/src/main/resources/public/icons/calculator.svg b/frontend/src/main/resources/icons/calculator.svg similarity index 100% rename from frontend/src/main/resources/public/icons/calculator.svg rename to frontend/src/main/resources/icons/calculator.svg diff --git a/frontend/src/main/resources/public/icons/calendar.svg b/frontend/src/main/resources/icons/calendar.svg similarity index 100% rename from frontend/src/main/resources/public/icons/calendar.svg rename to frontend/src/main/resources/icons/calendar.svg diff --git a/frontend/src/main/resources/public/icons/camera.svg b/frontend/src/main/resources/icons/camera.svg similarity index 100% rename from frontend/src/main/resources/public/icons/camera.svg rename to frontend/src/main/resources/icons/camera.svg diff --git a/frontend/src/main/resources/public/icons/cash.svg b/frontend/src/main/resources/icons/cash.svg similarity index 100% rename from frontend/src/main/resources/public/icons/cash.svg rename to frontend/src/main/resources/icons/cash.svg diff --git a/frontend/src/main/resources/public/icons/chart-bar.svg b/frontend/src/main/resources/icons/chart-bar.svg similarity index 100% rename from frontend/src/main/resources/public/icons/chart-bar.svg rename to frontend/src/main/resources/icons/chart-bar.svg diff --git a/frontend/src/main/resources/public/icons/chart-pie.svg b/frontend/src/main/resources/icons/chart-pie.svg similarity index 100% rename from frontend/src/main/resources/public/icons/chart-pie.svg rename to frontend/src/main/resources/icons/chart-pie.svg diff --git a/frontend/src/main/resources/public/icons/chart-square-bar.svg b/frontend/src/main/resources/icons/chart-square-bar.svg similarity index 100% rename from frontend/src/main/resources/public/icons/chart-square-bar.svg rename to frontend/src/main/resources/icons/chart-square-bar.svg diff --git a/frontend/src/main/resources/public/icons/chat-alt-2.svg b/frontend/src/main/resources/icons/chat-alt-2.svg similarity index 100% rename from frontend/src/main/resources/public/icons/chat-alt-2.svg rename to frontend/src/main/resources/icons/chat-alt-2.svg diff --git a/frontend/src/main/resources/public/icons/chat-alt.svg b/frontend/src/main/resources/icons/chat-alt.svg similarity index 100% rename from frontend/src/main/resources/public/icons/chat-alt.svg rename to frontend/src/main/resources/icons/chat-alt.svg diff --git a/frontend/src/main/resources/public/icons/chat.svg b/frontend/src/main/resources/icons/chat.svg similarity index 100% rename from frontend/src/main/resources/public/icons/chat.svg rename to frontend/src/main/resources/icons/chat.svg diff --git a/frontend/src/main/resources/public/icons/check-circle.svg b/frontend/src/main/resources/icons/check-circle.svg similarity index 100% rename from frontend/src/main/resources/public/icons/check-circle.svg rename to frontend/src/main/resources/icons/check-circle.svg diff --git a/frontend/src/main/resources/public/icons/check.svg b/frontend/src/main/resources/icons/check.svg similarity index 100% rename from frontend/src/main/resources/public/icons/check.svg rename to frontend/src/main/resources/icons/check.svg diff --git a/frontend/src/main/resources/public/icons/chevron-double-down.svg b/frontend/src/main/resources/icons/chevron-double-down.svg similarity index 100% rename from frontend/src/main/resources/public/icons/chevron-double-down.svg rename to frontend/src/main/resources/icons/chevron-double-down.svg diff --git a/frontend/src/main/resources/public/icons/chevron-double-left.svg b/frontend/src/main/resources/icons/chevron-double-left.svg similarity index 100% rename from frontend/src/main/resources/public/icons/chevron-double-left.svg rename to frontend/src/main/resources/icons/chevron-double-left.svg diff --git a/frontend/src/main/resources/public/icons/chevron-double-right.svg b/frontend/src/main/resources/icons/chevron-double-right.svg similarity index 100% rename from frontend/src/main/resources/public/icons/chevron-double-right.svg rename to frontend/src/main/resources/icons/chevron-double-right.svg diff --git a/frontend/src/main/resources/public/icons/chevron-double-up.svg b/frontend/src/main/resources/icons/chevron-double-up.svg similarity index 100% rename from frontend/src/main/resources/public/icons/chevron-double-up.svg rename to frontend/src/main/resources/icons/chevron-double-up.svg diff --git a/frontend/src/main/resources/public/icons/chevron-down.svg b/frontend/src/main/resources/icons/chevron-down.svg similarity index 100% rename from frontend/src/main/resources/public/icons/chevron-down.svg rename to frontend/src/main/resources/icons/chevron-down.svg diff --git a/frontend/src/main/resources/public/icons/chevron-left.svg b/frontend/src/main/resources/icons/chevron-left.svg similarity index 100% rename from frontend/src/main/resources/public/icons/chevron-left.svg rename to frontend/src/main/resources/icons/chevron-left.svg diff --git a/frontend/src/main/resources/public/icons/chevron-right.svg b/frontend/src/main/resources/icons/chevron-right.svg similarity index 100% rename from frontend/src/main/resources/public/icons/chevron-right.svg rename to frontend/src/main/resources/icons/chevron-right.svg diff --git a/frontend/src/main/resources/public/icons/chevron-up.svg b/frontend/src/main/resources/icons/chevron-up.svg similarity index 100% rename from frontend/src/main/resources/public/icons/chevron-up.svg rename to frontend/src/main/resources/icons/chevron-up.svg diff --git a/frontend/src/main/resources/public/icons/chip.svg b/frontend/src/main/resources/icons/chip.svg similarity index 100% rename from frontend/src/main/resources/public/icons/chip.svg rename to frontend/src/main/resources/icons/chip.svg diff --git a/frontend/src/main/resources/public/icons/clipboard-check.svg b/frontend/src/main/resources/icons/clipboard-check.svg similarity index 100% rename from frontend/src/main/resources/public/icons/clipboard-check.svg rename to frontend/src/main/resources/icons/clipboard-check.svg diff --git a/frontend/src/main/resources/public/icons/clipboard-copy.svg b/frontend/src/main/resources/icons/clipboard-copy.svg similarity index 100% rename from frontend/src/main/resources/public/icons/clipboard-copy.svg rename to frontend/src/main/resources/icons/clipboard-copy.svg diff --git a/frontend/src/main/resources/public/icons/clipboard-list.svg b/frontend/src/main/resources/icons/clipboard-list.svg similarity index 100% rename from frontend/src/main/resources/public/icons/clipboard-list.svg rename to frontend/src/main/resources/icons/clipboard-list.svg diff --git a/frontend/src/main/resources/public/icons/clipboard.svg b/frontend/src/main/resources/icons/clipboard.svg similarity index 100% rename from frontend/src/main/resources/public/icons/clipboard.svg rename to frontend/src/main/resources/icons/clipboard.svg diff --git a/frontend/src/main/resources/public/icons/clock.svg b/frontend/src/main/resources/icons/clock.svg similarity index 100% rename from frontend/src/main/resources/public/icons/clock.svg rename to frontend/src/main/resources/icons/clock.svg diff --git a/frontend/src/main/resources/public/icons/cloud-download.svg b/frontend/src/main/resources/icons/cloud-download.svg similarity index 100% rename from frontend/src/main/resources/public/icons/cloud-download.svg rename to frontend/src/main/resources/icons/cloud-download.svg diff --git a/frontend/src/main/resources/public/icons/cloud-upload.svg b/frontend/src/main/resources/icons/cloud-upload.svg similarity index 100% rename from frontend/src/main/resources/public/icons/cloud-upload.svg rename to frontend/src/main/resources/icons/cloud-upload.svg diff --git a/frontend/src/main/resources/public/icons/cloud.svg b/frontend/src/main/resources/icons/cloud.svg similarity index 100% rename from frontend/src/main/resources/public/icons/cloud.svg rename to frontend/src/main/resources/icons/cloud.svg diff --git a/frontend/src/main/resources/public/icons/code.svg b/frontend/src/main/resources/icons/code.svg similarity index 100% rename from frontend/src/main/resources/public/icons/code.svg rename to frontend/src/main/resources/icons/code.svg diff --git a/frontend/src/main/resources/public/icons/cog.svg b/frontend/src/main/resources/icons/cog.svg similarity index 100% rename from frontend/src/main/resources/public/icons/cog.svg rename to frontend/src/main/resources/icons/cog.svg diff --git a/frontend/src/main/resources/public/icons/collection.svg b/frontend/src/main/resources/icons/collection.svg similarity index 100% rename from frontend/src/main/resources/public/icons/collection.svg rename to frontend/src/main/resources/icons/collection.svg diff --git a/frontend/src/main/resources/public/icons/color-swatch.svg b/frontend/src/main/resources/icons/color-swatch.svg similarity index 100% rename from frontend/src/main/resources/public/icons/color-swatch.svg rename to frontend/src/main/resources/icons/color-swatch.svg diff --git a/frontend/src/main/resources/public/icons/credit-card.svg b/frontend/src/main/resources/icons/credit-card.svg similarity index 100% rename from frontend/src/main/resources/public/icons/credit-card.svg rename to frontend/src/main/resources/icons/credit-card.svg diff --git a/frontend/src/main/resources/public/icons/cube-transparent.svg b/frontend/src/main/resources/icons/cube-transparent.svg similarity index 100% rename from frontend/src/main/resources/public/icons/cube-transparent.svg rename to frontend/src/main/resources/icons/cube-transparent.svg diff --git a/frontend/src/main/resources/public/icons/cube.svg b/frontend/src/main/resources/icons/cube.svg similarity index 100% rename from frontend/src/main/resources/public/icons/cube.svg rename to frontend/src/main/resources/icons/cube.svg diff --git a/frontend/src/main/resources/public/icons/currency-bangladeshi.svg b/frontend/src/main/resources/icons/currency-bangladeshi.svg similarity index 100% rename from frontend/src/main/resources/public/icons/currency-bangladeshi.svg rename to frontend/src/main/resources/icons/currency-bangladeshi.svg diff --git a/frontend/src/main/resources/public/icons/currency-dollar.svg b/frontend/src/main/resources/icons/currency-dollar.svg similarity index 100% rename from frontend/src/main/resources/public/icons/currency-dollar.svg rename to frontend/src/main/resources/icons/currency-dollar.svg diff --git a/frontend/src/main/resources/public/icons/currency-euro.svg b/frontend/src/main/resources/icons/currency-euro.svg similarity index 100% rename from frontend/src/main/resources/public/icons/currency-euro.svg rename to frontend/src/main/resources/icons/currency-euro.svg diff --git a/frontend/src/main/resources/public/icons/currency-pound.svg b/frontend/src/main/resources/icons/currency-pound.svg similarity index 100% rename from frontend/src/main/resources/public/icons/currency-pound.svg rename to frontend/src/main/resources/icons/currency-pound.svg diff --git a/frontend/src/main/resources/public/icons/currency-rupee.svg b/frontend/src/main/resources/icons/currency-rupee.svg similarity index 100% rename from frontend/src/main/resources/public/icons/currency-rupee.svg rename to frontend/src/main/resources/icons/currency-rupee.svg diff --git a/frontend/src/main/resources/public/icons/currency-yen.svg b/frontend/src/main/resources/icons/currency-yen.svg similarity index 100% rename from frontend/src/main/resources/public/icons/currency-yen.svg rename to frontend/src/main/resources/icons/currency-yen.svg diff --git a/frontend/src/main/resources/public/icons/cursor-click.svg b/frontend/src/main/resources/icons/cursor-click.svg similarity index 100% rename from frontend/src/main/resources/public/icons/cursor-click.svg rename to frontend/src/main/resources/icons/cursor-click.svg diff --git a/frontend/src/main/resources/public/icons/database.svg b/frontend/src/main/resources/icons/database.svg similarity index 100% rename from frontend/src/main/resources/public/icons/database.svg rename to frontend/src/main/resources/icons/database.svg diff --git a/frontend/src/main/resources/public/icons/desktop-computer.svg b/frontend/src/main/resources/icons/desktop-computer.svg similarity index 100% rename from frontend/src/main/resources/public/icons/desktop-computer.svg rename to frontend/src/main/resources/icons/desktop-computer.svg diff --git a/frontend/src/main/resources/public/icons/device-mobile.svg b/frontend/src/main/resources/icons/device-mobile.svg similarity index 100% rename from frontend/src/main/resources/public/icons/device-mobile.svg rename to frontend/src/main/resources/icons/device-mobile.svg diff --git a/frontend/src/main/resources/public/icons/device-tablet.svg b/frontend/src/main/resources/icons/device-tablet.svg similarity index 100% rename from frontend/src/main/resources/public/icons/device-tablet.svg rename to frontend/src/main/resources/icons/device-tablet.svg diff --git a/frontend/src/main/resources/public/icons/document-add.svg b/frontend/src/main/resources/icons/document-add.svg similarity index 100% rename from frontend/src/main/resources/public/icons/document-add.svg rename to frontend/src/main/resources/icons/document-add.svg diff --git a/frontend/src/main/resources/public/icons/document-download.svg b/frontend/src/main/resources/icons/document-download.svg similarity index 100% rename from frontend/src/main/resources/public/icons/document-download.svg rename to frontend/src/main/resources/icons/document-download.svg diff --git a/frontend/src/main/resources/public/icons/document-duplicate.svg b/frontend/src/main/resources/icons/document-duplicate.svg similarity index 100% rename from frontend/src/main/resources/public/icons/document-duplicate.svg rename to frontend/src/main/resources/icons/document-duplicate.svg diff --git a/frontend/src/main/resources/public/icons/document-remove.svg b/frontend/src/main/resources/icons/document-remove.svg similarity index 100% rename from frontend/src/main/resources/public/icons/document-remove.svg rename to frontend/src/main/resources/icons/document-remove.svg diff --git a/frontend/src/main/resources/public/icons/document-report.svg b/frontend/src/main/resources/icons/document-report.svg similarity index 100% rename from frontend/src/main/resources/public/icons/document-report.svg rename to frontend/src/main/resources/icons/document-report.svg diff --git a/frontend/src/main/resources/public/icons/document-search.svg b/frontend/src/main/resources/icons/document-search.svg similarity index 100% rename from frontend/src/main/resources/public/icons/document-search.svg rename to frontend/src/main/resources/icons/document-search.svg diff --git a/frontend/src/main/resources/public/icons/document-text.svg b/frontend/src/main/resources/icons/document-text.svg similarity index 100% rename from frontend/src/main/resources/public/icons/document-text.svg rename to frontend/src/main/resources/icons/document-text.svg diff --git a/frontend/src/main/resources/public/icons/document.svg b/frontend/src/main/resources/icons/document.svg similarity index 100% rename from frontend/src/main/resources/public/icons/document.svg rename to frontend/src/main/resources/icons/document.svg diff --git a/frontend/src/main/resources/public/icons/dots-circle-horizontal.svg b/frontend/src/main/resources/icons/dots-circle-horizontal.svg similarity index 100% rename from frontend/src/main/resources/public/icons/dots-circle-horizontal.svg rename to frontend/src/main/resources/icons/dots-circle-horizontal.svg diff --git a/frontend/src/main/resources/public/icons/dots-horizontal.svg b/frontend/src/main/resources/icons/dots-horizontal.svg similarity index 100% rename from frontend/src/main/resources/public/icons/dots-horizontal.svg rename to frontend/src/main/resources/icons/dots-horizontal.svg diff --git a/frontend/src/main/resources/public/icons/dots-vertical.svg b/frontend/src/main/resources/icons/dots-vertical.svg similarity index 100% rename from frontend/src/main/resources/public/icons/dots-vertical.svg rename to frontend/src/main/resources/icons/dots-vertical.svg diff --git a/frontend/src/main/resources/public/icons/download.svg b/frontend/src/main/resources/icons/download.svg similarity index 100% rename from frontend/src/main/resources/public/icons/download.svg rename to frontend/src/main/resources/icons/download.svg diff --git a/frontend/src/main/resources/public/icons/duplicate.svg b/frontend/src/main/resources/icons/duplicate.svg similarity index 100% rename from frontend/src/main/resources/public/icons/duplicate.svg rename to frontend/src/main/resources/icons/duplicate.svg diff --git a/frontend/src/main/resources/public/icons/emoji-happy.svg b/frontend/src/main/resources/icons/emoji-happy.svg similarity index 100% rename from frontend/src/main/resources/public/icons/emoji-happy.svg rename to frontend/src/main/resources/icons/emoji-happy.svg diff --git a/frontend/src/main/resources/public/icons/emoji-sad.svg b/frontend/src/main/resources/icons/emoji-sad.svg similarity index 100% rename from frontend/src/main/resources/public/icons/emoji-sad.svg rename to frontend/src/main/resources/icons/emoji-sad.svg diff --git a/frontend/src/main/resources/public/icons/exclamation-circle.svg b/frontend/src/main/resources/icons/exclamation-circle.svg similarity index 100% rename from frontend/src/main/resources/public/icons/exclamation-circle.svg rename to frontend/src/main/resources/icons/exclamation-circle.svg diff --git a/frontend/src/main/resources/public/icons/exclamation.svg b/frontend/src/main/resources/icons/exclamation.svg similarity index 100% rename from frontend/src/main/resources/public/icons/exclamation.svg rename to frontend/src/main/resources/icons/exclamation.svg diff --git a/frontend/src/main/resources/public/icons/external-link.svg b/frontend/src/main/resources/icons/external-link.svg similarity index 100% rename from frontend/src/main/resources/public/icons/external-link.svg rename to frontend/src/main/resources/icons/external-link.svg diff --git a/frontend/src/main/resources/public/icons/eye-off.svg b/frontend/src/main/resources/icons/eye-off.svg similarity index 100% rename from frontend/src/main/resources/public/icons/eye-off.svg rename to frontend/src/main/resources/icons/eye-off.svg diff --git a/frontend/src/main/resources/public/icons/eye.svg b/frontend/src/main/resources/icons/eye.svg similarity index 100% rename from frontend/src/main/resources/public/icons/eye.svg rename to frontend/src/main/resources/icons/eye.svg diff --git a/frontend/src/main/resources/public/icons/fast-forward.svg b/frontend/src/main/resources/icons/fast-forward.svg similarity index 100% rename from frontend/src/main/resources/public/icons/fast-forward.svg rename to frontend/src/main/resources/icons/fast-forward.svg diff --git a/frontend/src/main/resources/public/icons/film.svg b/frontend/src/main/resources/icons/film.svg similarity index 100% rename from frontend/src/main/resources/public/icons/film.svg rename to frontend/src/main/resources/icons/film.svg diff --git a/frontend/src/main/resources/public/icons/filter.svg b/frontend/src/main/resources/icons/filter.svg similarity index 100% rename from frontend/src/main/resources/public/icons/filter.svg rename to frontend/src/main/resources/icons/filter.svg diff --git a/frontend/src/main/resources/public/icons/finger-print.svg b/frontend/src/main/resources/icons/finger-print.svg similarity index 100% rename from frontend/src/main/resources/public/icons/finger-print.svg rename to frontend/src/main/resources/icons/finger-print.svg diff --git a/frontend/src/main/resources/public/icons/fire.svg b/frontend/src/main/resources/icons/fire.svg similarity index 100% rename from frontend/src/main/resources/public/icons/fire.svg rename to frontend/src/main/resources/icons/fire.svg diff --git a/frontend/src/main/resources/public/icons/flag.svg b/frontend/src/main/resources/icons/flag.svg similarity index 100% rename from frontend/src/main/resources/public/icons/flag.svg rename to frontend/src/main/resources/icons/flag.svg diff --git a/frontend/src/main/resources/public/icons/folder-add.svg b/frontend/src/main/resources/icons/folder-add.svg similarity index 100% rename from frontend/src/main/resources/public/icons/folder-add.svg rename to frontend/src/main/resources/icons/folder-add.svg diff --git a/frontend/src/main/resources/public/icons/folder-download.svg b/frontend/src/main/resources/icons/folder-download.svg similarity index 100% rename from frontend/src/main/resources/public/icons/folder-download.svg rename to frontend/src/main/resources/icons/folder-download.svg diff --git a/frontend/src/main/resources/public/icons/folder-open.svg b/frontend/src/main/resources/icons/folder-open.svg similarity index 100% rename from frontend/src/main/resources/public/icons/folder-open.svg rename to frontend/src/main/resources/icons/folder-open.svg diff --git a/frontend/src/main/resources/public/icons/folder-remove.svg b/frontend/src/main/resources/icons/folder-remove.svg similarity index 100% rename from frontend/src/main/resources/public/icons/folder-remove.svg rename to frontend/src/main/resources/icons/folder-remove.svg diff --git a/frontend/src/main/resources/public/icons/folder.svg b/frontend/src/main/resources/icons/folder.svg similarity index 100% rename from frontend/src/main/resources/public/icons/folder.svg rename to frontend/src/main/resources/icons/folder.svg diff --git a/frontend/src/main/resources/public/icons/gift.svg b/frontend/src/main/resources/icons/gift.svg similarity index 100% rename from frontend/src/main/resources/public/icons/gift.svg rename to frontend/src/main/resources/icons/gift.svg diff --git a/frontend/src/main/resources/public/icons/globe-alt.svg b/frontend/src/main/resources/icons/globe-alt.svg similarity index 100% rename from frontend/src/main/resources/public/icons/globe-alt.svg rename to frontend/src/main/resources/icons/globe-alt.svg diff --git a/frontend/src/main/resources/public/icons/globe.svg b/frontend/src/main/resources/icons/globe.svg similarity index 100% rename from frontend/src/main/resources/public/icons/globe.svg rename to frontend/src/main/resources/icons/globe.svg diff --git a/frontend/src/main/resources/public/icons/hand.svg b/frontend/src/main/resources/icons/hand.svg similarity index 100% rename from frontend/src/main/resources/public/icons/hand.svg rename to frontend/src/main/resources/icons/hand.svg diff --git a/frontend/src/main/resources/public/icons/hashtag.svg b/frontend/src/main/resources/icons/hashtag.svg similarity index 100% rename from frontend/src/main/resources/public/icons/hashtag.svg rename to frontend/src/main/resources/icons/hashtag.svg diff --git a/frontend/src/main/resources/public/icons/heart.svg b/frontend/src/main/resources/icons/heart.svg similarity index 100% rename from frontend/src/main/resources/public/icons/heart.svg rename to frontend/src/main/resources/icons/heart.svg diff --git a/frontend/src/main/resources/public/icons/home.svg b/frontend/src/main/resources/icons/home.svg similarity index 100% rename from frontend/src/main/resources/public/icons/home.svg rename to frontend/src/main/resources/icons/home.svg diff --git a/frontend/src/main/resources/public/icons/identification.svg b/frontend/src/main/resources/icons/identification.svg similarity index 100% rename from frontend/src/main/resources/public/icons/identification.svg rename to frontend/src/main/resources/icons/identification.svg diff --git a/frontend/src/main/resources/public/icons/inbox-in.svg b/frontend/src/main/resources/icons/inbox-in.svg similarity index 100% rename from frontend/src/main/resources/public/icons/inbox-in.svg rename to frontend/src/main/resources/icons/inbox-in.svg diff --git a/frontend/src/main/resources/public/icons/inbox.svg b/frontend/src/main/resources/icons/inbox.svg similarity index 100% rename from frontend/src/main/resources/public/icons/inbox.svg rename to frontend/src/main/resources/icons/inbox.svg diff --git a/frontend/src/main/resources/public/icons/information-circle.svg b/frontend/src/main/resources/icons/information-circle.svg similarity index 100% rename from frontend/src/main/resources/public/icons/information-circle.svg rename to frontend/src/main/resources/icons/information-circle.svg diff --git a/frontend/src/main/resources/public/icons/key.svg b/frontend/src/main/resources/icons/key.svg similarity index 100% rename from frontend/src/main/resources/public/icons/key.svg rename to frontend/src/main/resources/icons/key.svg diff --git a/frontend/src/main/resources/public/icons/library.svg b/frontend/src/main/resources/icons/library.svg similarity index 100% rename from frontend/src/main/resources/public/icons/library.svg rename to frontend/src/main/resources/icons/library.svg diff --git a/frontend/src/main/resources/public/icons/light-bulb.svg b/frontend/src/main/resources/icons/light-bulb.svg similarity index 100% rename from frontend/src/main/resources/public/icons/light-bulb.svg rename to frontend/src/main/resources/icons/light-bulb.svg diff --git a/frontend/src/main/resources/public/icons/lightning-bolt.svg b/frontend/src/main/resources/icons/lightning-bolt.svg similarity index 100% rename from frontend/src/main/resources/public/icons/lightning-bolt.svg rename to frontend/src/main/resources/icons/lightning-bolt.svg diff --git a/frontend/src/main/resources/public/icons/link.svg b/frontend/src/main/resources/icons/link.svg similarity index 100% rename from frontend/src/main/resources/public/icons/link.svg rename to frontend/src/main/resources/icons/link.svg diff --git a/frontend/src/main/resources/public/icons/location-marker.svg b/frontend/src/main/resources/icons/location-marker.svg similarity index 100% rename from frontend/src/main/resources/public/icons/location-marker.svg rename to frontend/src/main/resources/icons/location-marker.svg diff --git a/frontend/src/main/resources/public/icons/lock-closed.svg b/frontend/src/main/resources/icons/lock-closed.svg similarity index 100% rename from frontend/src/main/resources/public/icons/lock-closed.svg rename to frontend/src/main/resources/icons/lock-closed.svg diff --git a/frontend/src/main/resources/public/icons/lock-open.svg b/frontend/src/main/resources/icons/lock-open.svg similarity index 100% rename from frontend/src/main/resources/public/icons/lock-open.svg rename to frontend/src/main/resources/icons/lock-open.svg diff --git a/frontend/src/main/resources/public/icons/login.svg b/frontend/src/main/resources/icons/login.svg similarity index 100% rename from frontend/src/main/resources/public/icons/login.svg rename to frontend/src/main/resources/icons/login.svg diff --git a/frontend/src/main/resources/public/icons/logout.svg b/frontend/src/main/resources/icons/logout.svg similarity index 100% rename from frontend/src/main/resources/public/icons/logout.svg rename to frontend/src/main/resources/icons/logout.svg diff --git a/frontend/src/main/resources/public/icons/mail-open.svg b/frontend/src/main/resources/icons/mail-open.svg similarity index 100% rename from frontend/src/main/resources/public/icons/mail-open.svg rename to frontend/src/main/resources/icons/mail-open.svg diff --git a/frontend/src/main/resources/public/icons/mail.svg b/frontend/src/main/resources/icons/mail.svg similarity index 100% rename from frontend/src/main/resources/public/icons/mail.svg rename to frontend/src/main/resources/icons/mail.svg diff --git a/frontend/src/main/resources/public/icons/map.svg b/frontend/src/main/resources/icons/map.svg similarity index 100% rename from frontend/src/main/resources/public/icons/map.svg rename to frontend/src/main/resources/icons/map.svg diff --git a/frontend/src/main/resources/public/icons/menu-alt-1.svg b/frontend/src/main/resources/icons/menu-alt-1.svg similarity index 100% rename from frontend/src/main/resources/public/icons/menu-alt-1.svg rename to frontend/src/main/resources/icons/menu-alt-1.svg diff --git a/frontend/src/main/resources/public/icons/menu-alt-2.svg b/frontend/src/main/resources/icons/menu-alt-2.svg similarity index 100% rename from frontend/src/main/resources/public/icons/menu-alt-2.svg rename to frontend/src/main/resources/icons/menu-alt-2.svg diff --git a/frontend/src/main/resources/public/icons/menu-alt-3.svg b/frontend/src/main/resources/icons/menu-alt-3.svg similarity index 100% rename from frontend/src/main/resources/public/icons/menu-alt-3.svg rename to frontend/src/main/resources/icons/menu-alt-3.svg diff --git a/frontend/src/main/resources/public/icons/menu-alt-4.svg b/frontend/src/main/resources/icons/menu-alt-4.svg similarity index 100% rename from frontend/src/main/resources/public/icons/menu-alt-4.svg rename to frontend/src/main/resources/icons/menu-alt-4.svg diff --git a/frontend/src/main/resources/public/icons/menu.svg b/frontend/src/main/resources/icons/menu.svg similarity index 100% rename from frontend/src/main/resources/public/icons/menu.svg rename to frontend/src/main/resources/icons/menu.svg diff --git a/frontend/src/main/resources/public/icons/microphone.svg b/frontend/src/main/resources/icons/microphone.svg similarity index 100% rename from frontend/src/main/resources/public/icons/microphone.svg rename to frontend/src/main/resources/icons/microphone.svg diff --git a/frontend/src/main/resources/public/icons/minus-circle.svg b/frontend/src/main/resources/icons/minus-circle.svg similarity index 100% rename from frontend/src/main/resources/public/icons/minus-circle.svg rename to frontend/src/main/resources/icons/minus-circle.svg diff --git a/frontend/src/main/resources/public/icons/minus-sm.svg b/frontend/src/main/resources/icons/minus-sm.svg similarity index 100% rename from frontend/src/main/resources/public/icons/minus-sm.svg rename to frontend/src/main/resources/icons/minus-sm.svg diff --git a/frontend/src/main/resources/public/icons/minus.svg b/frontend/src/main/resources/icons/minus.svg similarity index 100% rename from frontend/src/main/resources/public/icons/minus.svg rename to frontend/src/main/resources/icons/minus.svg diff --git a/frontend/src/main/resources/public/icons/moon.svg b/frontend/src/main/resources/icons/moon.svg similarity index 100% rename from frontend/src/main/resources/public/icons/moon.svg rename to frontend/src/main/resources/icons/moon.svg diff --git a/frontend/src/main/resources/public/icons/music-note.svg b/frontend/src/main/resources/icons/music-note.svg similarity index 100% rename from frontend/src/main/resources/public/icons/music-note.svg rename to frontend/src/main/resources/icons/music-note.svg diff --git a/frontend/src/main/resources/public/icons/newspaper.svg b/frontend/src/main/resources/icons/newspaper.svg similarity index 100% rename from frontend/src/main/resources/public/icons/newspaper.svg rename to frontend/src/main/resources/icons/newspaper.svg diff --git a/frontend/src/main/resources/public/icons/office-building.svg b/frontend/src/main/resources/icons/office-building.svg similarity index 100% rename from frontend/src/main/resources/public/icons/office-building.svg rename to frontend/src/main/resources/icons/office-building.svg diff --git a/frontend/src/main/resources/public/icons/paper-airplane.svg b/frontend/src/main/resources/icons/paper-airplane.svg similarity index 100% rename from frontend/src/main/resources/public/icons/paper-airplane.svg rename to frontend/src/main/resources/icons/paper-airplane.svg diff --git a/frontend/src/main/resources/public/icons/paper-clip.svg b/frontend/src/main/resources/icons/paper-clip.svg similarity index 100% rename from frontend/src/main/resources/public/icons/paper-clip.svg rename to frontend/src/main/resources/icons/paper-clip.svg diff --git a/frontend/src/main/resources/public/icons/pause.svg b/frontend/src/main/resources/icons/pause.svg similarity index 100% rename from frontend/src/main/resources/public/icons/pause.svg rename to frontend/src/main/resources/icons/pause.svg diff --git a/frontend/src/main/resources/public/icons/pencil-alt.svg b/frontend/src/main/resources/icons/pencil-alt.svg similarity index 100% rename from frontend/src/main/resources/public/icons/pencil-alt.svg rename to frontend/src/main/resources/icons/pencil-alt.svg diff --git a/frontend/src/main/resources/public/icons/pencil.svg b/frontend/src/main/resources/icons/pencil.svg similarity index 100% rename from frontend/src/main/resources/public/icons/pencil.svg rename to frontend/src/main/resources/icons/pencil.svg diff --git a/frontend/src/main/resources/public/icons/phone-incoming.svg b/frontend/src/main/resources/icons/phone-incoming.svg similarity index 100% rename from frontend/src/main/resources/public/icons/phone-incoming.svg rename to frontend/src/main/resources/icons/phone-incoming.svg diff --git a/frontend/src/main/resources/public/icons/phone-missed-call.svg b/frontend/src/main/resources/icons/phone-missed-call.svg similarity index 100% rename from frontend/src/main/resources/public/icons/phone-missed-call.svg rename to frontend/src/main/resources/icons/phone-missed-call.svg diff --git a/frontend/src/main/resources/public/icons/phone-outgoing.svg b/frontend/src/main/resources/icons/phone-outgoing.svg similarity index 100% rename from frontend/src/main/resources/public/icons/phone-outgoing.svg rename to frontend/src/main/resources/icons/phone-outgoing.svg diff --git a/frontend/src/main/resources/public/icons/phone.svg b/frontend/src/main/resources/icons/phone.svg similarity index 100% rename from frontend/src/main/resources/public/icons/phone.svg rename to frontend/src/main/resources/icons/phone.svg diff --git a/frontend/src/main/resources/public/icons/photograph.svg b/frontend/src/main/resources/icons/photograph.svg similarity index 100% rename from frontend/src/main/resources/public/icons/photograph.svg rename to frontend/src/main/resources/icons/photograph.svg diff --git a/frontend/src/main/resources/public/icons/play.svg b/frontend/src/main/resources/icons/play.svg similarity index 100% rename from frontend/src/main/resources/public/icons/play.svg rename to frontend/src/main/resources/icons/play.svg diff --git a/frontend/src/main/resources/public/icons/plus-circle.svg b/frontend/src/main/resources/icons/plus-circle.svg similarity index 100% rename from frontend/src/main/resources/public/icons/plus-circle.svg rename to frontend/src/main/resources/icons/plus-circle.svg diff --git a/frontend/src/main/resources/public/icons/plus-sm.svg b/frontend/src/main/resources/icons/plus-sm.svg similarity index 100% rename from frontend/src/main/resources/public/icons/plus-sm.svg rename to frontend/src/main/resources/icons/plus-sm.svg diff --git a/frontend/src/main/resources/public/icons/plus.svg b/frontend/src/main/resources/icons/plus.svg similarity index 100% rename from frontend/src/main/resources/public/icons/plus.svg rename to frontend/src/main/resources/icons/plus.svg diff --git a/frontend/src/main/resources/public/icons/presentation-chart-bar.svg b/frontend/src/main/resources/icons/presentation-chart-bar.svg similarity index 100% rename from frontend/src/main/resources/public/icons/presentation-chart-bar.svg rename to frontend/src/main/resources/icons/presentation-chart-bar.svg diff --git a/frontend/src/main/resources/public/icons/presentation-chart-line.svg b/frontend/src/main/resources/icons/presentation-chart-line.svg similarity index 100% rename from frontend/src/main/resources/public/icons/presentation-chart-line.svg rename to frontend/src/main/resources/icons/presentation-chart-line.svg diff --git a/frontend/src/main/resources/public/icons/printer.svg b/frontend/src/main/resources/icons/printer.svg similarity index 100% rename from frontend/src/main/resources/public/icons/printer.svg rename to frontend/src/main/resources/icons/printer.svg diff --git a/frontend/src/main/resources/public/icons/puzzle.svg b/frontend/src/main/resources/icons/puzzle.svg similarity index 100% rename from frontend/src/main/resources/public/icons/puzzle.svg rename to frontend/src/main/resources/icons/puzzle.svg diff --git a/frontend/src/main/resources/public/icons/qrcode.svg b/frontend/src/main/resources/icons/qrcode.svg similarity index 100% rename from frontend/src/main/resources/public/icons/qrcode.svg rename to frontend/src/main/resources/icons/qrcode.svg diff --git a/frontend/src/main/resources/public/icons/question-mark-circle.svg b/frontend/src/main/resources/icons/question-mark-circle.svg similarity index 100% rename from frontend/src/main/resources/public/icons/question-mark-circle.svg rename to frontend/src/main/resources/icons/question-mark-circle.svg diff --git a/frontend/src/main/resources/public/icons/receipt-refund.svg b/frontend/src/main/resources/icons/receipt-refund.svg similarity index 100% rename from frontend/src/main/resources/public/icons/receipt-refund.svg rename to frontend/src/main/resources/icons/receipt-refund.svg diff --git a/frontend/src/main/resources/public/icons/receipt-tax.svg b/frontend/src/main/resources/icons/receipt-tax.svg similarity index 100% rename from frontend/src/main/resources/public/icons/receipt-tax.svg rename to frontend/src/main/resources/icons/receipt-tax.svg diff --git a/frontend/src/main/resources/public/icons/refresh.svg b/frontend/src/main/resources/icons/refresh.svg similarity index 100% rename from frontend/src/main/resources/public/icons/refresh.svg rename to frontend/src/main/resources/icons/refresh.svg diff --git a/frontend/src/main/resources/public/icons/reply.svg b/frontend/src/main/resources/icons/reply.svg similarity index 100% rename from frontend/src/main/resources/public/icons/reply.svg rename to frontend/src/main/resources/icons/reply.svg diff --git a/frontend/src/main/resources/public/icons/rewind.svg b/frontend/src/main/resources/icons/rewind.svg similarity index 100% rename from frontend/src/main/resources/public/icons/rewind.svg rename to frontend/src/main/resources/icons/rewind.svg diff --git a/frontend/src/main/resources/public/icons/rss.svg b/frontend/src/main/resources/icons/rss.svg similarity index 100% rename from frontend/src/main/resources/public/icons/rss.svg rename to frontend/src/main/resources/icons/rss.svg diff --git a/frontend/src/main/resources/public/icons/save-as.svg b/frontend/src/main/resources/icons/save-as.svg similarity index 100% rename from frontend/src/main/resources/public/icons/save-as.svg rename to frontend/src/main/resources/icons/save-as.svg diff --git a/frontend/src/main/resources/public/icons/save.svg b/frontend/src/main/resources/icons/save.svg similarity index 100% rename from frontend/src/main/resources/public/icons/save.svg rename to frontend/src/main/resources/icons/save.svg diff --git a/frontend/src/main/resources/public/icons/scale.svg b/frontend/src/main/resources/icons/scale.svg similarity index 100% rename from frontend/src/main/resources/public/icons/scale.svg rename to frontend/src/main/resources/icons/scale.svg diff --git a/frontend/src/main/resources/public/icons/scissors.svg b/frontend/src/main/resources/icons/scissors.svg similarity index 100% rename from frontend/src/main/resources/public/icons/scissors.svg rename to frontend/src/main/resources/icons/scissors.svg diff --git a/frontend/src/main/resources/public/icons/search-circle.svg b/frontend/src/main/resources/icons/search-circle.svg similarity index 100% rename from frontend/src/main/resources/public/icons/search-circle.svg rename to frontend/src/main/resources/icons/search-circle.svg diff --git a/frontend/src/main/resources/public/icons/search.svg b/frontend/src/main/resources/icons/search.svg similarity index 100% rename from frontend/src/main/resources/public/icons/search.svg rename to frontend/src/main/resources/icons/search.svg diff --git a/frontend/src/main/resources/public/icons/selector.svg b/frontend/src/main/resources/icons/selector.svg similarity index 100% rename from frontend/src/main/resources/public/icons/selector.svg rename to frontend/src/main/resources/icons/selector.svg diff --git a/frontend/src/main/resources/public/icons/server.svg b/frontend/src/main/resources/icons/server.svg similarity index 100% rename from frontend/src/main/resources/public/icons/server.svg rename to frontend/src/main/resources/icons/server.svg diff --git a/frontend/src/main/resources/public/icons/share.svg b/frontend/src/main/resources/icons/share.svg similarity index 100% rename from frontend/src/main/resources/public/icons/share.svg rename to frontend/src/main/resources/icons/share.svg diff --git a/frontend/src/main/resources/public/icons/shield-check.svg b/frontend/src/main/resources/icons/shield-check.svg similarity index 100% rename from frontend/src/main/resources/public/icons/shield-check.svg rename to frontend/src/main/resources/icons/shield-check.svg diff --git a/frontend/src/main/resources/public/icons/shield-exclamation.svg b/frontend/src/main/resources/icons/shield-exclamation.svg similarity index 100% rename from frontend/src/main/resources/public/icons/shield-exclamation.svg rename to frontend/src/main/resources/icons/shield-exclamation.svg diff --git a/frontend/src/main/resources/public/icons/shopping-bag.svg b/frontend/src/main/resources/icons/shopping-bag.svg similarity index 100% rename from frontend/src/main/resources/public/icons/shopping-bag.svg rename to frontend/src/main/resources/icons/shopping-bag.svg diff --git a/frontend/src/main/resources/public/icons/shopping-cart.svg b/frontend/src/main/resources/icons/shopping-cart.svg similarity index 100% rename from frontend/src/main/resources/public/icons/shopping-cart.svg rename to frontend/src/main/resources/icons/shopping-cart.svg diff --git a/frontend/src/main/resources/public/icons/sort-ascending.svg b/frontend/src/main/resources/icons/sort-ascending.svg similarity index 100% rename from frontend/src/main/resources/public/icons/sort-ascending.svg rename to frontend/src/main/resources/icons/sort-ascending.svg diff --git a/frontend/src/main/resources/public/icons/sort-descending.svg b/frontend/src/main/resources/icons/sort-descending.svg similarity index 100% rename from frontend/src/main/resources/public/icons/sort-descending.svg rename to frontend/src/main/resources/icons/sort-descending.svg diff --git a/frontend/src/main/resources/public/icons/sparkles.svg b/frontend/src/main/resources/icons/sparkles.svg similarity index 100% rename from frontend/src/main/resources/public/icons/sparkles.svg rename to frontend/src/main/resources/icons/sparkles.svg diff --git a/frontend/src/main/resources/public/icons/speakerphone.svg b/frontend/src/main/resources/icons/speakerphone.svg similarity index 100% rename from frontend/src/main/resources/public/icons/speakerphone.svg rename to frontend/src/main/resources/icons/speakerphone.svg diff --git a/frontend/src/main/resources/public/icons/star.svg b/frontend/src/main/resources/icons/star.svg similarity index 100% rename from frontend/src/main/resources/public/icons/star.svg rename to frontend/src/main/resources/icons/star.svg diff --git a/frontend/src/main/resources/public/icons/status-offline.svg b/frontend/src/main/resources/icons/status-offline.svg similarity index 100% rename from frontend/src/main/resources/public/icons/status-offline.svg rename to frontend/src/main/resources/icons/status-offline.svg diff --git a/frontend/src/main/resources/public/icons/status-online.svg b/frontend/src/main/resources/icons/status-online.svg similarity index 100% rename from frontend/src/main/resources/public/icons/status-online.svg rename to frontend/src/main/resources/icons/status-online.svg diff --git a/frontend/src/main/resources/public/icons/stop.svg b/frontend/src/main/resources/icons/stop.svg similarity index 100% rename from frontend/src/main/resources/public/icons/stop.svg rename to frontend/src/main/resources/icons/stop.svg diff --git a/frontend/src/main/resources/public/icons/sun.svg b/frontend/src/main/resources/icons/sun.svg similarity index 100% rename from frontend/src/main/resources/public/icons/sun.svg rename to frontend/src/main/resources/icons/sun.svg diff --git a/frontend/src/main/resources/public/icons/support.svg b/frontend/src/main/resources/icons/support.svg similarity index 100% rename from frontend/src/main/resources/public/icons/support.svg rename to frontend/src/main/resources/icons/support.svg diff --git a/frontend/src/main/resources/public/icons/switch-horizontal.svg b/frontend/src/main/resources/icons/switch-horizontal.svg similarity index 100% rename from frontend/src/main/resources/public/icons/switch-horizontal.svg rename to frontend/src/main/resources/icons/switch-horizontal.svg diff --git a/frontend/src/main/resources/public/icons/switch-vertical.svg b/frontend/src/main/resources/icons/switch-vertical.svg similarity index 100% rename from frontend/src/main/resources/public/icons/switch-vertical.svg rename to frontend/src/main/resources/icons/switch-vertical.svg diff --git a/frontend/src/main/resources/public/icons/table.svg b/frontend/src/main/resources/icons/table.svg similarity index 100% rename from frontend/src/main/resources/public/icons/table.svg rename to frontend/src/main/resources/icons/table.svg diff --git a/frontend/src/main/resources/public/icons/tag.svg b/frontend/src/main/resources/icons/tag.svg similarity index 100% rename from frontend/src/main/resources/public/icons/tag.svg rename to frontend/src/main/resources/icons/tag.svg diff --git a/frontend/src/main/resources/public/icons/template.svg b/frontend/src/main/resources/icons/template.svg similarity index 100% rename from frontend/src/main/resources/public/icons/template.svg rename to frontend/src/main/resources/icons/template.svg diff --git a/frontend/src/main/resources/public/icons/terminal.svg b/frontend/src/main/resources/icons/terminal.svg similarity index 100% rename from frontend/src/main/resources/public/icons/terminal.svg rename to frontend/src/main/resources/icons/terminal.svg diff --git a/frontend/src/main/resources/public/icons/thumb-down.svg b/frontend/src/main/resources/icons/thumb-down.svg similarity index 100% rename from frontend/src/main/resources/public/icons/thumb-down.svg rename to frontend/src/main/resources/icons/thumb-down.svg diff --git a/frontend/src/main/resources/public/icons/thumb-up.svg b/frontend/src/main/resources/icons/thumb-up.svg similarity index 100% rename from frontend/src/main/resources/public/icons/thumb-up.svg rename to frontend/src/main/resources/icons/thumb-up.svg diff --git a/frontend/src/main/resources/public/icons/ticket.svg b/frontend/src/main/resources/icons/ticket.svg similarity index 100% rename from frontend/src/main/resources/public/icons/ticket.svg rename to frontend/src/main/resources/icons/ticket.svg diff --git a/frontend/src/main/resources/public/icons/translate.svg b/frontend/src/main/resources/icons/translate.svg similarity index 100% rename from frontend/src/main/resources/public/icons/translate.svg rename to frontend/src/main/resources/icons/translate.svg diff --git a/frontend/src/main/resources/public/icons/trash.svg b/frontend/src/main/resources/icons/trash.svg similarity index 100% rename from frontend/src/main/resources/public/icons/trash.svg rename to frontend/src/main/resources/icons/trash.svg diff --git a/frontend/src/main/resources/public/icons/trending-down.svg b/frontend/src/main/resources/icons/trending-down.svg similarity index 100% rename from frontend/src/main/resources/public/icons/trending-down.svg rename to frontend/src/main/resources/icons/trending-down.svg diff --git a/frontend/src/main/resources/public/icons/trending-up.svg b/frontend/src/main/resources/icons/trending-up.svg similarity index 100% rename from frontend/src/main/resources/public/icons/trending-up.svg rename to frontend/src/main/resources/icons/trending-up.svg diff --git a/frontend/src/main/resources/public/icons/truck.svg b/frontend/src/main/resources/icons/truck.svg similarity index 100% rename from frontend/src/main/resources/public/icons/truck.svg rename to frontend/src/main/resources/icons/truck.svg diff --git a/frontend/src/main/resources/public/icons/upload.svg b/frontend/src/main/resources/icons/upload.svg similarity index 100% rename from frontend/src/main/resources/public/icons/upload.svg rename to frontend/src/main/resources/icons/upload.svg diff --git a/frontend/src/main/resources/public/icons/user-add.svg b/frontend/src/main/resources/icons/user-add.svg similarity index 100% rename from frontend/src/main/resources/public/icons/user-add.svg rename to frontend/src/main/resources/icons/user-add.svg diff --git a/frontend/src/main/resources/public/icons/user-circle.svg b/frontend/src/main/resources/icons/user-circle.svg similarity index 100% rename from frontend/src/main/resources/public/icons/user-circle.svg rename to frontend/src/main/resources/icons/user-circle.svg diff --git a/frontend/src/main/resources/public/icons/user-group.svg b/frontend/src/main/resources/icons/user-group.svg similarity index 100% rename from frontend/src/main/resources/public/icons/user-group.svg rename to frontend/src/main/resources/icons/user-group.svg diff --git a/frontend/src/main/resources/public/icons/user-remove.svg b/frontend/src/main/resources/icons/user-remove.svg similarity index 100% rename from frontend/src/main/resources/public/icons/user-remove.svg rename to frontend/src/main/resources/icons/user-remove.svg diff --git a/frontend/src/main/resources/public/icons/user.svg b/frontend/src/main/resources/icons/user.svg similarity index 100% rename from frontend/src/main/resources/public/icons/user.svg rename to frontend/src/main/resources/icons/user.svg diff --git a/frontend/src/main/resources/public/icons/users.svg b/frontend/src/main/resources/icons/users.svg similarity index 100% rename from frontend/src/main/resources/public/icons/users.svg rename to frontend/src/main/resources/icons/users.svg diff --git a/frontend/src/main/resources/public/icons/variable.svg b/frontend/src/main/resources/icons/variable.svg similarity index 100% rename from frontend/src/main/resources/public/icons/variable.svg rename to frontend/src/main/resources/icons/variable.svg diff --git a/frontend/src/main/resources/public/icons/video-camera.svg b/frontend/src/main/resources/icons/video-camera.svg similarity index 100% rename from frontend/src/main/resources/public/icons/video-camera.svg rename to frontend/src/main/resources/icons/video-camera.svg diff --git a/frontend/src/main/resources/public/icons/view-boards.svg b/frontend/src/main/resources/icons/view-boards.svg similarity index 100% rename from frontend/src/main/resources/public/icons/view-boards.svg rename to frontend/src/main/resources/icons/view-boards.svg diff --git a/frontend/src/main/resources/public/icons/view-grid-add.svg b/frontend/src/main/resources/icons/view-grid-add.svg similarity index 100% rename from frontend/src/main/resources/public/icons/view-grid-add.svg rename to frontend/src/main/resources/icons/view-grid-add.svg diff --git a/frontend/src/main/resources/public/icons/view-grid.svg b/frontend/src/main/resources/icons/view-grid.svg similarity index 100% rename from frontend/src/main/resources/public/icons/view-grid.svg rename to frontend/src/main/resources/icons/view-grid.svg diff --git a/frontend/src/main/resources/public/icons/view-list.svg b/frontend/src/main/resources/icons/view-list.svg similarity index 100% rename from frontend/src/main/resources/public/icons/view-list.svg rename to frontend/src/main/resources/icons/view-list.svg diff --git a/frontend/src/main/resources/public/icons/volume-off.svg b/frontend/src/main/resources/icons/volume-off.svg similarity index 100% rename from frontend/src/main/resources/public/icons/volume-off.svg rename to frontend/src/main/resources/icons/volume-off.svg diff --git a/frontend/src/main/resources/public/icons/volume-up.svg b/frontend/src/main/resources/icons/volume-up.svg similarity index 100% rename from frontend/src/main/resources/public/icons/volume-up.svg rename to frontend/src/main/resources/icons/volume-up.svg diff --git a/frontend/src/main/resources/public/icons/wifi.svg b/frontend/src/main/resources/icons/wifi.svg similarity index 100% rename from frontend/src/main/resources/public/icons/wifi.svg rename to frontend/src/main/resources/icons/wifi.svg diff --git a/frontend/src/main/resources/public/icons/x-circle.svg b/frontend/src/main/resources/icons/x-circle.svg similarity index 100% rename from frontend/src/main/resources/public/icons/x-circle.svg rename to frontend/src/main/resources/icons/x-circle.svg diff --git a/frontend/src/main/resources/public/icons/x.svg b/frontend/src/main/resources/icons/x.svg similarity index 100% rename from frontend/src/main/resources/public/icons/x.svg rename to frontend/src/main/resources/icons/x.svg diff --git a/frontend/src/main/resources/public/icons/zoom-in.svg b/frontend/src/main/resources/icons/zoom-in.svg similarity index 100% rename from frontend/src/main/resources/public/icons/zoom-in.svg rename to frontend/src/main/resources/icons/zoom-in.svg diff --git a/frontend/src/main/resources/public/icons/zoom-out.svg b/frontend/src/main/resources/icons/zoom-out.svg similarity index 100% rename from frontend/src/main/resources/public/icons/zoom-out.svg rename to frontend/src/main/resources/icons/zoom-out.svg diff --git a/frontend/src/main/resources/public/images/Emblem_Bronze.png b/frontend/src/main/resources/images/Emblem_Bronze.png similarity index 100% rename from frontend/src/main/resources/public/images/Emblem_Bronze.png rename to frontend/src/main/resources/images/Emblem_Bronze.png diff --git a/frontend/src/main/resources/public/images/Emblem_Challenger.png b/frontend/src/main/resources/images/Emblem_Challenger.png similarity index 100% rename from frontend/src/main/resources/public/images/Emblem_Challenger.png rename to frontend/src/main/resources/images/Emblem_Challenger.png diff --git a/frontend/src/main/resources/public/images/Emblem_Diamond.png b/frontend/src/main/resources/images/Emblem_Diamond.png similarity index 100% rename from frontend/src/main/resources/public/images/Emblem_Diamond.png rename to frontend/src/main/resources/images/Emblem_Diamond.png diff --git a/frontend/src/main/resources/public/images/Emblem_Gold.png b/frontend/src/main/resources/images/Emblem_Gold.png similarity index 100% rename from frontend/src/main/resources/public/images/Emblem_Gold.png rename to frontend/src/main/resources/images/Emblem_Gold.png diff --git a/frontend/src/main/resources/public/images/Emblem_Grandmaster.png b/frontend/src/main/resources/images/Emblem_Grandmaster.png similarity index 100% rename from frontend/src/main/resources/public/images/Emblem_Grandmaster.png rename to frontend/src/main/resources/images/Emblem_Grandmaster.png diff --git a/frontend/src/main/resources/public/images/Emblem_Iron.png b/frontend/src/main/resources/images/Emblem_Iron.png similarity index 100% rename from frontend/src/main/resources/public/images/Emblem_Iron.png rename to frontend/src/main/resources/images/Emblem_Iron.png diff --git a/frontend/src/main/resources/public/images/Emblem_Master.png b/frontend/src/main/resources/images/Emblem_Master.png similarity index 100% rename from frontend/src/main/resources/public/images/Emblem_Master.png rename to frontend/src/main/resources/images/Emblem_Master.png diff --git a/frontend/src/main/resources/public/images/Emblem_Platinum.png b/frontend/src/main/resources/images/Emblem_Platinum.png similarity index 100% rename from frontend/src/main/resources/public/images/Emblem_Platinum.png rename to frontend/src/main/resources/images/Emblem_Platinum.png diff --git a/frontend/src/main/resources/public/images/Emblem_Provisional.png b/frontend/src/main/resources/images/Emblem_Provisional.png similarity index 100% rename from frontend/src/main/resources/public/images/Emblem_Provisional.png rename to frontend/src/main/resources/images/Emblem_Provisional.png diff --git a/frontend/src/main/resources/public/images/Emblem_Silver.png b/frontend/src/main/resources/images/Emblem_Silver.png similarity index 100% rename from frontend/src/main/resources/public/images/Emblem_Silver.png rename to frontend/src/main/resources/images/Emblem_Silver.png diff --git a/frontend/src/main/resources/public/images/Emblem_Unranked.png b/frontend/src/main/resources/images/Emblem_Unranked.png similarity index 100% rename from frontend/src/main/resources/public/images/Emblem_Unranked.png rename to frontend/src/main/resources/images/Emblem_Unranked.png diff --git a/frontend/src/main/resources/public/images/X_redt.png b/frontend/src/main/resources/images/X_redt.png similarity index 100% rename from frontend/src/main/resources/public/images/X_redt.png rename to frontend/src/main/resources/images/X_redt.png diff --git a/frontend/src/main/resources/public/images/aleph256.png b/frontend/src/main/resources/images/aleph256.png similarity index 100% rename from frontend/src/main/resources/public/images/aleph256.png rename to frontend/src/main/resources/images/aleph256.png diff --git a/frontend/src/main/resources/public/images/aleph256full.png b/frontend/src/main/resources/images/aleph256full.png similarity index 100% rename from frontend/src/main/resources/public/images/aleph256full.png rename to frontend/src/main/resources/images/aleph256full.png diff --git a/frontend/src/main/resources/public/images/amumu_error.png b/frontend/src/main/resources/images/amumu_error.png similarity index 100% rename from frontend/src/main/resources/public/images/amumu_error.png rename to frontend/src/main/resources/images/amumu_error.png diff --git a/frontend/src/main/resources/public/images/blitzcrank_logo.png b/frontend/src/main/resources/images/blitzcrank_logo.png similarity index 100% rename from frontend/src/main/resources/public/images/blitzcrank_logo.png rename to frontend/src/main/resources/images/blitzcrank_logo.png diff --git a/frontend/src/main/resources/images/discord_logo.png b/frontend/src/main/resources/images/discord_logo.png new file mode 100644 index 0000000..d5346b7 Binary files /dev/null and b/frontend/src/main/resources/images/discord_logo.png differ diff --git a/frontend/src/main/resources/public/images/gh_logo.png b/frontend/src/main/resources/images/gh_logo.png similarity index 100% rename from frontend/src/main/resources/public/images/gh_logo.png rename to frontend/src/main/resources/images/gh_logo.png diff --git a/frontend/src/main/resources/public/images/placeholder_champion.png b/frontend/src/main/resources/images/placeholder_champion.png similarity index 100% rename from frontend/src/main/resources/public/images/placeholder_champion.png rename to frontend/src/main/resources/images/placeholder_champion.png diff --git a/frontend/src/main/resources/public/images/slash_red_256.png b/frontend/src/main/resources/images/slash_red_256.png similarity index 100% rename from frontend/src/main/resources/public/images/slash_red_256.png rename to frontend/src/main/resources/images/slash_red_256.png diff --git a/frontend/src/main/scala/org/kys/athena/App.scala b/frontend/src/main/scala/org/kys/athena/App.scala index 1c09c09..34018d0 100644 --- a/frontend/src/main/scala/org/kys/athena/App.scala +++ b/frontend/src/main/scala/org/kys/athena/App.scala @@ -9,31 +9,50 @@ import org.kys.athena.riot.api.dto.common.Platform import org.kys.athena.util.CSSUtil import org.kys.athena.components.LandingPage import org.kys.athena.components.common.{AppBar, Footer} +import org.kys.athena.components.notfound.NotFoundPage import org.kys.athena.components.ongoing.OngoingPage +import org.kys.athena.components.pregame.PregamePage import org.scalajs.dom +import org.scalajs.dom.document import urldsl.errors.DummyError -import urldsl.vocabulary.{FromString, Printer} +import urldsl.vocabulary.{FromString, Printer, UrlMatching} object App { + private implicit val platformDecoder: FromString[Platform, DummyError] = new FromString[Platform, DummyError] { + override def fromString(str: String): Either[DummyError, Platform] = { + Platform.withNameEither(str).left.map(_ => DummyError.dummyError) + } + } + + private implicit val platformPrinter: Printer[Platform] = new Printer[Platform] { + override def print(t: Platform): String = t.entryName + } + + private implicit val listDecoder: FromString[List[String], DummyError] = new FromString[List[String], DummyError] { + override def fromString(str: String): Either[DummyError, List[String]] = Right(str.split(',').toList) + } + + private implicit val listPrinter: Printer[List[String]] = new Printer[List[String]] { + override def print(t: List[String]) = t.mkString(",") + } private val routes: List[Route[_ <: PageRoute, _]] = List( Route.static(LandingRoute, root / endOfSegments), + Route.withQuery[PregameRoute, Platform, List[String]]( + encode = p => UrlMatching(p.realm, p.names), + decode = a => PregameRoute(a.path, a.params), + pattern = (root / segment[Platform] / "pregame" / endOfSegments) ? + (param[List[String]]("summoners"))), Route[OngoingRoute, (Platform, String)]( encode = p => (p.realm, p.name), decode = a => OngoingRoute(a._1, a._2), - pattern = { - implicit val fs = new FromString[Platform, DummyError] { - override def fromString(str: String): Either[DummyError, Platform] = { - Platform.withNameEither(str).left.map(_ => DummyError.dummyError) - } - } - implicit val pr = new Printer[Platform] { - override def print(t: Platform): String = t.entryName - } - root / segment[Platform] / segment[String] / endOfSegments - }) - ) + pattern = root / segment[Platform] / segment[String] / endOfSegments), + // This must be last (catch-all to prevent the router from crashing) + Route[RouteNotFound, List[String]]( + encode = p => p.restOfSegments, + decode = a => RouteNotFound(a), + pattern = root / remainingSegments)) private val router = new Router[PageRoute]( initialUrl = dom.document.location.href, @@ -41,23 +60,29 @@ object App { routes = routes, owner = unsafeWindowOwner, // this router will live as long as the window $popStateEvent = windowEvents.onPopState, - getPageTitle = _.title, // mock page title (displayed in the browser tab next to favicon) + getPageTitle = _.title, // Currently ignored by most browsers (per MDN api spec) serializePage = page => page.asJson.noSpaces, // serialize page data for storage in History API log deserializePage = pageStr => { - decode[PageRoute](pageStr).fold(e => ErrorRoute(e.getMessage), identity) - } // deserialize the above - ) + decode[PageRoute](pageStr).fold(e => RouteNotFound(List("error")), identity) + }) + + // Currently title in pushState is ignored by most (all?) browsers. To fix, dispatch a custom event + // to update the title on route events + router.$currentPage.foreach { page => + document.title = page.title + }(unsafeWindowOwner) private val hideSearchBar = Var(false) private val splitter: SplitRender[PageRoute, HtmlElement] = - SplitRender[PageRoute, HtmlElement](router.$currentPage) - .collectStatic(LandingRoute) { - LandingPage.render(windowEvents.onMouseMove) - }.collectStatic(RouteNotFound) { - LandingPage.render(windowEvents.onMouseMove) + SplitRender[PageRoute, HtmlElement](router.$currentPage).collectStatic(LandingRoute) { + LandingPage.render }.collect[OngoingRoute] { page => OngoingPage.render(page, hideSearchBar.writer) + }.collect[PregameRoute] { page => + PregamePage.render(page) + }.collect[RouteNotFound] { page => + NotFoundPage.render(page.restOfSegments) } diff --git a/frontend/src/main/scala/org/kys/athena/components/LandingPage.scala b/frontend/src/main/scala/org/kys/athena/components/LandingPage.scala index c34467e..6be34ff 100644 --- a/frontend/src/main/scala/org/kys/athena/components/LandingPage.scala +++ b/frontend/src/main/scala/org/kys/athena/components/LandingPage.scala @@ -9,59 +9,15 @@ import org.scalajs.dom.MouseEvent import scala.math._ import org.kys.athena.util.SearchHistoryManager import org.kys.athena.components.common.FocusCapturer._ +import org.kys.athena.util.assets.AssetLoader +import org.kys.athena.components.common.AlephEye +import org.kys.athena.riot.api.dto.common.Platform object LandingPage { - def render(mouseES:EventStream[MouseEvent]): HtmlElement = { - val referenceDiv = div( - position := "relative", - top := "44.5%", - left := "56%", - width := "0px", - height := "0px") - - val eyeCoords = mouseES.map { ev => - val eyeRef = referenceDiv.ref.getBoundingClientRect() - // calc theta from mouse coordinates - val th = atan2(ev.pageY - eyeRef.top, ev.pageX - eyeRef.left) - // determine distance % mouse is from center and then multiply by polar distance coord "r" - // (tan-1 (y/x) multiplied by max allowed circle draw - val r = sqrt(pow(1, 2) + pow(5, 2)) - // convert back to cartesian - val x = r * cos(th) - val y = r * sin(th) - - (x, y) - }.toSignal((-5D,0D)) + def render: HtmlElement = { val focusBus = new EventBus[EventFired] div(cls := "flex flex-col items-center container-md flex-grow justify-center", - //center of eye aleph256full.png (152,125) - - - div( - width := "256px", - height := "256px", - cls := "ml-1", - div( - position := "absolute", - height := "256px", - div(img(src := "/images/aleph256full.png", width := "256px", height := "256px"), - zIndex := 1)), - - div( - position := "relative", - // draw eye at coordinates - top <-- eyeCoords.map(coords => s"${coords._2 + 44.5}%"), - left <-- eyeCoords.map(coords => s"${coords._1 + 56}%"), - zIndex := 2, - cls := "rounded-full flex items-center justify-center", - width := "20px", - height := "20px", - backgroundColor := "#a6a6a6", - ), - - //reference point of eye - referenceDiv - ), + AlephEye(), span(fontFamily := "heorotregular", fontSize := "6rem", color := "#780522", diff --git a/frontend/src/main/scala/org/kys/athena/components/common/AlephEye.scala b/frontend/src/main/scala/org/kys/athena/components/common/AlephEye.scala new file mode 100644 index 0000000..7dd250b --- /dev/null +++ b/frontend/src/main/scala/org/kys/athena/components/common/AlephEye.scala @@ -0,0 +1,59 @@ +package org.kys.athena.components.common + +import com.raquo.laminar.api.L._ +import org.kys.athena.util.assets.AssetLoader + +import scala.math._ + + +object AlephEye { + def apply(): HtmlElement = { + //center of eye aleph256full.png (152,125) + val referenceDiv = div( + position := "relative", + top := "44.5%", + left := "56%", + width := "0px", + height := "0px") + + val eyeCoords = windowEvents.onMouseMove.map { ev => + val eyeRef = referenceDiv.ref.getBoundingClientRect() + // calc theta from mouse coordinates + val th = atan2(ev.pageY - eyeRef.top, ev.pageX - eyeRef.left) + // determine distance % mouse is from center and then multiply by polar distance coord "r" + // (tan-1 (y/x) multiplied by max allowed circle draw + val r = sqrt(pow(1, 2) + pow(5, 2)) + // convert back to cartesian + val x = r * cos(th) + val y = r * sin(th) + + (x, y) + }.toSignal((-5D,0D)) + + div( + width := "256px", + height := "256px", + cls := "ml-1", + div( + position := "absolute", + height := "256px", + div( + img( + src := AssetLoader.require("/images/aleph256full.png"), + width := "256px", + height := "256px"), + zIndex := 1)), + div( + position := "relative", + // draw eye at coordinates + top <-- eyeCoords.map(coords => s"${coords._2 + 44.5}%"), + left <-- eyeCoords.map(coords => s"${coords._1 + 56}%"), + zIndex := 2, + cls := "rounded-full flex items-center justify-center", + width := "20px", + height := "20px", + backgroundColor := "#a6a6a6"), + //reference point of eye + referenceDiv) + } +} diff --git a/frontend/src/main/scala/org/kys/athena/components/common/AppBar.scala b/frontend/src/main/scala/org/kys/athena/components/common/AppBar.scala index 7fa38e3..001ba6a 100644 --- a/frontend/src/main/scala/org/kys/athena/components/common/AppBar.scala +++ b/frontend/src/main/scala/org/kys/athena/components/common/AppBar.scala @@ -8,6 +8,7 @@ import org.kys.athena.datastructures.Config import org.kys.athena.App import org.kys.athena.routes.OngoingRoute import org.kys.athena.components.common.FocusCapturer._ +import org.kys.athena.util.assets.AssetLoader object AppBar { @@ -36,8 +37,10 @@ object AppBar { }, div( cls := "flex flex-row h-full", + a(cls := "mx-2", href := Config.DISCORD_INVITE_URL, target := "_blank", + img(src := AssetLoader.require("/images/discord_logo.png"), width := "40px")), a(cls := "mx-2", href := "https://github.com/greg2010/Athena", target := "_blank", - ImgSized(s"${Config.FRONTEND_URL}/images/gh_logo.png", 40, Some(40))), + ImgSized(AssetLoader.require("/images/gh_logo.png"), 40, Some(40))), child <-- showSignal.map { case true => FocusCapturer( @@ -59,7 +62,6 @@ object AppBar { case FocusOut => "none" })) case false => div() - }) - ) + })) } } diff --git a/frontend/src/main/scala/org/kys/athena/components/common/SearchBar.scala b/frontend/src/main/scala/org/kys/athena/components/common/SearchBar.scala index 21aa753..9386c0d 100644 --- a/frontend/src/main/scala/org/kys/athena/components/common/SearchBar.scala +++ b/frontend/src/main/scala/org/kys/athena/components/common/SearchBar.scala @@ -4,7 +4,8 @@ package org.kys.athena.components.common import com.raquo.laminar.api.L._ import org.kys.athena.App import org.kys.athena.riot.api.dto.common.Platform -import org.kys.athena.routes.OngoingRoute +import org.kys.athena.routes.{OngoingRoute, PregameRoute} +import org.kys.athena.util.assets.AssetLoader import org.scalajs.dom import org.scalajs.dom.Event @@ -17,7 +18,14 @@ object SearchBar { Observer[dom.Event](onNext = _ => { (platform.now(), summoner.now()) match { case (_, "") => () - case (p, s) => App.pushState(OngoingRoute(p, s)) + case (p, s) => { + val sums = s.split(',').toList + if (sums.length > 1) { + App.pushState(PregameRoute(p, sums)) + } else { + App.pushState(OngoingRoute(p, s)) + } + } } }) @@ -33,7 +41,9 @@ object SearchBar { Some(s"border shadow-lg border-gray-500 p-1 rounded-sm bg-white"), Some("focus:outline-none text-md"), cls := s"px-1 focus:outline-none appearance-none"), - button(`type` := "submit", img(src := "/icons/search.svg", width := "24px", height := "auto")), + button(`type` := "submit", img(src := AssetLoader.require("/icons/search.svg"), + width := "24px", + height := "auto")), onSubmit.preventDefault --> formObserver, mods) } diff --git a/frontend/src/main/scala/org/kys/athena/components/notfound/NotFoundPage.scala b/frontend/src/main/scala/org/kys/athena/components/notfound/NotFoundPage.scala new file mode 100644 index 0000000..16c2a93 --- /dev/null +++ b/frontend/src/main/scala/org/kys/athena/components/notfound/NotFoundPage.scala @@ -0,0 +1,27 @@ +package org.kys.athena.components.notfound + +import com.raquo.laminar.api.L._ +import org.kys.athena.components.common.{AlephEye, Link} +import org.kys.athena.routes.LandingRoute +import org.kys.athena.util.CSSUtil.{paletteContainer, paperCls} + + +object NotFoundPage { + def render(segments: List[String]): HtmlElement = { + div( + cls := s"flex flex-col items-center justify-center p-4 lg:p-8 divide-y divide-gray-500 $paperCls", + backgroundColor := paletteContainer, + AlephEye(), + div( + cls := "flex flex-col justify-center items-center", + span( + cls := "text-xl my-2", + s"Oops! Page /${segments.mkString("/")} is not found. Make sure your URL is correct." + ), + Link( + LandingRoute, + button( + cls := "px-4 py-2 border border-gray-500 rounded-lg mt-2", + "Go home")))) + } +} diff --git a/frontend/src/main/scala/org/kys/athena/components/ongoing/OngoingError.scala b/frontend/src/main/scala/org/kys/athena/components/ongoing/OngoingError.scala index 6676a5c..2218ad9 100644 --- a/frontend/src/main/scala/org/kys/athena/components/ongoing/OngoingError.scala +++ b/frontend/src/main/scala/org/kys/athena/components/ongoing/OngoingError.scala @@ -2,6 +2,7 @@ package org.kys.athena.components.ongoing import com.raquo.laminar.api.L._ import org.kys.athena.routes.OngoingRoute +import org.kys.athena.util.assets.AssetLoader import org.scalajs.dom @@ -10,7 +11,7 @@ object OngoingError { div( cls := s"flex flex-col items-center p-4", img( - src := "/images/amumu_error.png" + src := AssetLoader.require("/images/amumu_error.png") ), span( cls := "text-xl mt-4", diff --git a/frontend/src/main/scala/org/kys/athena/components/ongoing/OngoingNotFound.scala b/frontend/src/main/scala/org/kys/athena/components/ongoing/OngoingNotFound.scala index 36da7e2..95546b8 100644 --- a/frontend/src/main/scala/org/kys/athena/components/ongoing/OngoingNotFound.scala +++ b/frontend/src/main/scala/org/kys/athena/components/ongoing/OngoingNotFound.scala @@ -4,6 +4,7 @@ import com.raquo.airstream.eventbus.EventBus import org.kys.athena.routes.OngoingRoute import com.raquo.laminar.api.L._ import org.kys.athena.components.common.{HistoryMenu, SearchBar} +import org.kys.athena.util.assets.AssetLoader import org.scalajs.dom import org.kys.athena.components.common.FocusCapturer._ import org.kys.athena.components.common.FocusCapturer @@ -15,7 +16,7 @@ object OngoingNotFound { val focusBus = new EventBus[EventFired] div( cls := s"flex flex-col items-center p-4", - img(src := "/images/blitzcrank_logo.png"), + img(src := AssetLoader.require("/images/blitzcrank_logo.png")), span( cls := "text-xl mt-4", "Summoner ", b(s"${p.realm.toString}/${p.name}"), " is not currently in game."), FocusCapturer( diff --git a/frontend/src/main/scala/org/kys/athena/components/ongoing/OngoingPage.scala b/frontend/src/main/scala/org/kys/athena/components/ongoing/OngoingPage.scala index 40a60ba..81e8458 100644 --- a/frontend/src/main/scala/org/kys/athena/components/ongoing/OngoingPage.scala +++ b/frontend/src/main/scala/org/kys/athena/components/ongoing/OngoingPage.scala @@ -3,7 +3,7 @@ package org.kys.athena.components.ongoing import com.raquo.domtypes.generic.keys.{Style => CStyle} import com.raquo.laminar.api.L._ import com.raquo.laminar.nodes.ReactiveHtmlElement -import org.kys.athena.http.Client._ +import org.kys.athena.http.BackendClient._ import org.kys.athena.http.backend.BackendDataHelpers import org.kys.athena.http.dd.CombinedDD import org.kys.athena.http.errors.{BackendApiError, InternalServerError, NotFoundError} @@ -11,12 +11,14 @@ import org.kys.athena.http.models.current._ import org.kys.athena.http.models.premade.{PlayerGroup, PremadeResponse} import org.kys.athena.components.common import org.kys.athena.components.common.{ChampionIcon, ImgSized, OpggLink, UggLink} +import org.kys.athena.http.DDClient import org.kys.athena.routes.OngoingRoute import org.kys.athena.riot.api.dto.common.{GameQueueTypeEnum, Platform} import org.kys.athena.riot.api.dto.currentgameinfo.BannedChampion import org.kys.athena.util.CSSUtil._ import org.kys.athena.datastructures.{DataState, Failed, Infallible, Loading, Ready} import org.kys.athena.datastructures.Config +import org.kys.athena.util.assets.AssetLoader import org.scalajs.dom.html import zio._ @@ -34,7 +36,9 @@ object OngoingPage { def fetchAndWriteDDragon(ddObs: Observer[DataState[CombinedDD]]): IO[BackendApiError, Unit] = { for { dd <- { - ZIO.tupledPar(fetchCachedDDragonChampion(), fetchCachedDDragonRunes(), fetchCachedDDragonSummoners()) + ZIO.tupledPar(DDClient.fetchCachedDDragonChampion(), + DDClient.fetchCachedDDragonRunes(), + DDClient.fetchCachedDDragonSummoners()) .either.map { case Left(ex) => Failed(ex) case Right((c, r, s)) => Ready(CombinedDD(c, r, s)) @@ -312,7 +316,7 @@ object OngoingPage { zIndex := 1, new CStyle("filter", "filter") := "grayscale(50%)", cls := "rounded-lg"), - ImgSized(s"${Config.FRONTEND_URL}/images/slash_red_256.png", + ImgSized(AssetLoader.require("/images/slash_red_256.png"), imgWidth = 64, imgHeight = Some(64), position := "relative", diff --git a/frontend/src/main/scala/org/kys/athena/components/ongoing/OngoingPlayerCard.scala b/frontend/src/main/scala/org/kys/athena/components/ongoing/OngoingPlayerCard.scala index 1dba881..c0ee12d 100644 --- a/frontend/src/main/scala/org/kys/athena/components/ongoing/OngoingPlayerCard.scala +++ b/frontend/src/main/scala/org/kys/athena/components/ongoing/OngoingPlayerCard.scala @@ -4,18 +4,33 @@ import com.raquo.laminar.api.L._ import com.raquo.laminar.nodes.ReactiveHtmlElement import org.kys.athena.http.backend.BackendDataHelpers import org.kys.athena.http.dd.CombinedDD -import org.kys.athena.http.models.current.{InGameSummoner, RankedLeague} +import org.kys.athena.http.models.current.InGameSummoner import org.kys.athena.components.common import org.kys.athena.components.common.{ChampionIcon, ImgSized, OpggLink, UggLink} +import org.kys.athena.http.models.common.RankedLeague import org.kys.athena.riot.api.dto.common.{Platform, SummonerSpellsEnum} import org.kys.athena.riot.api.dto.ddragon.runes.Rune import org.kys.athena.riot.api.dto.league.{MiniSeries, RankedQueueTypeEnum, TierEnum} import org.kys.athena.datastructures.{Infallible, Loading, Ready} import org.kys.athena.datastructures.Config +import org.kys.athena.util.assets.AssetLoader import org.scalajs.dom.html + object OngoingPlayerCard { + // Ugly, but enables webpack's asset importer plugin thus giving more safety. + // Cannot be replaced by a loop as the paths **must** be compile-time literals to be inlined properly. + private val rankedAssets: Map[TierEnum, String] = Map( + (TierEnum.Iron, AssetLoader.require("/images/Emblem_Iron.png")), + (TierEnum.Bronze, AssetLoader.require("/images/Emblem_Bronze.png")), + (TierEnum.Silver, AssetLoader.require("/images/Emblem_Silver.png")), + (TierEnum.Gold, AssetLoader.require("/images/Emblem_Gold.png")), + (TierEnum.Platinum, AssetLoader.require("/images/Emblem_Platinum.png")), + (TierEnum.Diamond, AssetLoader.require("/images/Emblem_Diamond.png")), + (TierEnum.Grandmaster, AssetLoader.require("/images/Emblem_Grandmaster.png")), + (TierEnum.Challenger, AssetLoader.require("/images/Emblem_Challenger.png"))) + private def renderSummonerSpell(ss: SummonerSpellsEnum)(implicit dd: CombinedDD) = { val url = dd.summonerUrlById(ss.value).getOrElse("") ImgSized(url, 32, Some(32), minWidth := "32px", cls := "rounded-md") @@ -90,21 +105,23 @@ object OngoingPlayerCard { cls := "flex flex-col items-center justify-center mr-1", width := "86px", rl match { case Some(l) => { - val t = l.tier.entryName.toLowerCase.capitalize - val url = s"${Config.FRONTEND_URL}/images/Emblem_${t}.png" + val url = rankedAssets.getOrElse(l.tier, AssetLoader.require("/images/Emblem_Unranked.png")) List( ImgSized(url, 40, None), l.tier match { case t if t.in(TierEnum.Master, TierEnum.Grandmaster, TierEnum.Challenger) => { span(cls := "text-xs leading-tight mt-1", s"${t}") } - case _ => span(cls := "text-xs leading-tight mt-1", s"${t} ${l.rank}") + case _ => { + span(cls := "text-xs leading-tight mt-1", + s"${l.tier.entryName.toLowerCase.capitalize} ${l.rank}") + } }, span(cls := "text-xs leading-tight", s"${l.leaguePoints} LP"), l.miniSeries.map(renderMiniSeries).getOrElse(div())) } case None => { - val url = s"${Config.FRONTEND_URL}/images/Emblem_Unranked.png" + val url = AssetLoader.require("/images/Emblem_Unranked.png") List( ImgSized(url, 46, None), span(cls := "text-sm leading-tight mt-1", "Unranked")) diff --git a/frontend/src/main/scala/org/kys/athena/components/pregame/PregamePage.scala b/frontend/src/main/scala/org/kys/athena/components/pregame/PregamePage.scala new file mode 100644 index 0000000..7811e57 --- /dev/null +++ b/frontend/src/main/scala/org/kys/athena/components/pregame/PregamePage.scala @@ -0,0 +1,114 @@ +package org.kys.athena.components.pregame + +import com.raquo.laminar.api.L._ +import org.kys.athena.http.BackendClient.{fetchPregameGameByName, fetchPregameGroupsByName, fetchPregameGroupsByUUID} +import org.kys.athena.http.DDClient +import org.kys.athena.http.dd.CombinedDD +import org.kys.athena.http.errors.{BackendApiError, InternalServerError} +import org.kys.athena.http.models.pregame.PregameResponse +import org.kys.athena.http.models.premade.PlayerGroup +import org.kys.athena.riot.api.dto.common.Platform +import org.kys.athena.routes.PregameRoute +import org.kys.athena.util.CSSUtil.{paletteContainer, paperCls} +import org.kys.athena.datastructures.{DataState, Failed, Loading, Ready} +import zio.{IO, Runtime, UIO, ZIO} + + +object PregamePage { + @SuppressWarnings(Array("org.wartremover.warts.Product", "org.wartremover.warts.Serializable")) + def fetchAndWriteDDragon(ddObs: Observer[DataState[CombinedDD]]): IO[BackendApiError, Unit] = { + for { + dd <- { + ZIO.tupledPar(DDClient.fetchCachedDDragonChampion(), + DDClient.fetchCachedDDragonRunes(), + DDClient.fetchCachedDDragonSummoners()) + .either.map { + case Left(ex) => Failed(ex) + case Right((c, r, s)) => Ready(CombinedDD(c, r, s)) + } + } + _ <- UIO.effectTotal(ddObs.onNext(dd)) + } yield () + } + + @SuppressWarnings(Array("org.wartremover.warts.Product", "org.wartremover.warts.Serializable")) + def fetchAndWriteGameInfo(platform: Platform, + names: List[String], + ongoingObs: Observer[DataState[PregameResponse]], + groupsObs: Observer[DataState[Set[PlayerGroup]]]): IO[BackendApiError, Unit] = { + for { + _ <- UIO.succeed(ongoingObs.onNext(Loading)) + o <- fetchPregameGameByName(platform, names) + .map(r => Ready(r)) + .catchAll(err => UIO.succeed(Failed(err))) + _ <- UIO.effectTotal(ongoingObs.onNext(o)) + + gr <- (o match { + case Failed(_) => IO.fail(InternalServerError("Fetch for players failed, not fetching groups")) + case _ => { + o.map(_.groupUuid).toOption.flatten + .fold(fetchPregameGroupsByName(platform, names))(uuid => fetchPregameGroupsByUUID(uuid)) + } + }).map(r => Ready(r)) + .catchAll(err => UIO.succeed(Failed(err))) + _ <- UIO.effectTotal(groupsObs.onNext(gr)) + } yield () + } + + + def fetchAndWriteAll(platform: Platform, + names: List[String], + ddObs: Observer[DataState[CombinedDD]], + ongoingObs: Observer[DataState[PregameResponse]], + groupsObs: Observer[DataState[Set[PlayerGroup]]]): IO[BackendApiError, Unit] = { + ZIO.tupledPar(fetchAndWriteDDragon(ddObs), fetchAndWriteGameInfo(platform, names, ongoingObs, groupsObs)).unit + } + + + def render(p: PregameRoute): HtmlElement = { + lazy val ddVar = Var[DataState[CombinedDD]](Loading) + lazy val pregameVar = Var[DataState[PregameResponse]](Loading) + lazy val groupsVar = Var[DataState[Set[PlayerGroup]]](Loading) + + val runtime: Runtime[zio.ZEnv] = Runtime.default + + def refreshGame: Unit = { + runtime.unsafeRunAsync_(fetchAndWriteGameInfo(p.realm, + p.names, + pregameVar.writer, + groupsVar.writer)) + } + def refreshAll: Unit = { + runtime.unsafeRunAsync_(fetchAndWriteAll(p.realm, + p.names, + ddVar.writer, + pregameVar.writer, + groupsVar.writer)) + } + + div( + onMountCallback(_ => refreshAll), + cls := s"flex flex-col items-center justify-center lg:px-12 mx-4 my-2 divide-y divide-gray-500 $paperCls", + backgroundColor := paletteContainer, + span(cls := "text-3xl p-2 text-center", s"Pregame lobby of ${p.names.mkString(", ")}"), + div( + cls := s"flex flex-col lg:flex-row items-center justify-center divide-x divide-gray-500", + children <-- pregameVar.signal.map { + case Loading => List(div()) + case Ready(data) => + data.summoners. map { summoner => + div( + cls := "flex flex-col justify-center items-center", + span(summoner.name), + span(summoner.summonerLevel.toString), + div( + summoner.rankedLeagues.map { rl => + div(rl.leagueId) + } + ) + ) + }.toList + } + )) + } +} diff --git a/frontend/src/main/scala/org/kys/athena/datastructures/Config.scala b/frontend/src/main/scala/org/kys/athena/datastructures/Config.scala index 6808902..0b42121 100644 --- a/frontend/src/main/scala/org/kys/athena/datastructures/Config.scala +++ b/frontend/src/main/scala/org/kys/athena/datastructures/Config.scala @@ -17,4 +17,5 @@ object Config extends js.Object { val USE_FAKE_DATA : String = js.native val ANALYTICS_SCRIPT_URL: String = js.native val ANALYTICS_WEBSITE_ID: String = js.native + val DISCORD_INVITE_URL : String = js.native } diff --git a/frontend/src/main/scala/org/kys/athena/http/BackendClient.scala b/frontend/src/main/scala/org/kys/athena/http/BackendClient.scala new file mode 100644 index 0000000..970b7b1 --- /dev/null +++ b/frontend/src/main/scala/org/kys/athena/http/BackendClient.scala @@ -0,0 +1,80 @@ +package org.kys.athena.http + +import org.kys.athena.http.errors._ +import org.kys.athena.http.models.current.OngoingGameResponse +import org.kys.athena.http.models.pregame.PregameResponse +import org.kys.athena.http.models.premade.{PlayerGroup, PremadeResponse} +import org.kys.athena.http.routes.Endpoints +import org.kys.athena.riot.api.dto.common.Platform +import org.kys.athena.datastructures.Config +import sttp.client3._ +import sttp.model.Uri +import sttp.tapir.{DecodeResult, Endpoint} +import zio.{IO, UIO} +import sttp.tapir.client.sttp.SttpClientInterpreter + +import java.util.UUID + + +object BackendClient { + + private def liftErrors[T](r: DecodeResult[Either[BackendApiError, T]]): IO[BackendApiError, T] = { + r match { + case _: DecodeResult.Failure => IO.fail(InternalServerError(s"Unknown decoding error")) + case DecodeResult.Value(Left(err)) => IO.fail(err) + case DecodeResult.Value(Right(res)) => IO.succeed(res) + } + } + + private val cb = TaskFetchBackend() + + private def fetchAndLift[T](req: Request[DecodeResult[Either[BackendApiError, T]], Any]) + : IO[BackendApiError, T] = { + UIO.effectTotal(scribe.info(s"Sending request to url=${req.uri}")).zipRight { + cb.send(req).mapError { err => + InternalServerError(s"Failed to fetch response from server, error=${err.getMessage}") + }.flatMap(r => liftErrors(r.body)) + } + } + + private val debug = Config.USE_FAKE_DATA match { + case "true" => true + case _ => false + } + + val serverBaseUrl: Option[Uri] = Some(uri"${Config.BACKEND_API_URL}") + + def interpret[I, E, O](endpoint: Endpoint[I, E, O, Any]): I => Request[DecodeResult[Either[E, O]], Any] = { + SttpClientInterpreter.toRequest(endpoint, serverBaseUrl) + } + + def fetchOngoingGameByName(realm: Platform, name: String): IO[BackendApiError, OngoingGameResponse] = { + val q = interpret(Endpoints.currentGameByName) + fetchAndLift(q((realm, name, Some(true), None))) + } + + def fetchGroupsByUUID(uuid: UUID): IO[BackendApiError, PremadeResponse] = { + val q = interpret(Endpoints.currentGameGroupsByUUID) + fetchAndLift(q((uuid, None))) + } + + def fetchGroupsByName(realm: Platform, name: String): IO[BackendApiError, PremadeResponse] = { + val q = interpret(Endpoints.currentGameGroupsByName) + fetchAndLift(q((realm, name, None))) + } + + def fetchPregameGameByName(realm: Platform, names: List[String]): IO[BackendApiError, PregameResponse] = { + val q = interpret(Endpoints.pregameByName) + fetchAndLift(q((realm, names.toSet, Some(true), None))) + } + + def fetchPregameGroupsByUUID(uuid: UUID): IO[BackendApiError, Set[PlayerGroup]] = { + val q = interpret(Endpoints.pregameGameGroupsByUUID) + fetchAndLift(q((uuid, None))) + } + + def fetchPregameGroupsByName(realm: Platform, names: List[String]): IO[BackendApiError, Set[PlayerGroup]] = { + val q = interpret(Endpoints.pregameGroupsByName) + fetchAndLift(q((realm, names.toSet, None))) + } +} diff --git a/frontend/src/main/scala/org/kys/athena/http/Client.scala b/frontend/src/main/scala/org/kys/athena/http/DDClient.scala similarity index 69% rename from frontend/src/main/scala/org/kys/athena/http/Client.scala rename to frontend/src/main/scala/org/kys/athena/http/DDClient.scala index 343d438..358dc94 100644 --- a/frontend/src/main/scala/org/kys/athena/http/Client.scala +++ b/frontend/src/main/scala/org/kys/athena/http/DDClient.scala @@ -3,10 +3,7 @@ package org.kys.athena.http import io.circe.generic.auto._ import io.circe.{Decoder, Encoder} import org.kys.athena.http.errors._ -import org.kys.athena.http.models.current.OngoingGameResponse -import org.kys.athena.http.models.premade.PremadeResponse import org.kys.athena.riot.api.RequestError -import org.kys.athena.riot.api.dto.common.Platform import org.kys.athena.riot.api.dto.ddragon.champions.Champions import org.kys.athena.riot.api.dto.ddragon.runes.RuneTree import org.kys.athena.riot.api.dto.ddragon.summonerspells.SummonerSpells @@ -16,12 +13,9 @@ import sttp.client3._ import sttp.client3.circe._ import sttp.model.StatusCode import zio.{IO, UIO} - -import java.util.UUID import scala.concurrent.duration.Duration - -object Client { +object DDClient { private def liftErrors[T](r: Response[Either[RequestError, T]]): IO[BackendApiError, T] = { r.body match { case Left(HttpError(_, statusCode)) => { @@ -79,39 +73,6 @@ object Client { } } - private val debug = Config.USE_FAKE_DATA match { - case "true" => true - case _ => false - } - - def fetchOngoingGameByName(realm: Platform, name: String): IO[BackendApiError, OngoingGameResponse] = { - val url = if (!debug) - uri"${Config.BACKEND_API_URL}/current/by-summoner-name/${realm.entryName}/$name?fetchGroups=true" - else uri"http://localhost:8080/sampleongoing.json" - val q = basicRequest - .get(url) - .response(asJson[OngoingGameResponse]) - fetchAndLift(q) - } - - def fetchGroupsByUUID(uuid: UUID): IO[BackendApiError, PremadeResponse] = { - val url = if (!debug) - uri"${Config.BACKEND_API_URL}/current/by-uuid/${uuid}/groups" - else uri"http://localhost:8080/samplepremades.json" - val q = basicRequest.get(url) - .response(asJson[PremadeResponse]) - fetchAndLift(q) - } - - def fetchGroupsByName(realm: Platform, name: String): IO[BackendApiError, PremadeResponse] = { - val url = if (!debug) - uri"${Config.BACKEND_API_URL}/current/by-summoner-name/${realm.entryName}/$name/groups" - else uri"http://localhost:8080/samplepremades.json" - val q = basicRequest.get(url) - .response(asJson[PremadeResponse]) - fetchAndLift(q) - } - def fetchCachedDDragonChampion(): IO[BackendApiError, Champions] = { val url = uri"${Config.DDRAGON_BASE_URL}${Config.DDRAGON_VERSION}/data/${Config.LOCALE}/champion.json" val q = basicRequest.get(url).response(asJson[Champions]) diff --git a/frontend/src/main/scala/org/kys/athena/http/backend/BackendDataHelpers.scala b/frontend/src/main/scala/org/kys/athena/http/backend/BackendDataHelpers.scala index 18e648c..70311d5 100644 --- a/frontend/src/main/scala/org/kys/athena/http/backend/BackendDataHelpers.scala +++ b/frontend/src/main/scala/org/kys/athena/http/backend/BackendDataHelpers.scala @@ -1,6 +1,6 @@ package org.kys.athena.http.backend -import org.kys.athena.http.models.current.RankedLeague +import org.kys.athena.http.models.common.RankedLeague import org.kys.athena.riot.api.dto.league.RankedQueueTypeEnum diff --git a/frontend/src/main/scala/org/kys/athena/routes/package.scala b/frontend/src/main/scala/org/kys/athena/routes/package.scala index af2f325..71011be 100644 --- a/frontend/src/main/scala/org/kys/athena/routes/package.scala +++ b/frontend/src/main/scala/org/kys/athena/routes/package.scala @@ -11,16 +11,21 @@ package object routes { } final case object LandingRoute extends PageRoute { - override val title = "Athena" + override val title = "Athena - Landing Page" } - final case object RouteNotFound extends PageRoute { + final case class RouteNotFound(restOfSegments: List[String]) extends PageRoute { override val title: String = "Athena - Page not Found" } + final case class ErrorRoute(msg: String) extends PageRoute { override val title: String = "Athena - Error" } final case class OngoingRoute(realm: Platform, name: String) extends PageRoute { - override val title: String = s"Athena - $realm/$name" + override val title: String = s"Athena - Current game of $realm/$name" + } + + final case class PregameRoute(realm: Platform, names: List[String]) extends PageRoute { + override val title: String = "Athena - Pregame Lobby" } implicit val codecPage = deriveCodec[PageRoute] diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index 9227e21..32d2993 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -7,8 +7,8 @@ const CopyWebpackPlugin = require('copy-webpack-plugin'); const {CleanWebpackPlugin} = require('clean-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin') -const scalaOutputPath = path.resolve(__dirname, './target/scala-2.13'); -const scalaResourcesPath = path.resolve(__dirname, './src/main/resources') +const scalaOutputPath = path.resolve(__dirname, 'target/scala-2.13'); +const scalaResourcesPath = path.resolve(__dirname, 'src/main/resources') const devServerPort = 8080; @@ -24,7 +24,7 @@ const devServer = { historyApiFallback: { index: '' }, - stats: {warnings: false} + //stats: {warnings: false} }; function common(variables, mode) { @@ -38,7 +38,7 @@ function common(variables, mode) { }, output: { publicPath: '/', - filename: '[name].[hash].js', + filename: '[name].[fullhash].js', library: 'app', libraryTarget: 'var' }, @@ -46,7 +46,7 @@ function common(variables, mode) { path.resolve(scalaResourcesPath, './index.css') ], module: { - rules: [{ + rules: [/*{ test: /\.js$/, use: [{ loader: "scalajs-friendly-source-map-loader", @@ -54,14 +54,14 @@ function common(variables, mode) { name: '[name].[contenthash:8].[ext]', skipFileURLWarnings: true, // or false, default is true bundleHttp: true, // or false, default is true, - cachePath: ".scala-js-sources", // cache dir name, exclude in .gitignore + cachePath: path.resolve(__dirname, ".scala-js-sources"), // cache dir name, exclude in .gitignore noisyCache: false, // whether http cache changes are output useCache: true, // false => remove any http cache processing } }], enforce: "pre", include: [scalaOutputPath], - }, + },*/ { test: /\.js$/, use: ["source-map-loader"], @@ -111,13 +111,7 @@ function common(variables, mode) { }, { test: /\.(woff(2)?|ttf|eot|svg|png|jpg|ico|txt|json)(\?v=\d+\.\d+\.\d+)?$/, - use: [{ - loader: 'file-loader', - options: { - name: '[name].[ext]', - outputPath: './' - } - }] + type: 'asset' } ] }, @@ -131,7 +125,7 @@ function common(variables, mode) { }), new ExtractCssChunks({ - filename: '[name].[hash].css', + filename: '[name].[fullhash].css', chunkFilename: '[id].css' }), new CopyWebpackPlugin({ diff --git a/frontend/yarn.lock b/frontend/yarn.lock index d38da99..0aa39f3 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -10,15 +10,15 @@ "@babel/highlight" "^7.12.13" "@babel/core@>=7.9.0", "@babel/core@^7.1.0", "@babel/core@^7.7.5": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.13.tgz#b73a87a3a3e7d142a66248bf6ad88b9ceb093425" - integrity sha512-BQKE9kXkPlXHPeqissfxo0lySWJcYdEP0hdtJOH/iJfDdhOCcgtNCjftCJg3qqauB4h+lz2N6ixM++b9DN1Tcw== + version "7.12.16" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.16.tgz#8c6ba456b23b680a6493ddcfcd9d3c3ad51cab7c" + integrity sha512-t/hHIB504wWceOeaOoONOhu+gX+hpjfeN6YRBT209X/4sibZQfSF1I0HFRRlBe97UZZosGx5XwUg1ZgNbelmNw== dependencies: "@babel/code-frame" "^7.12.13" - "@babel/generator" "^7.12.13" + "@babel/generator" "^7.12.15" "@babel/helper-module-transforms" "^7.12.13" "@babel/helpers" "^7.12.13" - "@babel/parser" "^7.12.13" + "@babel/parser" "^7.12.16" "@babel/template" "^7.12.13" "@babel/traverse" "^7.12.13" "@babel/types" "^7.12.13" @@ -30,7 +30,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.12.13": +"@babel/generator@^7.12.13", "@babel/generator@^7.12.15": version "7.12.15" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.15.tgz#4617b5d0b25cc572474cc1aafee1edeaf9b5368f" integrity sha512-6F2xHxBiFXWNSGb7vyCUTBF8RCLY66rS0zEPcP8t/nQyXjha5EuK4z7H5o7fWG8B4M7y6mqVWq1J+1PuwRhecQ== @@ -56,9 +56,9 @@ "@babel/types" "^7.12.13" "@babel/helper-member-expression-to-functions@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.13.tgz#c5715695b4f8bab32660dbdcdc2341dec7e3df40" - integrity sha512-B+7nN0gIL8FZ8SvMcF+EPyB21KnCcZHQZFczCxbiNGV/O0rsrSBlWGLzmtBJ3GMjSVMIm4lpFhR+VdVBuIsUcQ== + version "7.12.16" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.16.tgz#41e0916b99f8d5f43da4f05d85f4930fa3d62b22" + integrity sha512-zYoZC1uvebBFmj1wFAlXwt35JLEgecefATtKp20xalwEK8vHAixLBXTGxNrVGEmTT+gzOThUgr8UEdgtalc1BQ== dependencies: "@babel/types" "^7.12.13" @@ -143,10 +143,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.12.13": - version "7.12.15" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.15.tgz#2b20de7f0b4b332d9b119dd9c33409c538b8aacf" - integrity sha512-AQBOU2Z9kWwSZMd6lNjCX0GUgFonL1wAM1db8L8PMk9UDaGsRCArBkU4Sc+UCM3AE4hjbXx+h58Lb3QT4oRmrA== +"@babel/parser@^7.1.0", "@babel/parser@^7.12.13", "@babel/parser@^7.12.16": + version "7.12.16" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.16.tgz#cc31257419d2c3189d394081635703f549fc1ed4" + integrity sha512-c/+u9cqV6F0+4Hpq01jnJO+GLp2DdT63ppz9Xa+6cHaajM9VFzK/iDXiKK65YtpeVwu+ctfS6iqlMqRgQRzeCw== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -302,9 +302,9 @@ resolve-from "^5.0.0" "@istanbuljs/schema@^0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" - integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== "@jest/console@^26.6.2": version "26.6.2" @@ -595,9 +595,9 @@ "@types/node" "*" "@types/graceful-fs@^4.1.2": - version "4.1.4" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.4.tgz#4ff9f641a7c6d1a3508ff88bc3141b152772e753" - integrity sha512-mWA/4zFQhfvOA8zWkXobwJvBD7vzcxgrOQ0J5CH1votGqdq9m7+FwtGaqyCZqC3NyyBkc9z4m+iry4LlqcMWJg== + version "4.1.5" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" + integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== dependencies: "@types/node" "*" @@ -648,9 +648,9 @@ integrity sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg== "@types/node@*": - version "14.14.25" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.25.tgz#15967a7b577ff81383f9b888aa6705d43fbbae93" - integrity sha512-EPpXLOVqDvisVxtlbvzfyqSsFeQxltFbluZNRndIb8tr9KiBnYNLzrc1N3pyKUCww2RNrfHDViqDWWE1LCJQtQ== + version "14.14.28" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.28.tgz#cade4b64f8438f588951a6b35843ce536853f25b" + integrity sha512-lg55ArB+ZiHHbBBttLpzD07akz0QPrZgUODNakeC09i62dnrywr9mFErHuaPlB6I7z+sEbK+IYmplahvplCj2g== "@types/normalize-package-data@^2.4.0": version "2.4.0" @@ -663,9 +663,9 @@ integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== "@types/prettier@^2.0.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.0.tgz#a4e8205a4955690eef712a6d0394a1d2e121e721" - integrity sha512-O3SQC6+6AySHwrspYn2UvC6tjo6jCTMMmylxZUFhE1CulVu5l3AxU6ca9lrJDTQDVllF62LIxVSx5fuYL6LiZg== + version "2.2.1" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.1.tgz#374e31645d58cb18a07b3ecd8e9dede4deb2cccd" + integrity sha512-DxZZbyMAM9GWEzXL+BMZROWz9oo6A9EilwwOMET2UVu2uZTqMWS5S69KVtuVKaRjCUpcrOXRalet86/OpG4kqw== "@types/q@^1.5.1": version "1.5.4" @@ -951,9 +951,9 @@ ajv@^6.1.0, ajv@^6.12.3, ajv@^6.12.5: uri-js "^4.2.2" ajv@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-7.0.4.tgz#827e5f5ae32f5e5c1637db61f253a112229b5e2f" - integrity sha512-xzzzaqgEQfmuhbhAoqjJ8T/1okb6gAzXn/eQRNpAN1AEUoHJTNF9xCDRTtf/s3SKldtZfa+RJeTs+BQq+eZ/sw== + version "7.1.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-7.1.1.tgz#1e6b37a454021fa9941713f38b952fc1c8d32a84" + integrity sha512-ga/aqDYnUy/o7vbsRTFhhTsNeXiYb5JWDIcRIeZfwRNCefwjNTVYCGdGSUrEmiu3yDK3vFvNbgJxvrQW4JXrYQ== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" @@ -2070,9 +2070,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001181: - version "1.0.30001185" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001185.tgz#3482a407d261da04393e2f0d61eefbc53be43b95" - integrity sha512-Fpi4kVNtNvJ15H0F6vwmXtb3tukv3Zg3qhKkOGUq7KJ1J6b9kf4dnNgtEAFXhRsJo0gNj9W60+wBvn0JcTvdTg== + version "1.0.30001187" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001187.tgz#5706942631f83baa5a0218b7dfa6ced29f845438" + integrity sha512-w7/EP1JRZ9552CyrThUnay2RkZ1DXxKe/Q2swTC4+LElLh9RRYrL1Z+27LlakB8kzY0fSmHw9mc7XYDUKAKWMA== capture-exit@^2.0.0: version "2.0.0" @@ -2334,9 +2334,9 @@ commander@^6.0.0: integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== commander@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.0.0.tgz#3e2bbfd8bb6724760980988fb5b22b7ee6b71ab2" - integrity sha512-ovx/7NkTrnPuIV8sqk/GjUIIM1+iUQeqA3ye2VNpq9sVoiZsooObWlQy+OPWGI17GDaEoybuAGJm6U8yC077BA== + version "7.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.1.0.tgz#f2eaecf131f10e36e07d894698226e36ae0eb5ff" + integrity sha512-pRxBna3MJe6HKnBGsDyMv8ETbptw3axEdYHoqNh7gu5oDcew8fs0xnivZGm06Ogk8zGAJ9VX+OPEr2GXEQK4dg== component-emitter@^1.2.1: version "1.3.0" @@ -2957,9 +2957,9 @@ ee-first@1.1.1: integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= electron-to-chromium@^1.3.47, electron-to-chromium@^1.3.649: - version "1.3.662" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.662.tgz#43305bcf88a3340feb553b815d6fd7466659d5ee" - integrity sha512-IGBXmTGwdVGUVTnZ8ISEvkhDfhhD+CDFndG4//BhvDcEtPYiVrzoB+rzT/Y12OQCf5bvRCrVmrUbGrS9P7a6FQ== + version "1.3.667" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.667.tgz#18ca4f243ec163c3e354e506ba22ef46d31d925e" + integrity sha512-Ot1pPtAVb5nd7jeVF651zmfLFilRVFomlDzwXmdlWe5jyzOGa6mVsQ06XnAurT7wWfg5VEIY+LopbAdD/bpo5w== emittery@^0.7.1: version "0.7.2" @@ -3972,9 +3972,9 @@ html-tags@^3.1.0: integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg== html-webpack-plugin@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.0.0.tgz#457a9defb33ce368135078b4e0387a27f3fe244d" - integrity sha512-kxTyb8cyZwEyUqXTgdHRUOF4C7uCrquzw2T+YTudehm/yspodgCkREjdmc4dXI8k2P4NEjqOVbnOOlPZg4TKJA== + version "5.1.0" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.1.0.tgz#1c11bbe01ab9d1262c4b601edebcf394364b1f60" + integrity sha512-2axkp+2NHmvHUWrKe1dY4LyM3WatQEdFChr42OY7R/Ad7f0AQzaKscGCcqN/FtQBxo8rdfJP7M3RMFDttqok3g== dependencies: "@types/html-minifier-terser" "^5.0.0" html-minifier-terser "^5.0.1" @@ -4311,7 +4311,7 @@ is-color-stop@^1.0.0: rgb-regex "^1.0.1" rgba-regex "^1.0.0" -is-core-module@^2.1.0: +is-core-module@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== @@ -5517,17 +5517,17 @@ micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.0.5" -mime-db@1.45.0, "mime-db@>= 1.43.0 < 2": - version "1.45.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" - integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w== +mime-db@1.46.0, "mime-db@>= 1.43.0 < 2": + version "1.46.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee" + integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ== mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: - version "2.1.28" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd" - integrity sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ== + version "2.1.29" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.29.tgz#1d4ab77da64b91f5f72489df29236563754bb1b2" + integrity sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ== dependencies: - mime-db "1.45.0" + mime-db "1.46.0" mime@1.6.0: version "1.6.0" @@ -5535,9 +5535,9 @@ mime@1.6.0: integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== mime@^2.4.4: - version "2.5.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.0.tgz#2b4af934401779806ee98026bb42e8c1ae1876b1" - integrity sha512-ft3WayFSFUVBuJj7BMLKAQcSlItKtfjsKDDsii3rqFDAZ7t11zRe8ASw/GlmivGwVUYtwkQrxiGGpL6gFvB0ag== + version "2.5.2" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" + integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== mimic-fn@^2.1.0: version "2.1.0" @@ -7221,11 +7221,11 @@ resolve-url@^0.2.1: integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= resolve@^1.1.7, resolve@^1.10.0, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.8.1, resolve@^1.9.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" - integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== dependencies: - is-core-module "^2.1.0" + is-core-module "^2.2.0" path-parse "^1.0.6" ret@~0.1.10: @@ -8180,9 +8180,9 @@ terser@^4.6.3: source-map-support "~0.5.12" terser@^5.5.1: - version "5.5.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.5.1.tgz#540caa25139d6f496fdea056e414284886fb2289" - integrity sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ== + version "5.6.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.6.0.tgz#138cdf21c5e3100b1b3ddfddf720962f88badcd2" + integrity sha512-vyqLMoqadC1uR0vywqOZzriDYzgEkNJFK4q9GeyOBHIbiECHiWLKcWfbQWAUaPfxkjDhapSlZB9f7fkMrvkVjA== dependencies: commander "^2.20.0" source-map "~0.7.2" @@ -8476,9 +8476,9 @@ urix@^0.1.0: integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= url-parse@^1.4.3, url-parse@^1.4.7: - version "1.4.7" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" - integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== + version "1.5.0" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.0.tgz#90aba6c902aeb2d80eac17b91131c27665d5d828" + integrity sha512-9iT6N4s93SMfzunOyDPe4vo4nLcSu1yq0IQK1gURmjm8tQNlM6loiuCRrKG1hHGXfB2EWd6H4cGi7tGdaygMFw== dependencies: querystringify "^2.1.1" requires-port "^1.0.0" @@ -8739,9 +8739,9 @@ webpack-sources@^2.1.1: source-map "^0.6.1" webpack@^5.6.0: - version "5.21.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.21.2.tgz#647507e50d3637695be28af58a6a8246050394e7" - integrity sha512-xHflCenx+AM4uWKX71SWHhxml5aMXdy2tu/vdi4lClm7PADKxlyDAFFN1rEFzNV0MAoPpHtBeJnl/+K6F4QBPg== + version "5.22.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.22.0.tgz#8505158bc52dcbbdb01ac94796a8aed61badf11a" + integrity sha512-xqlb6r9RUXda/d9iA6P7YRTP1ChWeP50TEESKMMNIg0u8/Rb66zN9YJJO7oYgJTRyFyYi43NVC5feG45FSO1vQ== dependencies: "@types/eslint-scope" "^3.7.0" "@types/estree" "^0.0.46" @@ -8930,9 +8930,9 @@ yargs-parser@^18.1.2: decamelize "^1.2.0" yargs-parser@^20.2.3: - version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + version "20.2.5" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.5.tgz#5d37729146d3f894f39fc94b6796f5b239513186" + integrity sha512-jYRGS3zWy20NtDtK2kBgo/TlAoy5YUuhD9/LZ7z7W4j1Fdw2cqD0xEEclf8fxc8xjD6X5Qr+qQQwCEsP8iRiYg== yargs@^13.3.2: version "13.3.2" diff --git a/macro/src/main/scala/org/kys/athena/util/assets/AssetLoader.scala b/macro/src/main/scala/org/kys/athena/util/assets/AssetLoader.scala new file mode 100644 index 0000000..aadc4b3 --- /dev/null +++ b/macro/src/main/scala/org/kys/athena/util/assets/AssetLoader.scala @@ -0,0 +1,34 @@ +package org.kys.athena.util.assets + +import scala.annotation.compileTimeOnly +import scala.language.experimental.macros +import scala.reflect.macros.blackbox + + +@compileTimeOnly("enable macro paradise to expand macro annotations") +object AssetLoader { + def test: Unit = {} + def require(path: String): String = macro requireImpl + + def requireImpl(c: blackbox.Context)(path: c.Expr[String]): c.Expr[String] = { + import c.universe._ + def eval[B](tree: Tree): B = c.eval[B](c.Expr[B](c.untypecheck(tree.duplicate))) + try { + val pathStr = eval[String](path.tree) + val expr = c.Expr[String](Literal(Constant(pathStr))) + reify { + JSImporter.require[String](s"../../src/main/resources" + expr.splice) + } + } catch { + case e: Throwable => + c.abort(c.enclosingPosition, + s""" + |Exception during require macro expansion. + |This method cannot be called with values not known at compile-time. + |This will produce an invalid JS require that will likely break things. + |Caused by: $e + |""".stripMargin) + } + } + +} diff --git a/macro/src/main/scala/org/kys/athena/util/assets/JSImporter.scala b/macro/src/main/scala/org/kys/athena/util/assets/JSImporter.scala new file mode 100644 index 0000000..0498a8d --- /dev/null +++ b/macro/src/main/scala/org/kys/athena/util/assets/JSImporter.scala @@ -0,0 +1,11 @@ +package org.kys.athena.util.assets + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSGlobalScope + + +@js.native +@JSGlobalScope +object JSImporter extends js.Any { + def require[A](path: String): A = js.native +} diff --git a/project/plugins.sbt b/project/plugins.sbt index 8be44e5..ec4c5d2 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,5 +2,7 @@ addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0") addSbtPlugin("org.wartremover" % "sbt-wartremover" % "2.4.13") addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.5.1") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.4.0") -addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") \ No newline at end of file +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.0") +addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") + +addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.4.8") \ No newline at end of file