mirror of
https://github.com/arcan1s/ffxivbis.git
synced 2025-04-25 09:47:18 +00:00
multi item support
This commit is contained in:
parent
25b05aa289
commit
2e16a8c1fa
@ -0,0 +1,2 @@
|
|||||||
|
drop index bis_piece_player_id_idx;
|
||||||
|
create index bis_piece_type_player_id_idx on bis(player_id, piece, piece_type);
|
@ -0,0 +1,2 @@
|
|||||||
|
drop index bis_piece_player_id_idx;
|
||||||
|
create index bis_piece_type_player_id_idx on bis(player_id, piece, piece_type);
|
@ -37,10 +37,13 @@ trait BiSHelper extends BisProviderHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def putBiS(playerId: PlayerId, link: String)
|
def putBiS(playerId: PlayerId, link: String)
|
||||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] =
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = {
|
||||||
downloadBiS(link, playerId.job).flatMap { bis =>
|
(storage ? DatabaseBiSHandler.RemovePiecesFromBiS(playerId)).flatMap { _ =>
|
||||||
Future.traverse(bis.pieces)(addPieceBiS(playerId, _))
|
downloadBiS(link, playerId.job).flatMap { bis =>
|
||||||
}.map(_ => ())
|
Future.traverse(bis.pieces)(addPieceBiS(playerId, _))
|
||||||
|
}.map(_ => ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def removePieceBiS(playerId: PlayerId, piece: Piece)
|
def removePieceBiS(playerId: PlayerId, piece: Piece)
|
||||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
||||||
|
@ -21,13 +21,15 @@ case class PlayerResponse(
|
|||||||
@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)), loot.getOrElse(Seq.empty).map(_.toLoot),
|
BiS(bis.getOrElse(Seq.empty).map(_.toPiece)),
|
||||||
|
loot.getOrElse(Seq.empty).map(_.toLoot),
|
||||||
link, priority.getOrElse(0))
|
link, priority.getOrElse(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
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.loot.map(LootResponse.fromLoot)),
|
Some(player.bis.pieces.map(PieceResponse.fromPiece)),
|
||||||
|
Some(player.loot.map(LootResponse.fromLoot)),
|
||||||
player.link, Some(player.priority))
|
player.link, Some(player.priority))
|
||||||
}
|
}
|
||||||
|
@ -113,7 +113,7 @@ object BiSView {
|
|||||||
input(name:="add", id:="add", `type`:="submit", value:="add")
|
input(name:="add", id:="add", `type`:="submit", value:="add")
|
||||||
),
|
),
|
||||||
|
|
||||||
form(action:="/bis", method:="post")(
|
form(action:=s"/party/$partyId/bis", method:="post")(
|
||||||
select(name:="player", id:="player", title:="player")
|
select(name:="player", id:="player", title:="player")
|
||||||
(for (player <- party) yield option(player.playerId.toString)),
|
(for (player <- party) yield option(player.playerId.toString)),
|
||||||
input(name:="link", id:="link", placeholder:="player bis link", title:="link", `type`:="text"),
|
input(name:="link", id:="link", placeholder:="player bis link", title:="link", `type`:="text"),
|
||||||
|
@ -47,8 +47,8 @@ class PlayerView(override val storage: ActorRef, override val ariyala: ActorRef)
|
|||||||
post {
|
post {
|
||||||
formFields("nick".as[String], "job".as[String], "priority".as[Int].?, "link".as[String].?, "action".as[String]) {
|
formFields("nick".as[String], "job".as[String], "priority".as[Int].?, "link".as[String].?, "action".as[String]) {
|
||||||
(nick, job, maybePriority, maybeLink, action) =>
|
(nick, job, maybePriority, maybeLink, action) =>
|
||||||
onComplete(modifyPartyCall(partyId, nick, job, maybePriority, maybeLink, action)) {
|
onComplete(modifyPartyCall(partyId, nick, job, maybePriority, maybeLink, action)) { _ =>
|
||||||
case _ => redirect(s"/party/$partyId/players", StatusCodes.Found)
|
redirect(s"/party/$partyId/players", StatusCodes.Found)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,7 +62,7 @@ class PlayerView(override val storage: ActorRef, override val ariyala: ActorRef)
|
|||||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = {
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = {
|
||||||
def maybePlayerId = PlayerId(partyId, Some(nick), Some(job))
|
def maybePlayerId = PlayerId(partyId, Some(nick), Some(job))
|
||||||
def player(playerId: PlayerId) =
|
def player(playerId: PlayerId) =
|
||||||
Player(-1, partyId, playerId.job, playerId.nick, BiS(), Seq.empty, maybeLink, maybePriority.getOrElse(0))
|
Player(-1, partyId, playerId.job, playerId.nick, BiS.empty, Seq.empty, maybeLink, maybePriority.getOrElse(0))
|
||||||
|
|
||||||
(action, maybePlayerId) match {
|
(action, maybePlayerId) match {
|
||||||
case ("add", Some(playerId)) => addPlayer(player(playerId)).map(_ => ())
|
case ("add", Some(playerId)) => addPlayer(player(playerId)).map(_ => ())
|
||||||
|
@ -8,21 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
package me.arcanis.ffxivbis.models
|
package me.arcanis.ffxivbis.models
|
||||||
|
|
||||||
case class BiS(weapon: Option[Piece],
|
case class BiS(pieces: Seq[Piece]) {
|
||||||
head: Option[Piece],
|
|
||||||
body: Option[Piece],
|
|
||||||
hands: Option[Piece],
|
|
||||||
waist: Option[Piece],
|
|
||||||
legs: Option[Piece],
|
|
||||||
feet: Option[Piece],
|
|
||||||
ears: Option[Piece],
|
|
||||||
neck: Option[Piece],
|
|
||||||
wrist: Option[Piece],
|
|
||||||
leftRing: Option[Piece],
|
|
||||||
rightRing: Option[Piece]) {
|
|
||||||
|
|
||||||
val pieces: Seq[Piece] =
|
|
||||||
Seq(weapon, head, body, hands, waist, legs, feet, ears, neck, wrist, leftRing, rightRing).flatten
|
|
||||||
|
|
||||||
def hasPiece(piece: Piece): Boolean = piece match {
|
def hasPiece(piece: Piece): Boolean = piece match {
|
||||||
case upgrade: PieceUpgrade => upgrades.contains(upgrade)
|
case upgrade: PieceUpgrade => upgrades.contains(upgrade)
|
||||||
@ -31,50 +17,27 @@ case class BiS(weapon: Option[Piece],
|
|||||||
|
|
||||||
def upgrades: Map[PieceUpgrade, Int] =
|
def upgrades: Map[PieceUpgrade, Int] =
|
||||||
pieces.groupBy(_.upgrade).foldLeft(Map.empty[PieceUpgrade, Int]) {
|
pieces.groupBy(_.upgrade).foldLeft(Map.empty[PieceUpgrade, Int]) {
|
||||||
case (acc, (Some(k), v)) => acc + (k -> v.length)
|
case (acc, (Some(k), v)) => acc + (k -> v.size)
|
||||||
case (acc, _) => acc
|
case (acc, _) => acc
|
||||||
} withDefaultValue 0
|
} withDefaultValue 0
|
||||||
|
|
||||||
def withPiece(piece: Piece): BiS = copyWithPiece(piece.piece, Some(piece))
|
def withPiece(piece: Piece): BiS = copy(pieces :+ piece)
|
||||||
def withoutPiece(piece: Piece): BiS = copyWithPiece(piece.piece, None)
|
def withoutPiece(piece: Piece): BiS = copy(pieces.filterNot(_.strictEqual(piece)))
|
||||||
|
|
||||||
private def copyWithPiece(name: String, piece: Option[Piece]): BiS = {
|
override def equals(obj: Any): Boolean = {
|
||||||
val params = Map(
|
def comparePieces(left: Seq[Piece], right: Seq[Piece]): Boolean =
|
||||||
"weapon" -> weapon,
|
left.groupBy(identity).view.mapValues(_.size).forall {
|
||||||
"head" -> head,
|
case (key, count) => right.count(_.strictEqual(key)) == count
|
||||||
"body" -> body,
|
}
|
||||||
"hands" -> hands,
|
|
||||||
"waist" -> waist,
|
obj match {
|
||||||
"legs" -> legs,
|
case left: BiS => comparePieces(left.pieces, pieces)
|
||||||
"feet" -> feet,
|
case _ => false
|
||||||
"ears" -> ears,
|
}
|
||||||
"neck" -> neck,
|
|
||||||
"wrist" -> wrist,
|
|
||||||
"left ring" -> leftRing,
|
|
||||||
"right ring" -> rightRing
|
|
||||||
) + (name -> piece)
|
|
||||||
BiS(params)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object BiS {
|
object BiS {
|
||||||
def apply(data: Map[String, Option[Piece]]): BiS =
|
|
||||||
BiS(
|
|
||||||
data.get("weapon").flatten,
|
|
||||||
data.get("head").flatten,
|
|
||||||
data.get("body").flatten,
|
|
||||||
data.get("hands").flatten,
|
|
||||||
data.get("waist").flatten,
|
|
||||||
data.get("legs").flatten,
|
|
||||||
data.get("feet").flatten,
|
|
||||||
data.get("ears").flatten,
|
|
||||||
data.get("neck").flatten,
|
|
||||||
data.get("wrist").flatten,
|
|
||||||
data.get("left ring").flatten,
|
|
||||||
data.get("right ring").flatten)
|
|
||||||
|
|
||||||
def apply(): BiS = BiS(Seq.empty)
|
def empty: BiS = BiS(Seq.empty)
|
||||||
|
|
||||||
def apply(pieces: Seq[Piece]): BiS =
|
|
||||||
BiS(pieces.map(piece => piece.piece -> Some(piece)).toMap)
|
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
package me.arcanis.ffxivbis.models
|
package me.arcanis.ffxivbis.models
|
||||||
|
|
||||||
object Job {
|
object Job {
|
||||||
|
|
||||||
sealed trait RightSide
|
sealed trait RightSide
|
||||||
object AccessoriesDex extends RightSide
|
object AccessoriesDex extends RightSide
|
||||||
object AccessoriesInt extends RightSide
|
object AccessoriesInt extends RightSide
|
||||||
@ -26,6 +27,7 @@ object Job {
|
|||||||
object BodyRanges extends LeftSide
|
object BodyRanges extends LeftSide
|
||||||
|
|
||||||
sealed trait Job {
|
sealed trait Job {
|
||||||
|
|
||||||
def leftSide: LeftSide
|
def leftSide: LeftSide
|
||||||
def rightSide: RightSide
|
def rightSide: RightSide
|
||||||
|
|
||||||
|
@ -11,5 +11,6 @@ package me.arcanis.ffxivbis.models
|
|||||||
import java.time.Instant
|
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"
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ case class Party(partyDescription: PartyDescription, rules: Seq[String], players
|
|||||||
}
|
}
|
||||||
|
|
||||||
object Party {
|
object Party {
|
||||||
|
|
||||||
def apply(party: PartyDescription, config: Config,
|
def apply(party: PartyDescription, config: Config,
|
||||||
players: Map[Long, Player], bis: Seq[Loot], loot: Seq[Loot]): Party = {
|
players: Map[Long, Player], bis: Seq[Loot], loot: Seq[Loot]): Party = {
|
||||||
val bisByPlayer = bis.groupBy(_.playerId).view.mapValues(piece => BiS(piece.map(_.piece)))
|
val bisByPlayer = bis.groupBy(_.playerId).view.mapValues(piece => BiS(piece.map(_.piece)))
|
||||||
|
@ -9,9 +9,11 @@
|
|||||||
package me.arcanis.ffxivbis.models
|
package me.arcanis.ffxivbis.models
|
||||||
|
|
||||||
case class PartyDescription(partyId: String, partyAlias: Option[String]) {
|
case class PartyDescription(partyId: String, partyAlias: Option[String]) {
|
||||||
|
|
||||||
def alias: String = partyAlias.getOrElse(partyId)
|
def alias: String = partyAlias.getOrElse(partyId)
|
||||||
}
|
}
|
||||||
|
|
||||||
object PartyDescription {
|
object PartyDescription {
|
||||||
|
|
||||||
def empty(partyId: String): PartyDescription = PartyDescription(partyId, None)
|
def empty(partyId: String): PartyDescription = PartyDescription(partyId, None)
|
||||||
}
|
}
|
@ -8,7 +8,8 @@
|
|||||||
*/
|
*/
|
||||||
package me.arcanis.ffxivbis.models
|
package me.arcanis.ffxivbis.models
|
||||||
|
|
||||||
sealed trait Piece {
|
sealed trait Piece extends Equals {
|
||||||
|
|
||||||
def pieceType: PieceType.PieceType
|
def pieceType: PieceType.PieceType
|
||||||
def job: Job.Job
|
def job: Job.Job
|
||||||
def piece: String
|
def piece: String
|
||||||
@ -22,6 +23,9 @@ sealed trait Piece {
|
|||||||
case _: PieceBody => Some(BodyUpgrade)
|
case _: PieceBody => Some(BodyUpgrade)
|
||||||
case _: PieceWeapon => Some(WeaponUpgrade)
|
case _: PieceWeapon => Some(WeaponUpgrade)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// used for ring comparison
|
||||||
|
def strictEqual(obj: Any): Boolean = equals(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
trait PieceAccessory extends Piece
|
trait PieceAccessory extends Piece
|
||||||
@ -78,10 +82,16 @@ case class Wrist(override val pieceType: PieceType.PieceType, override val job:
|
|||||||
case class Ring(override val pieceType: PieceType.PieceType, override val job: Job.Job, override val piece: String = "ring")
|
case class Ring(override val pieceType: PieceType.PieceType, override val job: Job.Job, override val piece: String = "ring")
|
||||||
extends PieceAccessory {
|
extends PieceAccessory {
|
||||||
def withJob(other: Job.Job): Piece = copy(job = other)
|
def withJob(other: Job.Job): Piece = copy(job = other)
|
||||||
|
|
||||||
override def equals(obj: Any): Boolean = obj match {
|
override def equals(obj: Any): Boolean = obj match {
|
||||||
case Ring(thatPieceType, thatJob, _) => (thatPieceType == pieceType) && (thatJob == job)
|
case Ring(thatPieceType, thatJob, _) => (thatPieceType == pieceType) && (thatJob == job)
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def strictEqual(obj: Any): Boolean = obj match {
|
||||||
|
case ring: Ring => equals(obj) && (ring.piece == this.piece)
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case object AccessoryUpgrade extends PieceUpgrade {
|
case object AccessoryUpgrade extends PieceUpgrade {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package me.arcanis.ffxivbis.models
|
package me.arcanis.ffxivbis.models
|
||||||
|
|
||||||
object PieceType {
|
object PieceType {
|
||||||
|
|
||||||
sealed trait PieceType
|
sealed trait PieceType
|
||||||
|
|
||||||
case object Crafted extends PieceType
|
case object Crafted extends PieceType
|
||||||
|
@ -12,6 +12,7 @@ import scala.util.Try
|
|||||||
import scala.util.matching.Regex
|
import scala.util.matching.Regex
|
||||||
|
|
||||||
trait PlayerIdBase {
|
trait PlayerIdBase {
|
||||||
|
|
||||||
def job: Job.Job
|
def job: Job.Job
|
||||||
def nick: String
|
def nick: String
|
||||||
|
|
||||||
@ -21,6 +22,7 @@ trait PlayerIdBase {
|
|||||||
case class PlayerId(partyId: String, job: Job.Job, nick: String) extends PlayerIdBase
|
case class PlayerId(partyId: String, job: Job.Job, nick: String) extends PlayerIdBase
|
||||||
|
|
||||||
object PlayerId {
|
object PlayerId {
|
||||||
|
|
||||||
def apply(partyId: String, maybeNick: Option[String], maybeJob: Option[String]): Option[PlayerId] =
|
def apply(partyId: String, maybeNick: Option[String], maybeJob: Option[String]): Option[PlayerId] =
|
||||||
(maybeNick, maybeJob) match {
|
(maybeNick, maybeJob) match {
|
||||||
case (Some(nick), Some(job)) => Try(PlayerId(partyId, Job.withName(job), nick)).toOption
|
case (Some(nick), Some(job)) => Try(PlayerId(partyId, Job.withName(job), nick)).toOption
|
||||||
|
@ -38,6 +38,7 @@ case class PlayerIdWithCounters(partyId: String,
|
|||||||
}
|
}
|
||||||
|
|
||||||
object PlayerIdWithCounters {
|
object PlayerIdWithCounters {
|
||||||
|
|
||||||
private case class PlayerCountersComparator(values: Int*) {
|
private case class PlayerCountersComparator(values: Int*) {
|
||||||
def >(that: PlayerCountersComparator): Boolean = {
|
def >(that: PlayerCountersComparator): Boolean = {
|
||||||
@scala.annotation.tailrec
|
@scala.annotation.tailrec
|
||||||
|
@ -29,6 +29,10 @@ trait DatabaseBiSHandler { this: Database =>
|
|||||||
case RemovePieceFromBiS(playerId, piece) =>
|
case RemovePieceFromBiS(playerId, piece) =>
|
||||||
val client = sender()
|
val client = sender()
|
||||||
profile.deletePieceBiS(playerId, piece).pipeTo(client)
|
profile.deletePieceBiS(playerId, piece).pipeTo(client)
|
||||||
|
|
||||||
|
case RemovePiecesFromBiS(playerId) =>
|
||||||
|
val client = sender()
|
||||||
|
profile.deletePiecesBiS(playerId).pipeTo(client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,4 +44,7 @@ object DatabaseBiSHandler {
|
|||||||
case class RemovePieceFromBiS(playerId: PlayerId, piece: Piece) extends Database.DatabaseRequest {
|
case class RemovePieceFromBiS(playerId: PlayerId, piece: Piece) extends Database.DatabaseRequest {
|
||||||
override def partyId: String = playerId.partyId
|
override def partyId: String = playerId.partyId
|
||||||
}
|
}
|
||||||
|
case class RemovePiecesFromBiS(playerId: PlayerId) extends Database.DatabaseRequest {
|
||||||
|
override def partyId: String = playerId.partyId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,14 +46,21 @@ trait BiSProfile { this: DatabaseProfile =>
|
|||||||
|
|
||||||
def deletePieceBiSById(piece: Piece)(playerId: Long): Future[Int] =
|
def deletePieceBiSById(piece: Piece)(playerId: Long): Future[Int] =
|
||||||
db.run(pieceBiS(BiSRep.fromPiece(playerId, piece)).delete)
|
db.run(pieceBiS(BiSRep.fromPiece(playerId, piece)).delete)
|
||||||
|
def deletePiecesBiSById(playerId: Long): Future[Int] =
|
||||||
|
db.run(piecesBiS(Seq(playerId)).delete)
|
||||||
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))
|
db.run(piecesBiS(playerIds).result).map(_.map(_.toLoot))
|
||||||
def insertPieceBiSById(piece: Piece)(playerId: Long): Future[Int] =
|
def insertPieceBiSById(piece: Piece)(playerId: Long): Future[Int] =
|
||||||
db.run(bisTable.insertOrUpdate(BiSRep.fromPiece(playerId, piece)))
|
getPiecesBiSById(playerId).flatMap {
|
||||||
|
case pieces if pieces.exists(loot => loot.piece.strictEqual(piece)) => Future.successful(0)
|
||||||
|
case _ => db.run(bisTable.insertOrUpdate(BiSRep.fromPiece(playerId, piece)))
|
||||||
|
}
|
||||||
|
|
||||||
private def pieceBiS(piece: BiSRep) =
|
private def pieceBiS(piece: BiSRep) =
|
||||||
piecesBiS(Seq(piece.playerId)).filter(_.piece === piece.piece)
|
piecesBiS(Seq(piece.playerId)).filter { stored =>
|
||||||
|
(stored.piece === piece.piece) && (stored.pieceType === piece.pieceType)
|
||||||
|
}
|
||||||
private def piecesBiS(playerIds: Seq[Long]) =
|
private def piecesBiS(playerIds: Seq[Long]) =
|
||||||
bisTable.filter(_.playerId.inSet(playerIds.toSet))
|
bisTable.filter(_.playerId.inSet(playerIds.toSet))
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,8 @@ class DatabaseProfile(context: ExecutionContext, config: Config)
|
|||||||
// generic bis api
|
// generic bis api
|
||||||
def deletePieceBiS(playerId: PlayerId, piece: Piece): Future[Int] =
|
def deletePieceBiS(playerId: PlayerId, piece: Piece): Future[Int] =
|
||||||
byPlayerId(playerId, deletePieceBiSById(piece))
|
byPlayerId(playerId, deletePieceBiSById(piece))
|
||||||
|
def deletePiecesBiS(playerId: PlayerId): Future[Int] =
|
||||||
|
byPlayerId(playerId, deletePiecesBiSById)
|
||||||
def getPiecesBiS(playerId: PlayerId): Future[Seq[Loot]] =
|
def getPiecesBiS(playerId: PlayerId): Future[Seq[Loot]] =
|
||||||
byPlayerId(playerId, getPiecesBiSById)
|
byPlayerId(playerId, getPiecesBiSById)
|
||||||
def getPiecesBiS(partyId: String): Future[Seq[Loot]] =
|
def getPiecesBiS(partyId: String): Future[Seq[Loot]] =
|
||||||
@ -57,7 +59,7 @@ 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).map(callback).flatten
|
getPlayers(partyId).flatMap(callback)
|
||||||
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)
|
||||||
|
@ -19,7 +19,7 @@ trait PlayersProfile { this: DatabaseProfile =>
|
|||||||
nick: String, job: String, link: Option[String], priority: Int) {
|
nick: String, job: String, link: Option[String], priority: Int) {
|
||||||
def toPlayer: Player =
|
def toPlayer: Player =
|
||||||
Player(playerId.getOrElse(-1), partyId, Job.withName(job), nick,
|
Player(playerId.getOrElse(-1), partyId, Job.withName(job), nick,
|
||||||
BiS(Seq.empty), List.empty, link, priority)
|
BiS.empty, Seq.empty, link, priority)
|
||||||
}
|
}
|
||||||
object PlayerRep {
|
object PlayerRep {
|
||||||
def fromPlayer(player: Player, id: Option[Long]): PlayerRep =
|
def fromPlayer(player: Player, id: Option[Long]): PlayerRep =
|
||||||
|
@ -26,6 +26,7 @@ object Fixtures {
|
|||||||
|
|
||||||
lazy val lootWeapon: Piece = Weapon(pieceType = PieceType.Tome, Job.AnyJob)
|
lazy val lootWeapon: Piece = Weapon(pieceType = PieceType.Tome, Job.AnyJob)
|
||||||
lazy val lootBody: Piece = Body(pieceType = PieceType.Savage, Job.AnyJob)
|
lazy val lootBody: Piece = Body(pieceType = PieceType.Savage, Job.AnyJob)
|
||||||
|
lazy val lootBodyCrafted: Piece = Body(pieceType = PieceType.Crafted, Job.AnyJob)
|
||||||
lazy val lootHands: Piece = Hands(pieceType = PieceType.Tome, Job.AnyJob)
|
lazy val lootHands: Piece = Hands(pieceType = PieceType.Tome, Job.AnyJob)
|
||||||
lazy val lootWaist: Piece = Waist(pieceType = PieceType.Tome, Job.AnyJob)
|
lazy val lootWaist: Piece = Waist(pieceType = PieceType.Tome, Job.AnyJob)
|
||||||
lazy val lootLegs: Piece = Legs(pieceType = PieceType.Savage, Job.AnyJob)
|
lazy val lootLegs: Piece = Legs(pieceType = PieceType.Savage, Job.AnyJob)
|
||||||
@ -40,7 +41,7 @@ object Fixtures {
|
|||||||
lazy val partyId2: String = Party.randomPartyId
|
lazy val partyId2: String = Party.randomPartyId
|
||||||
|
|
||||||
lazy val playerEmpty: Player =
|
lazy val playerEmpty: Player =
|
||||||
Player(1, partyId, Job.DNC, "Siuan Sanche", BiS(), Seq.empty, Some(link))
|
Player(1, partyId, Job.DNC, "Siuan Sanche", BiS.empty, Seq.empty, Some(link))
|
||||||
lazy val playerWithBiS: Player = playerEmpty.copy(bis = bis)
|
lazy val playerWithBiS: Player = playerEmpty.copy(bis = bis)
|
||||||
|
|
||||||
lazy val userPassword: String = "password"
|
lazy val userPassword: String = "password"
|
||||||
|
@ -10,10 +10,11 @@ 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
|
import me.arcanis.ffxivbis.models.{BiS, Body, Job, PieceType}
|
||||||
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.{PartyService, impl}
|
||||||
import me.arcanis.ffxivbis.storage.Migration
|
import me.arcanis.ffxivbis.storage.Migration
|
||||||
|
import me.arcanis.ffxivbis.utils.Compare
|
||||||
import org.scalatest.{Matchers, WordSpec}
|
import org.scalatest.{Matchers, WordSpec}
|
||||||
|
|
||||||
import scala.concurrent.Await
|
import scala.concurrent.Await
|
||||||
@ -48,6 +49,15 @@ class BiSEndpointTest extends WordSpec
|
|||||||
Settings.clearDatabase(system.settings.config)
|
Settings.clearDatabase(system.settings.config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def compareBiSResponse(actual: PlayerResponse, expected: PlayerResponse): Unit = {
|
||||||
|
actual.partyId shouldEqual expected.partyId
|
||||||
|
actual.nick shouldEqual expected.nick
|
||||||
|
actual.job shouldEqual expected.job
|
||||||
|
Compare.seqEquals(actual.bis.get, expected.bis.get) shouldEqual true
|
||||||
|
actual.link shouldEqual expected.link
|
||||||
|
actual.priority shouldEqual expected.priority
|
||||||
|
}
|
||||||
|
|
||||||
"api v1 bis endpoint" must {
|
"api v1 bis endpoint" must {
|
||||||
|
|
||||||
"create best in slot set from ariyala" in {
|
"create best in slot set from ariyala" in {
|
||||||
@ -61,11 +71,13 @@ class BiSEndpointTest extends WordSpec
|
|||||||
|
|
||||||
"return best in slot set" in {
|
"return best in slot set" in {
|
||||||
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
|
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
|
||||||
val response = Seq(PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis))))
|
val response = PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))
|
||||||
|
|
||||||
Get(uri).withHeaders(auth) ~> route ~> check {
|
Get(uri).withHeaders(auth) ~> route ~> check {
|
||||||
status shouldEqual StatusCodes.OK
|
status shouldEqual StatusCodes.OK
|
||||||
responseAs[Seq[PlayerResponse]] shouldEqual response
|
val actual = responseAs[Seq[PlayerResponse]]
|
||||||
|
actual.length shouldEqual 1
|
||||||
|
actual.foreach(compareBiSResponse(_, response))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,11 +92,13 @@ class BiSEndpointTest extends WordSpec
|
|||||||
|
|
||||||
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
|
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
|
||||||
val bis = BiS(Fixtures.bis.pieces.filterNot(_ == Fixtures.lootBody))
|
val bis = BiS(Fixtures.bis.pieces.filterNot(_ == Fixtures.lootBody))
|
||||||
val response = Seq(PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(bis))))
|
val response = PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(bis)))
|
||||||
|
|
||||||
Get(uri).withHeaders(auth) ~> route ~> check {
|
Get(uri).withHeaders(auth) ~> route ~> check {
|
||||||
status shouldEqual StatusCodes.OK
|
status shouldEqual StatusCodes.OK
|
||||||
responseAs[Seq[PlayerResponse]] shouldEqual response
|
val actual = responseAs[Seq[PlayerResponse]]
|
||||||
|
actual.length shouldEqual 1
|
||||||
|
actual.foreach(compareBiSResponse(_, response))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,11 +112,102 @@ class BiSEndpointTest extends WordSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
|
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
|
||||||
val response = Seq(PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis))))
|
val response = PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))
|
||||||
|
|
||||||
Get(uri).withHeaders(auth) ~> route ~> check {
|
Get(uri).withHeaders(auth) ~> route ~> check {
|
||||||
status shouldEqual StatusCodes.OK
|
status shouldEqual StatusCodes.OK
|
||||||
responseAs[Seq[PlayerResponse]] shouldEqual response
|
val actual = responseAs[Seq[PlayerResponse]]
|
||||||
|
actual.length shouldEqual 1
|
||||||
|
actual.foreach(compareBiSResponse(_, response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"do not allow to add same item to best in slot set" in {
|
||||||
|
val piece = PieceResponse.fromPiece(Fixtures.lootBody.withJob(Job.DNC))
|
||||||
|
val entity = PieceActionResponse(ApiAction.add, piece, playerId, None)
|
||||||
|
|
||||||
|
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
|
||||||
|
status shouldEqual StatusCodes.Accepted
|
||||||
|
responseAs[String] shouldEqual ""
|
||||||
|
}
|
||||||
|
|
||||||
|
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
|
||||||
|
val response = PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))
|
||||||
|
|
||||||
|
Get(uri).withHeaders(auth) ~> route ~> check {
|
||||||
|
status shouldEqual StatusCodes.OK
|
||||||
|
val actual = responseAs[Seq[PlayerResponse]]
|
||||||
|
actual.length shouldEqual 1
|
||||||
|
actual.foreach(compareBiSResponse(_, response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"allow to add item with another type to best in slot set" in {
|
||||||
|
val piece = PieceResponse.fromPiece(Fixtures.lootBodyCrafted.withJob(Job.DNC))
|
||||||
|
val entity = PieceActionResponse(ApiAction.add, piece, playerId, None)
|
||||||
|
|
||||||
|
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
|
||||||
|
status shouldEqual StatusCodes.Accepted
|
||||||
|
responseAs[String] shouldEqual ""
|
||||||
|
}
|
||||||
|
|
||||||
|
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
|
||||||
|
val bis = Fixtures.bis.withPiece(piece.toPiece)
|
||||||
|
val response = PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(bis)))
|
||||||
|
|
||||||
|
Get(uri).withHeaders(auth) ~> route ~> check {
|
||||||
|
status shouldEqual StatusCodes.OK
|
||||||
|
val actual = responseAs[Seq[PlayerResponse]]
|
||||||
|
actual.length shouldEqual 1
|
||||||
|
actual.foreach(compareBiSResponse(_, response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"remove only specific item from best in slot set" in {
|
||||||
|
val piece = PieceResponse.fromPiece(Fixtures.lootBodyCrafted.withJob(Job.DNC))
|
||||||
|
val entity = PieceActionResponse(ApiAction.remove, piece, playerId, None)
|
||||||
|
|
||||||
|
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
|
||||||
|
status shouldEqual StatusCodes.Accepted
|
||||||
|
responseAs[String] shouldEqual ""
|
||||||
|
}
|
||||||
|
|
||||||
|
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
|
||||||
|
val response = PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))
|
||||||
|
|
||||||
|
Get(uri).withHeaders(auth) ~> route ~> check {
|
||||||
|
status shouldEqual StatusCodes.OK
|
||||||
|
val actual = responseAs[Seq[PlayerResponse]]
|
||||||
|
actual.length shouldEqual 1
|
||||||
|
actual.foreach(compareBiSResponse(_, response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"totaly replace player bis" in {
|
||||||
|
// add random item first
|
||||||
|
val piece = PieceResponse.fromPiece(Fixtures.lootBodyCrafted.withJob(Job.DNC))
|
||||||
|
val entity = PieceActionResponse(ApiAction.add, piece, playerId, None)
|
||||||
|
|
||||||
|
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
|
||||||
|
status shouldEqual StatusCodes.Accepted
|
||||||
|
responseAs[String] shouldEqual ""
|
||||||
|
}
|
||||||
|
|
||||||
|
val bisEntity = PlayerBiSLinkResponse(Fixtures.link, playerId)
|
||||||
|
|
||||||
|
Put(endpoint, bisEntity).withHeaders(auth) ~> route ~> check {
|
||||||
|
status shouldEqual StatusCodes.Created
|
||||||
|
responseAs[String] shouldEqual ""
|
||||||
|
}
|
||||||
|
|
||||||
|
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
|
||||||
|
val response = PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))
|
||||||
|
|
||||||
|
Get(uri).withHeaders(auth) ~> route ~> check {
|
||||||
|
status shouldEqual StatusCodes.OK
|
||||||
|
val actual = responseAs[Seq[PlayerResponse]]
|
||||||
|
actual.length shouldEqual 1
|
||||||
|
actual.foreach(compareBiSResponse(_, response))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,18 +29,14 @@ class BiSTest extends WordSpecLike with Matchers with BeforeAndAfterAll {
|
|||||||
val bis = BiS(Seq(Fixtures.lootLegs))
|
val bis = BiS(Seq(Fixtures.lootLegs))
|
||||||
val newBis = bis.withPiece(Fixtures.lootHands)
|
val newBis = bis.withPiece(Fixtures.lootHands)
|
||||||
|
|
||||||
newBis.legs shouldEqual Some(Fixtures.lootLegs)
|
newBis shouldEqual BiS(Seq(Fixtures.lootLegs, Fixtures.lootHands))
|
||||||
newBis.hands shouldEqual Some(Fixtures.lootHands)
|
|
||||||
newBis.pieces.length shouldEqual 2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"create copy without piece" in {
|
"create copy without piece" in {
|
||||||
val bis = BiS(Seq(Fixtures.lootHands, Fixtures.lootLegs))
|
val bis = BiS(Seq(Fixtures.lootHands, Fixtures.lootLegs))
|
||||||
val newBis = bis.withoutPiece(Fixtures.lootHands)
|
val newBis = bis.withoutPiece(Fixtures.lootHands)
|
||||||
|
|
||||||
newBis.legs shouldEqual Some(Fixtures.lootLegs)
|
newBis shouldEqual BiS(Seq(Fixtures.lootLegs))
|
||||||
newBis.hands shouldEqual None
|
|
||||||
newBis.pieces.length shouldEqual 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"ignore upgrade on modification" in {
|
"ignore upgrade on modification" in {
|
||||||
|
@ -72,7 +72,7 @@ class DatabaseBiSHandlerTest
|
|||||||
|
|
||||||
database ! impl.DatabaseBiSHandler.GetBiS(Fixtures.playerEmpty.partyId, None)
|
database ! impl.DatabaseBiSHandler.GetBiS(Fixtures.playerEmpty.partyId, None)
|
||||||
expectMsgPF(timeout) {
|
expectMsgPF(timeout) {
|
||||||
case party: Seq[_] if partyBiSCompare(party, Seq(newPiece)) => ()
|
case party: Seq[_] if partyBiSCompare(party, Seq(Fixtures.lootHands, newPiece)) => ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,8 +18,8 @@ class LootSelectorTest extends TestKit(ActorSystem("lootselector"))
|
|||||||
import me.arcanis.ffxivbis.utils.Converters._
|
import me.arcanis.ffxivbis.utils.Converters._
|
||||||
|
|
||||||
private var default: Party = Party(PartyDescription.empty(Fixtures.partyId), Settings.config(Map.empty), Map.empty, Seq.empty, Seq.empty)
|
private var default: Party = Party(PartyDescription.empty(Fixtures.partyId), Settings.config(Map.empty), Map.empty, Seq.empty, Seq.empty)
|
||||||
private var dnc: Player = Player(-1, Fixtures.partyId, Job.DNC, "a nick", BiS(), Seq.empty, Some(Fixtures.link))
|
private var dnc: Player = Player(-1, Fixtures.partyId, Job.DNC, "a nick", BiS.empty, Seq.empty, Some(Fixtures.link))
|
||||||
private var drg: Player = Player(-1, Fixtures.partyId, Job.DRG, "another nick", BiS(), Seq.empty, Some(Fixtures.link2))
|
private var drg: Player = Player(-1, Fixtures.partyId, Job.DRG, "another nick", BiS.empty, Seq.empty, Some(Fixtures.link2))
|
||||||
private val timeout: FiniteDuration = 60 seconds
|
private val timeout: FiniteDuration = 60 seconds
|
||||||
|
|
||||||
override def beforeAll(): Unit = {
|
override def beforeAll(): Unit = {
|
||||||
@ -51,7 +51,7 @@ class LootSelectorTest extends TestKit(ActorSystem("lootselector"))
|
|||||||
"suggest upgrade" in {
|
"suggest upgrade" in {
|
||||||
val party = default.withPlayer(
|
val party = default.withPlayer(
|
||||||
dnc.withBiS(
|
dnc.withBiS(
|
||||||
Some(dnc.bis.copy(weapon = Some(Weapon(pieceType = PieceType.Tome, Job.DNC))))
|
Some(dnc.bis.withPiece(Weapon(pieceType = PieceType.Tome, Job.DNC)))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user