use typed actors (#12)

* initial typed actor impl
* fix sending response on ask
This commit is contained in:
Evgenii Alekseev 2020-12-04 11:45:23 +03:00 committed by GitHub
parent 2e16a8c1fa
commit 8d516cdb15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
77 changed files with 849 additions and 700 deletions

View File

@ -1,19 +1,33 @@
val AkkaVersion = "2.6.10"
val AkkaHttpVersion = "10.2.1"
val SlickVersion = "3.3.3"
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3" libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3"
libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.2" libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.2"
libraryDependencies += "com.typesafe.akka" %% "akka-http" % "10.1.10" libraryDependencies += "com.typesafe.akka" %% "akka-http" % AkkaHttpVersion
libraryDependencies += "com.typesafe.akka" %% "akka-http-spray-json" % "10.1.10" libraryDependencies += "com.typesafe.akka" %% "akka-http-spray-json" % AkkaHttpVersion
libraryDependencies += "com.typesafe.akka" %% "akka-stream" % "2.5.23" libraryDependencies += "com.typesafe.akka" %% "akka-actor-typed" % AkkaVersion
libraryDependencies += "com.github.swagger-akka-http" %% "swagger-akka-http" % "2.0.4" libraryDependencies += "com.typesafe.akka" %% "akka-stream" % AkkaVersion
libraryDependencies += "com.github.swagger-akka-http" %% "swagger-akka-http" % "2.3.0"
libraryDependencies += "javax.ws.rs" % "javax.ws.rs-api" % "2.1.1" libraryDependencies += "javax.ws.rs" % "javax.ws.rs-api" % "2.1.1"
libraryDependencies += "io.spray" %% "spray-json" % "1.3.5" libraryDependencies += "io.spray" %% "spray-json" % "1.3.6"
libraryDependencies += "com.lihaoyi" %% "scalatags" % "0.7.0" libraryDependencies += "com.lihaoyi" %% "scalatags" % "0.9.2"
libraryDependencies += "com.typesafe.slick" %% "slick" % "3.3.2" libraryDependencies += "com.typesafe.slick" %% "slick" % SlickVersion
libraryDependencies += "com.typesafe.slick" %% "slick-hikaricp" % "3.3.2" libraryDependencies += "com.typesafe.slick" %% "slick-hikaricp" % SlickVersion
libraryDependencies += "org.flywaydb" % "flyway-core" % "6.0.6" libraryDependencies += "org.flywaydb" % "flyway-core" % "6.0.6"
libraryDependencies += "org.xerial" % "sqlite-jdbc" % "3.28.0" libraryDependencies += "org.xerial" % "sqlite-jdbc" % "3.32.3.2"
libraryDependencies += "org.postgresql" % "postgresql" % "9.3-1104-jdbc4" libraryDependencies += "org.postgresql" % "postgresql" % "42.2.18"
libraryDependencies += "org.mindrot" % "jbcrypt" % "0.3m" libraryDependencies += "org.mindrot" % "jbcrypt" % "0.3m"
// testing
libraryDependencies += "org.scalactic" %% "scalactic" % "3.1.4" % "test"
libraryDependencies += "org.scalatest" %% "scalatest" % "3.1.4" % "test"
libraryDependencies += "com.typesafe.akka" %% "akka-actor-testkit-typed" % AkkaVersion % "test"
libraryDependencies += "com.typesafe.akka" %% "akka-stream-testkit" % AkkaVersion % "test"
libraryDependencies += "com.typesafe.akka" %% "akka-http-testkit" % AkkaHttpVersion % "test"

View File

@ -54,12 +54,6 @@ me.arcanis.ffxivbis {
port = 8000 port = 8000
# hostname to use in docs, if not set host:port will be used # hostname to use in docs, if not set host:port will be used
#hostname = "127.0.0.1:8000" #hostname = "127.0.0.1:8000"
# rate limits
limits {
intetval = 1m
max-count = 60
}
} }
default-dispatcher { default-dispatcher {

View File

@ -8,46 +8,53 @@
*/ */
package me.arcanis.ffxivbis package me.arcanis.ffxivbis
import akka.actor.{Actor, Props} import akka.actor.typed.{Behavior, PostStop, Signal}
import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors}
import akka.http.scaladsl.Http import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer import akka.stream.Materializer
import com.typesafe.scalalogging.StrictLogging import com.typesafe.scalalogging.StrictLogging
import me.arcanis.ffxivbis.http.RootEndpoint import me.arcanis.ffxivbis.http.RootEndpoint
import me.arcanis.ffxivbis.service.bis.BisProvider import me.arcanis.ffxivbis.service.bis.BisProvider
import me.arcanis.ffxivbis.service.impl.DatabaseImpl import me.arcanis.ffxivbis.service.{Database, PartyService}
import me.arcanis.ffxivbis.service.PartyService
import me.arcanis.ffxivbis.storage.Migration import me.arcanis.ffxivbis.storage.Migration
import scala.concurrent.duration.Duration import scala.concurrent.ExecutionContext
import scala.concurrent.{Await, ExecutionContext}
import scala.util.{Failure, Success}
class Application extends Actor with StrictLogging { class Application(context: ActorContext[Nothing])
implicit private val executionContext: ExecutionContext = context.system.dispatcher extends AbstractBehavior[Nothing](context) with StrictLogging {
implicit private val materializer: ActorMaterializer = ActorMaterializer()
private val config = context.system.settings.config logger.info("root supervisor started")
private val host = config.getString("me.arcanis.ffxivbis.web.host") startApplication()
private val port = config.getInt("me.arcanis.ffxivbis.web.port")
override def receive: Receive = Actor.emptyBehavior override def onMessage(msg: Nothing): Behavior[Nothing] = Behaviors.unhandled
Migration(config).onComplete { override def onSignal: PartialFunction[Signal, Behavior[Nothing]] = {
case Success(_) => case PostStop =>
val bisProvider = context.system.actorOf(BisProvider.props, "bis-provider") logger.info("root supervisor stopped")
val storage = context.system.actorOf(DatabaseImpl.props, "storage") Behaviors.same
val party = context.system.actorOf(PartyService.props(storage), "party") }
val http = new RootEndpoint(context.system, party, bisProvider)
logger.info(s"start server at $host:$port") private def startApplication(): Unit = {
val bind = Http()(context.system).bindAndHandle(http.route, host, port) val config = context.system.settings.config
Await.result(context.system.whenTerminated, Duration.Inf) val host = config.getString("me.arcanis.ffxivbis.web.host")
bind.foreach(_.unbind()) val port = config.getInt("me.arcanis.ffxivbis.web.port")
case Failure(exception) => throw exception implicit val executionContext: ExecutionContext = context.system.executionContext
implicit val materializer: Materializer = Materializer(context)
Migration(config)
val bisProvider = context.spawn(BisProvider(), "bis-provider")
val storage = context.spawn(Database(), "storage")
val party = context.spawn(PartyService(storage), "party")
val http = new RootEndpoint(context.system, party, bisProvider)
Http()(context.system).newServerAt(host, port).bindFlow(http.route)
} }
} }
object Application { object Application {
def props: Props = Props(new Application)
def apply(): Behavior[Nothing] =
Behaviors.setup[Nothing](context => new Application(context))
} }

View File

@ -8,13 +8,13 @@
*/ */
package me.arcanis.ffxivbis package me.arcanis.ffxivbis
import akka.actor.ActorSystem import akka.actor.typed.ActorSystem
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
object ffxivbis { object ffxivbis {
def main(args: Array[String]): Unit = { def main(args: Array[String]): Unit = {
val config = ConfigFactory.load() val config = ConfigFactory.load()
val actorSystem = ActorSystem("ffxivbis", config) ActorSystem[Nothing](Application(), "ffxivbis", config)
actorSystem.actorOf(Application.props, "ffxivbis")
} }
} }

View File

@ -8,22 +8,22 @@
*/ */
package me.arcanis.ffxivbis.http package me.arcanis.ffxivbis.http
import akka.actor.ActorRef import akka.actor.typed.scaladsl.AskPattern.Askable
import akka.actor.typed.{ActorRef, Scheduler}
import akka.http.scaladsl.model.headers._ import akka.http.scaladsl.model.headers._
import akka.http.scaladsl.server.AuthenticationFailedRejection._ import akka.http.scaladsl.server.AuthenticationFailedRejection._
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server._ import akka.http.scaladsl.server._
import akka.pattern.ask
import akka.util.Timeout import akka.util.Timeout
import me.arcanis.ffxivbis.models.{Permission, User} import me.arcanis.ffxivbis.messages.{GetUser, Message}
import me.arcanis.ffxivbis.service.impl.DatabaseUserHandler import me.arcanis.ffxivbis.models.Permission
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
// idea comes from https://synkre.com/bcrypt-for-akka-http-password-encryption/ // idea comes from https://synkre.com/bcrypt-for-akka-http-password-encryption/
trait Authorization { trait Authorization {
def storage: ActorRef def storage: ActorRef[Message]
def authenticateBasicBCrypt[T](realm: String, def authenticateBasicBCrypt[T](realm: String,
authenticate: (String, String) => Future[Option[T]]): Directive1[T] = { authenticate: (String, String) => Future[Option[T]]): Directive1[T] = {
@ -40,21 +40,21 @@ trait Authorization {
} }
def authenticator(scope: Permission.Value, partyId: String)(username: String, password: String) def authenticator(scope: Permission.Value, partyId: String)(username: String, password: String)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Option[String]] = (implicit executionContext: ExecutionContext, timeout: Timeout, scheduler: Scheduler): Future[Option[String]] =
(storage ? DatabaseUserHandler.GetUser(partyId, username)).mapTo[Option[User]].map { storage.ask(GetUser(partyId, username, _)).map {
case Some(user) if user.verify(password) && user.verityScope(scope) => Some(username) case Some(user) if user.verify(password) && user.verityScope(scope) => Some(username)
case _ => None case _ => None
} }
def authAdmin(partyId: String)(username: String, password: String) def authAdmin(partyId: String)(username: String, password: String)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Option[String]] = (implicit executionContext: ExecutionContext, timeout: Timeout, scheduler: Scheduler): Future[Option[String]] =
authenticator(Permission.admin, partyId)(username, password) authenticator(Permission.admin, partyId)(username, password)
def authGet(partyId: String)(username: String, password: String) def authGet(partyId: String)(username: String, password: String)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Option[String]] = (implicit executionContext: ExecutionContext, timeout: Timeout, scheduler: Scheduler): Future[Option[String]] =
authenticator(Permission.get, partyId)(username, password) authenticator(Permission.get, partyId)(username, password)
def authPost(partyId: String)(username: String, password: String) def authPost(partyId: String)(username: String, password: String)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Option[String]] = (implicit executionContext: ExecutionContext, timeout: Timeout, scheduler: Scheduler): Future[Option[String]] =
authenticator(Permission.post, partyId)(username, password) authenticator(Permission.post, partyId)(username, password)
} }

View File

@ -8,37 +8,37 @@
*/ */
package me.arcanis.ffxivbis.http package me.arcanis.ffxivbis.http
import akka.actor.ActorRef import akka.actor.typed.scaladsl.AskPattern.Askable
import akka.pattern.ask import akka.actor.typed.{ActorRef, Scheduler}
import akka.util.Timeout import akka.util.Timeout
import me.arcanis.ffxivbis.http.api.v1.json.ApiAction import me.arcanis.ffxivbis.http.api.v1.json.ApiAction
import me.arcanis.ffxivbis.messages.{AddPieceToBis, GetBiS, Message, RemovePieceFromBiS, RemovePiecesFromBiS}
import me.arcanis.ffxivbis.models.{Piece, Player, PlayerId} import me.arcanis.ffxivbis.models.{Piece, Player, PlayerId}
import me.arcanis.ffxivbis.service.impl.DatabaseBiSHandler
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
trait BiSHelper extends BisProviderHelper { trait BiSHelper extends BisProviderHelper {
def storage: ActorRef def storage: ActorRef[Message]
def addPieceBiS(playerId: PlayerId, piece: Piece) def addPieceBiS(playerId: PlayerId, piece: Piece)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = (implicit timeout: Timeout, scheduler: Scheduler): Future[Unit] =
(storage ? DatabaseBiSHandler.AddPieceToBis(playerId, piece.withJob(playerId.job))).mapTo[Int] storage.ask(AddPieceToBis(playerId, piece.withJob(playerId.job), _))
def bis(partyId: String, playerId: Option[PlayerId]) def bis(partyId: String, playerId: Option[PlayerId])
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[Player]] = (implicit timeout: Timeout, scheduler: Scheduler): Future[Seq[Player]] =
(storage ? DatabaseBiSHandler.GetBiS(partyId, playerId)).mapTo[Seq[Player]] storage.ask(GetBiS(partyId, playerId, _))
def doModifyBiS(action: ApiAction.Value, playerId: PlayerId, piece: Piece) def doModifyBiS(action: ApiAction.Value, playerId: PlayerId, piece: Piece)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = (implicit timeout: Timeout, scheduler: Scheduler): Future[Unit] =
action match { action match {
case ApiAction.add => addPieceBiS(playerId, piece) case ApiAction.add => addPieceBiS(playerId, piece)
case ApiAction.remove => removePieceBiS(playerId, piece) case ApiAction.remove => removePieceBiS(playerId, piece)
} }
def putBiS(playerId: PlayerId, link: String) def putBiS(playerId: PlayerId, link: String)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = { (implicit executionContext: ExecutionContext, timeout: Timeout, scheduler: Scheduler): Future[Unit] = {
(storage ? DatabaseBiSHandler.RemovePiecesFromBiS(playerId)).flatMap { _ => storage.ask(RemovePiecesFromBiS(playerId, _)).flatMap { _ =>
downloadBiS(link, playerId.job).flatMap { bis => downloadBiS(link, playerId.job).flatMap { bis =>
Future.traverse(bis.pieces)(addPieceBiS(playerId, _)) Future.traverse(bis.pieces)(addPieceBiS(playerId, _))
}.map(_ => ()) }.map(_ => ())
@ -46,7 +46,7 @@ trait BiSHelper extends BisProviderHelper {
} }
def removePieceBiS(playerId: PlayerId, piece: Piece) def removePieceBiS(playerId: PlayerId, piece: Piece)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = (implicit timeout: Timeout, scheduler: Scheduler): Future[Unit] =
(storage ? DatabaseBiSHandler.RemovePieceFromBiS(playerId, piece)).mapTo[Int] storage.ask(RemovePieceFromBiS(playerId, piece, _))
} }

View File

@ -8,19 +8,19 @@
*/ */
package me.arcanis.ffxivbis.http package me.arcanis.ffxivbis.http
import akka.actor.ActorRef import akka.actor.typed.{ActorRef, Scheduler}
import akka.pattern.ask import akka.actor.typed.scaladsl.AskPattern.Askable
import akka.util.Timeout import akka.util.Timeout
import me.arcanis.ffxivbis.messages.{BiSProviderMessage, DownloadBiS}
import me.arcanis.ffxivbis.models.{BiS, Job} import me.arcanis.ffxivbis.models.{BiS, Job}
import me.arcanis.ffxivbis.service.bis.BisProvider
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.Future
trait BisProviderHelper { trait BisProviderHelper {
def ariyala: ActorRef def provider: ActorRef[BiSProviderMessage]
def downloadBiS(link: String, job: Job.Job) def downloadBiS(link: String, job: Job.Job)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[BiS] = (implicit timeout: Timeout, scheduler: Scheduler): Future[BiS] =
(ariyala ? BisProvider.GetBiS(link, job)).mapTo[BiS] provider.ask(DownloadBiS(link, job, _))
} }

View File

@ -8,26 +8,26 @@
*/ */
package me.arcanis.ffxivbis.http package me.arcanis.ffxivbis.http
import akka.actor.ActorRef import akka.actor.typed.scaladsl.AskPattern.Askable
import akka.pattern.ask import akka.actor.typed.{ActorRef, Scheduler}
import akka.util.Timeout import akka.util.Timeout
import me.arcanis.ffxivbis.http.api.v1.json.ApiAction import me.arcanis.ffxivbis.http.api.v1.json.ApiAction
import me.arcanis.ffxivbis.messages.{AddPieceTo, GetLoot, Message, RemovePieceFrom, SuggestLoot}
import me.arcanis.ffxivbis.models.{Piece, Player, PlayerId, PlayerIdWithCounters} import me.arcanis.ffxivbis.models.{Piece, Player, PlayerId, PlayerIdWithCounters}
import me.arcanis.ffxivbis.service.LootSelector.LootSelectorResult
import me.arcanis.ffxivbis.service.impl.DatabaseLootHandler
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
trait LootHelper { trait LootHelper {
def storage: ActorRef def storage: ActorRef[Message]
def addPieceLoot(playerId: PlayerId, piece: Piece, isFreeLoot: Boolean) def addPieceLoot(playerId: PlayerId, piece: Piece, isFreeLoot: Boolean)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = (implicit timeout: Timeout, scheduler: Scheduler): Future[Unit] =
(storage ? DatabaseLootHandler.AddPieceTo(playerId, piece, isFreeLoot)).mapTo[Int] storage.ask(
AddPieceTo(playerId, piece, isFreeLoot, _))
def doModifyLoot(action: ApiAction.Value, playerId: PlayerId, piece: Piece, maybeFree: Option[Boolean]) def doModifyLoot(action: ApiAction.Value, playerId: PlayerId, piece: Piece, maybeFree: Option[Boolean])
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = (implicit timeout: Timeout, scheduler: Scheduler): Future[Unit] =
(action, maybeFree) match { (action, maybeFree) match {
case (ApiAction.add, Some(isFreeLoot)) => addPieceLoot(playerId, piece, isFreeLoot) case (ApiAction.add, Some(isFreeLoot)) => addPieceLoot(playerId, piece, isFreeLoot)
case (ApiAction.remove, _) => removePieceLoot(playerId, piece) case (ApiAction.remove, _) => removePieceLoot(playerId, piece)
@ -35,14 +35,14 @@ trait LootHelper {
} }
def loot(partyId: String, playerId: Option[PlayerId]) def loot(partyId: String, playerId: Option[PlayerId])
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[Player]] = (implicit timeout: Timeout, scheduler: Scheduler): Future[Seq[Player]] =
(storage ? DatabaseLootHandler.GetLoot(partyId, playerId)).mapTo[Seq[Player]] storage.ask(GetLoot(partyId, playerId, _))
def removePieceLoot(playerId: PlayerId, piece: Piece) def removePieceLoot(playerId: PlayerId, piece: Piece)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = (implicit timeout: Timeout, scheduler: Scheduler): Future[Unit] =
(storage ? DatabaseLootHandler.RemovePieceFrom(playerId, piece)).mapTo[Int] storage.ask(RemovePieceFrom(playerId, piece, _))
def suggestPiece(partyId: String, piece: Piece) def suggestPiece(partyId: String, piece: Piece)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[PlayerIdWithCounters]] = (implicit executionContext: ExecutionContext, timeout: Timeout, scheduler: Scheduler): Future[Seq[PlayerIdWithCounters]] =
(storage ? DatabaseLootHandler.SuggestLoot(partyId, piece)).mapTo[LootSelectorResult].map(_.result) storage.ask(SuggestLoot(partyId, piece, _)).map(_.result)
} }

View File

@ -8,56 +8,56 @@
*/ */
package me.arcanis.ffxivbis.http package me.arcanis.ffxivbis.http
import akka.actor.ActorRef import akka.actor.typed.{ActorRef, Scheduler}
import akka.pattern.ask import akka.actor.typed.scaladsl.AskPattern.Askable
import akka.util.Timeout import akka.util.Timeout
import me.arcanis.ffxivbis.http.api.v1.json.ApiAction import me.arcanis.ffxivbis.http.api.v1.json.ApiAction
import me.arcanis.ffxivbis.models.{Party, PartyDescription, Player, PlayerId} import me.arcanis.ffxivbis.messages.{AddPieceToBis, AddPlayer, GetParty, GetPartyDescription, GetPlayer, Message, RemovePlayer, UpdateParty}
import me.arcanis.ffxivbis.service.impl.{DatabaseBiSHandler, DatabasePartyHandler} import me.arcanis.ffxivbis.models.{PartyDescription, Player, PlayerId}
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
trait PlayerHelper extends BisProviderHelper { trait PlayerHelper extends BisProviderHelper {
def storage: ActorRef def storage: ActorRef[Message]
def addPlayer(player: Player) def addPlayer(player: Player)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = (implicit executionContext: ExecutionContext, timeout: Timeout, scheduler: Scheduler): Future[Unit] =
(storage ? DatabasePartyHandler.AddPlayer(player)).mapTo[Int].map { res => storage.ask(ref => AddPlayer(player, ref)).map { res =>
player.link match { player.link match {
case Some(link) => case Some(link) =>
downloadBiS(link, player.job).map { bis => downloadBiS(link, player.job).map { bis =>
bis.pieces.map(storage ? DatabaseBiSHandler.AddPieceToBis(player.playerId, _)) bis.pieces.map(piece => storage.ask(AddPieceToBis(player.playerId, piece, _)))
}.map(_ => res) }.map(_ => res)
case None => Future.successful(res) case None => Future.successful(res)
} }
}.flatten }.flatten
def doModifyPlayer(action: ApiAction.Value, player: Player) def doModifyPlayer(action: ApiAction.Value, player: Player)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = (implicit executionContext: ExecutionContext, timeout: Timeout, scheduler: Scheduler): Future[Unit] =
action match { action match {
case ApiAction.add => addPlayer(player) case ApiAction.add => addPlayer(player)
case ApiAction.remove => removePlayer(player.playerId) case ApiAction.remove => removePlayer(player.playerId)
} }
def getPartyDescription(partyId: String) def getPartyDescription(partyId: String)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[PartyDescription] = (implicit timeout: Timeout, scheduler: Scheduler): Future[PartyDescription] =
(storage ? DatabasePartyHandler.GetPartyDescription(partyId)).mapTo[PartyDescription] storage.ask(GetPartyDescription(partyId, _))
def getPlayers(partyId: String, maybePlayerId: Option[PlayerId]) def getPlayers(partyId: String, maybePlayerId: Option[PlayerId])
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[Player]] = (implicit executionContext: ExecutionContext, timeout: Timeout, scheduler: Scheduler): Future[Seq[Player]] =
maybePlayerId match { maybePlayerId match {
case Some(playerId) => case Some(playerId) =>
(storage ? DatabasePartyHandler.GetPlayer(playerId)).mapTo[Option[Player]].map(_.toSeq) storage.ask(GetPlayer(playerId, _)).map(_.toSeq)
case None => case None =>
(storage ? DatabasePartyHandler.GetParty(partyId)).mapTo[Party].map(_.players.values.toSeq) storage.ask(GetParty(partyId, _)).map(_.players.values.toSeq)
} }
def removePlayer(playerId: PlayerId) def removePlayer(playerId: PlayerId)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = (implicit timeout: Timeout, scheduler: Scheduler): Future[Unit] =
(storage ? DatabasePartyHandler.RemovePlayer(playerId)).mapTo[Int] storage.ask(RemovePlayer(playerId, _))
def updateDescription(partyDescription: PartyDescription) def updateDescription(partyDescription: PartyDescription)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = (implicit executionContext: ExecutionContext, timeout: Timeout, scheduler: Scheduler): Future[Unit] =
(storage ? DatabasePartyHandler.UpdateParty(partyDescription)).mapTo[Int] storage.ask(UpdateParty(partyDescription, _))
} }

View File

@ -10,25 +10,29 @@ package me.arcanis.ffxivbis.http
import java.time.Instant import java.time.Instant
import akka.actor.{ActorRef, ActorSystem} import akka.actor.typed.{ActorRef, ActorSystem, Scheduler}
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server._ import akka.http.scaladsl.server._
import akka.util.Timeout import akka.util.Timeout
import com.typesafe.scalalogging.{Logger, StrictLogging} import com.typesafe.scalalogging.{Logger, StrictLogging}
import me.arcanis.ffxivbis.http.api.v1.RootApiV1Endpoint import me.arcanis.ffxivbis.http.api.v1.RootApiV1Endpoint
import me.arcanis.ffxivbis.http.view.RootView import me.arcanis.ffxivbis.http.view.RootView
import me.arcanis.ffxivbis.messages.{BiSProviderMessage, Message}
class RootEndpoint(system: ActorSystem, storage: ActorRef, ariyala: ActorRef) class RootEndpoint(system: ActorSystem[Nothing],
storage: ActorRef[Message],
provider: ActorRef[BiSProviderMessage])
extends StrictLogging { extends StrictLogging {
import me.arcanis.ffxivbis.utils.Implicits._ import me.arcanis.ffxivbis.utils.Implicits._
private val config = system.settings.config private val config = system.settings.config
implicit val scheduler: Scheduler = system.scheduler
implicit val timeout: Timeout = implicit val timeout: Timeout =
config.getDuration("me.arcanis.ffxivbis.settings.request-timeout") config.getDuration("me.arcanis.ffxivbis.settings.request-timeout")
private val rootApiV1Endpoint: RootApiV1Endpoint = new RootApiV1Endpoint(storage, ariyala, config) private val rootApiV1Endpoint: RootApiV1Endpoint = new RootApiV1Endpoint(storage, provider, config)
private val rootView: RootView = new RootView(storage, ariyala) private val rootView: RootView = new RootView(storage, provider)
private val swagger: Swagger = new Swagger(config) private val swagger: Swagger = new Swagger(config)
private val httpLogger = Logger("http") private val httpLogger = Logger("http")

View File

@ -16,6 +16,7 @@ import io.swagger.v3.oas.models.security.SecurityScheme
import scala.io.Source import scala.io.Source
class Swagger(config: Config) extends SwaggerHttpService { class Swagger(config: Config) extends SwaggerHttpService {
override val apiClasses: Set[Class[_]] = Set( override val apiClasses: Set[Class[_]] = Set(
classOf[api.v1.BiSEndpoint], classOf[api.v1.LootEndpoint], classOf[api.v1.BiSEndpoint], classOf[api.v1.LootEndpoint],
classOf[api.v1.PartyEndpoint], classOf[api.v1.PlayerEndpoint], classOf[api.v1.PartyEndpoint], classOf[api.v1.PlayerEndpoint],

View File

@ -8,35 +8,34 @@
*/ */
package me.arcanis.ffxivbis.http package me.arcanis.ffxivbis.http
import akka.actor.ActorRef import akka.actor.typed.scaladsl.AskPattern.Askable
import akka.pattern.ask import akka.actor.typed.{ActorRef, Scheduler}
import akka.util.Timeout import akka.util.Timeout
import me.arcanis.ffxivbis.messages.{AddUser, DeleteUser, GetNewPartyId, GetUser, GetUsers, Message}
import me.arcanis.ffxivbis.models.User import me.arcanis.ffxivbis.models.User
import me.arcanis.ffxivbis.service.PartyService
import me.arcanis.ffxivbis.service.impl.DatabaseUserHandler
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.Future
trait UserHelper { trait UserHelper {
def storage: ActorRef def storage: ActorRef[Message]
def addUser(user: User, isHashedPassword: Boolean) def addUser(user: User, isHashedPassword: Boolean)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = (implicit timeout: Timeout, scheduler: Scheduler): Future[Unit] =
(storage ? DatabaseUserHandler.AddUser(user, isHashedPassword)).mapTo[Int] storage.ask(AddUser(user, isHashedPassword, _))
def newPartyId(implicit executionContext: ExecutionContext, timeout: Timeout): Future[String] = def newPartyId(implicit timeout: Timeout, scheduler: Scheduler): Future[String] =
(storage ? PartyService.GetNewPartyId).mapTo[String] storage.ask(GetNewPartyId)
def user(partyId: String, username: String) def user(partyId: String, username: String)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Option[User]] = (implicit timeout: Timeout, scheduler: Scheduler): Future[Option[User]] =
(storage ? DatabaseUserHandler.GetUser(partyId, username)).mapTo[Option[User]] storage.ask(GetUser(partyId, username, _))
def users(partyId: String) def users(partyId: String)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[User]] = (implicit timeout: Timeout, scheduler: Scheduler): Future[Seq[User]] =
(storage ? DatabaseUserHandler.GetUsers(partyId)).mapTo[Seq[User]] storage.ask(GetUsers(partyId, _))
def removeUser(partyId: String, username: String) def removeUser(partyId: String, username: String)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = (implicit timeout: Timeout, scheduler: Scheduler): Future[Unit] =
(storage ? DatabaseUserHandler.DeleteUser(partyId, username)).mapTo[Int] storage.ask(DeleteUser(partyId, username, _))
} }

View File

@ -8,7 +8,7 @@
*/ */
package me.arcanis.ffxivbis.http.api.v1 package me.arcanis.ffxivbis.http.api.v1
import akka.actor.ActorRef import akka.actor.typed.{ActorRef, Scheduler}
import akka.http.scaladsl.model.{HttpEntity, StatusCodes} import akka.http.scaladsl.model.{HttpEntity, StatusCodes}
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server._ import akka.http.scaladsl.server._
@ -22,12 +22,15 @@ import io.swagger.v3.oas.annotations.{Operation, Parameter}
import javax.ws.rs._ import javax.ws.rs._
import me.arcanis.ffxivbis.http.api.v1.json._ import me.arcanis.ffxivbis.http.api.v1.json._
import me.arcanis.ffxivbis.http.{Authorization, BiSHelper} import me.arcanis.ffxivbis.http.{Authorization, BiSHelper}
import me.arcanis.ffxivbis.messages.{BiSProviderMessage, Message}
import me.arcanis.ffxivbis.models.PlayerId import me.arcanis.ffxivbis.models.PlayerId
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
@Path("api/v1") @Path("api/v1")
class BiSEndpoint(override val storage: ActorRef, override val ariyala: ActorRef)(implicit timeout: Timeout) class BiSEndpoint(override val storage: ActorRef[Message],
override val provider: ActorRef[BiSProviderMessage])
(implicit timeout: Timeout, scheduler: Scheduler)
extends BiSHelper with Authorization with JsonSupport { extends BiSHelper with Authorization with JsonSupport {
def route: Route = createBiS ~ getBiS ~ modifyBiS def route: Route = createBiS ~ getBiS ~ modifyBiS

View File

@ -31,7 +31,7 @@ trait HttpHandler extends StrictLogging { this: JsonSupport =>
.mapRejectionResponse { .mapRejectionResponse {
case response @ HttpResponse(_, _, entity: HttpEntity.Strict, _) => case response @ HttpResponse(_, _, entity: HttpEntity.Strict, _) =>
val message = ErrorResponse(entity.data.utf8String).toJson val message = ErrorResponse(entity.data.utf8String).toJson
response.copy(entity = HttpEntity(ContentTypes.`application/json`, message.compactPrint)) response.withEntity(HttpEntity(ContentTypes.`application/json`, message.compactPrint))
case other => other case other => other
} }
} }

View File

@ -8,7 +8,7 @@
*/ */
package me.arcanis.ffxivbis.http.api.v1 package me.arcanis.ffxivbis.http.api.v1
import akka.actor.ActorRef import akka.actor.typed.{ActorRef, Scheduler}
import akka.http.scaladsl.model.{HttpEntity, StatusCodes} import akka.http.scaladsl.model.{HttpEntity, StatusCodes}
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server._ import akka.http.scaladsl.server._
@ -22,12 +22,14 @@ import io.swagger.v3.oas.annotations.{Operation, Parameter}
import javax.ws.rs._ import javax.ws.rs._
import me.arcanis.ffxivbis.http.api.v1.json._ import me.arcanis.ffxivbis.http.api.v1.json._
import me.arcanis.ffxivbis.http.{Authorization, LootHelper} import me.arcanis.ffxivbis.http.{Authorization, LootHelper}
import me.arcanis.ffxivbis.messages.Message
import me.arcanis.ffxivbis.models.PlayerId import me.arcanis.ffxivbis.models.PlayerId
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
@Path("api/v1") @Path("api/v1")
class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) class LootEndpoint(override val storage: ActorRef[Message])
(implicit timeout: Timeout, scheduler: Scheduler)
extends LootHelper with Authorization with JsonSupport with HttpHandler { extends LootHelper with Authorization with JsonSupport with HttpHandler {
def route: Route = getLoot ~ modifyLoot def route: Route = getLoot ~ modifyLoot

View File

@ -8,7 +8,7 @@
*/ */
package me.arcanis.ffxivbis.http.api.v1 package me.arcanis.ffxivbis.http.api.v1
import akka.actor.ActorRef import akka.actor.typed.{ActorRef, Scheduler}
import akka.http.scaladsl.model.{HttpEntity, StatusCodes} import akka.http.scaladsl.model.{HttpEntity, StatusCodes}
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server._ import akka.http.scaladsl.server._
@ -22,11 +22,14 @@ import io.swagger.v3.oas.annotations.{Operation, Parameter}
import javax.ws.rs._ import javax.ws.rs._
import me.arcanis.ffxivbis.http.api.v1.json._ import me.arcanis.ffxivbis.http.api.v1.json._
import me.arcanis.ffxivbis.http.{Authorization, PlayerHelper} import me.arcanis.ffxivbis.http.{Authorization, PlayerHelper}
import me.arcanis.ffxivbis.messages.{BiSProviderMessage, Message}
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
@Path("api/v1") @Path("api/v1")
class PartyEndpoint(override val storage: ActorRef, override val ariyala: ActorRef)(implicit timeout: Timeout) class PartyEndpoint(override val storage: ActorRef[Message],
override val provider: ActorRef[BiSProviderMessage])
(implicit timeout: Timeout, scheduler: Scheduler)
extends PlayerHelper with Authorization with JsonSupport with HttpHandler { extends PlayerHelper with Authorization with JsonSupport with HttpHandler {
def route: Route = getPartyDescription ~ modifyPartyDescription def route: Route = getPartyDescription ~ modifyPartyDescription

View File

@ -8,7 +8,7 @@
*/ */
package me.arcanis.ffxivbis.http.api.v1 package me.arcanis.ffxivbis.http.api.v1
import akka.actor.ActorRef import akka.actor.typed.{ActorRef, Scheduler}
import akka.http.scaladsl.model.{HttpEntity, StatusCodes} import akka.http.scaladsl.model.{HttpEntity, StatusCodes}
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server._ import akka.http.scaladsl.server._
@ -22,12 +22,15 @@ import io.swagger.v3.oas.annotations.{Operation, Parameter}
import javax.ws.rs._ import javax.ws.rs._
import me.arcanis.ffxivbis.http.api.v1.json._ import me.arcanis.ffxivbis.http.api.v1.json._
import me.arcanis.ffxivbis.http.{Authorization, PlayerHelper} import me.arcanis.ffxivbis.http.{Authorization, PlayerHelper}
import me.arcanis.ffxivbis.messages.{BiSProviderMessage, Message}
import me.arcanis.ffxivbis.models.PlayerId import me.arcanis.ffxivbis.models.PlayerId
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
@Path("api/v1") @Path("api/v1")
class PlayerEndpoint(override val storage: ActorRef, override val ariyala: ActorRef)(implicit timeout: Timeout) class PlayerEndpoint(override val storage: ActorRef[Message],
override val provider: ActorRef[BiSProviderMessage])
(implicit timeout: Timeout, scheduler: Scheduler)
extends PlayerHelper with Authorization with JsonSupport with HttpHandler { extends PlayerHelper with Authorization with JsonSupport with HttpHandler {
def route: Route = getParty ~ modifyParty def route: Route = getParty ~ modifyParty

View File

@ -8,21 +8,23 @@
*/ */
package me.arcanis.ffxivbis.http.api.v1 package me.arcanis.ffxivbis.http.api.v1
import akka.actor.ActorRef import akka.actor.typed.{ActorRef, Scheduler}
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route import akka.http.scaladsl.server.Route
import akka.util.Timeout import akka.util.Timeout
import com.typesafe.config.Config import com.typesafe.config.Config
import me.arcanis.ffxivbis.http.api.v1.json.JsonSupport import me.arcanis.ffxivbis.http.api.v1.json.JsonSupport
import me.arcanis.ffxivbis.messages.{BiSProviderMessage, Message}
class RootApiV1Endpoint(storage: ActorRef, ariyala: ActorRef, config: Config) class RootApiV1Endpoint(storage: ActorRef[Message],
(implicit timeout: Timeout) provider: ActorRef[BiSProviderMessage],
config: Config)(implicit timeout: Timeout, scheduler: Scheduler)
extends JsonSupport with HttpHandler { extends JsonSupport with HttpHandler {
private val biSEndpoint = new BiSEndpoint(storage, ariyala) private val biSEndpoint = new BiSEndpoint(storage, provider)
private val lootEndpoint = new LootEndpoint(storage) private val lootEndpoint = new LootEndpoint(storage)
private val partyEndpoint = new PartyEndpoint(storage, ariyala) private val partyEndpoint = new PartyEndpoint(storage, provider)
private val playerEndpoint = new PlayerEndpoint(storage, ariyala) private val playerEndpoint = new PlayerEndpoint(storage, provider)
private val typesEndpoint = new TypesEndpoint(config) private val typesEndpoint = new TypesEndpoint(config)
private val userEndpoint = new UserEndpoint(storage) private val userEndpoint = new UserEndpoint(storage)

View File

@ -8,7 +8,7 @@
*/ */
package me.arcanis.ffxivbis.http.api.v1 package me.arcanis.ffxivbis.http.api.v1
import akka.actor.ActorRef import akka.actor.typed.{ActorRef, Scheduler}
import akka.http.scaladsl.model.{HttpEntity, StatusCodes} import akka.http.scaladsl.model.{HttpEntity, StatusCodes}
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server._ import akka.http.scaladsl.server._
@ -22,12 +22,14 @@ import io.swagger.v3.oas.annotations.{Operation, Parameter}
import javax.ws.rs._ import javax.ws.rs._
import me.arcanis.ffxivbis.http.api.v1.json._ import me.arcanis.ffxivbis.http.api.v1.json._
import me.arcanis.ffxivbis.http.{Authorization, UserHelper} import me.arcanis.ffxivbis.http.{Authorization, UserHelper}
import me.arcanis.ffxivbis.messages.Message
import me.arcanis.ffxivbis.models.Permission import me.arcanis.ffxivbis.models.Permission
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
@Path("api/v1") @Path("api/v1")
class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) class UserEndpoint(override val storage: ActorRef[Message])
(implicit timeout: Timeout, scheduler: Scheduler)
extends UserHelper with Authorization with JsonSupport { extends UserHelper with Authorization with JsonSupport {
def route: Route = createParty ~ createUser ~ deleteUser ~ getUsers def route: Route = createParty ~ createUser ~ deleteUser ~ getUsers

View File

@ -9,10 +9,12 @@ case class LootResponse(
@Schema(description = "looted piece", required = true) piece: PieceResponse, @Schema(description = "looted piece", required = true) piece: PieceResponse,
@Schema(description = "loot timestamp", required = true) timestamp: Instant, @Schema(description = "loot timestamp", required = true) timestamp: Instant,
@Schema(description = "is loot free for all", required = true) isFreeLoot: Boolean) { @Schema(description = "is loot free for all", required = true) isFreeLoot: Boolean) {
def toLoot: Loot = Loot(-1, piece.toPiece, timestamp, isFreeLoot) def toLoot: Loot = Loot(-1, piece.toPiece, timestamp, isFreeLoot)
} }
object LootResponse { object LootResponse {
def fromLoot(loot: Loot): LootResponse = def fromLoot(loot: Loot): LootResponse =
LootResponse(PieceResponse.fromPiece(loot.piece), loot.timestamp, loot.isFreeLoot) LootResponse(PieceResponse.fromPiece(loot.piece), loot.timestamp, loot.isFreeLoot)
} }

View File

@ -14,10 +14,12 @@ import me.arcanis.ffxivbis.models.PartyDescription
case class PartyDescriptionResponse( case class PartyDescriptionResponse(
@Schema(description = "party id", required = true) partyId: String, @Schema(description = "party id", required = true) partyId: String,
@Schema(description = "party name") partyAlias: Option[String]) { @Schema(description = "party name") partyAlias: Option[String]) {
def toDescription: PartyDescription = PartyDescription(partyId, partyAlias) def toDescription: PartyDescription = PartyDescription(partyId, partyAlias)
} }
object PartyDescriptionResponse { object PartyDescriptionResponse {
def fromDescription(description: PartyDescription): PartyDescriptionResponse = def fromDescription(description: PartyDescription): PartyDescriptionResponse =
PartyDescriptionResponse(description.partyId, description.partyAlias) PartyDescriptionResponse(description.partyId, description.partyAlias)
} }

View File

@ -15,10 +15,12 @@ case class PieceResponse(
@Schema(description = "piece type", required = true) pieceType: String, @Schema(description = "piece type", required = true) pieceType: String,
@Schema(description = "job name to which piece belong or AnyJob", required = true, example = "DNC") job: String, @Schema(description = "job name to which piece belong or AnyJob", required = true, example = "DNC") job: String,
@Schema(description = "piece name", required = true, example = "body") piece: String) { @Schema(description = "piece name", required = true, example = "body") piece: String) {
def toPiece: Piece = Piece(piece, PieceType.withName(pieceType), Job.withName(job)) def toPiece: Piece = Piece(piece, PieceType.withName(pieceType), Job.withName(job))
} }
object PieceResponse { object PieceResponse {
def fromPiece(piece: Piece): PieceResponse = def fromPiece(piece: Piece): PieceResponse =
PieceResponse(piece.pieceType.toString, piece.job.toString, piece.piece) PieceResponse(piece.pieceType.toString, piece.job.toString, piece.piece)
} }

View File

@ -15,11 +15,13 @@ case class PlayerIdResponse(
@Schema(description = "unique party ID. Required in responses", example = "abcdefgh") partyId: Option[String], @Schema(description = "unique party ID. Required in responses", example = "abcdefgh") partyId: Option[String],
@Schema(description = "job name", required = true, example = "DNC") job: String, @Schema(description = "job name", required = true, example = "DNC") job: String,
@Schema(description = "player nick name", required = true, example = "Siuan Sanche") nick: String) { @Schema(description = "player nick name", required = true, example = "Siuan Sanche") nick: String) {
def withPartyId(partyId: String): PlayerId = def withPartyId(partyId: String): PlayerId =
PlayerId(partyId, Job.withName(job), nick) PlayerId(partyId, Job.withName(job), nick)
} }
object PlayerIdResponse { object PlayerIdResponse {
def fromPlayerId(playerId: PlayerId): PlayerIdResponse = def fromPlayerId(playerId: PlayerId): PlayerIdResponse =
PlayerIdResponse(Some(playerId.partyId), playerId.job.toString, playerId.nick) PlayerIdResponse(Some(playerId.partyId), playerId.job.toString, playerId.nick)
} }

View File

@ -23,6 +23,7 @@ case class PlayerIdWithCountersResponse(
@Schema(description = "total count of looted pieces", required = true) lootCountTotal: Int) @Schema(description = "total count of looted pieces", required = true) lootCountTotal: Int)
object PlayerIdWithCountersResponse { object PlayerIdWithCountersResponse {
def fromPlayerId(playerIdWithCounters: PlayerIdWithCounters): PlayerIdWithCountersResponse = def fromPlayerId(playerIdWithCounters: PlayerIdWithCounters): PlayerIdWithCountersResponse =
PlayerIdWithCountersResponse( PlayerIdWithCountersResponse(
playerIdWithCounters.partyId, playerIdWithCounters.partyId,

View File

@ -19,6 +19,7 @@ case class PlayerResponse(
@Schema(description = "looted pieces") loot: Option[Seq[LootResponse]], @Schema(description = "looted pieces") loot: Option[Seq[LootResponse]],
@Schema(description = "link to best in slot", example = "https://ffxiv.ariyala.com/19V5R") link: Option[String], @Schema(description = "link to best in slot", example = "https://ffxiv.ariyala.com/19V5R") link: Option[String],
@Schema(description = "player loot priority", `type` = "number") priority: Option[Int]) { @Schema(description = "player loot priority", `type` = "number") priority: Option[Int]) {
def toPlayer: Player = def toPlayer: Player =
Player(-1, partyId, Job.withName(job), nick, Player(-1, partyId, Job.withName(job), nick,
BiS(bis.getOrElse(Seq.empty).map(_.toPiece)), BiS(bis.getOrElse(Seq.empty).map(_.toPiece)),
@ -27,6 +28,7 @@ case class PlayerResponse(
} }
object PlayerResponse { object PlayerResponse {
def fromPlayer(player: Player): PlayerResponse = def fromPlayer(player: Player): PlayerResponse =
PlayerResponse(player.partyId, player.job.toString, player.nick, PlayerResponse(player.partyId, player.job.toString, player.nick,
Some(player.bis.pieces.map(PieceResponse.fromPiece)), Some(player.bis.pieces.map(PieceResponse.fromPiece)),

View File

@ -16,11 +16,13 @@ case class UserResponse(
@Schema(description = "username to login to party", required = true, example = "siuan") username: String, @Schema(description = "username to login to party", required = true, example = "siuan") username: String,
@Schema(description = "password to login to party", required = true, example = "pa55w0rd") password: String, @Schema(description = "password to login to party", required = true, example = "pa55w0rd") password: String,
@Schema(description = "user permission", defaultValue = "get", allowableValues = Array("get", "post", "admin")) permission: Option[Permission.Value] = None) { @Schema(description = "user permission", defaultValue = "get", allowableValues = Array("get", "post", "admin")) permission: Option[Permission.Value] = None) {
def toUser: User = def toUser: User =
User(partyId, username, password, permission.getOrElse(Permission.get)) User(partyId, username, password, permission.getOrElse(Permission.get))
} }
object UserResponse { object UserResponse {
def fromUser(user: User): UserResponse = def fromUser(user: User): UserResponse =
UserResponse(user.partyId, user.username, "", Some(user.permission)) UserResponse(user.partyId, user.username, "", Some(user.permission))
} }

View File

@ -8,16 +8,19 @@
*/ */
package me.arcanis.ffxivbis.http.view package me.arcanis.ffxivbis.http.view
import akka.actor.ActorRef import akka.actor.typed.{ActorRef, Scheduler}
import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server._ import akka.http.scaladsl.server._
import akka.util.Timeout import akka.util.Timeout
import me.arcanis.ffxivbis.http.{Authorization, PlayerHelper} import me.arcanis.ffxivbis.http.{Authorization, PlayerHelper}
import me.arcanis.ffxivbis.messages.{BiSProviderMessage, Message}
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
class BasePartyView(override val storage: ActorRef, override val ariyala: ActorRef)(implicit timeout: Timeout) class BasePartyView(override val storage: ActorRef[Message],
override val provider: ActorRef[BiSProviderMessage])
(implicit timeout: Timeout, scheduler: Scheduler)
extends PlayerHelper with Authorization { extends PlayerHelper with Authorization {
def route: Route = getIndex def route: Route = getIndex

View File

@ -8,18 +8,21 @@
*/ */
package me.arcanis.ffxivbis.http.view package me.arcanis.ffxivbis.http.view
import akka.actor.ActorRef import akka.actor.typed.{ActorRef, Scheduler}
import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server._ import akka.http.scaladsl.server._
import akka.util.Timeout import akka.util.Timeout
import me.arcanis.ffxivbis.http.{Authorization, BiSHelper} import me.arcanis.ffxivbis.http.{Authorization, BiSHelper}
import me.arcanis.ffxivbis.messages.{BiSProviderMessage, Message}
import me.arcanis.ffxivbis.models.{Piece, PieceType, Player, PlayerId} import me.arcanis.ffxivbis.models.{Piece, PieceType, Player, PlayerId}
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try import scala.util.Try
class BiSView(override val storage: ActorRef, override val ariyala: ActorRef)(implicit timeout: Timeout) class BiSView(override val storage: ActorRef[Message],
override val provider: ActorRef[BiSProviderMessage])
(implicit timeout: Timeout, scheduler: Scheduler)
extends BiSHelper with Authorization { extends BiSHelper with Authorization {
def route: Route = getBiS ~ modifyBiS def route: Route = getBiS ~ modifyBiS
@ -64,7 +67,7 @@ class BiSView(override val storage: ActorRef, override val ariyala: ActorRef)(im
def getPiece(playerId: PlayerId, piece: String, pieceType: String) = def getPiece(playerId: PlayerId, piece: String, pieceType: String) =
Try(Piece(piece, PieceType.withName(pieceType), playerId.job)).toOption Try(Piece(piece, PieceType.withName(pieceType), playerId.job)).toOption
def bisAction(playerId: PlayerId, piece: String, pieceType: String)(fn: Piece => Future[Int]) = def bisAction(playerId: PlayerId, piece: String, pieceType: String)(fn: Piece => Future[Unit]) =
getPiece(playerId, piece, pieceType) match { getPiece(playerId, piece, pieceType) match {
case Some(item) => fn(item).map(_ => ()) case Some(item) => fn(item).map(_ => ())
case _ => Future.failed(new Error(s"Could not construct piece from `$piece ($pieceType)`")) case _ => Future.failed(new Error(s"Could not construct piece from `$piece ($pieceType)`"))
@ -73,9 +76,9 @@ class BiSView(override val storage: ActorRef, override val ariyala: ActorRef)(im
PlayerId(partyId, player) match { PlayerId(partyId, player) match {
case Some(playerId) => (maybePiece, maybePieceType, action, maybeLink) match { case Some(playerId) => (maybePiece, maybePieceType, action, maybeLink) match {
case (Some(piece), Some(pieceType), "add", _) => case (Some(piece), Some(pieceType), "add", _) =>
bisAction(playerId, piece, pieceType) { item => addPieceBiS(playerId, item) } bisAction(playerId, piece, pieceType)(addPieceBiS(playerId, _))
case (Some(piece), Some(pieceType), "remove", _) => case (Some(piece), Some(pieceType), "remove", _) =>
bisAction(playerId, piece, pieceType) { item => removePieceBiS(playerId, item) } bisAction(playerId, piece, pieceType)(removePieceBiS(playerId, _))
case (_, _, "create", Some(link)) => putBiS(playerId, link).map(_ => ()) case (_, _, "create", Some(link)) => putBiS(playerId, link).map(_ => ())
case _ => Future.failed(new Error(s"Could not perform $action")) case _ => Future.failed(new Error(s"Could not perform $action"))
} }

View File

@ -12,6 +12,7 @@ import scalatags.Text
import scalatags.Text.all._ import scalatags.Text.all._
object ErrorView { object ErrorView {
def template(error: Option[String]): Text.TypedTag[String] = error match { def template(error: Option[String]): Text.TypedTag[String] = error match {
case Some(text) => p(id:="error", s"Error occurs: $text") case Some(text) => p(id:="error", s"Error occurs: $text")
case None => p("") case None => p("")

View File

@ -12,6 +12,7 @@ import scalatags.Text
import scalatags.Text.all._ import scalatags.Text.all._
object ExportToCSVView { object ExportToCSVView {
def template: Text.TypedTag[String] = def template: Text.TypedTag[String] =
div( div(
button(onclick:="exportTableToCsv('result.csv')")("Export to CSV"), button(onclick:="exportTableToCsv('result.csv')")("Export to CSV"),

View File

@ -8,18 +8,21 @@
*/ */
package me.arcanis.ffxivbis.http.view package me.arcanis.ffxivbis.http.view
import akka.actor.ActorRef import akka.actor.typed.{ActorRef, Scheduler}
import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server._ import akka.http.scaladsl.server._
import akka.util.Timeout import akka.util.Timeout
import me.arcanis.ffxivbis.http.{PlayerHelper, UserHelper} import me.arcanis.ffxivbis.http.{PlayerHelper, UserHelper}
import me.arcanis.ffxivbis.messages.{BiSProviderMessage, Message}
import me.arcanis.ffxivbis.models.{PartyDescription, Permission, User} import me.arcanis.ffxivbis.models.{PartyDescription, Permission, User}
import scala.concurrent.Future import scala.concurrent.Future
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
class IndexView(override val storage: ActorRef, override val ariyala: ActorRef)(implicit timeout: Timeout) class IndexView(override val storage: ActorRef[Message],
override val provider: ActorRef[BiSProviderMessage])
(implicit timeout: Timeout, scheduler: Scheduler)
extends PlayerHelper with UserHelper { extends PlayerHelper with UserHelper {
def route: Route = createParty ~ getIndex def route: Route = createParty ~ getIndex

View File

@ -8,18 +8,20 @@
*/ */
package me.arcanis.ffxivbis.http.view package me.arcanis.ffxivbis.http.view
import akka.actor.ActorRef import akka.actor.typed.{ActorRef, Scheduler}
import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route import akka.http.scaladsl.server.Route
import akka.util.Timeout import akka.util.Timeout
import me.arcanis.ffxivbis.http.{Authorization, LootHelper} import me.arcanis.ffxivbis.http.{Authorization, LootHelper}
import me.arcanis.ffxivbis.messages.Message
import me.arcanis.ffxivbis.models.{Job, Piece, PieceType, PlayerIdWithCounters} import me.arcanis.ffxivbis.models.{Job, Piece, PieceType, PlayerIdWithCounters}
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success, Try} import scala.util.{Failure, Success, Try}
class LootSuggestView(override val storage: ActorRef)(implicit timeout: Timeout) class LootSuggestView(override val storage: ActorRef[Message])
(implicit timeout: Timeout, scheduler: Scheduler)
extends LootHelper with Authorization { extends LootHelper with Authorization {
def route: Route = getIndex ~ suggestLoot def route: Route = getIndex ~ suggestLoot

View File

@ -8,18 +8,20 @@
*/ */
package me.arcanis.ffxivbis.http.view package me.arcanis.ffxivbis.http.view
import akka.actor.ActorRef import akka.actor.typed.{ActorRef, Scheduler}
import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route import akka.http.scaladsl.server.Route
import akka.util.Timeout import akka.util.Timeout
import me.arcanis.ffxivbis.http.{Authorization, LootHelper} import me.arcanis.ffxivbis.http.{Authorization, LootHelper}
import me.arcanis.ffxivbis.messages.Message
import me.arcanis.ffxivbis.models.{Piece, PieceType, Player, PlayerId} import me.arcanis.ffxivbis.models.{Piece, PieceType, Player, PlayerId}
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try import scala.util.Try
class LootView (override val storage: ActorRef)(implicit timeout: Timeout) class LootView(override val storage: ActorRef[Message])
(implicit timeout: Timeout, scheduler: Scheduler)
extends LootHelper with Authorization { extends LootHelper with Authorization {
def route: Route = getLoot ~ modifyLoot def route: Route = getLoot ~ modifyLoot

View File

@ -8,17 +8,20 @@
*/ */
package me.arcanis.ffxivbis.http.view package me.arcanis.ffxivbis.http.view
import akka.actor.ActorRef import akka.actor.typed.{ActorRef, Scheduler}
import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route import akka.http.scaladsl.server.Route
import akka.util.Timeout import akka.util.Timeout
import me.arcanis.ffxivbis.http.{Authorization, PlayerHelper} import me.arcanis.ffxivbis.http.{Authorization, PlayerHelper}
import me.arcanis.ffxivbis.messages.{BiSProviderMessage, Message}
import me.arcanis.ffxivbis.models._ import me.arcanis.ffxivbis.models._
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
class PlayerView(override val storage: ActorRef, override val ariyala: ActorRef)(implicit timeout: Timeout) class PlayerView(override val storage: ActorRef[Message],
override val provider: ActorRef[BiSProviderMessage])
(implicit timeout: Timeout, scheduler: Scheduler)
extends PlayerHelper with Authorization { extends PlayerHelper with Authorization {
def route: Route = getParty ~ modifyParty def route: Route = getParty ~ modifyParty

View File

@ -8,21 +8,24 @@
*/ */
package me.arcanis.ffxivbis.http.view package me.arcanis.ffxivbis.http.view
import akka.actor.ActorRef import akka.actor.typed.{ActorRef, Scheduler}
import akka.http.scaladsl.model.{ContentTypes, HttpEntity} import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route import akka.http.scaladsl.server.Route
import akka.util.Timeout import akka.util.Timeout
import me.arcanis.ffxivbis.messages.{BiSProviderMessage, Message}
class RootView(storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout) { class RootView(storage: ActorRef[Message],
provider: ActorRef[BiSProviderMessage])
(implicit timeout: Timeout, scheduler: Scheduler) {
private val basePartyView = new BasePartyView(storage, ariyala) private val basePartyView = new BasePartyView(storage, provider)
private val indexView = new IndexView(storage, ariyala) private val indexView = new IndexView(storage, provider)
private val biSView = new BiSView(storage, ariyala) private val biSView = new BiSView(storage, provider)
private val lootView = new LootView(storage) private val lootView = new LootView(storage)
private val lootSuggestView = new LootSuggestView(storage) private val lootSuggestView = new LootSuggestView(storage)
private val playerView = new PlayerView(storage, ariyala) private val playerView = new PlayerView(storage, provider)
private val userView = new UserView(storage) private val userView = new UserView(storage)
def route: Route = def route: Route =
@ -31,6 +34,7 @@ class RootView(storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout)
} }
object RootView { object RootView {
def toHtml(template: String): HttpEntity.Strict = def toHtml(template: String): HttpEntity.Strict =
HttpEntity(ContentTypes.`text/html(UTF-8)`, template) HttpEntity(ContentTypes.`text/html(UTF-8)`, template)
} }

View File

@ -12,6 +12,7 @@ import scalatags.Text
import scalatags.Text.all._ import scalatags.Text.all._
object SearchLineView { object SearchLineView {
def template: Text.TypedTag[String] = def template: Text.TypedTag[String] =
div( div(
input( input(

View File

@ -8,18 +8,20 @@
*/ */
package me.arcanis.ffxivbis.http.view package me.arcanis.ffxivbis.http.view
import akka.actor.ActorRef import akka.actor.typed.{ActorRef, Scheduler}
import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route import akka.http.scaladsl.server.Route
import akka.util.Timeout import akka.util.Timeout
import me.arcanis.ffxivbis.http.{Authorization, UserHelper} import me.arcanis.ffxivbis.http.{Authorization, UserHelper}
import me.arcanis.ffxivbis.messages.Message
import me.arcanis.ffxivbis.models.{Permission, User} import me.arcanis.ffxivbis.models.{Permission, User}
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try import scala.util.Try
class UserView(override val storage: ActorRef)(implicit timeout: Timeout) class UserView(override val storage: ActorRef[Message])
(implicit timeout: Timeout, scheduler: Scheduler)
extends UserHelper with Authorization { extends UserHelper with Authorization {
def route: Route = getUsers ~ modifyUsers def route: Route = getUsers ~ modifyUsers

View File

@ -0,0 +1,8 @@
package me.arcanis.ffxivbis.messages
import akka.actor.typed.ActorRef
import me.arcanis.ffxivbis.models.{BiS, Job}
sealed trait BiSProviderMessage
case class DownloadBiS(link: String, job: Job.Job, replyTo: ActorRef[BiS]) extends BiSProviderMessage

View File

@ -0,0 +1,10 @@
package me.arcanis.ffxivbis.messages
import akka.actor.typed.ActorRef
import me.arcanis.ffxivbis.models.Party
case class ForgetParty(partyId: String) extends Message
case class GetNewPartyId(replyTo: ActorRef[String]) extends Message
case class StoreParty(partyId: String, party: Party) extends Message

View File

@ -0,0 +1,77 @@
package me.arcanis.ffxivbis.messages
import akka.actor.typed.{ActorRef, Behavior}
import me.arcanis.ffxivbis.models.{Party, PartyDescription, Piece, Player, PlayerId, User}
import me.arcanis.ffxivbis.service.LootSelector
sealed trait DatabaseMessage extends Message {
def partyId: String
}
object DatabaseMessage {
type Handler = PartialFunction[DatabaseMessage, Behavior[DatabaseMessage]]
}
// bis handler
case class AddPieceToBis(playerId: PlayerId, piece: Piece, replyTo: ActorRef[Unit]) extends DatabaseMessage {
override def partyId: String = playerId.partyId
}
case class GetBiS(partyId: String, playerId: Option[PlayerId], replyTo: ActorRef[Seq[Player]]) extends DatabaseMessage
case class RemovePieceFromBiS(playerId: PlayerId, piece: Piece, replyTo: ActorRef[Unit]) extends DatabaseMessage {
override def partyId: String = playerId.partyId
}
case class RemovePiecesFromBiS(playerId: PlayerId, replyTo: ActorRef[Unit]) extends DatabaseMessage {
override def partyId: String = playerId.partyId
}
// loot handler
case class AddPieceTo(playerId: PlayerId, piece: Piece, isFreeLoot: Boolean, replyTo: ActorRef[Unit]) extends DatabaseMessage {
override def partyId: String = playerId.partyId
}
case class GetLoot(partyId: String, playerId: Option[PlayerId], replyTo: ActorRef[Seq[Player]]) extends DatabaseMessage
case class RemovePieceFrom(playerId: PlayerId, piece: Piece, replyTo: ActorRef[Unit]) extends DatabaseMessage {
override def partyId: String = playerId.partyId
}
case class SuggestLoot(partyId: String, piece: Piece, replyTo: ActorRef[LootSelector.LootSelectorResult]) extends DatabaseMessage
// party handler
case class AddPlayer(player: Player, replyTo: ActorRef[Unit]) extends DatabaseMessage {
override def partyId: String = player.partyId
}
case class GetParty(partyId: String, replyTo: ActorRef[Party]) extends DatabaseMessage
case class GetPartyDescription(partyId: String, replyTo: ActorRef[PartyDescription]) extends DatabaseMessage
case class GetPlayer(playerId: PlayerId, replyTo: ActorRef[Option[Player]]) extends DatabaseMessage {
override def partyId: String = playerId.partyId
}
case class RemovePlayer(playerId: PlayerId, replyTo: ActorRef[Unit]) extends DatabaseMessage {
override def partyId: String = playerId.partyId
}
case class UpdateParty(partyDescription: PartyDescription, replyTo: ActorRef[Unit]) extends DatabaseMessage {
override def partyId: String = partyDescription.partyId
}
// user handler
case class AddUser(user: User, isHashedPassword: Boolean, replyTo: ActorRef[Unit]) extends DatabaseMessage {
override def partyId: String = user.partyId
}
case class DeleteUser(partyId: String, username: String, replyTo: ActorRef[Unit]) extends DatabaseMessage
case class Exists(partyId: String, replyTo: ActorRef[Boolean]) extends DatabaseMessage
case class GetUser(partyId: String, username: String, replyTo: ActorRef[Option[User]]) extends DatabaseMessage
case class GetUsers(partyId: String, replyTo: ActorRef[Seq[User]]) extends DatabaseMessage

View File

@ -0,0 +1,9 @@
package me.arcanis.ffxivbis.messages
import akka.actor.typed.Behavior
trait Message
object Message {
type Handler = PartialFunction[Message, Behavior[Message]]
}

View File

@ -26,14 +26,15 @@ object Job {
object BodyTanks extends LeftSide object BodyTanks extends LeftSide
object BodyRanges extends LeftSide object BodyRanges extends LeftSide
sealed trait Job { sealed trait Job extends Equals {
def leftSide: LeftSide def leftSide: LeftSide
def rightSide: RightSide def rightSide: RightSide
// conversion to string to avoid recursion // conversion to string to avoid recursion
override def canEqual(that: Any): Boolean = that.isInstanceOf[Job]
override def equals(obj: Any): Boolean = { override def equals(obj: Any): Boolean = {
def canEqual(obj: Any): Boolean = obj.isInstanceOf[Job]
def equality(objRepr: String): Boolean = objRepr match { def equality(objRepr: String): Boolean = objRepr match {
case _ if objRepr == AnyJob.toString => true case _ if objRepr == AnyJob.toString => true
case _ if this.toString == AnyJob.toString => true case _ if this.toString == AnyJob.toString => true

View File

@ -8,21 +8,22 @@
*/ */
package me.arcanis.ffxivbis.service package me.arcanis.ffxivbis.service
import akka.actor.Actor import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.Behaviors
import com.typesafe.config.Config
import com.typesafe.scalalogging.StrictLogging import com.typesafe.scalalogging.StrictLogging
import me.arcanis.ffxivbis.messages.{DatabaseMessage}
import me.arcanis.ffxivbis.models.{Party, Player, PlayerId} import me.arcanis.ffxivbis.models.{Party, Player, PlayerId}
import me.arcanis.ffxivbis.service.impl.DatabaseImpl
import me.arcanis.ffxivbis.storage.DatabaseProfile import me.arcanis.ffxivbis.storage.DatabaseProfile
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
trait Database extends Actor with StrictLogging { trait Database extends StrictLogging {
implicit def executionContext: ExecutionContext
def profile: DatabaseProfile
override def postStop(): Unit = { implicit def executionContext: ExecutionContext
profile.db.close() def config: Config
super.postStop() def profile: DatabaseProfile
}
def filterParty(party: Party, maybePlayerId: Option[PlayerId]): Seq[Player] = def filterParty(party: Party, maybePlayerId: Option[PlayerId]): Seq[Player] =
maybePlayerId match { maybePlayerId match {
@ -36,11 +37,11 @@ trait Database extends Actor with StrictLogging {
players <- profile.getParty(partyId) players <- profile.getParty(partyId)
bis <- if (withBiS) profile.getPiecesBiS(partyId) else Future(Seq.empty) bis <- if (withBiS) profile.getPiecesBiS(partyId) else Future(Seq.empty)
loot <- if (withLoot) profile.getPieces(partyId) else Future(Seq.empty) loot <- if (withLoot) profile.getPieces(partyId) else Future(Seq.empty)
} yield Party(partyDescription, context.system.settings.config, players, bis, loot) } yield Party(partyDescription, config, players, bis, loot)
} }
object Database { object Database {
trait DatabaseRequest {
def partyId: String def apply(): Behavior[DatabaseMessage] =
} Behaviors.setup[DatabaseMessage](context => new DatabaseImpl(context))
} }

View File

@ -21,6 +21,7 @@ class LootSelector(players: Seq[Player], piece: Piece, orderBy: Seq[String]) {
} }
object LootSelector { object LootSelector {
def apply(players: Seq[Player], piece: Piece, orderBy: Seq[String]): LootSelectorResult = def apply(players: Seq[Player], piece: Piece, orderBy: Seq[String]): LootSelectorResult =
new LootSelector(players, piece, orderBy).suggest new LootSelector(players, piece, orderBy).suggest

View File

@ -8,57 +8,65 @@
*/ */
package me.arcanis.ffxivbis.service package me.arcanis.ffxivbis.service
import akka.actor.{Actor, ActorRef, Props} import akka.actor.typed.scaladsl.AskPattern.Askable
import akka.pattern.{ask, pipe} import akka.actor.typed.{ActorRef, Behavior, DispatcherSelector, Scheduler}
import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors}
import akka.util.Timeout import akka.util.Timeout
import com.typesafe.scalalogging.StrictLogging import com.typesafe.scalalogging.StrictLogging
import me.arcanis.ffxivbis.messages.{DatabaseMessage, Exists, ForgetParty, GetNewPartyId, GetParty, Message, StoreParty}
import me.arcanis.ffxivbis.models.Party import me.arcanis.ffxivbis.models.Party
import scala.concurrent.duration.FiniteDuration import scala.concurrent.duration.FiniteDuration
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
class PartyService(storage: ActorRef) extends Actor with StrictLogging { class PartyService(context: ActorContext[Message], storage: ActorRef[DatabaseMessage])
import PartyService._ extends AbstractBehavior[Message](context) with StrictLogging {
import me.arcanis.ffxivbis.utils.Implicits._ import me.arcanis.ffxivbis.utils.Implicits._
private val cacheTimeout: FiniteDuration = private val cacheTimeout: FiniteDuration =
context.system.settings.config.getDuration("me.arcanis.ffxivbis.settings.cache-timeout") context.system.settings.config.getDuration("me.arcanis.ffxivbis.settings.cache-timeout")
implicit private val executionContext: ExecutionContext = implicit private val executionContext: ExecutionContext = {
context.system.dispatchers.lookup("me.arcanis.ffxivbis.default-dispatcher") val selector = DispatcherSelector.fromConfig("me.arcanis.ffxivbis.default-dispatcher")
context.system.dispatchers.lookup(selector)
}
implicit private val timeout: Timeout = implicit private val timeout: Timeout =
context.system.settings.config.getDuration("me.arcanis.ffxivbis.settings.request-timeout") context.system.settings.config.getDuration("me.arcanis.ffxivbis.settings.request-timeout")
implicit private val scheduler: Scheduler = context.system.scheduler
override def receive: Receive = handle(Map.empty) override def onMessage(msg: Message): Behavior[Message] = handle(Map.empty)(msg)
private def handle(cache: Map[String, Party]): Receive = { private def handle(cache: Map[String, Party]): Message.Handler = {
case ForgetParty(partyId) => case ForgetParty(partyId) =>
context become handle(cache - partyId) Behaviors.receiveMessage(handle(cache - partyId))
case GetNewPartyId => case GetNewPartyId(client) =>
val client = sender() getPartyId.foreach(client ! _)
getPartyId.pipeTo(client) Behaviors.same
case req @ impl.DatabasePartyHandler.GetParty(partyId) => case StoreParty(partyId, party) =>
val client = sender() Behaviors.receiveMessage(handle(cache.updated(partyId, party)))
case GetParty(partyId, client) =>
val party = cache.get(partyId) match { val party = cache.get(partyId) match {
case Some(party) => Future.successful(party) case Some(party) => Future.successful(party)
case None => case None =>
(storage ? req).mapTo[Party].map { party => storage.ask(ref => GetParty(partyId, ref)).map { party =>
context become handle(cache + (partyId -> party)) context.self ! StoreParty(partyId, party)
context.system.scheduler.scheduleOnce(cacheTimeout, self, ForgetParty(partyId)) context.system.scheduler.scheduleOnce(cacheTimeout, () => context.self ! ForgetParty(partyId))
party party
} }
} }
party.pipeTo(client) party.foreach(client ! _)
Behaviors.same
case req: Database.DatabaseRequest => case req: DatabaseMessage =>
self ! ForgetParty(req.partyId) storage ! req
storage.forward(req) Behaviors.receiveMessage(handle(cache - req.partyId))
} }
private def getPartyId: Future[String] = { private def getPartyId: Future[String] = {
val partyId = Party.randomPartyId val partyId = Party.randomPartyId
(storage ? impl.DatabaseUserHandler.Exists(partyId)).mapTo[Boolean].flatMap { storage.ask(ref => Exists(partyId, ref)).flatMap {
case true => getPartyId case true => getPartyId
case false => Future.successful(partyId) case false => Future.successful(partyId)
} }
@ -66,8 +74,7 @@ class PartyService(storage: ActorRef) extends Actor with StrictLogging {
} }
object PartyService { object PartyService {
def props(storage: ActorRef): Props = Props(new PartyService(storage))
case class ForgetParty(partyId: String) def apply(storage: ActorRef[DatabaseMessage]): Behavior[Message] =
case object GetNewPartyId Behaviors.setup[Message](context => new PartyService(context, storage))
} }

View File

@ -1,47 +0,0 @@
package me.arcanis.ffxivbis.service
import java.time.Instant
import akka.actor.Actor
import scala.concurrent.ExecutionContext
import scala.concurrent.duration.FiniteDuration
class RateLimiter extends Actor {
import RateLimiter._
import me.arcanis.ffxivbis.utils.Implicits._
implicit private val executionContext: ExecutionContext = context.system.dispatcher
private val maxRequestCount: Int = context.system.settings.config.getInt("me.arcanis.ffxivbis.web.limits.max-count")
private val requestInterval: FiniteDuration = context.system.settings.config.getDuration("me.arcanis.ffxivbis.web.limits.interval")
override def receive: Receive = handle(Map.empty)
private def handle(cache: Map[String, Usage]): Receive = {
case username: String =>
val client = sender()
val usage = if (cache.contains(username)) {
cache(username)
} else {
context.system.scheduler.scheduleOnce(requestInterval, self, Reset(username))
Usage()
}
context become handle(cache + (username -> usage.increment))
val response = if (usage.count > maxRequestCount) Some(usage.left) else None
client ! response
case Reset(username) =>
context become handle(cache - username)
}
}
object RateLimiter {
private case class Usage(count: Int = 0, since: Instant = Instant.now) {
def increment: Usage = copy(count = count + 1)
def left: Long = (Instant.now.toEpochMilli - since.toEpochMilli) / 1000
}
case class Reset(username: String)
}

View File

@ -10,26 +10,33 @@ package me.arcanis.ffxivbis.service.bis
import java.nio.file.Paths import java.nio.file.Paths
import akka.actor.{Actor, Props} import akka.actor.ClassicActorSystemProvider
import akka.actor.typed.{Behavior, PostStop, Signal}
import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors}
import akka.http.scaladsl.model._ import akka.http.scaladsl.model._
import akka.pattern.pipe
import com.typesafe.scalalogging.StrictLogging import com.typesafe.scalalogging.StrictLogging
import me.arcanis.ffxivbis.messages.{BiSProviderMessage, DownloadBiS}
import me.arcanis.ffxivbis.models.{BiS, Job, Piece, PieceType} import me.arcanis.ffxivbis.models.{BiS, Job, Piece, PieceType}
import spray.json._ import spray.json._
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
class BisProvider extends Actor with XivApi with StrictLogging { class BisProvider(context: ActorContext[BiSProviderMessage])
extends AbstractBehavior[BiSProviderMessage](context) with XivApi with StrictLogging {
override def receive: Receive = { override def system: ClassicActorSystemProvider = context.system
case BisProvider.GetBiS(link, job) =>
val client = sender()
get(link, job).map(BiS(_)).pipeTo(client)
}
override def postStop(): Unit = { override def onMessage(msg: BiSProviderMessage): Behavior[BiSProviderMessage] =
shutdown() msg match {
super.postStop() case DownloadBiS(link, job, client) =>
get(link, job).map(BiS(_)).foreach(client ! _)
Behaviors.same
}
override def onSignal: PartialFunction[Signal, Behavior[BiSProviderMessage]] = {
case PostStop =>
shutdown()
Behaviors.same
} }
private def get(link: String, job: Job.Job): Future[Seq[Piece]] = { private def get(link: String, job: Job.Job): Future[Seq[Piece]] = {
@ -49,9 +56,8 @@ class BisProvider extends Actor with XivApi with StrictLogging {
object BisProvider { object BisProvider {
def props: Props = Props(new BisProvider) def apply(): Behavior[BiSProviderMessage] =
Behaviors.setup[BiSProviderMessage](context => new BisProvider(context))
case class GetBiS(link: String, job: Job.Job)
private def parseBisJsonToPieces(job: Job.Job, private def parseBisJsonToPieces(job: Job.Job,
idParser: (Job.Job, JsObject) => Future[Map[String, Long]], idParser: (Job.Job, JsObject) => Future[Map[String, Long]],

View File

@ -8,11 +8,11 @@
*/ */
package me.arcanis.ffxivbis.service.bis package me.arcanis.ffxivbis.service.bis
import akka.actor.ActorContext import akka.actor.ClassicActorSystemProvider
import akka.http.scaladsl.Http import akka.http.scaladsl.Http
import akka.http.scaladsl.model.headers.Location import akka.http.scaladsl.model.headers.Location
import akka.http.scaladsl.model.{HttpRequest, HttpResponse, Uri} import akka.http.scaladsl.model.{HttpRequest, HttpResponse, Uri}
import akka.stream.ActorMaterializer import akka.stream.Materializer
import akka.stream.scaladsl.{Keep, Sink} import akka.stream.scaladsl.{Keep, Sink}
import akka.util.ByteString import akka.util.ByteString
import spray.json._ import spray.json._
@ -21,12 +21,12 @@ import scala.concurrent.{ExecutionContext, Future}
trait RequestExecutor { trait RequestExecutor {
def context: ActorContext def system: ClassicActorSystemProvider
private val http = Http()(context.system) private val http = Http()(system)
implicit val materializer: ActorMaterializer = ActorMaterializer()(context) implicit val materializer: Materializer = Materializer.createMaterializer(system)
implicit val executionContext: ExecutionContext = implicit val executionContext: ExecutionContext =
context.system.dispatchers.lookup("me.arcanis.ffxivbis.default-dispatcher") system.classicSystem.dispatchers.lookup("me.arcanis.ffxivbis.default-dispatcher")
def sendRequest[T](uri: Uri, parser: JsObject => Future[T]): Future[T] = def sendRequest[T](uri: Uri, parser: JsObject => Future[T]): Future[T] =
http.singleRequest(HttpRequest(uri = uri)).map { http.singleRequest(HttpRequest(uri = uri)).map {

View File

@ -17,7 +17,7 @@ import scala.util.Try
trait XivApi extends RequestExecutor { trait XivApi extends RequestExecutor {
private val config = context.system.settings.config private val config = system.classicSystem.settings.config
private val xivapiUrl = config.getString("me.arcanis.ffxivbis.bis-provider.xivapi-url") private val xivapiUrl = config.getString("me.arcanis.ffxivbis.bis-provider.xivapi-url")
private val xivapiKey = Try(config.getString("me.arcanis.ffxivbis.bis-provider.xivapi-key")).toOption private val xivapiKey = Try(config.getString("me.arcanis.ffxivbis.bis-provider.xivapi-key")).toOption

View File

@ -8,43 +8,30 @@
*/ */
package me.arcanis.ffxivbis.service.impl package me.arcanis.ffxivbis.service.impl
import akka.pattern.pipe import akka.actor.typed.scaladsl.Behaviors
import me.arcanis.ffxivbis.models.{Piece, PlayerId} import me.arcanis.ffxivbis.messages.{AddPieceToBis, DatabaseMessage, GetBiS, RemovePieceFromBiS, RemovePiecesFromBiS}
import me.arcanis.ffxivbis.service.Database import me.arcanis.ffxivbis.service.Database
trait DatabaseBiSHandler { this: Database => trait DatabaseBiSHandler { this: Database =>
import DatabaseBiSHandler._
def bisHandler: Receive = { def bisHandler: DatabaseMessage.Handler = {
case AddPieceToBis(playerId, piece) => case AddPieceToBis(playerId, piece, client) =>
val client = sender() profile.insertPieceBiS(playerId, piece).foreach(_ => client ! ())
profile.insertPieceBiS(playerId, piece).pipeTo(client) Behaviors.same
case GetBiS(partyId, maybePlayerId) => case GetBiS(partyId, maybePlayerId, client) =>
val client = sender()
getParty(partyId, withBiS = true, withLoot = false) getParty(partyId, withBiS = true, withLoot = false)
.map(filterParty(_, maybePlayerId)) .map(filterParty(_, maybePlayerId))
.pipeTo(client) .foreach(client ! _)
Behaviors.same
case RemovePieceFromBiS(playerId, piece) => case RemovePieceFromBiS(playerId, piece, client) =>
val client = sender() profile.deletePieceBiS(playerId, piece).foreach(_ => client ! ())
profile.deletePieceBiS(playerId, piece).pipeTo(client) Behaviors.same
case RemovePiecesFromBiS(playerId) => case RemovePiecesFromBiS(playerId, client) =>
val client = sender() profile.deletePiecesBiS(playerId).foreach(_ => client ! ())
profile.deletePiecesBiS(playerId).pipeTo(client) Behaviors.same
} }
} }
object DatabaseBiSHandler {
case class AddPieceToBis(playerId: PlayerId, piece: Piece) extends Database.DatabaseRequest {
override def partyId: String = playerId.partyId
}
case class GetBiS(partyId: String, playerId: Option[PlayerId]) extends Database.DatabaseRequest
case class RemovePieceFromBiS(playerId: PlayerId, piece: Piece) extends Database.DatabaseRequest {
override def partyId: String = playerId.partyId
}
case class RemovePiecesFromBiS(playerId: PlayerId) extends Database.DatabaseRequest {
override def partyId: String = playerId.partyId
}
}

View File

@ -8,24 +8,29 @@
*/ */
package me.arcanis.ffxivbis.service.impl package me.arcanis.ffxivbis.service.impl
import akka.actor.Props import akka.actor.typed.{Behavior, DispatcherSelector}
import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext}
import com.typesafe.config.Config
import me.arcanis.ffxivbis.messages.DatabaseMessage
import me.arcanis.ffxivbis.service.Database import me.arcanis.ffxivbis.service.Database
import me.arcanis.ffxivbis.storage.DatabaseProfile import me.arcanis.ffxivbis.storage.DatabaseProfile
import scala.concurrent.ExecutionContext import scala.concurrent.ExecutionContext
class DatabaseImpl extends Database class DatabaseImpl(context: ActorContext[DatabaseMessage])
extends AbstractBehavior[DatabaseMessage](context) with Database
with DatabaseBiSHandler with DatabaseLootHandler with DatabaseBiSHandler with DatabaseLootHandler
with DatabasePartyHandler with DatabaseUserHandler { with DatabasePartyHandler with DatabaseUserHandler {
implicit val executionContext: ExecutionContext = override implicit val executionContext: ExecutionContext = {
context.system.dispatchers.lookup("me.arcanis.ffxivbis.default-dispatcher") val selector = DispatcherSelector.fromConfig("me.arcanis.ffxivbis.default-dispatcher")
val profile = new DatabaseProfile(executionContext, context.system.settings.config) context.system.dispatchers.lookup(selector)
}
override val config: Config = context.system.settings.config
override val profile: DatabaseProfile = new DatabaseProfile(executionContext, config)
override def receive: Receive = override def onMessage(msg: DatabaseMessage): Behavior[DatabaseMessage] = handle(msg)
private def handle: DatabaseMessage.Handler =
bisHandler orElse lootHandler orElse partyHandler orElse userHandler bisHandler orElse lootHandler orElse partyHandler orElse userHandler
} }
object DatabaseImpl {
def props: Props = Props(new DatabaseImpl)
}

View File

@ -10,42 +10,33 @@ package me.arcanis.ffxivbis.service.impl
import java.time.Instant import java.time.Instant
import akka.pattern.pipe import akka.actor.typed.scaladsl.Behaviors
import me.arcanis.ffxivbis.models.{Loot, Piece, PlayerId} import me.arcanis.ffxivbis.messages.{AddPieceTo, DatabaseMessage, GetLoot, RemovePieceFrom, SuggestLoot}
import me.arcanis.ffxivbis.models.Loot
import me.arcanis.ffxivbis.service.Database import me.arcanis.ffxivbis.service.Database
trait DatabaseLootHandler { this: Database => trait DatabaseLootHandler { this: Database =>
import DatabaseLootHandler._
def lootHandler: Receive = { def lootHandler: DatabaseMessage.Handler = {
case AddPieceTo(playerId, piece, isFreeLoot) => case AddPieceTo(playerId, piece, isFreeLoot, client) =>
val client = sender()
val loot = Loot(-1, piece, Instant.now, isFreeLoot) val loot = Loot(-1, piece, Instant.now, isFreeLoot)
profile.insertPiece(playerId, loot).pipeTo(client) profile.insertPiece(playerId, loot).foreach(_ => client ! ())
Behaviors.same
case GetLoot(partyId, maybePlayerId) => case GetLoot(partyId, maybePlayerId, client) =>
val client = sender()
getParty(partyId, withBiS = false, withLoot = true) getParty(partyId, withBiS = false, withLoot = true)
.map(filterParty(_, maybePlayerId)) .map(filterParty(_, maybePlayerId))
.pipeTo(client) .foreach(client ! _)
Behaviors.same
case RemovePieceFrom(playerId, piece) => case RemovePieceFrom(playerId, piece, client) =>
val client = sender() profile.deletePiece(playerId, piece).foreach(_ => client ! ())
profile.deletePiece(playerId, piece).pipeTo(client) Behaviors.same
case SuggestLoot(partyId, piece) => case SuggestLoot(partyId, piece, client) =>
val client = sender() getParty(partyId, withBiS = true, withLoot = true)
getParty(partyId, withBiS = true, withLoot = true).map(_.suggestLoot(piece)).pipeTo(client) .map(_.suggestLoot(piece))
.foreach(client ! _)
Behaviors.same
} }
} }
object DatabaseLootHandler {
case class AddPieceTo(playerId: PlayerId, piece: Piece, isFreeLoot: Boolean) extends Database.DatabaseRequest {
override def partyId: String = playerId.partyId
}
case class GetLoot(partyId: String, playerId: Option[PlayerId]) extends Database.DatabaseRequest
case class RemovePieceFrom(playerId: PlayerId, piece: Piece) extends Database.DatabaseRequest {
override def partyId: String = playerId.partyId
}
case class SuggestLoot(partyId: String, piece: Piece) extends Database.DatabaseRequest
}

View File

@ -8,30 +8,29 @@
*/ */
package me.arcanis.ffxivbis.service.impl package me.arcanis.ffxivbis.service.impl
import akka.pattern.pipe import akka.actor.typed.scaladsl.Behaviors
import me.arcanis.ffxivbis.models.{BiS, PartyDescription, Player, PlayerId} import me.arcanis.ffxivbis.messages.{AddPlayer, DatabaseMessage, GetParty, GetPartyDescription, GetPlayer, RemovePlayer, UpdateParty}
import me.arcanis.ffxivbis.models.{BiS, Player}
import me.arcanis.ffxivbis.service.Database import me.arcanis.ffxivbis.service.Database
import scala.concurrent.Future import scala.concurrent.Future
trait DatabasePartyHandler { this: Database => trait DatabasePartyHandler { this: Database =>
import DatabasePartyHandler._
def partyHandler: Receive = { def partyHandler: DatabaseMessage.Handler = {
case AddPlayer(player) => case AddPlayer(player, client) =>
val client = sender() profile.insertPlayer(player).foreach(_ => client ! ())
profile.insertPlayer(player).pipeTo(client) Behaviors.same
case GetParty(partyId) => case GetParty(partyId, client) =>
val client = sender() getParty(partyId, withBiS = true, withLoot = true).foreach(client ! _)
getParty(partyId, withBiS = true, withLoot = true).pipeTo(client) Behaviors.same
case GetPartyDescription(partyId) => case GetPartyDescription(partyId, client) =>
val client = sender() profile.getPartyDescription(partyId).foreach(client ! _)
profile.getPartyDescription(partyId).pipeTo(client) Behaviors.same
case GetPlayer(playerId) => case GetPlayer(playerId, client) =>
val client = sender()
val player = profile.getPlayerFull(playerId).flatMap { maybePlayerData => val player = profile.getPlayerFull(playerId).flatMap { maybePlayerData =>
Future.traverse(maybePlayerData.toSeq) { playerData => Future.traverse(maybePlayerData.toSeq) { playerData =>
for { for {
@ -42,31 +41,15 @@ trait DatabasePartyHandler { this: Database =>
playerData.link, playerData.priority) playerData.link, playerData.priority)
} }
}.map(_.headOption) }.map(_.headOption)
player.pipeTo(client) player.foreach(client ! _)
Behaviors.same
case RemovePlayer(playerId) => case RemovePlayer(playerId, client) =>
val client = sender() profile.deletePlayer(playerId).foreach(_ => client ! ())
profile.deletePlayer(playerId).pipeTo(client) Behaviors.same
case UpdateParty(description) => case UpdateParty(description, client) =>
val client = sender() profile.insertPartyDescription(description).foreach(_ => client ! ())
profile.insertPartyDescription(description).pipeTo(client) Behaviors.same
}
}
object DatabasePartyHandler {
case class AddPlayer(player: Player) extends Database.DatabaseRequest {
override def partyId: String = player.partyId
}
case class GetParty(partyId: String) extends Database.DatabaseRequest
case class GetPartyDescription(partyId: String) extends Database.DatabaseRequest
case class GetPlayer(playerId: PlayerId) extends Database.DatabaseRequest {
override def partyId: String = playerId.partyId
}
case class RemovePlayer(playerId: PlayerId) extends Database.DatabaseRequest {
override def partyId: String = playerId.partyId
}
case class UpdateParty(partyDescription: PartyDescription) extends Database.DatabaseRequest {
override def partyId: String = partyDescription.partyId
} }
} }

View File

@ -8,43 +8,32 @@
*/ */
package me.arcanis.ffxivbis.service.impl package me.arcanis.ffxivbis.service.impl
import akka.pattern.pipe import akka.actor.typed.scaladsl.Behaviors
import me.arcanis.ffxivbis.models.User import me.arcanis.ffxivbis.messages.{AddUser, DatabaseMessage, DeleteUser, Exists, GetUser, GetUsers}
import me.arcanis.ffxivbis.service.Database import me.arcanis.ffxivbis.service.Database
trait DatabaseUserHandler { this: Database => trait DatabaseUserHandler { this: Database =>
import DatabaseUserHandler._
def userHandler: Receive = { def userHandler: DatabaseMessage.Handler = {
case AddUser(user, isHashedPassword) => case AddUser(user, isHashedPassword, client) =>
val client = sender()
val toInsert = if (isHashedPassword) user else user.withHashedPassword val toInsert = if (isHashedPassword) user else user.withHashedPassword
profile.insertUser(toInsert).pipeTo(client) profile.insertUser(toInsert).foreach(_ => client ! ())
Behaviors.same
case DeleteUser(partyId, username) => case DeleteUser(partyId, username, client) =>
val client = sender() profile.deleteUser(partyId, username).foreach(_ => client ! ())
profile.deleteUser(partyId, username).pipeTo(client) Behaviors.same
case Exists(partyId) => case Exists(partyId, client) =>
val client = sender() profile.exists(partyId).foreach(client ! _)
profile.exists(partyId).pipeTo(client) Behaviors.same
case GetUser(partyId, username) => case GetUser(partyId, username, client) =>
val client = sender() profile.getUser(partyId, username).foreach(client ! _)
profile.getUser(partyId, username).pipeTo(client) Behaviors.same
case GetUsers(partyId) => case GetUsers(partyId, client) =>
val client = sender() profile.getUsers(partyId).foreach(client ! _)
profile.getUsers(partyId).pipeTo(client) Behaviors.same
} }
} }
object DatabaseUserHandler {
case class AddUser(user: User, isHashedPassword: Boolean) extends Database.DatabaseRequest {
override def partyId: String = user.partyId
}
case class DeleteUser(partyId: String, username: String) extends Database.DatabaseRequest
case class Exists(partyId: String) extends Database.DatabaseRequest
case class GetUser(partyId: String, username: String) extends Database.DatabaseRequest
case class GetUsers(partyId: String) extends Database.DatabaseRequest
}

View File

@ -68,6 +68,7 @@ class DatabaseProfile(context: ExecutionContext, config: Config)
} }
object DatabaseProfile { object DatabaseProfile {
def now: Long = Instant.now.toEpochMilli def now: Long = Instant.now.toEpochMilli
def getSection(config: Config): Config = { def getSection(config: Config): Config = {
val section = config.getString("me.arcanis.ffxivbis.database.mode") val section = config.getString("me.arcanis.ffxivbis.database.mode")

View File

@ -15,6 +15,7 @@ import org.flywaydb.core.api.configuration.ClassicConfiguration
import scala.concurrent.Future import scala.concurrent.Future
class Migration(config: Config) { class Migration(config: Config) {
def performMigration(): Future[Int] = { def performMigration(): Future[Int] = {
val section = DatabaseProfile.getSection(config) val section = DatabaseProfile.getSection(config)
@ -37,5 +38,6 @@ class Migration(config: Config) {
} }
object Migration { object Migration {
def apply(config: Config): Future[Int] = new Migration(config).performMigration() def apply(config: Config): Future[Int] = new Migration(config).performMigration()
} }

View File

@ -1,52 +1,55 @@
package me.arcanis.ffxivbis.http.api.v1 package me.arcanis.ffxivbis.http.api.v1
import akka.actor.ActorRef import akka.actor.testkit.typed.scaladsl.ActorTestKit
import akka.actor.typed.scaladsl.AskPattern.Askable
import akka.http.scaladsl.model.{StatusCodes, Uri} import akka.http.scaladsl.model.{StatusCodes, Uri}
import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials} import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials}
import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest} import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest}
import akka.http.scaladsl.server._
import akka.pattern.ask
import akka.testkit.TestKit import akka.testkit.TestKit
import com.typesafe.config.Config import com.typesafe.config.Config
import me.arcanis.ffxivbis.{Fixtures, Settings} import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.http.api.v1.json._ import me.arcanis.ffxivbis.http.api.v1.json._
import me.arcanis.ffxivbis.models.{BiS, Body, Job, PieceType} import me.arcanis.ffxivbis.messages.{AddPlayer, AddUser}
import me.arcanis.ffxivbis.models.{BiS, Job}
import me.arcanis.ffxivbis.service.bis.BisProvider import me.arcanis.ffxivbis.service.bis.BisProvider
import me.arcanis.ffxivbis.service.{PartyService, impl} import me.arcanis.ffxivbis.service.{Database, PartyService}
import me.arcanis.ffxivbis.storage.Migration import me.arcanis.ffxivbis.storage.Migration
import me.arcanis.ffxivbis.utils.Compare import me.arcanis.ffxivbis.utils.Compare
import org.scalatest.{Matchers, WordSpec} import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
import scala.concurrent.Await import scala.concurrent.Await
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.language.postfixOps import scala.language.postfixOps
class BiSEndpointTest extends WordSpec class BiSEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRouteTest with JsonSupport {
with Matchers with ScalatestRouteTest with JsonSupport {
private val auth: Authorization = private val testKit = ActorTestKit(Settings.withRandomDatabase)
override val testConfig: Config = testKit.system.settings.config
private val auth =
Authorization(BasicHttpCredentials(Fixtures.userAdmin.username, Fixtures.userPassword)) Authorization(BasicHttpCredentials(Fixtures.userAdmin.username, Fixtures.userPassword))
private val endpoint: Uri = Uri(s"/party/${Fixtures.partyId}/bis") private val endpoint = Uri(s"/party/${Fixtures.partyId}/bis")
private val playerId = PlayerIdResponse.fromPlayerId(Fixtures.playerEmpty.playerId) private val playerId = PlayerIdResponse.fromPlayerId(Fixtures.playerEmpty.playerId)
private val timeout: FiniteDuration = 60 seconds private val askTimeout = 60 seconds
implicit private val routeTimeout: RouteTestTimeout = RouteTestTimeout(timeout) implicit private val routeTimeout: RouteTestTimeout = RouteTestTimeout(askTimeout)
private val storage: ActorRef = system.actorOf(impl.DatabaseImpl.props) private val storage = testKit.spawn(Database())
private val provider: ActorRef = system.actorOf(BisProvider.props) private val provider = testKit.spawn(BisProvider())
private val party: ActorRef = system.actorOf(PartyService.props(storage)) private val party = testKit.spawn(PartyService(storage))
private val route: Route = new BiSEndpoint(party, provider)(timeout).route private val route = new BiSEndpoint(party, provider)(askTimeout, testKit.scheduler).route
override def testConfig: Config = Settings.withRandomDatabase
override def beforeAll: Unit = { override def beforeAll: Unit = {
Await.result(Migration(system.settings.config), timeout) Await.result(Migration(testConfig), askTimeout)
Await.result((storage ? impl.DatabaseUserHandler.AddUser(Fixtures.userAdmin, isHashedPassword = true))(timeout).mapTo[Int], timeout) Await.result(storage.ask(AddUser(Fixtures.userAdmin, isHashedPassword = true, _))(askTimeout, testKit.scheduler), askTimeout)
Await.result((storage ? impl.DatabasePartyHandler.AddPlayer(Fixtures.playerEmpty))(timeout).mapTo[Int], timeout) Await.result(storage.ask(AddPlayer(Fixtures.playerEmpty, _))(askTimeout, testKit.scheduler), askTimeout)
} }
override def afterAll: Unit = { override def afterAll: Unit = {
super.afterAll()
Settings.clearDatabase(testConfig)
TestKit.shutdownActorSystem(system) TestKit.shutdownActorSystem(system)
Settings.clearDatabase(system.settings.config) testKit.shutdownTestKit()
} }
private def compareBiSResponse(actual: PlayerResponse, expected: PlayerResponse): Unit = { private def compareBiSResponse(actual: PlayerResponse, expected: PlayerResponse): Unit = {

View File

@ -2,49 +2,52 @@ package me.arcanis.ffxivbis.http.api.v1
import java.time.Instant import java.time.Instant
import akka.actor.ActorRef import akka.actor.testkit.typed.scaladsl.ActorTestKit
import akka.actor.typed.scaladsl.AskPattern.Askable
import akka.http.scaladsl.model.{StatusCodes, Uri} import akka.http.scaladsl.model.{StatusCodes, Uri}
import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials} import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials}
import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest} import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest}
import akka.http.scaladsl.server._
import akka.pattern.ask
import akka.testkit.TestKit import akka.testkit.TestKit
import com.typesafe.config.Config import com.typesafe.config.Config
import me.arcanis.ffxivbis.{Fixtures, Settings} import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.http.api.v1.json._ import me.arcanis.ffxivbis.http.api.v1.json._
import me.arcanis.ffxivbis.service.{PartyService, impl} import me.arcanis.ffxivbis.messages.{AddPlayer, AddUser}
import me.arcanis.ffxivbis.service.{Database, PartyService}
import me.arcanis.ffxivbis.storage.Migration import me.arcanis.ffxivbis.storage.Migration
import org.scalatest.{Matchers, WordSpec} import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
import scala.concurrent.Await import scala.concurrent.Await
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.language.postfixOps import scala.language.postfixOps
class LootEndpointTest extends WordSpec class LootEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRouteTest with JsonSupport {
with Matchers with ScalatestRouteTest with JsonSupport {
private val auth: Authorization = private val testKit = ActorTestKit(Settings.withRandomDatabase)
override val testConfig: Config = testKit.system.settings.config
private val auth =
Authorization(BasicHttpCredentials(Fixtures.userAdmin.username, Fixtures.userPassword)) Authorization(BasicHttpCredentials(Fixtures.userAdmin.username, Fixtures.userPassword))
private val endpoint: Uri = Uri(s"/party/${Fixtures.partyId}/loot") private val endpoint = Uri(s"/party/${Fixtures.partyId}/loot")
private val playerId = PlayerIdResponse.fromPlayerId(Fixtures.playerEmpty.playerId) private val playerId = PlayerIdResponse.fromPlayerId(Fixtures.playerEmpty.playerId)
private val timeout: FiniteDuration = 60 seconds private val askTimeout = 60 seconds
implicit private val routeTimeout: RouteTestTimeout = RouteTestTimeout(timeout) implicit private val routeTimeout: RouteTestTimeout = RouteTestTimeout(askTimeout)
private val storage: ActorRef = system.actorOf(impl.DatabaseImpl.props) private val storage = testKit.spawn(Database())
private val party: ActorRef = system.actorOf(PartyService.props(storage)) private val party = testKit.spawn(PartyService(storage))
private val route: Route = new LootEndpoint(party)(timeout).route private val route = new LootEndpoint(party)(askTimeout, testKit.scheduler).route
override def testConfig: Config = Settings.withRandomDatabase
override def beforeAll: Unit = { override def beforeAll: Unit = {
Await.result(Migration(system.settings.config), timeout) Await.result(Migration(testConfig), askTimeout)
Await.result((storage ? impl.DatabaseUserHandler.AddUser(Fixtures.userAdmin, isHashedPassword = true))(timeout).mapTo[Int], timeout) Await.result(storage.ask(AddUser(Fixtures.userAdmin, isHashedPassword = true, _))(askTimeout, testKit.scheduler), askTimeout)
Await.result((storage ? impl.DatabasePartyHandler.AddPlayer(Fixtures.playerEmpty))(timeout).mapTo[Int], timeout) Await.result(storage.ask(AddPlayer(Fixtures.playerEmpty, _))(askTimeout, testKit.scheduler), askTimeout)
} }
override def afterAll: Unit = { override def afterAll: Unit = {
super.afterAll()
Settings.clearDatabase(testConfig)
TestKit.shutdownActorSystem(system) TestKit.shutdownActorSystem(system)
Settings.clearDatabase(system.settings.config) testKit.shutdownTestKit()
} }
"api v1 loot endpoint" must { "api v1 loot endpoint" must {

View File

@ -1,48 +1,52 @@
package me.arcanis.ffxivbis.http.api.v1 package me.arcanis.ffxivbis.http.api.v1
import akka.actor.ActorRef import akka.actor.testkit.typed.scaladsl.ActorTestKit
import akka.actor.typed.scaladsl.AskPattern.Askable
import akka.http.scaladsl.model.{StatusCodes, Uri} import akka.http.scaladsl.model.{StatusCodes, Uri}
import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials} import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials}
import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest} import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest}
import akka.http.scaladsl.server._
import akka.testkit.TestKit import akka.testkit.TestKit
import akka.pattern.ask
import com.typesafe.config.Config import com.typesafe.config.Config
import me.arcanis.ffxivbis.{Fixtures, Settings} import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.http.api.v1.json._ import me.arcanis.ffxivbis.http.api.v1.json._
import me.arcanis.ffxivbis.messages.AddUser
import me.arcanis.ffxivbis.models.PartyDescription import me.arcanis.ffxivbis.models.PartyDescription
import me.arcanis.ffxivbis.service.bis.BisProvider import me.arcanis.ffxivbis.service.bis.BisProvider
import me.arcanis.ffxivbis.service.impl import me.arcanis.ffxivbis.service.{Database, PartyService}
import me.arcanis.ffxivbis.storage.Migration import me.arcanis.ffxivbis.storage.Migration
import org.scalatest.{Matchers, WordSpec} import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
import scala.concurrent.Await import scala.concurrent.Await
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.language.postfixOps import scala.language.postfixOps
class PartyEndpointTest extends WordSpec class PartyEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRouteTest with JsonSupport {
with Matchers with ScalatestRouteTest with JsonSupport {
private val auth: Authorization = private val testKit = ActorTestKit(Settings.withRandomDatabase)
override val testConfig: Config = testKit.system.settings.config
private val auth =
Authorization(BasicHttpCredentials(Fixtures.userAdmin.username, Fixtures.userPassword)) Authorization(BasicHttpCredentials(Fixtures.userAdmin.username, Fixtures.userPassword))
private val endpoint: Uri = Uri(s"/party/${Fixtures.partyId}/description") private val endpoint = Uri(s"/party/${Fixtures.partyId}/description")
private val timeout: FiniteDuration = 60 seconds private val askTimeout = 60 seconds
implicit private val routeTimeout: RouteTestTimeout = RouteTestTimeout(timeout) implicit private val routeTimeout: RouteTestTimeout = RouteTestTimeout(askTimeout)
private val storage: ActorRef = system.actorOf(impl.DatabaseImpl.props) private val storage = testKit.spawn(Database())
private val provider: ActorRef = system.actorOf(BisProvider.props) private val provider = testKit.spawn(BisProvider())
private val route: Route = new PartyEndpoint(storage, provider)(timeout).route private val party = testKit.spawn(PartyService(storage))
private val route = new PartyEndpoint(party, provider)(askTimeout, testKit.scheduler).route
override def testConfig: Config = Settings.withRandomDatabase
override def beforeAll: Unit = { override def beforeAll: Unit = {
Await.result(Migration(system.settings.config), timeout) Await.result(Migration(testConfig), askTimeout)
Await.result((storage ? impl.DatabaseUserHandler.AddUser(Fixtures.userAdmin, isHashedPassword = true))(timeout).mapTo[Int], timeout) Await.result(storage.ask(AddUser(Fixtures.userAdmin, isHashedPassword = true, _))(askTimeout, testKit.scheduler), askTimeout)
} }
override def afterAll: Unit = { override def afterAll: Unit = {
super.afterAll()
Settings.clearDatabase(testConfig)
TestKit.shutdownActorSystem(system) TestKit.shutdownActorSystem(system)
Settings.clearDatabase(system.settings.config) testKit.shutdownTestKit()
} }
"api v1 party endpoint" must { "api v1 party endpoint" must {

View File

@ -1,56 +1,57 @@
package me.arcanis.ffxivbis.http.api.v1 package me.arcanis.ffxivbis.http.api.v1
import akka.actor.ActorRef import akka.actor.testkit.typed.scaladsl.ActorTestKit
import akka.actor.typed.scaladsl.AskPattern.Askable
import akka.http.scaladsl.model.{StatusCodes, Uri} import akka.http.scaladsl.model.{StatusCodes, Uri}
import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials} import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials}
import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest} import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest}
import akka.http.scaladsl.server._
import akka.pattern.ask
import akka.testkit.TestKit import akka.testkit.TestKit
import com.typesafe.config.Config import com.typesafe.config.Config
import me.arcanis.ffxivbis.{Fixtures, Settings} import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.http.api.v1.json._ import me.arcanis.ffxivbis.http.api.v1.json._
import me.arcanis.ffxivbis.messages.{AddPlayer, AddUser}
import me.arcanis.ffxivbis.service.bis.BisProvider import me.arcanis.ffxivbis.service.bis.BisProvider
import me.arcanis.ffxivbis.service.{PartyService, impl} import me.arcanis.ffxivbis.service.{Database, PartyService}
import me.arcanis.ffxivbis.storage.Migration import me.arcanis.ffxivbis.storage.Migration
import org.scalatest.{Matchers, WordSpec} import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
import scala.concurrent.Await import scala.concurrent.Await
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.language.postfixOps import scala.language.postfixOps
class PlayerEndpointTest extends WordSpec class PlayerEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRouteTest with JsonSupport {
with Matchers with ScalatestRouteTest with JsonSupport {
private val auth: Authorization = private val testKit = ActorTestKit(Settings.withRandomDatabase)
override val testConfig: Config = testKit.system.settings.config
private val auth =
Authorization(BasicHttpCredentials(Fixtures.userAdmin.username, Fixtures.userPassword)) Authorization(BasicHttpCredentials(Fixtures.userAdmin.username, Fixtures.userPassword))
private val endpoint: Uri = Uri(s"/party/${Fixtures.partyId}") private val endpoint = Uri(s"/party/${Fixtures.partyId}")
private val playerId = PlayerIdResponse.fromPlayerId(Fixtures.playerEmpty.playerId) private val askTimeout = 60 seconds
private val timeout: FiniteDuration = 60 seconds implicit private val routeTimeout: RouteTestTimeout = RouteTestTimeout(askTimeout)
implicit private val routeTimeout: RouteTestTimeout = RouteTestTimeout(timeout)
private val storage: ActorRef = system.actorOf(impl.DatabaseImpl.props) private val storage = testKit.spawn(Database())
private val provider: ActorRef = system.actorOf(BisProvider.props) private val provider = testKit.spawn(BisProvider())
private val party: ActorRef = system.actorOf(PartyService.props(storage)) private val party = testKit.spawn(PartyService(storage))
private val route: Route = new PlayerEndpoint(party, provider)(timeout).route private val route = new PlayerEndpoint(party, provider)(askTimeout, testKit.scheduler).route
override def testConfig: Config = Settings.withRandomDatabase
override def beforeAll: Unit = { override def beforeAll: Unit = {
Await.result(Migration(system.settings.config), timeout) Await.result(Migration(testConfig), askTimeout)
Await.result((storage ? impl.DatabaseUserHandler.AddUser(Fixtures.userAdmin, isHashedPassword = true))(timeout).mapTo[Int], timeout) Await.result(storage.ask(AddUser(Fixtures.userAdmin, isHashedPassword = true, _))(askTimeout, testKit.scheduler), askTimeout)
Await.result((storage ? impl.DatabasePartyHandler.AddPlayer(Fixtures.playerEmpty))(timeout).mapTo[Int], timeout) Await.result(storage.ask(AddPlayer(Fixtures.playerEmpty, _))(askTimeout, testKit.scheduler), askTimeout)
} }
override def afterAll: Unit = { override def afterAll: Unit = {
super.afterAll()
Settings.clearDatabase(testConfig)
TestKit.shutdownActorSystem(system) TestKit.shutdownActorSystem(system)
Settings.clearDatabase(system.settings.config) testKit.shutdownTestKit()
} }
"api v1 player endpoint" must { "api v1 player endpoint" must {
"get users" in { "get users" in {
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
val response = Seq(PlayerResponse.fromPlayer(Fixtures.playerEmpty)) val response = Seq(PlayerResponse.fromPlayer(Fixtures.playerEmpty))
Get(endpoint).withHeaders(auth) ~> route ~> check { Get(endpoint).withHeaders(auth) ~> route ~> check {

View File

@ -2,21 +2,21 @@ package me.arcanis.ffxivbis.http.api.v1
import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.testkit.ScalatestRouteTest import akka.http.scaladsl.testkit.ScalatestRouteTest
import akka.http.scaladsl.server._
import com.typesafe.config.Config import com.typesafe.config.Config
import me.arcanis.ffxivbis.Settings import me.arcanis.ffxivbis.Settings
import me.arcanis.ffxivbis.http.api.v1.json._ import me.arcanis.ffxivbis.http.api.v1.json._
import me.arcanis.ffxivbis.models.{Job, Party, Permission, Piece, PieceType} import me.arcanis.ffxivbis.models.{Job, Party, Permission, Piece, PieceType}
import org.scalatest.{Matchers, WordSpec} import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
import scala.language.postfixOps import scala.language.postfixOps
class TypesEndpointTest extends WordSpec class TypesEndpointTest extends AnyWordSpecLike
with Matchers with ScalatestRouteTest with JsonSupport { with Matchers with ScalatestRouteTest with JsonSupport {
private val route: Route = new TypesEndpoint(testConfig).route override val testConfig: Config = Settings.withRandomDatabase
override def testConfig: Config = Settings.withRandomDatabase private val route = new TypesEndpoint(testConfig).route
"api v1 types endpoint" must { "api v1 types endpoint" must {

View File

@ -1,45 +1,47 @@
package me.arcanis.ffxivbis.http.api.v1 package me.arcanis.ffxivbis.http.api.v1
import akka.actor.ActorRef import akka.actor.testkit.typed.scaladsl.ActorTestKit
import akka.http.scaladsl.model.{StatusCodes, Uri} import akka.http.scaladsl.model.{StatusCodes, Uri}
import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials} import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials}
import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest} import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest}
import akka.http.scaladsl.server._
import akka.testkit.TestKit import akka.testkit.TestKit
import com.typesafe.config.Config import com.typesafe.config.Config
import me.arcanis.ffxivbis.{Fixtures, Settings} import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.http.api.v1.json._ import me.arcanis.ffxivbis.http.api.v1.json._
import me.arcanis.ffxivbis.service.{PartyService, impl} import me.arcanis.ffxivbis.service.{Database, PartyService}
import me.arcanis.ffxivbis.storage.Migration import me.arcanis.ffxivbis.storage.Migration
import org.scalatest.{Matchers, WordSpec} import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
import scala.concurrent.Await import scala.concurrent.Await
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.language.postfixOps import scala.language.postfixOps
class UserEndpointTest extends WordSpec class UserEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRouteTest with JsonSupport {
with Matchers with ScalatestRouteTest with JsonSupport {
private val auth: Authorization = private val testKit = ActorTestKit(Settings.withRandomDatabase)
override val testConfig: Config = testKit.system.settings.config
private val auth =
Authorization(BasicHttpCredentials(Fixtures.userAdmin.username, Fixtures.userPassword)) Authorization(BasicHttpCredentials(Fixtures.userAdmin.username, Fixtures.userPassword))
private def endpoint: Uri = Uri(s"/party/$partyId/users") private def endpoint = Uri(s"/party/$partyId/users")
private val timeout: FiniteDuration = 60 seconds private val askTimeout = 60 seconds
implicit private val routeTimeout: RouteTestTimeout = RouteTestTimeout(timeout) implicit private val routeTimeout: RouteTestTimeout = RouteTestTimeout(askTimeout)
private var partyId: String = Fixtures.partyId private var partyId = Fixtures.partyId
private val storage: ActorRef = system.actorOf(impl.DatabaseImpl.props) private val storage = testKit.spawn(Database())
private val party: ActorRef = system.actorOf(PartyService.props(storage)) private val party = testKit.spawn(PartyService(storage))
private val route: Route = new UserEndpoint(party)(timeout).route private val route = new UserEndpoint(party)(askTimeout, testKit.scheduler).route
override def testConfig: Config = Settings.withRandomDatabase
override def beforeAll: Unit = { override def beforeAll: Unit = {
Await.result(Migration(system.settings.config), timeout) Await.result(Migration(testConfig), askTimeout)
} }
override def afterAll: Unit = { override def afterAll: Unit = {
super.afterAll()
Settings.clearDatabase(testConfig)
TestKit.shutdownActorSystem(system) TestKit.shutdownActorSystem(system)
Settings.clearDatabase(system.settings.config) testKit.shutdownTestKit()
} }
"api v1 users endpoint" must { "api v1 users endpoint" must {

View File

@ -2,9 +2,10 @@ package me.arcanis.ffxivbis.models
import me.arcanis.ffxivbis.Fixtures import me.arcanis.ffxivbis.Fixtures
import me.arcanis.ffxivbis.utils.Compare import me.arcanis.ffxivbis.utils.Compare
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
class BiSTest extends WordSpecLike with Matchers with BeforeAndAfterAll { class BiSTest extends AnyWordSpecLike with Matchers {
"bis model" must { "bis model" must {

View File

@ -1,8 +1,9 @@
package me.arcanis.ffxivbis.models package me.arcanis.ffxivbis.models
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
class JobTest extends WordSpecLike with Matchers with BeforeAndAfterAll { class JobTest extends AnyWordSpecLike with Matchers {
"job model" must { "job model" must {
@ -22,7 +23,7 @@ class JobTest extends WordSpecLike with Matchers with BeforeAndAfterAll {
"equal AnyJob to others" in { "equal AnyJob to others" in {
Job.available.foreach { job => Job.available.foreach { job =>
Job.AnyJob shouldBe job Job.AnyJob shouldEqual job
} }
} }

View File

@ -2,9 +2,10 @@ package me.arcanis.ffxivbis.models
import me.arcanis.ffxivbis.Fixtures import me.arcanis.ffxivbis.Fixtures
import me.arcanis.ffxivbis.utils.Compare import me.arcanis.ffxivbis.utils.Compare
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
class PartyTest extends WordSpecLike with Matchers with BeforeAndAfterAll { class PartyTest extends AnyWordSpecLike with Matchers {
private val partyDescription = PartyDescription.empty(Fixtures.partyId) private val partyDescription = PartyDescription.empty(Fixtures.partyId)
private val party = private val party =

View File

@ -1,9 +1,10 @@
package me.arcanis.ffxivbis.models package me.arcanis.ffxivbis.models
import me.arcanis.ffxivbis.Fixtures import me.arcanis.ffxivbis.Fixtures
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
class PieceTest extends WordSpecLike with Matchers with BeforeAndAfterAll { class PieceTest extends AnyWordSpecLike with Matchers {
"piece model" must { "piece model" must {

View File

@ -1,8 +1,9 @@
package me.arcanis.ffxivbis.models package me.arcanis.ffxivbis.models
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
class PieceTypeTest extends WordSpecLike with Matchers with BeforeAndAfterAll { class PieceTypeTest extends AnyWordSpecLike with Matchers {
"piece type model" must { "piece type model" must {

View File

@ -1,9 +1,10 @@
package me.arcanis.ffxivbis.models package me.arcanis.ffxivbis.models
import me.arcanis.ffxivbis.Fixtures import me.arcanis.ffxivbis.Fixtures
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
class PlayerIdTest extends WordSpecLike with Matchers with BeforeAndAfterAll { class PlayerIdTest extends AnyWordSpecLike with Matchers {
"player id model" must { "player id model" must {

View File

@ -1,9 +1,10 @@
package me.arcanis.ffxivbis.models package me.arcanis.ffxivbis.models
import me.arcanis.ffxivbis.Fixtures import me.arcanis.ffxivbis.Fixtures
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
class PlayerTest extends WordSpecLike with Matchers with BeforeAndAfterAll { class PlayerTest extends AnyWordSpecLike with Matchers {
"player model" must { "player model" must {

View File

@ -1,9 +1,10 @@
package me.arcanis.ffxivbis.models package me.arcanis.ffxivbis.models
import me.arcanis.ffxivbis.Fixtures import me.arcanis.ffxivbis.Fixtures
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
class UserTest extends WordSpecLike with Matchers with BeforeAndAfterAll { class UserTest extends AnyWordSpecLike with Matchers {
"user model" must { "user model" must {

View File

@ -1,79 +1,85 @@
package me.arcanis.ffxivbis.service package me.arcanis.ffxivbis.service
import akka.actor.ActorSystem import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit
import akka.pattern.ask import akka.actor.typed.scaladsl.AskPattern.Askable
import akka.testkit.{ImplicitSender, TestKit} import me.arcanis.ffxivbis.messages.{AddPieceToBis, AddPlayer, GetBiS, RemovePieceFromBiS}
import me.arcanis.ffxivbis.{Fixtures, Settings} import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.models._ import me.arcanis.ffxivbis.models._
import me.arcanis.ffxivbis.storage.Migration import me.arcanis.ffxivbis.storage.Migration
import me.arcanis.ffxivbis.utils.Compare import me.arcanis.ffxivbis.utils.Compare
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.wordspec.AnyWordSpecLike
import scala.concurrent.Await import scala.concurrent.Await
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.language.postfixOps import scala.language.postfixOps
class DatabaseBiSHandlerTest class DatabaseBiSHandlerTest extends ScalaTestWithActorTestKit(Settings.withRandomDatabase)
extends TestKit(ActorSystem("database-bis-handler", Settings.withRandomDatabase)) with AnyWordSpecLike {
with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll {
private val database = system.actorOf(impl.DatabaseImpl.props) private val database = testKit.spawn(Database())
private val timeout: FiniteDuration = 60 seconds private val askTimeout: FiniteDuration = 60 seconds
override def beforeAll: Unit = { override def beforeAll: Unit = {
Await.result(Migration(system.settings.config), timeout) Await.result(Migration(testKit.system.settings.config), askTimeout)
Await.result((database ? impl.DatabasePartyHandler.AddPlayer(Fixtures.playerEmpty))(timeout).mapTo[Int], timeout) Await.result(database.ask(AddPlayer(Fixtures.playerEmpty, _))(askTimeout, testKit.scheduler), askTimeout)
} }
override def afterAll: Unit = { override def afterAll: Unit = {
TestKit.shutdownActorSystem(system) super.afterAll()
Settings.clearDatabase(system.settings.config) Settings.clearDatabase(testKit.system.settings.config)
} }
"database bis handler" must { "database bis handler" must {
"add pieces to bis" in { "add pieces to bis" in {
database ! impl.DatabaseBiSHandler.AddPieceToBis(Fixtures.playerEmpty.playerId, Fixtures.lootBody) val probe = testKit.createTestProbe[Unit]()
expectMsg(timeout, 1) database ! AddPieceToBis(Fixtures.playerEmpty.playerId, Fixtures.lootBody, probe.ref)
probe.expectMessage(askTimeout, ())
database ! impl.DatabaseBiSHandler.AddPieceToBis(Fixtures.playerEmpty.playerId, Fixtures.lootHands) database ! AddPieceToBis(Fixtures.playerEmpty.playerId, Fixtures.lootHands, probe.ref)
expectMsg(timeout, 1) probe.expectMessage(askTimeout, ())
} }
"get party bis set" in { "get party bis set" in {
database ! impl.DatabaseBiSHandler.GetBiS(Fixtures.playerEmpty.partyId, None) val probe = testKit.createTestProbe[Seq[Player]]()
expectMsgPF(timeout) { database ! GetBiS(Fixtures.playerEmpty.partyId, None, probe.ref)
case party: Seq[_] if partyBiSCompare(party, Seq(Fixtures.lootBody, Fixtures.lootHands)) => ()
} val party = probe.expectMessageType[Seq[Player]](askTimeout)
partyBiSCompare(party, Seq(Fixtures.lootBody, Fixtures.lootHands)) shouldEqual true
} }
"get bis set" in { "get bis set" in {
database ! impl.DatabaseBiSHandler.GetBiS(Fixtures.playerEmpty.partyId, Some(Fixtures.playerEmpty.playerId)) val probe = testKit.createTestProbe[Seq[Player]]()
expectMsgPF(timeout) { database ! GetBiS(Fixtures.playerEmpty.partyId, Some(Fixtures.playerEmpty.playerId), probe.ref)
case party: Seq[_] if partyBiSCompare(party, Seq(Fixtures.lootBody, Fixtures.lootHands)) => ()
} val party = probe.expectMessageType[Seq[Player]](askTimeout)
partyBiSCompare(party, Seq(Fixtures.lootBody, Fixtures.lootHands)) shouldEqual true
} }
"remove piece from bis set" in { "remove piece from bis set" in {
database ! impl.DatabaseBiSHandler.RemovePieceFromBiS(Fixtures.playerEmpty.playerId, Fixtures.lootBody) val updateProbe = testKit.createTestProbe[Unit]()
expectMsg(timeout, 1) database ! RemovePieceFromBiS(Fixtures.playerEmpty.playerId, Fixtures.lootBody, updateProbe.ref)
updateProbe.expectMessage(askTimeout, ())
database ! impl.DatabaseBiSHandler.GetBiS(Fixtures.playerEmpty.partyId, None) val probe = testKit.createTestProbe[Seq[Player]]()
expectMsgPF(timeout) { database ! GetBiS(Fixtures.playerEmpty.partyId, None, probe.ref)
case party: Seq[_] if partyBiSCompare(party, Seq(Fixtures.lootHands)) => ()
} val party = probe.expectMessageType[Seq[Player]](askTimeout)
partyBiSCompare(party, Seq(Fixtures.lootHands)) shouldEqual true
} }
"update piece in bis set" in { "update piece in bis set" in {
val updateProbe = testKit.createTestProbe[Unit]()
val newPiece = Hands(pieceType = PieceType.Savage, Job.DNC) val newPiece = Hands(pieceType = PieceType.Savage, Job.DNC)
database ! impl.DatabaseBiSHandler.AddPieceToBis(Fixtures.playerEmpty.playerId, newPiece) database ! AddPieceToBis(Fixtures.playerEmpty.playerId, newPiece, updateProbe.ref)
expectMsg(timeout, 1) updateProbe.expectMessage(askTimeout, ())
database ! impl.DatabaseBiSHandler.GetBiS(Fixtures.playerEmpty.partyId, None) val probe = testKit.createTestProbe[Seq[Player]]()
expectMsgPF(timeout) { database ! GetBiS(Fixtures.playerEmpty.partyId, None, probe.ref)
case party: Seq[_] if partyBiSCompare(party, Seq(Fixtures.lootHands, newPiece)) => ()
} val party = probe.expectMessageType[Seq[Player]](askTimeout)
partyBiSCompare(party, Seq(Fixtures.lootHands, newPiece)) shouldEqual true
} }
} }

View File

@ -1,83 +1,89 @@
package me.arcanis.ffxivbis.service package me.arcanis.ffxivbis.service
import akka.actor.ActorSystem import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit
import akka.pattern.ask import akka.actor.typed.scaladsl.AskPattern.Askable
import akka.testkit.{ImplicitSender, TestKit} import me.arcanis.ffxivbis.messages.{AddPieceTo, AddPlayer, GetLoot, RemovePieceFrom}
import me.arcanis.ffxivbis.{Fixtures, Settings} import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.models._ import me.arcanis.ffxivbis.models._
import me.arcanis.ffxivbis.storage.Migration import me.arcanis.ffxivbis.storage.Migration
import me.arcanis.ffxivbis.utils.Compare import me.arcanis.ffxivbis.utils.Compare
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.wordspec.AnyWordSpecLike
import scala.concurrent.Await import scala.concurrent.Await
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.language.postfixOps import scala.language.postfixOps
class DatabaseLootHandlerTest class DatabaseLootHandlerTest extends ScalaTestWithActorTestKit(Settings.withRandomDatabase)
extends TestKit(ActorSystem("database-loot-handler", Settings.withRandomDatabase)) with AnyWordSpecLike {
with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll {
private val database = system.actorOf(impl.DatabaseImpl.props) private val database = testKit.spawn(Database())
private val timeout: FiniteDuration = 60 seconds private val askTimeout = 60 seconds
override def beforeAll: Unit = { override def beforeAll: Unit = {
Await.result(Migration(system.settings.config), timeout) Await.result(Migration(testKit.system.settings.config), askTimeout)
Await.result((database ? impl.DatabasePartyHandler.AddPlayer(Fixtures.playerEmpty))(timeout).mapTo[Int], timeout) Await.result(database.ask(AddPlayer(Fixtures.playerEmpty, _))(askTimeout, testKit.scheduler), askTimeout)
} }
override def afterAll: Unit = { override def afterAll: Unit = {
TestKit.shutdownActorSystem(system) super.afterAll()
Settings.clearDatabase(system.settings.config) Settings.clearDatabase(testKit.system.settings.config)
} }
"database loot handler actor" must { "database loot handler actor" must {
"add loot" in { "add loot" in {
val probe = testKit.createTestProbe[Unit]()
Fixtures.loot.foreach { piece => Fixtures.loot.foreach { piece =>
database ! impl.DatabaseLootHandler.AddPieceTo(Fixtures.playerEmpty.playerId, piece, isFreeLoot = false) database ! AddPieceTo(Fixtures.playerEmpty.playerId, piece, isFreeLoot = false, probe.ref)
expectMsg(timeout, 1) probe.expectMessage(askTimeout, ())
} }
} }
"get party loot" in { "get party loot" in {
database ! impl.DatabaseLootHandler.GetLoot(Fixtures.playerEmpty.partyId, None) val probe = testKit.createTestProbe[Seq[Player]]()
expectMsgPF(timeout) { database ! GetLoot(Fixtures.playerEmpty.partyId, None, probe.ref)
case party: Seq[_] if partyLootCompare(party, Fixtures.loot) => ()
} val party = probe.expectMessageType[Seq[Player]](askTimeout)
partyLootCompare(party, Fixtures.loot) shouldEqual true
} }
"get loot" in { "get loot" in {
database ! impl.DatabaseLootHandler.GetLoot(Fixtures.playerEmpty.partyId, Some(Fixtures.playerEmpty.playerId)) val probe = testKit.createTestProbe[Seq[Player]]()
expectMsgPF(timeout) { database ! GetLoot(Fixtures.playerEmpty.partyId, Some(Fixtures.playerEmpty.playerId), probe.ref)
case party: Seq[_] if partyLootCompare(party, Fixtures.loot) => ()
} val party = probe.expectMessageType[Seq[Player]](askTimeout)
partyLootCompare(party, Fixtures.loot) shouldEqual true
} }
"remove loot" in { "remove loot" in {
database ! impl.DatabaseLootHandler.RemovePieceFrom(Fixtures.playerEmpty.playerId, Fixtures.lootBody) val updateProbe = testKit.createTestProbe[Unit]()
expectMsg(timeout, 1) database ! RemovePieceFrom(Fixtures.playerEmpty.playerId, Fixtures.lootBody, updateProbe.ref)
updateProbe.expectMessage(askTimeout, ())
val newLoot = Fixtures.loot.filterNot(_ == Fixtures.lootBody) val newLoot = Fixtures.loot.filterNot(_ == Fixtures.lootBody)
database ! impl.DatabaseLootHandler.GetLoot(Fixtures.playerEmpty.partyId, None) val probe = testKit.createTestProbe[Seq[Player]]()
expectMsgPF(timeout) { database ! GetLoot(Fixtures.playerEmpty.partyId, None, probe.ref)
case party: Seq[_] if partyLootCompare(party, newLoot) => ()
} val party = probe.expectMessageType[Seq[Player]](askTimeout)
partyLootCompare(party, newLoot) shouldEqual true
} }
"add same loot" in { "add same loot" in {
database ! impl.DatabaseLootHandler.AddPieceTo(Fixtures.playerEmpty.playerId, Fixtures.lootBody, isFreeLoot = false) val updateProbe = testKit.createTestProbe[Unit]()
expectMsg(timeout, 1) database ! AddPieceTo(Fixtures.playerEmpty.playerId, Fixtures.lootBody, isFreeLoot = false, updateProbe.ref)
updateProbe.expectMessage(askTimeout, ())
Fixtures.loot.foreach { piece => Fixtures.loot.foreach { piece =>
database ! impl.DatabaseLootHandler.AddPieceTo(Fixtures.playerEmpty.playerId, piece, isFreeLoot = false) database ! AddPieceTo(Fixtures.playerEmpty.playerId, piece, isFreeLoot = false, updateProbe.ref)
expectMsg(timeout, 1) updateProbe.expectMessage(askTimeout, ())
} }
database ! impl.DatabaseLootHandler.GetLoot(Fixtures.playerEmpty.partyId, None) val probe = testKit.createTestProbe[Seq[Player]]()
expectMsgPF(timeout) { database ! GetLoot(Fixtures.playerEmpty.partyId, None, probe.ref)
case party: Seq[_] if partyLootCompare(party, Fixtures.loot ++ Fixtures.loot) => ()
} val party = probe.expectMessageType[Seq[Player]](askTimeout)
partyLootCompare(party, Fixtures.loot ++ Fixtures.loot) shouldEqual true
} }
} }

View File

@ -1,73 +1,80 @@
package me.arcanis.ffxivbis.service package me.arcanis.ffxivbis.service
import akka.actor.ActorSystem import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit
import akka.testkit.{ImplicitSender, TestKit} import me.arcanis.ffxivbis.messages.{AddPlayer, GetParty, GetPlayer, RemovePlayer}
import me.arcanis.ffxivbis.{Fixtures, Settings} import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.models._ import me.arcanis.ffxivbis.models._
import me.arcanis.ffxivbis.storage.Migration import me.arcanis.ffxivbis.storage.Migration
import me.arcanis.ffxivbis.utils.Compare import me.arcanis.ffxivbis.utils.Compare
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.wordspec.AnyWordSpecLike
import scala.concurrent.Await import scala.concurrent.Await
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.language.postfixOps import scala.language.postfixOps
class DatabasePartyHandlerTest class DatabasePartyHandlerTest extends ScalaTestWithActorTestKit(Settings.withRandomDatabase)
extends TestKit(ActorSystem("database-party-handler", Settings.withRandomDatabase)) with AnyWordSpecLike {
with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll {
private val database = system.actorOf(impl.DatabaseImpl.props) private val database = testKit.spawn(Database())
private val timeout: FiniteDuration = 60 seconds private val askTimeout = 60 seconds
override def beforeAll: Unit = { override def beforeAll: Unit = {
Await.result(Migration(system.settings.config), timeout) Await.result(Migration(testKit.system.settings.config), askTimeout)
} }
override def afterAll: Unit = { override def afterAll: Unit = {
TestKit.shutdownActorSystem(system) super.afterAll()
Settings.clearDatabase(system.settings.config) Settings.clearDatabase(testKit.system.settings.config)
} }
"database party handler actor" must { "database party handler actor" must {
"add player" in { "add player" in {
database ! impl.DatabasePartyHandler.AddPlayer(Fixtures.playerEmpty) val probe = testKit.createTestProbe[Unit]()
expectMsg(timeout, 1) database ! AddPlayer(Fixtures.playerEmpty, probe.ref)
probe.expectMessage(askTimeout, ())
} }
"get party" in { "get party" in {
database ! impl.DatabasePartyHandler.GetParty(Fixtures.partyId) val probe = testKit.createTestProbe[Party]()
expectMsgPF(timeout) { database ! GetParty(Fixtures.partyId, probe.ref)
case p: Party if Compare.seqEquals(p.getPlayers, Seq(Fixtures.playerEmpty)) => ()
} val party = probe.expectMessageType[Party](askTimeout)
Compare.seqEquals(party.getPlayers, Seq(Fixtures.playerEmpty)) shouldEqual true
} }
"get player" in { "get player" in {
database ! impl.DatabasePartyHandler.GetPlayer(Fixtures.playerEmpty.playerId) val probe = testKit.createTestProbe[Option[Player]]()
expectMsg(timeout, Some(Fixtures.playerEmpty)) database ! GetPlayer(Fixtures.playerEmpty.playerId, probe.ref)
probe.expectMessage(askTimeout, Some(Fixtures.playerEmpty))
} }
"update player" in { "update player" in {
val updateProbe = testKit.createTestProbe[Unit]()
val newPlayer = Fixtures.playerEmpty.copy(priority = 2) val newPlayer = Fixtures.playerEmpty.copy(priority = 2)
database ! impl.DatabasePartyHandler.AddPlayer(newPlayer) database ! AddPlayer(newPlayer, updateProbe.ref)
expectMsg(timeout, 1) updateProbe.expectMessage(askTimeout, ())
database ! impl.DatabasePartyHandler.GetPlayer(newPlayer.playerId) val probe = testKit.createTestProbe[Option[Player]]()
expectMsg(timeout, Some(newPlayer)) database ! GetPlayer(newPlayer.playerId, probe.ref)
probe.expectMessage(askTimeout, Some(newPlayer))
database ! impl.DatabasePartyHandler.GetParty(Fixtures.partyId) val partyProbe = testKit.createTestProbe[Party]()
expectMsgPF(timeout) { database ! GetParty(Fixtures.partyId, partyProbe.ref)
case p: Party if Compare.seqEquals(p.getPlayers, Seq(newPlayer)) => ()
} val party = partyProbe.expectMessageType[Party](askTimeout)
Compare.seqEquals(party.getPlayers, Seq(newPlayer)) shouldEqual true
} }
"remove player" in { "remove player" in {
database ! impl.DatabasePartyHandler.RemovePlayer(Fixtures.playerEmpty.playerId) val updateProbe = testKit.createTestProbe[Unit]()
expectMsg(timeout, 1) database ! RemovePlayer(Fixtures.playerEmpty.playerId, updateProbe.ref)
updateProbe.expectMessage(askTimeout, ())
database ! impl.DatabasePartyHandler.GetPlayer(Fixtures.playerEmpty.playerId) val probe = testKit.createTestProbe[Option[Player]]()
expectMsg(timeout, None) database ! GetPlayer(Fixtures.playerEmpty.playerId, probe.ref)
probe.expectMessage(askTimeout, None)
} }
} }

View File

@ -1,76 +1,85 @@
package me.arcanis.ffxivbis.service package me.arcanis.ffxivbis.service
import akka.actor.ActorSystem import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit
import akka.testkit.{ImplicitSender, TestKit} import me.arcanis.ffxivbis.messages.{AddUser, DeleteUser, GetUser, GetUsers}
import me.arcanis.ffxivbis.models.User
import me.arcanis.ffxivbis.{Fixtures, Settings} import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.storage.Migration import me.arcanis.ffxivbis.storage.Migration
import me.arcanis.ffxivbis.utils.Compare import me.arcanis.ffxivbis.utils.Compare
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.wordspec.AnyWordSpecLike
import scala.concurrent.Await import scala.concurrent.Await
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.language.postfixOps import scala.language.postfixOps
class DatabaseUserHandlerTest class DatabaseUserHandlerTest extends ScalaTestWithActorTestKit(Settings.withRandomDatabase)
extends TestKit(ActorSystem("database-user-handler", Settings.withRandomDatabase)) with AnyWordSpecLike {
with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll {
private val database = system.actorOf(impl.DatabaseImpl.props) private val database = testKit.spawn(Database())
private val timeout: FiniteDuration = 60 seconds private val askTimeout = 60 seconds
override def beforeAll: Unit = { override def beforeAll: Unit = {
Await.result(Migration(system.settings.config), timeout) Await.result(Migration(testKit.system.settings.config), askTimeout)
} }
override def afterAll: Unit = { override def afterAll: Unit = {
TestKit.shutdownActorSystem(system) super.afterAll()
Settings.clearDatabase(system.settings.config) Settings.clearDatabase(testKit.system.settings.config)
} }
"database user handler actor" must { "database user handler actor" must {
"add user" in { "add user" in {
database ! impl.DatabaseUserHandler.AddUser(Fixtures.userAdmin, isHashedPassword = true) val probe = testKit.createTestProbe[Unit]()
expectMsg(timeout, 1) database ! AddUser(Fixtures.userAdmin, isHashedPassword = true, probe.ref)
probe.expectMessage(askTimeout, ())
} }
"get user" in { "get user" in {
database ! impl.DatabaseUserHandler.GetUser(Fixtures.partyId, Fixtures.userAdmin.username) val probe = testKit.createTestProbe[Option[User]]()
expectMsg(timeout, Some(Fixtures.userAdmin)) database ! GetUser(Fixtures.partyId, Fixtures.userAdmin.username, probe.ref)
probe.expectMessage(askTimeout, Some(Fixtures.userAdmin))
} }
"get users" in { "get users" in {
database ! impl.DatabaseUserHandler.AddUser(Fixtures.userGet, isHashedPassword = true) val updateProbe = testKit.createTestProbe[Unit]()
expectMsg(timeout, 1) database ! AddUser(Fixtures.userGet, isHashedPassword = true, updateProbe.ref)
updateProbe.expectMessage(askTimeout, ())
database ! impl.DatabaseUserHandler.GetUsers(Fixtures.partyId) val probe = testKit.createTestProbe[Seq[User]]()
expectMsgPF(timeout) { database ! GetUsers(Fixtures.partyId, probe.ref)
case u: Seq[_] if Compare.seqEquals(u, Fixtures.users) => ()
} val users = probe.expectMessageType[Seq[User]]
Compare.seqEquals(users, Fixtures.users) shouldEqual true
} }
"update user" in { "update user" in {
val newUser= Fixtures.userGet.copy(password = Fixtures.userPassword2).withHashedPassword val newUser= Fixtures.userGet.copy(password = Fixtures.userPassword2).withHashedPassword
val newUserSet = Seq(newUser, Fixtures.userAdmin) val newUserSet = Seq(newUser, Fixtures.userAdmin)
database ! impl.DatabaseUserHandler.AddUser(newUser, isHashedPassword = true) val updateProbe = testKit.createTestProbe[Unit]()
expectMsg(timeout, 1) database ! AddUser(newUser, isHashedPassword = true, updateProbe.ref)
updateProbe.expectMessage(askTimeout, ())
database ! impl.DatabaseUserHandler.GetUser(Fixtures.partyId, newUser.username) val probe = testKit.createTestProbe[Option[User]]()
expectMsg(timeout, Some(newUser)) database ! GetUser(Fixtures.partyId, newUser.username, probe.ref)
probe.expectMessage(askTimeout, Some(newUser))
database ! impl.DatabaseUserHandler.GetUsers(Fixtures.partyId) val partyProbe = testKit.createTestProbe[Seq[User]]()
expectMsgPF(timeout) { database ! GetUsers(Fixtures.partyId, partyProbe.ref)
case u: Seq[_] if Compare.seqEquals(u, newUserSet) => ()
} val users = partyProbe.expectMessageType[Seq[User]]
Compare.seqEquals(users, newUserSet) shouldEqual true
} }
"remove user" in { "remove user" in {
database ! impl.DatabaseUserHandler.DeleteUser(Fixtures.partyId, Fixtures.userGet.username) val updateProbe = testKit.createTestProbe[Unit]()
expectMsg(timeout, 1) database ! DeleteUser(Fixtures.partyId, Fixtures.userGet.username, updateProbe.ref)
updateProbe.expectMessage(askTimeout, ())
database ! impl.DatabaseUserHandler.GetUser(Fixtures.partyId, Fixtures.userGet.username) val probe = testKit.createTestProbe[Option[User]]()
expectMsg(timeout, None) database ! GetUser(Fixtures.partyId, Fixtures.userGet.username, probe.ref)
probe.expectMessage(askTimeout, None)
} }
} }
} }

View File

@ -1,19 +1,20 @@
package me.arcanis.ffxivbis.service package me.arcanis.ffxivbis.service
import akka.actor.ActorSystem import akka.actor.testkit.typed.scaladsl.ActorTestKit
import akka.pattern.ask import akka.actor.typed.scaladsl.AskPattern.Askable
import akka.testkit.{ImplicitSender, TestKit} import me.arcanis.ffxivbis.messages.DownloadBiS
import me.arcanis.ffxivbis.{Fixtures, Settings} import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.models._ import me.arcanis.ffxivbis.models._
import me.arcanis.ffxivbis.service.bis.BisProvider import me.arcanis.ffxivbis.service.bis.BisProvider
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.BeforeAndAfterAll
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
import scala.concurrent.Await import scala.concurrent.Await
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.language.postfixOps import scala.language.postfixOps
class LootSelectorTest extends TestKit(ActorSystem("lootselector")) class LootSelectorTest extends AnyWordSpecLike with Matchers with BeforeAndAfterAll {
with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll {
import me.arcanis.ffxivbis.utils.Converters._ import me.arcanis.ffxivbis.utils.Converters._
@ -23,16 +24,19 @@ class LootSelectorTest extends TestKit(ActorSystem("lootselector"))
private val timeout: FiniteDuration = 60 seconds private val timeout: FiniteDuration = 60 seconds
override def beforeAll(): Unit = { override def beforeAll(): Unit = {
val provider = system.actorOf(BisProvider.props) val testKit = ActorTestKit(Settings.withRandomDatabase)
val provider = testKit.spawn(BisProvider())
val dncSet = Await.result((provider ? BisProvider.GetBiS(Fixtures.link, Job.DNC) )(timeout).mapTo[BiS], timeout) val dncSet = Await.result(provider.ask(DownloadBiS(Fixtures.link, Job.DNC, _) )(timeout, testKit.scheduler), timeout)
dnc = dnc.withBiS(Some(dncSet)) dnc = dnc.withBiS(Some(dncSet))
val drgSet = Await.result((provider ? BisProvider.GetBiS(Fixtures.link2, Job.DRG) )(timeout).mapTo[BiS], timeout) val drgSet = Await.result(provider.ask(DownloadBiS(Fixtures.link2, Job.DRG, _) )(timeout, testKit.scheduler), timeout)
drg = drg.withBiS(Some(drgSet)) drg = drg.withBiS(Some(drgSet))
default = default.withPlayer(dnc).withPlayer(drg) default = default.withPlayer(dnc).withPlayer(drg)
system.stop(provider)
testKit.stop(provider)
testKit.shutdownTestKit()
} }
"loot selector" must { "loot selector" must {

View File

@ -1,33 +1,32 @@
package me.arcanis.ffxivbis.service.bis package me.arcanis.ffxivbis.service.bis
import akka.actor.ActorSystem import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit
import akka.testkit.{ImplicitSender, TestKit} import me.arcanis.ffxivbis.messages.DownloadBiS
import me.arcanis.ffxivbis.Fixtures import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.models._ import me.arcanis.ffxivbis.models._
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.wordspec.AnyWordSpecLike
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.language.postfixOps import scala.language.postfixOps
class BisProviderTest extends TestKit(ActorSystem("bis-provider")) class BisProviderTest extends ScalaTestWithActorTestKit(Settings.withRandomDatabase)
with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll { with AnyWordSpecLike {
private val timeout: FiniteDuration = 60 seconds private val provider = testKit.spawn(BisProvider())
private val askTimeout = 60 seconds
override def afterAll: Unit = TestKit.shutdownActorSystem(system)
"ariyala actor" must { "ariyala actor" must {
"get best in slot set (ariyala)" in { "get best in slot set (ariyala)" in {
val provider = system.actorOf(BisProvider.props) val probe = testKit.createTestProbe[BiS]()
provider ! BisProvider.GetBiS(Fixtures.link, Job.DNC) provider ! DownloadBiS(Fixtures.link, Job.DNC, probe.ref)
expectMsg(timeout, Fixtures.bis) probe.expectMessage(askTimeout, Fixtures.bis)
} }
"get best in slot set (etro)" in { "get best in slot set (etro)" in {
val provider = system.actorOf(BisProvider.props) val probe = testKit.createTestProbe[BiS]()
provider ! BisProvider.GetBiS(Fixtures.link3, Job.DNC) provider ! DownloadBiS(Fixtures.link3, Job.DNC, probe.ref)
expectMsg(timeout, Fixtures.bis) probe.expectMessage(askTimeout, Fixtures.bis)
} }
} }

View File

@ -1,6 +0,0 @@
libraryDependencies += "org.scalactic" %% "scalactic" % "3.0.8" % "test"
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.8" % "test"
libraryDependencies += "com.typesafe.akka" %% "akka-testkit" % "2.5.26" % "test"
libraryDependencies += "com.typesafe.akka" %% "akka-stream-testkit" % "2.5.26" % "test"
libraryDependencies += "com.typesafe.akka" %% "akka-http-testkit" % "10.1.10" % "test"