mirror of
https://github.com/arcan1s/ffxivbis.git
synced 2025-04-24 17:27:17 +00:00
migrate to anorm
I'm tired of ORM and would like to write clear sql requests. The following wrappers were checked: * doobie - cats api which is useless in this project * scalike - can't work with sqlite at all * anorm - awful api * something also Anorm fits more than any other my criteria so I migrated to it with native hikaricp usage
This commit is contained in:
parent
012cdd2d8b
commit
ced781bba2
@ -1,9 +1,9 @@
|
|||||||
val AkkaVersion = "2.6.17"
|
val AkkaVersion = "2.6.18"
|
||||||
val AkkaHttpVersion = "10.2.7"
|
val AkkaHttpVersion = "10.2.7"
|
||||||
val ScalaTestVersion = "3.2.10"
|
val ScalaTestVersion = "3.2.10"
|
||||||
val SlickVersion = "3.3.3"
|
val SlickVersion = "3.3.3"
|
||||||
|
|
||||||
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.9"
|
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.10"
|
||||||
libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.4"
|
libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.4"
|
||||||
|
|
||||||
libraryDependencies += "com.typesafe.akka" %% "akka-http" % AkkaHttpVersion
|
libraryDependencies += "com.typesafe.akka" %% "akka-http" % AkkaHttpVersion
|
||||||
@ -15,16 +15,15 @@ libraryDependencies += "jakarta.platform" % "jakarta.jakartaee-web-api" % "9.1.0
|
|||||||
|
|
||||||
libraryDependencies += "io.spray" %% "spray-json" % "1.3.6"
|
libraryDependencies += "io.spray" %% "spray-json" % "1.3.6"
|
||||||
|
|
||||||
libraryDependencies += "com.typesafe.slick" %% "slick" % SlickVersion
|
libraryDependencies += "org.playframework.anorm" %% "anorm" % "2.6.10"
|
||||||
libraryDependencies += "com.typesafe.slick" %% "slick-hikaricp" % SlickVersion
|
libraryDependencies += "com.zaxxer" % "HikariCP" % "5.0.1" exclude("org.slf4j", "slf4j-api")
|
||||||
libraryDependencies += "org.flywaydb" % "flyway-core" % "8.2.2"
|
libraryDependencies += "org.flywaydb" % "flyway-core" % "8.4.1"
|
||||||
libraryDependencies += "org.xerial" % "sqlite-jdbc" % "3.36.0.3"
|
libraryDependencies += "org.xerial" % "sqlite-jdbc" % "3.36.0.3"
|
||||||
libraryDependencies += "org.postgresql" % "postgresql" % "42.3.1"
|
libraryDependencies += "org.postgresql" % "postgresql" % "42.3.1"
|
||||||
|
|
||||||
libraryDependencies += "org.mindrot" % "jbcrypt" % "0.4"
|
libraryDependencies += "org.mindrot" % "jbcrypt" % "0.4"
|
||||||
libraryDependencies += "com.google.guava" % "guava" % "31.0.1-jre"
|
libraryDependencies += "com.google.guava" % "guava" % "31.0.1-jre"
|
||||||
|
|
||||||
|
|
||||||
// testing
|
// testing
|
||||||
libraryDependencies += "org.scalactic" %% "scalactic" % ScalaTestVersion % "test"
|
libraryDependencies += "org.scalactic" %% "scalactic" % ScalaTestVersion % "test"
|
||||||
libraryDependencies += "org.scalatest" %% "scalatest" % ScalaTestVersion % "test"
|
libraryDependencies += "org.scalatest" %% "scalatest" % ScalaTestVersion % "test"
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
drop index bis_piece_type_player_id_idx;
|
||||||
|
create unique index bis_piece_type_player_id_idx on bis(player_id, piece, piece_type);
|
@ -0,0 +1,2 @@
|
|||||||
|
drop index bis_piece_type_player_id_idx;
|
||||||
|
create unique index bis_piece_type_player_id_idx on bis(player_id, piece, piece_type);
|
@ -68,6 +68,8 @@
|
|||||||
data-show-search-clear-button="true"
|
data-show-search-clear-button="true"
|
||||||
data-single-select="true"
|
data-single-select="true"
|
||||||
data-sortable="true"
|
data-sortable="true"
|
||||||
|
data-sort-name="nick"
|
||||||
|
data-sort-order="desc"
|
||||||
data-sort-reset="true"
|
data-sort-reset="true"
|
||||||
data-toolbar="#toolbar">
|
data-toolbar="#toolbar">
|
||||||
<thead class="table-primary">
|
<thead class="table-primary">
|
||||||
|
@ -68,6 +68,8 @@
|
|||||||
data-show-search-clear-button="true"
|
data-show-search-clear-button="true"
|
||||||
data-single-select="true"
|
data-single-select="true"
|
||||||
data-sortable="true"
|
data-sortable="true"
|
||||||
|
data-sort-name="timestamp"
|
||||||
|
data-sort-order="desc"
|
||||||
data-sort-reset="true"
|
data-sort-reset="true"
|
||||||
data-toolbar="#toolbar">
|
data-toolbar="#toolbar">
|
||||||
<thead class="table-primary">
|
<thead class="table-primary">
|
||||||
@ -117,6 +119,15 @@
|
|||||||
<select id="job" name="job" class="form-control" title="job"></select>
|
<select id="job" name="job" class="form-control" title="job"></select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-sm-4"></div>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<div class="form-check">
|
||||||
|
<input id="free-loot" name="freeLoot" type="checkbox" class="form-check-input">
|
||||||
|
<label class="form-check-label" for="free-loot">as free loot</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<table id="stats" class="table table-striped table-hover">
|
<table id="stats" class="table table-striped table-hover">
|
||||||
<thead class="table-primary">
|
<thead class="table-primary">
|
||||||
@ -133,10 +144,6 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input id="free-loot" name="freeLoot" type="checkbox" class="form-check-input">
|
|
||||||
<label class="form-check-label" for="free-loot">as free loot</label>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn btn-danger" data-bs-dismiss="modal">close</button>
|
<button type="button" class="btn btn-danger" data-bs-dismiss="modal">close</button>
|
||||||
<button type="button" class="btn btn-secondary" onclick="suggestLoot()">suggest</button>
|
<button type="button" class="btn btn-secondary" onclick="suggestLoot()">suggest</button>
|
||||||
<button id="submit-btn" type="button" class="btn btn-primary" data-bs-dismiss="modal" onclick="addLoot()" disabled>add</button>
|
<button id="submit-btn" type="button" class="btn btn-primary" data-bs-dismiss="modal" onclick="addLoot()" disabled>add</button>
|
||||||
|
@ -64,6 +64,8 @@
|
|||||||
data-show-search-clear-button="true"
|
data-show-search-clear-button="true"
|
||||||
data-single-select="true"
|
data-single-select="true"
|
||||||
data-sortable="true"
|
data-sortable="true"
|
||||||
|
data-sort-name="nick"
|
||||||
|
data-sort-order="desc"
|
||||||
data-sort-reset="true"
|
data-sort-reset="true"
|
||||||
data-toolbar="#toolbar">
|
data-toolbar="#toolbar">
|
||||||
<thead class="table-primary">
|
<thead class="table-primary">
|
||||||
|
@ -68,6 +68,8 @@
|
|||||||
data-show-search-clear-button="true"
|
data-show-search-clear-button="true"
|
||||||
data-single-select="true"
|
data-single-select="true"
|
||||||
data-sortable="true"
|
data-sortable="true"
|
||||||
|
data-sort-name="username"
|
||||||
|
data-sort-order="desc"
|
||||||
data-sort-reset="true"
|
data-sort-reset="true"
|
||||||
data-toolbar="#toolbar">
|
data-toolbar="#toolbar">
|
||||||
<thead class="table-primary">
|
<thead class="table-primary">
|
||||||
|
@ -3,15 +3,14 @@
|
|||||||
<include resource="logback-application.xml" />
|
<include resource="logback-application.xml" />
|
||||||
<include resource="logback-http.xml" />
|
<include resource="logback-http.xml" />
|
||||||
|
|
||||||
<root level="debug">
|
<root level="DEBUG">
|
||||||
<appender-ref ref="application" />
|
<appender-ref ref="application" />
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
<logger name="me.arcanis.ffxivbis" level="DEBUG" />
|
|
||||||
<logger name="http" level="DEBUG" additivity="false">
|
<logger name="http" level="DEBUG" additivity="false">
|
||||||
<appender-ref ref="http" />
|
<appender-ref ref="http" />
|
||||||
</logger>
|
</logger>
|
||||||
<logger name="slick" level="INFO" />
|
|
||||||
<logger name="org.flywaydb.core.internal" level="INFO" />
|
<logger name="org.flywaydb.core.internal" level="INFO" />
|
||||||
|
<logger name="com.zaxxer.hikari.pool.HikariPool" level="INFO" />
|
||||||
|
|
||||||
</configuration>
|
</configuration>
|
||||||
|
@ -15,26 +15,17 @@ me.arcanis.ffxivbis {
|
|||||||
mode = "sqlite"
|
mode = "sqlite"
|
||||||
|
|
||||||
sqlite {
|
sqlite {
|
||||||
profile = "slick.jdbc.SQLiteProfile$"
|
driverClassName = "org.sqlite.JDBC"
|
||||||
db {
|
jdbcUrl = "jdbc:sqlite:ffxivbis.db"
|
||||||
url = "jdbc:sqlite:ffxivbis.db"
|
#username = "user"
|
||||||
#user = "user"
|
#password = "password"
|
||||||
#password = "password"
|
|
||||||
}
|
|
||||||
numThreads = 10
|
|
||||||
}
|
}
|
||||||
|
|
||||||
postgresql {
|
postgresql {
|
||||||
profile = "slick.jdbc.PostgresProfile$"
|
driverClassName = "org.postgresql.Driver"
|
||||||
db {
|
jdbcUrl = "jdbc:postgresql://localhost/ffxivbis"
|
||||||
url = "jdbc:postgresql://localhost/ffxivbis"
|
#username = "ffxivbis"
|
||||||
#user = "ffxivbis"
|
#password = "ffxivbis"
|
||||||
#password = "ffxivbis"
|
|
||||||
|
|
||||||
connectionPool = disabled
|
|
||||||
keepAliveConnection = yes
|
|
||||||
}
|
|
||||||
numThreads = 10
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,8 +17,7 @@ import com.typesafe.scalalogging.StrictLogging
|
|||||||
import me.arcanis.ffxivbis.http.RootEndpoint
|
import me.arcanis.ffxivbis.http.RootEndpoint
|
||||||
import me.arcanis.ffxivbis.service.PartyService
|
import me.arcanis.ffxivbis.service.PartyService
|
||||||
import me.arcanis.ffxivbis.service.bis.BisProvider
|
import me.arcanis.ffxivbis.service.bis.BisProvider
|
||||||
import me.arcanis.ffxivbis.service.database.Database
|
import me.arcanis.ffxivbis.service.database.{Database, Migration}
|
||||||
import me.arcanis.ffxivbis.storage.Migration
|
|
||||||
|
|
||||||
import scala.concurrent.ExecutionContext
|
import scala.concurrent.ExecutionContext
|
||||||
import scala.jdk.CollectionConverters._
|
import scala.jdk.CollectionConverters._
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2022 Evgeniy Alekseev.
|
||||||
|
*
|
||||||
|
* This file is part of ffxivbis
|
||||||
|
* (see https://github.com/arcan1s/ffxivbis).
|
||||||
|
*
|
||||||
|
* License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
package me.arcanis.ffxivbis.http.helpers
|
package me.arcanis.ffxivbis.http.helpers
|
||||||
|
|
||||||
import akka.actor.typed.scaladsl.AskPattern.Askable
|
import akka.actor.typed.scaladsl.AskPattern.Askable
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2022 Evgeniy Alekseev.
|
||||||
|
*
|
||||||
|
* This file is part of ffxivbis
|
||||||
|
* (see https://github.com/arcan1s/ffxivbis).
|
||||||
|
*
|
||||||
|
* License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
package me.arcanis.ffxivbis.http.helpers
|
package me.arcanis.ffxivbis.http.helpers
|
||||||
|
|
||||||
import akka.actor.typed.scaladsl.AskPattern.Askable
|
import akka.actor.typed.scaladsl.AskPattern.Askable
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2022 Evgeniy Alekseev.
|
||||||
|
*
|
||||||
|
* This file is part of ffxivbis
|
||||||
|
* (see https://github.com/arcan1s/ffxivbis).
|
||||||
|
*
|
||||||
|
* License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
package me.arcanis.ffxivbis.http.helpers
|
package me.arcanis.ffxivbis.http.helpers
|
||||||
|
|
||||||
import akka.actor.typed.scaladsl.AskPattern.Askable
|
import akka.actor.typed.scaladsl.AskPattern.Askable
|
||||||
@ -25,8 +33,8 @@ trait LootHelper {
|
|||||||
): Future[Unit] =
|
): 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, Some(isFreeLoot)) => removePieceLoot(playerId, piece, isFreeLoot)
|
||||||
case _ => throw new IllegalArgumentException(s"Invalid combinantion of action $action and fee loot $maybeFree")
|
case _ => throw new IllegalArgumentException("Loot modification must always contain `isFreeLoot` field")
|
||||||
}
|
}
|
||||||
|
|
||||||
def loot(partyId: String, playerId: Option[PlayerId])(implicit
|
def loot(partyId: String, playerId: Option[PlayerId])(implicit
|
||||||
@ -35,8 +43,11 @@ trait LootHelper {
|
|||||||
): Future[Seq[Player]] =
|
): Future[Seq[Player]] =
|
||||||
storage.ask(GetLoot(partyId, playerId, _))
|
storage.ask(GetLoot(partyId, playerId, _))
|
||||||
|
|
||||||
def removePieceLoot(playerId: PlayerId, piece: Piece)(implicit timeout: Timeout, scheduler: Scheduler): Future[Unit] =
|
def removePieceLoot(playerId: PlayerId, piece: Piece, isFreeLoot: Boolean)(implicit
|
||||||
storage.ask(RemovePieceFrom(playerId, piece, _))
|
timeout: Timeout,
|
||||||
|
scheduler: Scheduler
|
||||||
|
): Future[Unit] =
|
||||||
|
storage.ask(RemovePieceFrom(playerId, piece, isFreeLoot, _))
|
||||||
|
|
||||||
def suggestPiece(partyId: String, piece: Piece)(implicit
|
def suggestPiece(partyId: String, piece: Piece)(implicit
|
||||||
executionContext: ExecutionContext,
|
executionContext: ExecutionContext,
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2022 Evgeniy Alekseev.
|
||||||
|
*
|
||||||
|
* This file is part of ffxivbis
|
||||||
|
* (see https://github.com/arcan1s/ffxivbis).
|
||||||
|
*
|
||||||
|
* License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
package me.arcanis.ffxivbis.http.helpers
|
package me.arcanis.ffxivbis.http.helpers
|
||||||
|
|
||||||
import akka.actor.typed.scaladsl.AskPattern.Askable
|
import akka.actor.typed.scaladsl.AskPattern.Askable
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2022 Evgeniy Alekseev.
|
||||||
|
*
|
||||||
|
* This file is part of ffxivbis
|
||||||
|
* (see https://github.com/arcan1s/ffxivbis).
|
||||||
|
*
|
||||||
|
* License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
package me.arcanis.ffxivbis.http.helpers
|
package me.arcanis.ffxivbis.http.helpers
|
||||||
|
|
||||||
import akka.actor.typed.scaladsl.AskPattern.Askable
|
import akka.actor.typed.scaladsl.AskPattern.Askable
|
||||||
|
@ -51,7 +51,8 @@ case class AddPieceTo(playerId: PlayerId, piece: Piece, isFreeLoot: Boolean, rep
|
|||||||
case class GetLoot(partyId: String, playerId: Option[PlayerId], replyTo: ActorRef[Seq[Player]])
|
case class GetLoot(partyId: String, playerId: Option[PlayerId], replyTo: ActorRef[Seq[Player]])
|
||||||
extends LootDatabaseMessage
|
extends LootDatabaseMessage
|
||||||
|
|
||||||
case class RemovePieceFrom(playerId: PlayerId, piece: Piece, replyTo: ActorRef[Unit]) extends LootDatabaseMessage {
|
case class RemovePieceFrom(playerId: PlayerId, piece: Piece, isFreeLoot: Boolean, replyTo: ActorRef[Unit])
|
||||||
|
extends LootDatabaseMessage {
|
||||||
override def partyId: String = playerId.partyId
|
override def partyId: String = playerId.partyId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,4 +13,6 @@ import java.time.Instant
|
|||||||
case class Loot(playerId: Long, piece: Piece, timestamp: Instant, isFreeLoot: Boolean) {
|
case class Loot(playerId: Long, piece: Piece, timestamp: Instant, isFreeLoot: Boolean) {
|
||||||
|
|
||||||
def isFreeLootToString: String = if (isFreeLoot) "yes" else "no"
|
def isFreeLootToString: String = if (isFreeLoot) "yes" else "no"
|
||||||
|
|
||||||
|
def isFreeLootToInt: Int = if (isFreeLoot) 1 else 0
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,9 @@ import scala.util.{Failure, Success}
|
|||||||
trait Database extends StrictLogging {
|
trait Database extends StrictLogging {
|
||||||
|
|
||||||
implicit def executionContext: ExecutionContext
|
implicit def executionContext: ExecutionContext
|
||||||
|
|
||||||
def config: Config
|
def config: Config
|
||||||
|
|
||||||
def profile: DatabaseProfile
|
def profile: DatabaseProfile
|
||||||
|
|
||||||
def filterParty(party: Party, maybePlayerId: Option[PlayerId]): Seq[Player] =
|
def filterParty(party: Party, maybePlayerId: Option[PlayerId]): Seq[Player] =
|
||||||
@ -36,8 +38,8 @@ trait Database extends StrictLogging {
|
|||||||
for {
|
for {
|
||||||
partyDescription <- profile.getPartyDescription(partyId)
|
partyDescription <- profile.getPartyDescription(partyId)
|
||||||
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.successful(Seq.empty)
|
||||||
loot <- if (withLoot) profile.getPieces(partyId) else Future(Seq.empty)
|
loot <- if (withLoot) profile.getPieces(partyId) else Future.successful(Seq.empty)
|
||||||
} yield Party(partyDescription, config, players, bis, loot)
|
} yield Party(partyDescription, config, players, bis, loot)
|
||||||
|
|
||||||
protected def run[T](fn: => Future[T])(onSuccess: T => Unit): Unit =
|
protected def run[T](fn: => Future[T])(onSuccess: T => Unit): Unit =
|
||||||
|
@ -6,9 +6,10 @@
|
|||||||
*
|
*
|
||||||
* License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
|
* License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
|
||||||
*/
|
*/
|
||||||
package me.arcanis.ffxivbis.storage
|
package me.arcanis.ffxivbis.service.database
|
||||||
|
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
|
import me.arcanis.ffxivbis.storage.DatabaseProfile
|
||||||
import org.flywaydb.core.Flyway
|
import org.flywaydb.core.Flyway
|
||||||
import org.flywaydb.core.api.configuration.ClassicConfiguration
|
import org.flywaydb.core.api.configuration.ClassicConfiguration
|
||||||
import org.flywaydb.core.api.output.MigrateResult
|
import org.flywaydb.core.api.output.MigrateResult
|
||||||
@ -17,12 +18,14 @@ import scala.util.Try
|
|||||||
|
|
||||||
class Migration(config: Config) {
|
class Migration(config: Config) {
|
||||||
|
|
||||||
|
import me.arcanis.ffxivbis.utils.Implicits._
|
||||||
|
|
||||||
def performMigration(): Try[MigrateResult] = {
|
def performMigration(): Try[MigrateResult] = {
|
||||||
val section = DatabaseProfile.getSection(config)
|
val section = DatabaseProfile.getSection(config)
|
||||||
|
|
||||||
val url = section.getString("db.url")
|
val url = section.getString("jdbcUrl")
|
||||||
val username = Try(section.getString("db.user")).toOption.filter(_.nonEmpty).orNull
|
val username = section.getOptString("username").orNull
|
||||||
val password = Try(section.getString("db.password")).toOption.filter(_.nonEmpty).orNull
|
val password = section.getOptString("password").orNull
|
||||||
|
|
||||||
val provider = url match {
|
val provider = url match {
|
||||||
case s"jdbc:$p:$_" => p
|
case s"jdbc:$p:$_" => p
|
@ -30,8 +30,8 @@ trait DatabaseLootHandler { this: Database =>
|
|||||||
}(client ! _)
|
}(client ! _)
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
case RemovePieceFrom(playerId, piece, client) =>
|
case RemovePieceFrom(playerId, piece, isFreeLoot, client) =>
|
||||||
run(profile.deletePiece(playerId, piece))(_ => client ! ())
|
run(profile.deletePiece(playerId, piece, isFreeLoot))(_ => client ! ())
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
case SuggestLoot(partyId, piece, client) =>
|
case SuggestLoot(partyId, piece, client) =>
|
||||||
|
@ -8,66 +8,72 @@
|
|||||||
*/
|
*/
|
||||||
package me.arcanis.ffxivbis.storage
|
package me.arcanis.ffxivbis.storage
|
||||||
|
|
||||||
|
import anorm.SqlParser._
|
||||||
|
import anorm._
|
||||||
import me.arcanis.ffxivbis.models.{Job, Loot, Piece, PieceType}
|
import me.arcanis.ffxivbis.models.{Job, Loot, Piece, PieceType}
|
||||||
import slick.lifted.ForeignKeyQuery
|
|
||||||
|
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
|
|
||||||
trait BiSProfile { this: DatabaseProfile =>
|
trait BiSProfile extends DatabaseConnection {
|
||||||
import dbConfig.profile.api._
|
|
||||||
|
|
||||||
case class BiSRep(playerId: Long, created: Long, piece: String, pieceType: String, job: String) {
|
private val loot: RowParser[Loot] =
|
||||||
|
(long("player_id") ~ str("piece") ~ str("piece_type")
|
||||||
def toLoot: Loot = Loot(
|
~ str("job") ~ long("created"))
|
||||||
playerId,
|
.map { case playerId ~ piece ~ pieceType ~ job ~ created =>
|
||||||
Piece(piece, PieceType.withName(pieceType), Job.withName(job)),
|
Loot(
|
||||||
Instant.ofEpochMilli(created),
|
playerId = playerId,
|
||||||
isFreeLoot = false
|
piece = Piece(
|
||||||
)
|
piece = piece,
|
||||||
}
|
pieceType = PieceType.withName(pieceType),
|
||||||
|
job = Job.withName(job)
|
||||||
object BiSRep {
|
),
|
||||||
|
timestamp = Instant.ofEpochMilli(created),
|
||||||
def fromPiece(playerId: Long, piece: Piece): BiSRep =
|
isFreeLoot = false,
|
||||||
BiSRep(playerId, DatabaseProfile.now, piece.piece, piece.pieceType.toString, piece.job.toString)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class BiSPieces(tag: Tag) extends Table[BiSRep](tag, "bis") {
|
|
||||||
def playerId: Rep[Long] = column[Long]("player_id", O.PrimaryKey)
|
|
||||||
def created: Rep[Long] = column[Long]("created")
|
|
||||||
def piece: Rep[String] = column[String]("piece", O.PrimaryKey)
|
|
||||||
def pieceType: Rep[String] = column[String]("piece_type")
|
|
||||||
def job: Rep[String] = column[String]("job")
|
|
||||||
|
|
||||||
def * =
|
|
||||||
(playerId, created, piece, pieceType, job) <> ((BiSRep.apply _).tupled, BiSRep.unapply)
|
|
||||||
|
|
||||||
def fkPlayerId: ForeignKeyQuery[Players, PlayerRep] =
|
|
||||||
foreignKey("player_id", playerId, playersTable)(_.playerId, onDelete = ForeignKeyAction.Cascade)
|
|
||||||
}
|
|
||||||
|
|
||||||
def deletePieceBiSById(piece: Piece)(playerId: Long): Future[Int] =
|
def deletePieceBiSById(piece: Piece)(playerId: Long): Future[Int] =
|
||||||
db.run(pieceBiS(BiSRep.fromPiece(playerId, piece)).delete)
|
withConnection { implicit conn =>
|
||||||
|
SQL("""delete from bis
|
||||||
|
| where player_id = {player_id}
|
||||||
|
| and piece = {piece}
|
||||||
|
| and piece_type = {piece_type}""".stripMargin)
|
||||||
|
.on("player_id" -> playerId, "piece" -> piece.piece, "piece_type" -> piece.pieceType.toString)
|
||||||
|
.executeUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
def deletePiecesBiSById(playerId: Long): Future[Int] =
|
def deletePiecesBiSById(playerId: Long): Future[Int] =
|
||||||
db.run(piecesBiS(Seq(playerId)).delete)
|
withConnection { implicit conn =>
|
||||||
|
SQL("""delete from bis where player_id = {player_id}""")
|
||||||
|
.on("player_id" -> playerId)
|
||||||
|
.executeUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
def getPiecesBiSById(playerId: Long): Future[Seq[Loot]] = getPiecesBiSById(Seq(playerId))
|
def getPiecesBiSById(playerId: Long): Future[Seq[Loot]] = getPiecesBiSById(Seq(playerId))
|
||||||
|
|
||||||
def getPiecesBiSById(playerIds: Seq[Long]): Future[Seq[Loot]] =
|
def getPiecesBiSById(playerIds: Seq[Long]): Future[Seq[Loot]] =
|
||||||
db.run(piecesBiS(playerIds).result).map(_.map(_.toLoot))
|
withConnection { implicit conn =>
|
||||||
|
SQL("""select * from bis where player_id in ({player_ids})""")
|
||||||
|
.on("player_ids" -> playerIds)
|
||||||
|
.executeQuery()
|
||||||
|
.as(loot.*)
|
||||||
|
}
|
||||||
|
|
||||||
def insertPieceBiSById(piece: Piece)(playerId: Long): Future[Int] =
|
def insertPieceBiSById(piece: Piece)(playerId: Long): Future[Int] =
|
||||||
getPiecesBiSById(playerId).flatMap {
|
withConnection { implicit conn =>
|
||||||
case pieces if pieces.exists(loot => loot.piece.strictEqual(piece)) => Future.successful(0)
|
SQL("""insert into bis
|
||||||
case _ => db.run(bisTable.insertOrUpdate(BiSRep.fromPiece(playerId, piece)))
|
| (player_id, piece, piece_type, job, created)
|
||||||
|
| values
|
||||||
|
| ({player_id}, {piece}, {piece_type}, {job}, {created})
|
||||||
|
| on conflict (player_id, piece, piece_type) do nothing""".stripMargin)
|
||||||
|
.on(
|
||||||
|
"player_id" -> playerId,
|
||||||
|
"piece" -> piece.piece,
|
||||||
|
"piece_type" -> piece.pieceType.toString,
|
||||||
|
"job" -> piece.job.toString,
|
||||||
|
"created" -> DatabaseProfile.now
|
||||||
|
)
|
||||||
|
.executeUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
private def pieceBiS(piece: BiSRep) =
|
|
||||||
piecesBiS(Seq(piece.playerId)).filter { stored =>
|
|
||||||
(stored.piece === piece.piece) && (stored.pieceType === piece.pieceType)
|
|
||||||
}
|
|
||||||
private def piecesBiS(playerIds: Seq[Long]) =
|
|
||||||
bisTable.filter(_.playerId.inSet(playerIds.toSet))
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2022 Evgeniy Alekseev.
|
||||||
|
*
|
||||||
|
* This file is part of ffxivbis
|
||||||
|
* (see https://github.com/arcan1s/ffxivbis).
|
||||||
|
*
|
||||||
|
* License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package me.arcanis.ffxivbis.storage
|
||||||
|
|
||||||
|
import com.typesafe.config.Config
|
||||||
|
import com.zaxxer.hikari.HikariConfig
|
||||||
|
|
||||||
|
import java.sql.Connection
|
||||||
|
import java.util.Properties
|
||||||
|
import javax.sql.DataSource
|
||||||
|
import scala.concurrent.{ExecutionContext, Future, Promise}
|
||||||
|
import scala.jdk.CollectionConverters._
|
||||||
|
import scala.util.control.NonFatal
|
||||||
|
import scala.util.{Failure, Success, Try}
|
||||||
|
|
||||||
|
trait DatabaseConnection {
|
||||||
|
|
||||||
|
def datasource: DataSource
|
||||||
|
|
||||||
|
def executionContext: ExecutionContext
|
||||||
|
|
||||||
|
def withConnection[T](fn: Connection => T): Future[T] = {
|
||||||
|
val promise = Promise[T]()
|
||||||
|
|
||||||
|
executionContext.execute { () =>
|
||||||
|
Try(datasource.getConnection) match {
|
||||||
|
case Success(conn) =>
|
||||||
|
try {
|
||||||
|
val result = fn(conn)
|
||||||
|
promise.trySuccess(result)
|
||||||
|
} catch {
|
||||||
|
case NonFatal(exception) => promise.tryFailure(exception)
|
||||||
|
} finally
|
||||||
|
conn.close()
|
||||||
|
case Failure(exception) => promise.tryFailure(exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.future
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object DatabaseConnection {
|
||||||
|
|
||||||
|
def getDataSourceConfig(config: Config): HikariConfig = {
|
||||||
|
val properties = new Properties()
|
||||||
|
config.entrySet().asScala.map(_.getKey).foreach { key =>
|
||||||
|
properties.setProperty(key, config.getString(key))
|
||||||
|
}
|
||||||
|
new HikariConfig(properties)
|
||||||
|
}
|
||||||
|
}
|
@ -9,32 +9,32 @@
|
|||||||
package me.arcanis.ffxivbis.storage
|
package me.arcanis.ffxivbis.storage
|
||||||
|
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
|
import com.typesafe.scalalogging.StrictLogging
|
||||||
|
import com.zaxxer.hikari.HikariDataSource
|
||||||
import me.arcanis.ffxivbis.models.{Loot, Piece, PlayerId}
|
import me.arcanis.ffxivbis.models.{Loot, Piece, PlayerId}
|
||||||
import slick.basic.DatabaseConfig
|
|
||||||
import slick.jdbc.JdbcProfile
|
|
||||||
|
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
import javax.sql.DataSource
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
|
|
||||||
class DatabaseProfile(context: ExecutionContext, config: Config)
|
class DatabaseProfile(override val executionContext: ExecutionContext, config: Config)
|
||||||
extends BiSProfile
|
extends StrictLogging
|
||||||
|
with BiSProfile
|
||||||
with LootProfile
|
with LootProfile
|
||||||
with PartyProfile
|
with PartyProfile
|
||||||
with PlayersProfile
|
with PlayersProfile
|
||||||
with UsersProfile {
|
with UsersProfile {
|
||||||
|
|
||||||
implicit val executionContext: ExecutionContext = context
|
override val datasource: DataSource =
|
||||||
|
try {
|
||||||
val dbConfig: DatabaseConfig[JdbcProfile] =
|
val profile = DatabaseProfile.getSection(config)
|
||||||
DatabaseConfig.forConfig[JdbcProfile]("", DatabaseProfile.getSection(config))
|
val dataSourceConfig = DatabaseConnection.getDataSourceConfig(profile)
|
||||||
import dbConfig.profile.api._
|
new HikariDataSource(dataSourceConfig)
|
||||||
val db = dbConfig.db
|
} catch {
|
||||||
|
case exception: Exception =>
|
||||||
val bisTable: TableQuery[BiSPieces] = TableQuery[BiSPieces]
|
logger.error("exception during storage initialization", exception)
|
||||||
val lootTable: TableQuery[LootPieces] = TableQuery[LootPieces]
|
throw exception
|
||||||
val partiesTable: TableQuery[Parties] = TableQuery[Parties]
|
}
|
||||||
val playersTable: TableQuery[Players] = TableQuery[Players]
|
|
||||||
val usersTable: TableQuery[Users] = TableQuery[Users]
|
|
||||||
|
|
||||||
// generic bis api
|
// generic bis api
|
||||||
def deletePieceBiS(playerId: PlayerId, piece: Piece): Future[Int] =
|
def deletePieceBiS(playerId: PlayerId, piece: Piece): Future[Int] =
|
||||||
@ -53,9 +53,8 @@ class DatabaseProfile(context: ExecutionContext, config: Config)
|
|||||||
byPlayerId(playerId, insertPieceBiSById(piece))
|
byPlayerId(playerId, insertPieceBiSById(piece))
|
||||||
|
|
||||||
// generic loot api
|
// generic loot api
|
||||||
def deletePiece(playerId: PlayerId, piece: Piece): Future[Int] = {
|
def deletePiece(playerId: PlayerId, piece: Piece, isFreeLoot: Boolean): Future[Int] = {
|
||||||
// we don't really care here about loot
|
val loot = Loot(-1, piece, Instant.now, isFreeLoot)
|
||||||
val loot = Loot(-1, piece, Instant.now, isFreeLoot = false)
|
|
||||||
byPlayerId(playerId, deletePieceById(loot))
|
byPlayerId(playerId, deletePieceById(loot))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,21 +68,23 @@ class DatabaseProfile(context: ExecutionContext, config: Config)
|
|||||||
byPlayerId(playerId, insertPieceById(loot))
|
byPlayerId(playerId, insertPieceById(loot))
|
||||||
|
|
||||||
private def byPartyId[T](partyId: String, callback: Seq[Long] => Future[T]): Future[T] =
|
private def byPartyId[T](partyId: String, callback: Seq[Long] => Future[T]): Future[T] =
|
||||||
getPlayers(partyId).flatMap(callback)
|
getPlayers(partyId).flatMap(callback)(executionContext)
|
||||||
|
|
||||||
private def byPlayerId[T](playerId: PlayerId, callback: Long => Future[T]): Future[T] =
|
private def byPlayerId[T](playerId: PlayerId, callback: Long => Future[T]): Future[T] =
|
||||||
getPlayer(playerId).flatMap {
|
getPlayer(playerId).flatMap {
|
||||||
case Some(id) => callback(id)
|
case Some(id) => callback(id)
|
||||||
case None => Future.failed(new Error(s"Could not find player $playerId"))
|
case None => Future.failed(DatabaseProfile.PlayerNotFound(playerId))
|
||||||
}
|
}(executionContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
object DatabaseProfile {
|
object DatabaseProfile {
|
||||||
|
|
||||||
def now: Long = Instant.now.toEpochMilli
|
case class PlayerNotFound(playerId: PlayerId) extends Exception(s"Could not find player $playerId")
|
||||||
|
|
||||||
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")
|
||||||
config.getConfig("me.arcanis.ffxivbis.database").getConfig(section)
|
config.getConfig(s"me.arcanis.ffxivbis.database.$section")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def now: Long = Instant.now.toEpochMilli
|
||||||
}
|
}
|
||||||
|
@ -8,81 +8,78 @@
|
|||||||
*/
|
*/
|
||||||
package me.arcanis.ffxivbis.storage
|
package me.arcanis.ffxivbis.storage
|
||||||
|
|
||||||
|
import anorm.SqlParser._
|
||||||
|
import anorm._
|
||||||
import me.arcanis.ffxivbis.models.{Job, Loot, Piece, PieceType}
|
import me.arcanis.ffxivbis.models.{Job, Loot, Piece, PieceType}
|
||||||
import slick.lifted.{ForeignKeyQuery, Index}
|
|
||||||
|
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
|
|
||||||
trait LootProfile { this: DatabaseProfile =>
|
trait LootProfile extends DatabaseConnection {
|
||||||
import dbConfig.profile.api._
|
|
||||||
|
|
||||||
case class LootRep(
|
private val loot: RowParser[Loot] =
|
||||||
lootId: Option[Long],
|
(long("player_id") ~ str("piece") ~ str("piece_type")
|
||||||
playerId: Long,
|
~ str("job") ~ long("created") ~ int("is_free_loot"))
|
||||||
created: Long,
|
.map { case playerId ~ piece ~ pieceType ~ job ~ created ~ isFreeLoot =>
|
||||||
piece: String,
|
Loot(
|
||||||
pieceType: String,
|
playerId = playerId,
|
||||||
job: String,
|
piece = Piece(
|
||||||
isFreeLoot: Int
|
piece = piece,
|
||||||
) {
|
pieceType = PieceType.withName(pieceType),
|
||||||
|
job = Job.withName(job)
|
||||||
def toLoot: Loot = Loot(
|
),
|
||||||
playerId,
|
timestamp = Instant.ofEpochMilli(created),
|
||||||
Piece(piece, PieceType.withName(pieceType), Job.withName(job)),
|
isFreeLoot = isFreeLoot == 1,
|
||||||
Instant.ofEpochMilli(created),
|
)
|
||||||
isFreeLoot == 1
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
object LootRep {
|
|
||||||
def fromLoot(playerId: Long, loot: Loot): LootRep =
|
|
||||||
LootRep(
|
|
||||||
None,
|
|
||||||
playerId,
|
|
||||||
loot.timestamp.toEpochMilli,
|
|
||||||
loot.piece.piece,
|
|
||||||
loot.piece.pieceType.toString,
|
|
||||||
loot.piece.job.toString,
|
|
||||||
if (loot.isFreeLoot) 1 else 0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
class LootPieces(tag: Tag) extends Table[LootRep](tag, "loot") {
|
|
||||||
def lootId: Rep[Long] = column[Long]("loot_id", O.AutoInc, O.PrimaryKey)
|
|
||||||
def playerId: Rep[Long] = column[Long]("player_id")
|
|
||||||
def created: Rep[Long] = column[Long]("created")
|
|
||||||
def piece: Rep[String] = column[String]("piece")
|
|
||||||
def pieceType: Rep[String] = column[String]("piece_type")
|
|
||||||
def job: Rep[String] = column[String]("job")
|
|
||||||
def isFreeLoot: Rep[Int] = column[Int]("is_free_loot")
|
|
||||||
|
|
||||||
def * =
|
|
||||||
(lootId.?, playerId, created, piece, pieceType, job, isFreeLoot) <> ((LootRep.apply _).tupled, LootRep.unapply)
|
|
||||||
|
|
||||||
def fkPlayerId: ForeignKeyQuery[Players, PlayerRep] =
|
|
||||||
foreignKey("player_id", playerId, playersTable)(_.playerId, onDelete = ForeignKeyAction.Cascade)
|
|
||||||
def lootOwnerIdx: Index =
|
|
||||||
index("loot_owner_idx", playerId, unique = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
def deletePieceById(loot: Loot)(playerId: Long): Future[Int] =
|
def deletePieceById(loot: Loot)(playerId: Long): Future[Int] =
|
||||||
db.run(pieceLoot(LootRep.fromLoot(playerId, loot)).map(_.lootId).max.result).flatMap {
|
withConnection { implicit conn =>
|
||||||
case Some(id) => db.run(lootTable.filter(_.lootId === id).delete)
|
SQL("""delete from loot
|
||||||
case _ => throw new IllegalArgumentException(s"Could not find piece $loot belong to $playerId")
|
| where loot_id in
|
||||||
|
| (
|
||||||
|
| select loot_id from loot
|
||||||
|
| where player_id = {player_id}
|
||||||
|
| and piece = {piece}
|
||||||
|
| and piece_type = {piece_type}
|
||||||
|
| and job = {job}
|
||||||
|
| and is_free_loot = {is_free_loot}
|
||||||
|
| limit 1
|
||||||
|
| )""".stripMargin)
|
||||||
|
.on(
|
||||||
|
"player_id" -> playerId,
|
||||||
|
"piece" -> loot.piece.piece,
|
||||||
|
"piece_type" -> loot.piece.pieceType.toString,
|
||||||
|
"job" -> loot.piece.job.toString,
|
||||||
|
"is_free_loot" -> loot.isFreeLootToInt
|
||||||
|
)
|
||||||
|
.executeUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
def getPiecesById(playerId: Long): Future[Seq[Loot]] = getPiecesById(Seq(playerId))
|
def getPiecesById(playerId: Long): Future[Seq[Loot]] = getPiecesById(Seq(playerId))
|
||||||
|
|
||||||
def getPiecesById(playerIds: Seq[Long]): Future[Seq[Loot]] =
|
def getPiecesById(playerIds: Seq[Long]): Future[Seq[Loot]] =
|
||||||
db.run(piecesLoot(playerIds).result).map(_.map(_.toLoot))
|
withConnection { implicit conn =>
|
||||||
|
SQL("""select * from loot where player_id in ({player_ids})""")
|
||||||
|
.on("player_ids" -> playerIds)
|
||||||
|
.executeQuery()
|
||||||
|
.as(loot.*)
|
||||||
|
}
|
||||||
|
|
||||||
def insertPieceById(loot: Loot)(playerId: Long): Future[Int] =
|
def insertPieceById(loot: Loot)(playerId: Long): Future[Int] =
|
||||||
db.run(lootTable.insertOrUpdate(LootRep.fromLoot(playerId, loot)))
|
withConnection { implicit conn =>
|
||||||
|
SQL("""insert into loot
|
||||||
private def pieceLoot(piece: LootRep) =
|
| (player_id, piece, piece_type, job, created, is_free_loot)
|
||||||
piecesLoot(Seq(piece.playerId)).filter(_.piece === piece.piece)
|
| values
|
||||||
|
| ({player_id}, {piece}, {piece_type}, {job}, {created}, {is_free_loot})""".stripMargin)
|
||||||
private def piecesLoot(playerIds: Seq[Long]) =
|
.on(
|
||||||
lootTable.filter(_.playerId.inSet(playerIds.toSet))
|
"player_id" -> playerId,
|
||||||
|
"piece" -> loot.piece.piece,
|
||||||
|
"piece_type" -> loot.piece.pieceType.toString,
|
||||||
|
"job" -> loot.piece.job.toString,
|
||||||
|
"created" -> DatabaseProfile.now,
|
||||||
|
"is_free_loot" -> loot.isFreeLootToInt
|
||||||
|
)
|
||||||
|
.executeUpdate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,47 +8,41 @@
|
|||||||
*/
|
*/
|
||||||
package me.arcanis.ffxivbis.storage
|
package me.arcanis.ffxivbis.storage
|
||||||
|
|
||||||
|
import anorm.SqlParser._
|
||||||
|
import anorm._
|
||||||
import me.arcanis.ffxivbis.models.PartyDescription
|
import me.arcanis.ffxivbis.models.PartyDescription
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
|
|
||||||
trait PartyProfile { this: DatabaseProfile =>
|
trait PartyProfile extends DatabaseConnection {
|
||||||
import dbConfig.profile.api._
|
|
||||||
|
|
||||||
case class PartyRep(partyId: Option[Long], partyName: String, partyAlias: Option[String]) {
|
private val description: RowParser[PartyDescription] =
|
||||||
|
(str("party_name") ~ str("party_alias").?)
|
||||||
def toDescription: PartyDescription = PartyDescription(partyName, partyAlias)
|
.map { case partyName ~ partyAlias =>
|
||||||
}
|
PartyDescription(
|
||||||
|
partyId = partyName,
|
||||||
object PartyRep {
|
partyAlias = partyAlias,
|
||||||
|
)
|
||||||
def fromDescription(party: PartyDescription, id: Option[Long]): PartyRep =
|
}
|
||||||
PartyRep(id, party.partyId, party.partyAlias)
|
|
||||||
}
|
|
||||||
|
|
||||||
class Parties(tag: Tag) extends Table[PartyRep](tag, "parties") {
|
|
||||||
def partyId: Rep[Long] = column[Long]("party_id", O.AutoInc, O.PrimaryKey)
|
|
||||||
def partyName: Rep[String] = column[String]("party_name")
|
|
||||||
def partyAlias: Rep[Option[String]] = column[Option[String]]("party_alias")
|
|
||||||
|
|
||||||
def * =
|
|
||||||
(partyId.?, partyName, partyAlias) <> ((PartyRep.apply _).tupled, PartyRep.unapply)
|
|
||||||
}
|
|
||||||
|
|
||||||
def getPartyDescription(partyId: String): Future[PartyDescription] =
|
def getPartyDescription(partyId: String): Future[PartyDescription] =
|
||||||
db.run(
|
withConnection { implicit conn =>
|
||||||
partyDescription(partyId).result.headOption.map(_.map(_.toDescription).getOrElse(PartyDescription.empty(partyId)))
|
SQL("""select * from parties where party_name = {party_name}""")
|
||||||
)
|
.on("party_name" -> partyId)
|
||||||
|
.executeQuery()
|
||||||
def getUniquePartyId(partyId: String): Future[Option[Long]] =
|
.as(description.singleOpt)
|
||||||
db.run(partyDescription(partyId).map(_.partyId).result.headOption)
|
.getOrElse(PartyDescription.empty(partyId))
|
||||||
|
|
||||||
def insertPartyDescription(partyDescription: PartyDescription): Future[Int] =
|
|
||||||
getUniquePartyId(partyDescription.partyId).flatMap {
|
|
||||||
case Some(id) => db.run(partiesTable.update(PartyRep.fromDescription(partyDescription, Some(id))))
|
|
||||||
case _ => db.run(partiesTable.insertOrUpdate(PartyRep.fromDescription(partyDescription, None)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private def partyDescription(partyId: String) =
|
def insertPartyDescription(partyDescription: PartyDescription): Future[Int] =
|
||||||
partiesTable.filter(_.partyName === partyId)
|
withConnection { implicit conn =>
|
||||||
|
SQL("""insert into parties
|
||||||
|
| (party_name, party_alias)
|
||||||
|
| values
|
||||||
|
| ({party_name}, {party_alias})
|
||||||
|
| on conflict (party_name) do update set
|
||||||
|
| party_alias = {party_alias}""".stripMargin)
|
||||||
|
.on("party_name" -> partyDescription.partyId, "party_alias" -> partyDescription.partyAlias)
|
||||||
|
.executeUpdate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,76 +8,97 @@
|
|||||||
*/
|
*/
|
||||||
package me.arcanis.ffxivbis.storage
|
package me.arcanis.ffxivbis.storage
|
||||||
|
|
||||||
|
import anorm.SqlParser._
|
||||||
|
import anorm._
|
||||||
import me.arcanis.ffxivbis.models.{BiS, Job, Player, PlayerId}
|
import me.arcanis.ffxivbis.models.{BiS, Job, Player, PlayerId}
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
|
|
||||||
trait PlayersProfile { this: DatabaseProfile =>
|
trait PlayersProfile extends DatabaseConnection {
|
||||||
import dbConfig.profile.api._
|
|
||||||
|
|
||||||
case class PlayerRep(
|
private val player: RowParser[Player] =
|
||||||
partyId: String,
|
(long("player_id") ~ str("party_id") ~ str("job")
|
||||||
playerId: Option[Long],
|
~ str("nick") ~ str("bis_link").? ~ int("priority").?)
|
||||||
created: Long,
|
.map { case playerId ~ partyId ~ job ~ nick ~ link ~ priority =>
|
||||||
nick: String,
|
Player(
|
||||||
job: String,
|
id = playerId,
|
||||||
link: Option[String],
|
partyId = partyId,
|
||||||
priority: Int
|
job = Job.withName(job),
|
||||||
) {
|
nick = nick,
|
||||||
|
bis = BiS.empty,
|
||||||
|
loot = Seq.empty,
|
||||||
|
link = link,
|
||||||
|
priority = priority.getOrElse(0),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
def toPlayer: Player =
|
def deletePlayer(playerId: PlayerId): Future[Int] =
|
||||||
Player(playerId.getOrElse(-1), partyId, Job.withName(job), nick, BiS.empty, Seq.empty, link, priority)
|
withConnection { implicit conn =>
|
||||||
}
|
SQL("""delete from players
|
||||||
|
| where party_id = {party_id}
|
||||||
object PlayerRep {
|
| and nick = {nick}
|
||||||
|
| and job = {job}""".stripMargin)
|
||||||
def fromPlayer(player: Player, id: Option[Long]): PlayerRep =
|
.on("party_id" -> playerId.partyId, "nick" -> playerId.nick, "job" -> playerId.job.toString)
|
||||||
PlayerRep(player.partyId, id, DatabaseProfile.now, player.nick, player.job.toString, player.link, player.priority)
|
.executeUpdate()
|
||||||
}
|
|
||||||
|
|
||||||
class Players(tag: Tag) extends Table[PlayerRep](tag, "players") {
|
|
||||||
def partyId: Rep[String] = column[String]("party_id")
|
|
||||||
def playerId: Rep[Long] = column[Long]("player_id", O.AutoInc, O.PrimaryKey)
|
|
||||||
def created: Rep[Long] = column[Long]("created")
|
|
||||||
def nick: Rep[String] = column[String]("nick")
|
|
||||||
def job: Rep[String] = column[String]("job")
|
|
||||||
def bisLink: Rep[Option[String]] = column[Option[String]]("bis_link")
|
|
||||||
def priority: Rep[Int] = column[Int]("priority", O.Default(1))
|
|
||||||
|
|
||||||
def * =
|
|
||||||
(partyId, playerId.?, created, nick, job, bisLink, priority) <> ((PlayerRep.apply _).tupled, PlayerRep.unapply)
|
|
||||||
}
|
|
||||||
|
|
||||||
def deletePlayer(playerId: PlayerId): Future[Int] = db.run(player(playerId).delete)
|
|
||||||
|
|
||||||
def getParty(partyId: String): Future[Map[Long, Player]] =
|
|
||||||
db.run(players(partyId).result)
|
|
||||||
.map(_.foldLeft(Map.empty[Long, Player]) {
|
|
||||||
case (acc, p @ PlayerRep(_, Some(id), _, _, _, _, _)) => acc + (id -> p.toPlayer)
|
|
||||||
case (acc, _) => acc
|
|
||||||
})
|
|
||||||
|
|
||||||
def getPlayer(playerId: PlayerId): Future[Option[Long]] =
|
|
||||||
db.run(player(playerId).map(_.playerId).result.headOption)
|
|
||||||
|
|
||||||
def getPlayerFull(playerId: PlayerId): Future[Option[Player]] =
|
|
||||||
db.run(player(playerId).result.headOption.map(_.map(_.toPlayer)))
|
|
||||||
|
|
||||||
def getPlayers(partyId: String): Future[Seq[Long]] =
|
|
||||||
db.run(players(partyId).map(_.playerId).result)
|
|
||||||
|
|
||||||
def insertPlayer(playerObj: Player): Future[Int] =
|
|
||||||
getPlayer(playerObj.playerId).flatMap {
|
|
||||||
case Some(id) => db.run(playersTable.update(PlayerRep.fromPlayer(playerObj, Some(id))))
|
|
||||||
case _ => db.run(playersTable.insertOrUpdate(PlayerRep.fromPlayer(playerObj, None)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private def player(playerId: PlayerId) =
|
def getParty(partyId: String): Future[Map[Long, Player]] =
|
||||||
playersTable
|
withConnection { implicit conn =>
|
||||||
.filter(_.partyId === playerId.partyId)
|
SQL("""select * from players where party_id = {party_id}""")
|
||||||
.filter(_.job === playerId.job.toString)
|
.on("party_id" -> partyId)
|
||||||
.filter(_.nick === playerId.nick)
|
.executeQuery()
|
||||||
|
.as(player.*)
|
||||||
|
.map(p => p.id -> p)
|
||||||
|
.toMap
|
||||||
|
}
|
||||||
|
|
||||||
|
def getPlayer(playerId: PlayerId): Future[Option[Long]] =
|
||||||
|
withConnection { implicit conn =>
|
||||||
|
SQL("""select player_id from players
|
||||||
|
| where party_id = {party_id}
|
||||||
|
| and nick = {nick}
|
||||||
|
| and job = {job}""".stripMargin)
|
||||||
|
.on("party_id" -> playerId.partyId, "nick" -> playerId.nick, "job" -> playerId.job.toString)
|
||||||
|
.executeQuery()
|
||||||
|
.as(scalar[Long].singleOpt)
|
||||||
|
}
|
||||||
|
|
||||||
|
def getPlayerFull(playerId: PlayerId): Future[Option[Player]] =
|
||||||
|
withConnection { implicit conn =>
|
||||||
|
SQL("""select * from players
|
||||||
|
| where party_id = {party_id}
|
||||||
|
| and nick = {nick}
|
||||||
|
| and job = {job}""".stripMargin)
|
||||||
|
.on("party_id" -> playerId.partyId, "nick" -> playerId.nick, "job" -> playerId.job.toString)
|
||||||
|
.executeQuery()
|
||||||
|
.as(player.singleOpt)
|
||||||
|
}
|
||||||
|
|
||||||
|
def getPlayers(partyId: String): Future[Seq[Long]] =
|
||||||
|
withConnection { implicit conn =>
|
||||||
|
SQL("""select player_id from players where party_id = {party_id}""")
|
||||||
|
.on("party_id" -> partyId)
|
||||||
|
.executeQuery()
|
||||||
|
.as(scalar[Long].*)
|
||||||
|
}
|
||||||
|
|
||||||
|
def insertPlayer(player: Player): Future[Int] =
|
||||||
|
withConnection { implicit conn =>
|
||||||
|
SQL("""insert into players
|
||||||
|
| (party_id, created, job, nick, bis_link, priority)
|
||||||
|
| values
|
||||||
|
| ({party_id}, {created}, {job}, {nick}, {link}, {priority})
|
||||||
|
| on conflict (party_id, nick, job) do update set
|
||||||
|
| bis_link = {link}, priority = {priority}""".stripMargin)
|
||||||
|
.on(
|
||||||
|
"party_id" -> player.partyId,
|
||||||
|
"created" -> DatabaseProfile.now,
|
||||||
|
"job" -> player.job.toString,
|
||||||
|
"nick" -> player.nick,
|
||||||
|
"link" -> player.link,
|
||||||
|
"priority" -> player.priority
|
||||||
|
)
|
||||||
|
.executeUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
private def players(partyId: String) =
|
|
||||||
playersTable.filter(_.partyId === partyId)
|
|
||||||
}
|
}
|
||||||
|
@ -8,64 +8,67 @@
|
|||||||
*/
|
*/
|
||||||
package me.arcanis.ffxivbis.storage
|
package me.arcanis.ffxivbis.storage
|
||||||
|
|
||||||
|
import anorm.SqlParser._
|
||||||
|
import anorm._
|
||||||
import me.arcanis.ffxivbis.models.{Permission, User}
|
import me.arcanis.ffxivbis.models.{Permission, User}
|
||||||
import slick.lifted.{Index, PrimaryKey}
|
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
|
|
||||||
trait UsersProfile { this: DatabaseProfile =>
|
trait UsersProfile extends DatabaseConnection {
|
||||||
import dbConfig.profile.api._
|
|
||||||
|
|
||||||
case class UserRep(partyId: String, userId: Option[Long], username: String, password: String, permission: String) {
|
private val user: RowParser[User] =
|
||||||
|
(str("party_id") ~ str("username") ~ str("password") ~ str("permission"))
|
||||||
def toUser: User = User(partyId, username, password, Permission.withName(permission))
|
.map { case partyId ~ username ~ password ~ permission =>
|
||||||
}
|
User(
|
||||||
|
partyId = partyId,
|
||||||
object UserRep {
|
username = username,
|
||||||
|
password = password,
|
||||||
def fromUser(user: User, id: Option[Long]): UserRep =
|
permission = Permission.withName(permission),
|
||||||
UserRep(user.partyId, id, user.username, user.password, user.permission.toString)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Users(tag: Tag) extends Table[UserRep](tag, "users") {
|
|
||||||
def partyId: Rep[String] = column[String]("party_id")
|
|
||||||
def userId: Rep[Long] = column[Long]("user_id", O.AutoInc, O.PrimaryKey)
|
|
||||||
def username: Rep[String] = column[String]("username")
|
|
||||||
def password: Rep[String] = column[String]("password")
|
|
||||||
def permission: Rep[String] = column[String]("permission")
|
|
||||||
|
|
||||||
def * =
|
|
||||||
(partyId, userId.?, username, password, permission) <> ((UserRep.apply _).tupled, UserRep.unapply)
|
|
||||||
|
|
||||||
def pk: PrimaryKey = primaryKey("users_username_idx", (partyId, username))
|
|
||||||
def usersUsernameIdx: Index =
|
|
||||||
index("users_username_idx", (partyId, username), unique = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
def deleteUser(partyId: String, username: String): Future[Int] =
|
def deleteUser(partyId: String, username: String): Future[Int] =
|
||||||
db.run(
|
withConnection { implicit conn =>
|
||||||
user(partyId, Some(username))
|
SQL("""delete from users
|
||||||
.filter(_.permission =!= Permission.admin.toString) // we do not allow to remove admins
|
| where party_id = {party_id}
|
||||||
.delete
|
| and username = {username}
|
||||||
)
|
| and permission <> {admin}""".stripMargin)
|
||||||
|
.on("party_id" -> partyId, "username" -> username, "admin" -> Permission.admin.toString)
|
||||||
def exists(partyId: String): Future[Boolean] =
|
.executeUpdate()
|
||||||
db.run(user(partyId, None).exists.result)
|
|
||||||
|
|
||||||
def getUser(partyId: String, username: String): Future[Option[User]] =
|
|
||||||
db.run(user(partyId, Some(username)).result.headOption).map(_.map(_.toUser))
|
|
||||||
|
|
||||||
def getUsers(partyId: String): Future[Seq[User]] =
|
|
||||||
db.run(user(partyId, None).result).map(_.map(_.toUser))
|
|
||||||
|
|
||||||
def insertUser(userObj: User): Future[Int] =
|
|
||||||
db.run(user(userObj.partyId, Some(userObj.username)).map(_.userId).result.headOption).flatMap {
|
|
||||||
case Some(id) => db.run(usersTable.insertOrUpdate(UserRep.fromUser(userObj, Some(id))))
|
|
||||||
case _ => db.run(usersTable.insertOrUpdate(UserRep.fromUser(userObj, None)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private def user(partyId: String, username: Option[String]) =
|
def exists(partyId: String): Future[Boolean] = getUsers(partyId).map(_.nonEmpty)(executionContext)
|
||||||
usersTable
|
|
||||||
.filter(_.partyId === partyId)
|
def getUser(partyId: String, username: String): Future[Option[User]] =
|
||||||
.filterIf(username.isDefined)(_.username === username.orNull)
|
withConnection { implicit conn =>
|
||||||
|
SQL("""select * from users where party_id = {party_id} and username = {username}""")
|
||||||
|
.on("party_id" -> partyId, "username" -> username)
|
||||||
|
.executeQuery()
|
||||||
|
.as(user.singleOpt)
|
||||||
|
}
|
||||||
|
|
||||||
|
def getUsers(partyId: String): Future[Seq[User]] =
|
||||||
|
withConnection { implicit conn =>
|
||||||
|
SQL("""select * from users where party_id = {party_id}""")
|
||||||
|
.on("party_id" -> partyId)
|
||||||
|
.executeQuery()
|
||||||
|
.as(user.*)
|
||||||
|
}
|
||||||
|
|
||||||
|
def insertUser(user: User): Future[Int] =
|
||||||
|
withConnection { implicit conn =>
|
||||||
|
SQL("""insert into users
|
||||||
|
| (party_id, username, password, permission)
|
||||||
|
| values
|
||||||
|
| ({party_id}, {username}, {password}, {permission})
|
||||||
|
| on conflict (party_id, username) do update set
|
||||||
|
| password = {password}, permission = {permission}""".stripMargin)
|
||||||
|
.on(
|
||||||
|
"party_id" -> user.partyId,
|
||||||
|
"username" -> user.username,
|
||||||
|
"password" -> user.password,
|
||||||
|
"permission" -> user.permission.toString
|
||||||
|
)
|
||||||
|
.executeUpdate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,13 @@
|
|||||||
package me.arcanis.ffxivbis.utils
|
package me.arcanis.ffxivbis.utils
|
||||||
|
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
|
import com.typesafe.config.Config
|
||||||
|
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import scala.concurrent.duration.FiniteDuration
|
import scala.concurrent.duration.FiniteDuration
|
||||||
import scala.language.implicitConversions
|
import scala.language.implicitConversions
|
||||||
|
import scala.util.Try
|
||||||
|
|
||||||
object Implicits {
|
object Implicits {
|
||||||
|
|
||||||
@ -27,4 +29,10 @@ object Implicits {
|
|||||||
|
|
||||||
implicit def getTimeout(duration: Duration): Timeout =
|
implicit def getTimeout(duration: Duration): Timeout =
|
||||||
FiniteDuration(duration.toNanos, TimeUnit.NANOSECONDS)
|
FiniteDuration(duration.toNanos, TimeUnit.NANOSECONDS)
|
||||||
|
|
||||||
|
implicit class ConfigExtension(config: Config) {
|
||||||
|
|
||||||
|
def getOptString(path: String): Option[String] =
|
||||||
|
Try(config.getString(path)).toOption.filter(_.nonEmpty)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ object Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def clearDatabase(config: Config): Unit =
|
def clearDatabase(config: Config): Unit =
|
||||||
config.getString("me.arcanis.ffxivbis.database.sqlite.db.url").split(":")
|
config.getString("me.arcanis.ffxivbis.database.sqlite.jdbcUrl").split(":")
|
||||||
.lastOption.foreach { databasePath =>
|
.lastOption.foreach { databasePath =>
|
||||||
val databaseFile = new File(databasePath)
|
val databaseFile = new File(databasePath)
|
||||||
if (databaseFile.exists)
|
if (databaseFile.exists)
|
||||||
@ -25,5 +25,5 @@ object Settings {
|
|||||||
}
|
}
|
||||||
def randomDatabasePath: String = File.createTempFile("ffxivdb-",".db").toPath.toString
|
def randomDatabasePath: String = File.createTempFile("ffxivdb-",".db").toPath.toString
|
||||||
def withRandomDatabase: Config =
|
def withRandomDatabase: Config =
|
||||||
config(Map("me.arcanis.ffxivbis.database.sqlite.db.url" -> s"jdbc:sqlite:$randomDatabasePath"))
|
config(Map("me.arcanis.ffxivbis.database.sqlite.jdbcUrl" -> s"jdbc:sqlite:$randomDatabasePath"))
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,7 @@ import me.arcanis.ffxivbis.messages.{AddPlayer, AddUser}
|
|||||||
import me.arcanis.ffxivbis.models.{BiS, Job}
|
import me.arcanis.ffxivbis.models.{BiS, Job}
|
||||||
import me.arcanis.ffxivbis.service.PartyService
|
import me.arcanis.ffxivbis.service.PartyService
|
||||||
import me.arcanis.ffxivbis.service.bis.BisProvider
|
import me.arcanis.ffxivbis.service.bis.BisProvider
|
||||||
import me.arcanis.ffxivbis.service.database.Database
|
import me.arcanis.ffxivbis.service.database.{Database, Migration}
|
||||||
import me.arcanis.ffxivbis.storage.Migration
|
|
||||||
import me.arcanis.ffxivbis.utils.Compare
|
import me.arcanis.ffxivbis.utils.Compare
|
||||||
import me.arcanis.ffxivbis.{Fixtures, Settings}
|
import me.arcanis.ffxivbis.{Fixtures, Settings}
|
||||||
import org.scalatest.matchers.should.Matchers
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
@ -10,8 +10,7 @@ import com.typesafe.config.Config
|
|||||||
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.messages.{AddPlayer, AddUser}
|
||||||
import me.arcanis.ffxivbis.service.PartyService
|
import me.arcanis.ffxivbis.service.PartyService
|
||||||
import me.arcanis.ffxivbis.service.database.Database
|
import me.arcanis.ffxivbis.service.database.{Database, Migration}
|
||||||
import me.arcanis.ffxivbis.storage.Migration
|
|
||||||
import me.arcanis.ffxivbis.{Fixtures, Settings}
|
import me.arcanis.ffxivbis.{Fixtures, Settings}
|
||||||
import org.scalatest.matchers.should.Matchers
|
import org.scalatest.matchers.should.Matchers
|
||||||
import org.scalatest.wordspec.AnyWordSpecLike
|
import org.scalatest.wordspec.AnyWordSpecLike
|
||||||
|
@ -12,8 +12,7 @@ import me.arcanis.ffxivbis.messages.AddUser
|
|||||||
import me.arcanis.ffxivbis.models.PartyDescription
|
import me.arcanis.ffxivbis.models.PartyDescription
|
||||||
import me.arcanis.ffxivbis.service.PartyService
|
import me.arcanis.ffxivbis.service.PartyService
|
||||||
import me.arcanis.ffxivbis.service.bis.BisProvider
|
import me.arcanis.ffxivbis.service.bis.BisProvider
|
||||||
import me.arcanis.ffxivbis.service.database.Database
|
import me.arcanis.ffxivbis.service.database.{Database, Migration}
|
||||||
import me.arcanis.ffxivbis.storage.Migration
|
|
||||||
import me.arcanis.ffxivbis.{Fixtures, Settings}
|
import me.arcanis.ffxivbis.{Fixtures, Settings}
|
||||||
import org.scalatest.matchers.should.Matchers
|
import org.scalatest.matchers.should.Matchers
|
||||||
import org.scalatest.wordspec.AnyWordSpecLike
|
import org.scalatest.wordspec.AnyWordSpecLike
|
||||||
|
@ -11,8 +11,7 @@ import me.arcanis.ffxivbis.http.api.v1.json._
|
|||||||
import me.arcanis.ffxivbis.messages.{AddPlayer, AddUser}
|
import me.arcanis.ffxivbis.messages.{AddPlayer, AddUser}
|
||||||
import me.arcanis.ffxivbis.service.PartyService
|
import me.arcanis.ffxivbis.service.PartyService
|
||||||
import me.arcanis.ffxivbis.service.bis.BisProvider
|
import me.arcanis.ffxivbis.service.bis.BisProvider
|
||||||
import me.arcanis.ffxivbis.service.database.Database
|
import me.arcanis.ffxivbis.service.database.{Database, Migration}
|
||||||
import me.arcanis.ffxivbis.storage.Migration
|
|
||||||
import me.arcanis.ffxivbis.{Fixtures, Settings}
|
import me.arcanis.ffxivbis.{Fixtures, Settings}
|
||||||
import org.scalatest.matchers.should.Matchers
|
import org.scalatest.matchers.should.Matchers
|
||||||
import org.scalatest.wordspec.AnyWordSpecLike
|
import org.scalatest.wordspec.AnyWordSpecLike
|
||||||
|
@ -8,8 +8,7 @@ import akka.testkit.TestKit
|
|||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
import me.arcanis.ffxivbis.http.api.v1.json._
|
import me.arcanis.ffxivbis.http.api.v1.json._
|
||||||
import me.arcanis.ffxivbis.service.PartyService
|
import me.arcanis.ffxivbis.service.PartyService
|
||||||
import me.arcanis.ffxivbis.service.database.Database
|
import me.arcanis.ffxivbis.service.database.{Database, Migration}
|
||||||
import me.arcanis.ffxivbis.storage.Migration
|
|
||||||
import me.arcanis.ffxivbis.{Fixtures, Settings}
|
import me.arcanis.ffxivbis.{Fixtures, Settings}
|
||||||
import org.scalatest.matchers.should.Matchers
|
import org.scalatest.matchers.should.Matchers
|
||||||
import org.scalatest.wordspec.AnyWordSpecLike
|
import org.scalatest.wordspec.AnyWordSpecLike
|
||||||
|
@ -4,7 +4,6 @@ import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit
|
|||||||
import akka.actor.typed.scaladsl.AskPattern.Askable
|
import akka.actor.typed.scaladsl.AskPattern.Askable
|
||||||
import me.arcanis.ffxivbis.messages.{AddPieceToBis, AddPlayer, GetBiS, RemovePieceFromBiS}
|
import me.arcanis.ffxivbis.messages.{AddPieceToBis, AddPlayer, GetBiS, RemovePieceFromBiS}
|
||||||
import me.arcanis.ffxivbis.models._
|
import me.arcanis.ffxivbis.models._
|
||||||
import me.arcanis.ffxivbis.storage.Migration
|
|
||||||
import me.arcanis.ffxivbis.utils.Compare
|
import me.arcanis.ffxivbis.utils.Compare
|
||||||
import me.arcanis.ffxivbis.{Fixtures, Settings}
|
import me.arcanis.ffxivbis.{Fixtures, Settings}
|
||||||
import org.scalatest.wordspec.AnyWordSpecLike
|
import org.scalatest.wordspec.AnyWordSpecLike
|
||||||
@ -85,6 +84,6 @@ class DatabaseBiSHandlerTest extends ScalaTestWithActorTestKit(Settings.withRand
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private def partyBiSCompare[T](party: Seq[T], bis: Seq[Piece]): Boolean =
|
private def partyBiSCompare(party: Seq[Player], bis: Seq[Piece]): Boolean =
|
||||||
Compare.seqEquals(party.foldLeft(Seq.empty[Piece]){ case (acc, player) => acc ++ player.asInstanceOf[Player].bis.pieces }, bis)
|
Compare.seqEquals(party.foldLeft(Seq.empty[Piece]){ case (acc, player) => acc ++ player.bis.pieces }, bis)
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,9 @@ package me.arcanis.ffxivbis.service.database
|
|||||||
|
|
||||||
import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit
|
import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit
|
||||||
import akka.actor.typed.scaladsl.AskPattern.Askable
|
import akka.actor.typed.scaladsl.AskPattern.Askable
|
||||||
|
import ch.qos.logback.core.util.FixedDelay
|
||||||
import me.arcanis.ffxivbis.messages.{AddPieceTo, AddPlayer, GetLoot, RemovePieceFrom}
|
import me.arcanis.ffxivbis.messages.{AddPieceTo, AddPlayer, GetLoot, RemovePieceFrom}
|
||||||
import me.arcanis.ffxivbis.models._
|
import me.arcanis.ffxivbis.models._
|
||||||
import me.arcanis.ffxivbis.storage.Migration
|
|
||||||
import me.arcanis.ffxivbis.utils.Compare
|
import me.arcanis.ffxivbis.utils.Compare
|
||||||
import me.arcanis.ffxivbis.{Fixtures, Settings}
|
import me.arcanis.ffxivbis.{Fixtures, Settings}
|
||||||
import org.scalatest.wordspec.AnyWordSpecLike
|
import org.scalatest.wordspec.AnyWordSpecLike
|
||||||
@ -58,7 +58,7 @@ class DatabaseLootHandlerTest extends ScalaTestWithActorTestKit(Settings.withRan
|
|||||||
|
|
||||||
"remove loot" in {
|
"remove loot" in {
|
||||||
val updateProbe = testKit.createTestProbe[Unit]()
|
val updateProbe = testKit.createTestProbe[Unit]()
|
||||||
database ! RemovePieceFrom(Fixtures.playerEmpty.playerId, Fixtures.lootBody, updateProbe.ref)
|
database ! RemovePieceFrom(Fixtures.playerEmpty.playerId, Fixtures.lootBody, isFreeLoot = false, updateProbe.ref)
|
||||||
updateProbe.expectMessage(askTimeout, ())
|
updateProbe.expectMessage(askTimeout, ())
|
||||||
|
|
||||||
val newLoot = Fixtures.loot.filterNot(_ == Fixtures.lootBody)
|
val newLoot = Fixtures.loot.filterNot(_ == Fixtures.lootBody)
|
||||||
@ -87,10 +87,24 @@ class DatabaseLootHandlerTest extends ScalaTestWithActorTestKit(Settings.withRan
|
|||||||
partyLootCompare(party, Fixtures.loot ++ Fixtures.loot) shouldEqual true
|
partyLootCompare(party, Fixtures.loot ++ Fixtures.loot) shouldEqual true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"remove only one piece" in {
|
||||||
|
val updateProbe = testKit.createTestProbe[Unit]()
|
||||||
|
database ! RemovePieceFrom(Fixtures.playerEmpty.playerId, Fixtures.lootBody, isFreeLoot = false, updateProbe.ref)
|
||||||
|
updateProbe.expectMessage(askTimeout, ())
|
||||||
|
|
||||||
|
val probe = testKit.createTestProbe[Seq[Player]]()
|
||||||
|
database ! GetLoot(Fixtures.playerEmpty.partyId, None, probe.ref)
|
||||||
|
|
||||||
|
val party = probe.expectMessageType[Seq[Player]](askTimeout)
|
||||||
|
val player = party.filter(_.playerId == Fixtures.playerEmpty.playerId)
|
||||||
|
player should not be empty
|
||||||
|
player.flatMap(_.loot).map(_.piece) should contain (Fixtures.lootBody)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private def partyLootCompare[T](party: Seq[T], loot: Seq[Piece]): Boolean =
|
private def partyLootCompare(party: Seq[Player], loot: Seq[Piece]): Boolean =
|
||||||
Compare.seqEquals(party.foldLeft(Seq.empty[Piece]){ case (acc, player) =>
|
Compare.seqEquals(party.foldLeft(Seq.empty[Piece]){ case (acc, player) =>
|
||||||
acc ++ player.asInstanceOf[Player].loot.map(_.piece)
|
acc ++ player.loot.map(_.piece)
|
||||||
}, loot)
|
}, loot)
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package me.arcanis.ffxivbis.service.database
|
|||||||
import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit
|
import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit
|
||||||
import me.arcanis.ffxivbis.messages.{AddPlayer, GetParty, GetPlayer, RemovePlayer}
|
import me.arcanis.ffxivbis.messages.{AddPlayer, GetParty, GetPlayer, RemovePlayer}
|
||||||
import me.arcanis.ffxivbis.models._
|
import me.arcanis.ffxivbis.models._
|
||||||
import me.arcanis.ffxivbis.storage.Migration
|
|
||||||
import me.arcanis.ffxivbis.utils.Compare
|
import me.arcanis.ffxivbis.utils.Compare
|
||||||
import me.arcanis.ffxivbis.{Fixtures, Settings}
|
import me.arcanis.ffxivbis.{Fixtures, Settings}
|
||||||
import org.scalatest.wordspec.AnyWordSpecLike
|
import org.scalatest.wordspec.AnyWordSpecLike
|
||||||
|
@ -3,7 +3,6 @@ package me.arcanis.ffxivbis.service.database
|
|||||||
import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit
|
import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit
|
||||||
import me.arcanis.ffxivbis.messages.{AddUser, DeleteUser, GetUser, GetUsers}
|
import me.arcanis.ffxivbis.messages.{AddUser, DeleteUser, GetUser, GetUsers}
|
||||||
import me.arcanis.ffxivbis.models.User
|
import me.arcanis.ffxivbis.models.User
|
||||||
import me.arcanis.ffxivbis.storage.Migration
|
|
||||||
import me.arcanis.ffxivbis.utils.Compare
|
import me.arcanis.ffxivbis.utils.Compare
|
||||||
import me.arcanis.ffxivbis.{Fixtures, Settings}
|
import me.arcanis.ffxivbis.{Fixtures, Settings}
|
||||||
import org.scalatest.wordspec.AnyWordSpecLike
|
import org.scalatest.wordspec.AnyWordSpecLike
|
||||||
|
Loading…
Reference in New Issue
Block a user