diff --git a/src/main/resources/db/migration/postgresql/V5_0__Free_loot.sql b/src/main/resources/db/migration/postgresql/V5_0__Free_loot.sql new file mode 100644 index 0000000..25a4375 --- /dev/null +++ b/src/main/resources/db/migration/postgresql/V5_0__Free_loot.sql @@ -0,0 +1 @@ +alter table loot add column is_free_loot integer not null default 0; \ No newline at end of file diff --git a/src/main/resources/db/migration/sqlite/V5_0__Free_loot.sql b/src/main/resources/db/migration/sqlite/V5_0__Free_loot.sql new file mode 100644 index 0000000..b6ee098 --- /dev/null +++ b/src/main/resources/db/migration/sqlite/V5_0__Free_loot.sql @@ -0,0 +1,20 @@ +alter table loot add column is_free_loot integer; + +update loot set is_free_loot = 0; + +create table loot_new ( + loot_id integer primary key autoincrement, + player_id integer not null, + created integer not null, + piece text not null, + piece_type text not null, + job text not null, + is_free_loot integer not null, + foreign key (player_id) references players(player_id) on delete cascade); +insert into loot_new select loot_id, player_id, created, piece, piece_type, job, is_free_loot from loot; + +drop index loot_owner_idx; +drop table loot; + +alter table loot_new rename to loot; +create index loot_owner_idx on loot(player_id); \ No newline at end of file diff --git a/src/main/scala/me/arcanis/ffxivbis/http/LootHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/LootHelper.scala index 43cb31c..2ff2604 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/LootHelper.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/LootHelper.scala @@ -22,15 +22,16 @@ trait LootHelper { def storage: ActorRef - def addPieceLoot(playerId: PlayerId, piece: Piece) + def addPieceLoot(playerId: PlayerId, piece: Piece, isFreeLoot: Boolean) (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = - (storage ? DatabaseLootHandler.AddPieceTo(playerId, piece)).mapTo[Int] + (storage ? DatabaseLootHandler.AddPieceTo(playerId, piece, isFreeLoot)).mapTo[Int] - def doModifyLoot(action: ApiAction.Value, playerId: PlayerId, piece: Piece) + def doModifyLoot(action: ApiAction.Value, playerId: PlayerId, piece: Piece, maybeFree: Option[Boolean]) (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = - action match { - case ApiAction.add => addPieceLoot(playerId, piece) - case ApiAction.remove => removePieceLoot(playerId, piece) + (action, maybeFree) match { + case (ApiAction.add, Some(isFreeLoot)) => addPieceLoot(playerId, piece, isFreeLoot) + case (ApiAction.remove, _) => removePieceLoot(playerId, piece) + case _ => throw new IllegalArgumentException(s"Invalid combinantion of action $action and fee loot $maybeFree") } def loot(partyId: String, playerId: Option[PlayerId]) diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpoint.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpoint.scala index 66e5ef7..252f4e7 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpoint.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpoint.scala @@ -103,7 +103,7 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) post { entity(as[PieceActionResponse]) { action => val playerId = action.playerId.withPartyId(partyId) - onComplete(doModifyLoot(action.action, playerId, action.piece.toPiece)) { + onComplete(doModifyLoot(action.action, playerId, action.piece.toPiece, action.isFreeLoot)) { case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty) case Failure(exception) => throw exception } diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/JsonSupport.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/JsonSupport.scala index 27fd75f..9f74d9f 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/JsonSupport.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/JsonSupport.scala @@ -41,12 +41,12 @@ trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol { implicit val errorFormat: RootJsonFormat[ErrorResponse] = jsonFormat1(ErrorResponse.apply) implicit val partyIdFormat: RootJsonFormat[PartyIdResponse] = jsonFormat1(PartyIdResponse.apply) implicit val pieceFormat: RootJsonFormat[PieceResponse] = jsonFormat3(PieceResponse.apply) - implicit val lootFormat: RootJsonFormat[LootResponse] = jsonFormat2(LootResponse.apply) + implicit val lootFormat: RootJsonFormat[LootResponse] = jsonFormat3(LootResponse.apply) implicit val partyDescriptionFormat: RootJsonFormat[PartyDescriptionResponse] = jsonFormat2(PartyDescriptionResponse.apply) implicit val playerFormat: RootJsonFormat[PlayerResponse] = jsonFormat7(PlayerResponse.apply) implicit val playerActionFormat: RootJsonFormat[PlayerActionResponse] = jsonFormat2(PlayerActionResponse.apply) implicit val playerIdFormat: RootJsonFormat[PlayerIdResponse] = jsonFormat3(PlayerIdResponse.apply) - implicit val pieceActionFormat: RootJsonFormat[PieceActionResponse] = jsonFormat3(PieceActionResponse.apply) + implicit val pieceActionFormat: RootJsonFormat[PieceActionResponse] = jsonFormat4(PieceActionResponse.apply) implicit val playerBiSLinkFormat: RootJsonFormat[PlayerBiSLinkResponse] = jsonFormat2(PlayerBiSLinkResponse.apply) implicit val playerIdWithCountersFormat: RootJsonFormat[PlayerIdWithCountersResponse] = jsonFormat9(PlayerIdWithCountersResponse.apply) diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/LootResponse.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/LootResponse.scala index 4469105..2a465f3 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/LootResponse.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/LootResponse.scala @@ -7,11 +7,12 @@ import me.arcanis.ffxivbis.models.Loot case class LootResponse( @Schema(description = "looted piece", required = true) piece: PieceResponse, - @Schema(description = "loot timestamp", required = true) timestamp: Instant) { - def toLoot: Loot = Loot(-1, piece.toPiece, timestamp) + @Schema(description = "loot timestamp", required = true) timestamp: Instant, + @Schema(description = "is loot free for all", required = true) isFreeLoot: Boolean) { + def toLoot: Loot = Loot(-1, piece.toPiece, timestamp, isFreeLoot) } object LootResponse { def fromLoot(loot: Loot): LootResponse = - LootResponse(PieceResponse.fromPiece(loot.piece), loot.timestamp) + LootResponse(PieceResponse.fromPiece(loot.piece), loot.timestamp, loot.isFreeLoot) } \ No newline at end of file diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PieceActionResponse.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PieceActionResponse.scala index fe397e5..d81b563 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PieceActionResponse.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PieceActionResponse.scala @@ -13,4 +13,5 @@ import io.swagger.v3.oas.annotations.media.Schema case class PieceActionResponse( @Schema(description = "action to perform", required = true, `type` = "string", allowableValues = Array("add", "remove")) action: ApiAction.Value, @Schema(description = "piece description", required = true) piece: PieceResponse, - @Schema(description = "player description", required = true) playerId: PlayerIdResponse) + @Schema(description = "player description", required = true) playerId: PlayerIdResponse, + @Schema(description = "is piece free to roll or not") isFreeLoot: Option[Boolean]) diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/LootSuggestView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/LootSuggestView.scala index 52962e6..2b02da6 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/view/LootSuggestView.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/LootSuggestView.scala @@ -30,7 +30,7 @@ class LootSuggestView(override val storage: ActorRef)(implicit timeout: Timeout) authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => get { complete { - val text = LootSuggestView.template(partyId, Seq.empty, None, None) + val text = LootSuggestView.template(partyId, Seq.empty, None, false, None) (StatusCodes.OK, RootView.toHtml(text)) } } @@ -43,17 +43,20 @@ class LootSuggestView(override val storage: ActorRef)(implicit timeout: Timeout) extractExecutionContext { implicit executionContext => authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => post { - formFields("piece".as[String], "job".as[String], "piece_type".as[String]) { (piece, job, pieceType) => - val maybePiece = Try(Piece(piece, PieceType.withName(pieceType), Job.withName(job))).toOption + formFields("piece".as[String], "job".as[String], "piece_type".as[String], "free_loot".as[String].?) { + (piece, job, pieceType, maybeFreeLoot) => + import me.arcanis.ffxivbis.utils.Implicits._ - onComplete(suggestLootCall(partyId, maybePiece)) { - case Success(players) => - val text = LootSuggestView.template(partyId, players, maybePiece, None) - complete(StatusCodes.OK, RootView.toHtml(text)) - case Failure(exception) => - val text = LootSuggestView.template(partyId, Seq.empty, maybePiece, Some(exception.getMessage)) - complete(StatusCodes.OK, RootView.toHtml(text)) - } + val maybePiece = Try(Piece(piece, PieceType.withName(pieceType), Job.withName(job))).toOption + + onComplete(suggestLootCall(partyId, maybePiece)) { + case Success(players) => + val text = LootSuggestView.template(partyId, players, maybePiece, maybeFreeLoot, None) + complete(StatusCodes.OK, RootView.toHtml(text)) + case Failure(exception) => + val text = LootSuggestView.template(partyId, Seq.empty, None, false, Some(exception.getMessage)) + complete(StatusCodes.OK, RootView.toHtml(text)) + } } } } @@ -72,7 +75,8 @@ object LootSuggestView { import scalatags.Text.all._ import scalatags.Text.tags2.{title => titleTag} - def template(partyId: String, party: Seq[PlayerIdWithCounters], piece: Option[Piece], error: Option[String]): String = + def template(partyId: String, party: Seq[PlayerIdWithCounters], piece: Option[Piece], + isFreeLoot: Boolean, error: Option[String]): String = "" + html(lang:="en", head( @@ -93,6 +97,8 @@ object LootSuggestView { (for (job <- Job.availableWithAnyJob) yield option(job.toString)), select(name:="piece_type", id:="piece_type", title:="piece type") (for (pieceType <- PieceType.available) yield option(pieceType.toString)), + input(name:="free_loot", id:="free_loot", title:="is free loot", `type`:="checkbox"), + label(`for`:="free_loot")("is free loot"), input(name:="suggest", id:="suggest", `type`:="submit", value:="suggest") ), @@ -116,6 +122,7 @@ object LootSuggestView { input(name:="player", id:="player", `type`:="hidden", value:=player.playerId.toString), input(name:="piece", id:="piece", `type`:="hidden", value:=piece.map(_.piece).getOrElse("")), input(name:="piece_type", id:="piece_type", `type`:="hidden", value:=piece.map(_.pieceType.toString).getOrElse("")), + input(name:="free_loot", id:="free_loot", `type`:="hidden", value:=(if (isFreeLoot) "yes" else "no")), input(name:="action", id:="action", `type`:="hidden", value:="add"), input(name:="add", id:="add", `type`:="submit", value:="add") ) diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/LootView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/LootView.scala index cbecb7c..24f7202 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/view/LootView.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/LootView.scala @@ -46,9 +46,9 @@ class LootView (override val storage: ActorRef)(implicit timeout: Timeout) extractExecutionContext { implicit executionContext => authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => post { - formFields("player".as[String], "piece".as[String], "piece_type".as[String], "action".as[String]) { - (player, piece, pieceType, action) => - onComplete(modifyLootCall(partyId, player, piece, pieceType, action)) { _ => + formFields("player".as[String], "piece".as[String], "piece_type".as[String], "action".as[String], "free_loot".as[String].?) { + (player, piece, pieceType, action, isFreeLoot) => + onComplete(modifyLootCall(partyId, player, piece, pieceType, isFreeLoot, action)) { _ => redirect(s"/party/$partyId/loot", StatusCodes.Found) } } @@ -58,14 +58,17 @@ class LootView (override val storage: ActorRef)(implicit timeout: Timeout) } private def modifyLootCall(partyId: String, player: String, maybePiece: String, - maybePieceType: String, action: String) + maybePieceType: String, maybeFreeLoot: Option[String], + action: String) (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = { + import me.arcanis.ffxivbis.utils.Implicits._ + def getPiece(playerId: PlayerId) = Try(Piece(maybePiece, PieceType.withName(maybePieceType), playerId.job)).toOption PlayerId(partyId, player) match { case Some(playerId) => (getPiece(playerId), action) match { - case (Some(piece), "add") => addPieceLoot(playerId, piece).map(_ => ()) + case (Some(piece), "add") => addPieceLoot(playerId, piece, maybeFreeLoot).map(_ => ()) case (Some(piece), "remove") => removePieceLoot(playerId, piece).map(_ => ()) case _ => Future.failed(new Error(s"Could not construct piece from `$maybePiece ($maybePieceType)`")) } @@ -99,6 +102,8 @@ object LootView { (for (piece <- Piece.available) yield option(piece)), select(name:="piece_type", id:="piece_type", title:="piece type") (for (pieceType <- PieceType.available) yield option(pieceType.toString)), + input(name:="free_loot", id:="free_loot", title:="is free loot", `type`:="checkbox"), + label(`for`:="free_loot")("is free loot"), input(name:="action", id:="action", `type`:="hidden", value:="add"), input(name:="add", id:="add", `type`:="submit", value:="add") ), @@ -108,6 +113,7 @@ object LootView { th("player"), th("piece"), th("piece type"), + th("is free loot"), th("timestamp"), th("") ), @@ -115,12 +121,14 @@ object LootView { td(`class`:="include_search")(player.playerId.toString), td(`class`:="include_search")(loot.piece.piece), td(loot.piece.pieceType.toString), + td(loot.isFreeLootToString), td(loot.timestamp.toString), td( form(action:=s"/party/$partyId/loot", method:="post")( input(name:="player", id:="player", `type`:="hidden", value:=player.playerId.toString), input(name:="piece", id:="piece", `type`:="hidden", value:=loot.piece.piece), input(name:="piece_type", id:="piece_type", `type`:="hidden", value:=loot.piece.pieceType.toString), + input(name:="free_loot", id:="free_loot", `type`:="hidden", value:=loot.isFreeLootToString), input(name:="action", id:="action", `type`:="hidden", value:="remove"), input(name:="remove", id:="remove", `type`:="submit", value:="x") ) diff --git a/src/main/scala/me/arcanis/ffxivbis/models/Loot.scala b/src/main/scala/me/arcanis/ffxivbis/models/Loot.scala index 367d872..056a196 100644 --- a/src/main/scala/me/arcanis/ffxivbis/models/Loot.scala +++ b/src/main/scala/me/arcanis/ffxivbis/models/Loot.scala @@ -10,4 +10,6 @@ package me.arcanis.ffxivbis.models import java.time.Instant -case class Loot(playerId: Long, piece: Piece, timestamp: Instant) +case class Loot(playerId: Long, piece: Piece, timestamp: Instant, isFreeLoot: Boolean) { + def isFreeLootToString: String = if (isFreeLoot) "yes" else "no" +} diff --git a/src/main/scala/me/arcanis/ffxivbis/models/Player.scala b/src/main/scala/me/arcanis/ffxivbis/models/Player.scala index c31e916..0927db5 100644 --- a/src/main/scala/me/arcanis/ffxivbis/models/Player.scala +++ b/src/main/scala/me/arcanis/ffxivbis/models/Player.scala @@ -31,10 +31,7 @@ case class Player(id: Long, def withLoot(piece: Loot): Player = withLoot(Seq(piece)) def withLoot(list: Seq[Loot]): Player = { require(loot.forall(_.playerId == id), "player id must be same") - list match { - case Nil => this - case _ => copy(loot = list) - } + copy(loot = loot ++ list) } def isRequired(piece: Option[Piece]): Boolean = { @@ -48,10 +45,10 @@ case class Player(id: Long, def bisCountTotal(piece: Option[Piece]): Int = bis.pieces.count(_.pieceType == PieceType.Savage) def lootCount(piece: Option[Piece]): Int = piece match { - case Some(p) => loot.count(_.piece == p) + case Some(p) => loot.count(item => !item.isFreeLoot && item.piece == p) case None => lootCountTotal(piece) } def lootCountBiS(piece: Option[Piece]): Int = loot.map(_.piece).count(bis.hasPiece) - def lootCountTotal(piece: Option[Piece]): Int = loot.length + def lootCountTotal(piece: Option[Piece]): Int = loot.count(!_.isFreeLoot) def lootPriority(piece: Piece): Int = priority } diff --git a/src/main/scala/me/arcanis/ffxivbis/service/impl/DatabaseLootHandler.scala b/src/main/scala/me/arcanis/ffxivbis/service/impl/DatabaseLootHandler.scala index ae10f24..8251375 100644 --- a/src/main/scala/me/arcanis/ffxivbis/service/impl/DatabaseLootHandler.scala +++ b/src/main/scala/me/arcanis/ffxivbis/service/impl/DatabaseLootHandler.scala @@ -8,17 +8,20 @@ */ package me.arcanis.ffxivbis.service.impl +import java.time.Instant + import akka.pattern.pipe -import me.arcanis.ffxivbis.models.{Piece, PlayerId} +import me.arcanis.ffxivbis.models.{Loot, Piece, PlayerId} import me.arcanis.ffxivbis.service.Database trait DatabaseLootHandler { this: Database => import DatabaseLootHandler._ def lootHandler: Receive = { - case AddPieceTo(playerId, piece) => + case AddPieceTo(playerId, piece, isFreeLoot) => val client = sender() - profile.insertPiece(playerId, piece).pipeTo(client) + val loot = Loot(-1, piece, Instant.now, isFreeLoot) + profile.insertPiece(playerId, loot).pipeTo(client) case GetLoot(partyId, maybePlayerId) => val client = sender() @@ -37,7 +40,7 @@ trait DatabaseLootHandler { this: Database => } object DatabaseLootHandler { - case class AddPieceTo(playerId: PlayerId, piece: Piece) extends Database.DatabaseRequest { + case class AddPieceTo(playerId: PlayerId, piece: Piece, isFreeLoot: Boolean) extends Database.DatabaseRequest { override def partyId: String = playerId.partyId } case class GetLoot(partyId: String, playerId: Option[PlayerId]) extends Database.DatabaseRequest diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/BiSProfile.scala b/src/main/scala/me/arcanis/ffxivbis/storage/BiSProfile.scala index 06142b8..91c369b 100644 --- a/src/main/scala/me/arcanis/ffxivbis/storage/BiSProfile.scala +++ b/src/main/scala/me/arcanis/ffxivbis/storage/BiSProfile.scala @@ -22,7 +22,7 @@ trait BiSProfile { this: DatabaseProfile => pieceType: String, job: String) { def toLoot: Loot = Loot( playerId, Piece(piece, PieceType.withName(pieceType), Job.withName(job)), - Instant.ofEpochMilli(created)) + Instant.ofEpochMilli(created), isFreeLoot = false) } object BiSRep { def fromPiece(playerId: Long, piece: Piece): BiSRep = diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/DatabaseProfile.scala b/src/main/scala/me/arcanis/ffxivbis/storage/DatabaseProfile.scala index 7823820..0c2a2f8 100644 --- a/src/main/scala/me/arcanis/ffxivbis/storage/DatabaseProfile.scala +++ b/src/main/scala/me/arcanis/ffxivbis/storage/DatabaseProfile.scala @@ -44,14 +44,17 @@ class DatabaseProfile(context: ExecutionContext, config: Config) byPlayerId(playerId, insertPieceBiSById(piece)) // generic loot api - def deletePiece(playerId: PlayerId, piece: Piece): Future[Int] = - byPlayerId(playerId, deletePieceById(piece)) + def deletePiece(playerId: PlayerId, piece: Piece): Future[Int] = { + // we don't really care here about loot + val loot = Loot(-1, piece, Instant.now, isFreeLoot = false) + byPlayerId(playerId, deletePieceById(loot)) + } def getPieces(playerId: PlayerId): Future[Seq[Loot]] = byPlayerId(playerId, getPiecesById) def getPieces(partyId: String): Future[Seq[Loot]] = byPartyId(partyId, getPiecesById) - def insertPiece(playerId: PlayerId, piece: Piece): Future[Int] = - byPlayerId(playerId, insertPieceById(piece)) + def insertPiece(playerId: PlayerId, loot: Loot): Future[Int] = + byPlayerId(playerId, insertPieceById(loot)) private def byPartyId[T](partyId: String, callback: Seq[Long] => Future[T]): Future[T] = getPlayers(partyId).map(callback).flatten diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/LootProfile.scala b/src/main/scala/me/arcanis/ffxivbis/storage/LootProfile.scala index 2f6f7fd..eeecb31 100644 --- a/src/main/scala/me/arcanis/ffxivbis/storage/LootProfile.scala +++ b/src/main/scala/me/arcanis/ffxivbis/storage/LootProfile.scala @@ -19,16 +19,18 @@ trait LootProfile { this: DatabaseProfile => import dbConfig.profile.api._ case class LootRep(lootId: Option[Long], playerId: Long, created: Long, - piece: String, pieceType: String, job: String) { + piece: String, pieceType: String, job: String, + isFreeLoot: Int) { def toLoot: Loot = Loot( playerId, Piece(piece, PieceType.withName(pieceType), Job.withName(job)), - Instant.ofEpochMilli(created)) + Instant.ofEpochMilli(created), isFreeLoot == 1) } object LootRep { - def fromPiece(playerId: Long, piece: Piece): LootRep = - LootRep(None, playerId, DatabaseProfile.now, piece.piece, - piece.pieceType.toString, piece.job.toString) + 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") { @@ -38,9 +40,10 @@ trait LootProfile { this: DatabaseProfile => 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) <> ((LootRep.apply _).tupled, LootRep.unapply) + (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) @@ -48,16 +51,16 @@ trait LootProfile { this: DatabaseProfile => index("loot_owner_idx", (playerId), unique = false) } - def deletePieceById(piece: Piece)(playerId: Long): Future[Int] = - db.run(pieceLoot(LootRep.fromPiece(playerId, piece)).map(_.lootId).max.result).flatMap { + def deletePieceById(loot: Loot)(playerId: Long): Future[Int] = + db.run(pieceLoot(LootRep.fromLoot(playerId, loot)).map(_.lootId).max.result).flatMap { case Some(id) => db.run(lootTable.filter(_.lootId === id).delete) - case _ => throw new IllegalArgumentException(s"Could not find piece $piece belong to $playerId") + case _ => throw new IllegalArgumentException(s"Could not find piece $loot belong to $playerId") } def getPiecesById(playerId: Long): Future[Seq[Loot]] = getPiecesById(Seq(playerId)) def getPiecesById(playerIds: Seq[Long]): Future[Seq[Loot]] = db.run(piecesLoot(playerIds).result).map(_.map(_.toLoot)) - def insertPieceById(piece: Piece)(playerId: Long): Future[Int] = - db.run(lootTable.insertOrUpdate(LootRep.fromPiece(playerId, piece))) + def insertPieceById(loot: Loot)(playerId: Long): Future[Int] = + db.run(lootTable.insertOrUpdate(LootRep.fromLoot(playerId, loot))) private def pieceLoot(piece: LootRep) = piecesLoot(Seq(piece.playerId)).filter(_.piece === piece.piece) diff --git a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpointTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpointTest.scala index a3cddfe..d876e9a 100644 --- a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpointTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpointTest.scala @@ -70,7 +70,7 @@ class BiSEndpointTest extends WordSpec "remove item from best in slot set" in { val piece = PieceResponse.fromPiece(Fixtures.lootBody) - val entity = PieceActionResponse(ApiAction.remove, piece, playerId) + val entity = PieceActionResponse(ApiAction.remove, piece, playerId, None) Post(endpoint, entity).withHeaders(auth) ~> route ~> check { status shouldEqual StatusCodes.Accepted @@ -89,7 +89,7 @@ class BiSEndpointTest extends WordSpec "add item to best in slot set" in { val piece = PieceResponse.fromPiece(Fixtures.lootBody) - val entity = PieceActionResponse(ApiAction.add, piece, playerId) + val entity = PieceActionResponse(ApiAction.add, piece, playerId, None) Post(endpoint, entity).withHeaders(auth) ~> route ~> check { status shouldEqual StatusCodes.Accepted diff --git a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpointTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpointTest.scala index da0e5f5..861b949 100644 --- a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpointTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpointTest.scala @@ -51,7 +51,7 @@ class LootEndpointTest extends WordSpec "add item to loot" in { val piece = PieceResponse.fromPiece(Fixtures.lootBody) - val entity = PieceActionResponse(ApiAction.add, piece, playerId) + val entity = PieceActionResponse(ApiAction.add, piece, playerId, Some(false)) Post(endpoint, entity).withHeaders(auth) ~> route ~> check { status shouldEqual StatusCodes.Accepted @@ -76,7 +76,7 @@ class LootEndpointTest extends WordSpec "remove item from loot" in { val piece = PieceResponse.fromPiece(Fixtures.lootBody) - val entity = PieceActionResponse(ApiAction.remove, piece, playerId) + val entity = PieceActionResponse(ApiAction.remove, piece, playerId, Some(false)) Post(endpoint, entity).withHeaders(auth) ~> route ~> check { status shouldEqual StatusCodes.Accepted diff --git a/src/test/scala/me/arcanis/ffxivbis/service/DatabaseLootHandlerTest.scala b/src/test/scala/me/arcanis/ffxivbis/service/DatabaseLootHandlerTest.scala index b152458..2c4ead2 100644 --- a/src/test/scala/me/arcanis/ffxivbis/service/DatabaseLootHandlerTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/service/DatabaseLootHandlerTest.scala @@ -34,7 +34,7 @@ class DatabaseLootHandlerTest "add loot" in { Fixtures.loot.foreach { piece => - database ! impl.DatabaseLootHandler.AddPieceTo(Fixtures.playerEmpty.playerId, piece) + database ! impl.DatabaseLootHandler.AddPieceTo(Fixtures.playerEmpty.playerId, piece, isFreeLoot = false) expectMsg(timeout, 1) } } @@ -66,11 +66,11 @@ class DatabaseLootHandlerTest } "add same loot" in { - database ! impl.DatabaseLootHandler.AddPieceTo(Fixtures.playerEmpty.playerId, Fixtures.lootBody) + database ! impl.DatabaseLootHandler.AddPieceTo(Fixtures.playerEmpty.playerId, Fixtures.lootBody, isFreeLoot = false) expectMsg(timeout, 1) Fixtures.loot.foreach { piece => - database ! impl.DatabaseLootHandler.AddPieceTo(Fixtures.playerEmpty.playerId, piece) + database ! impl.DatabaseLootHandler.AddPieceTo(Fixtures.playerEmpty.playerId, piece, isFreeLoot = false) expectMsg(timeout, 1) } diff --git a/src/test/scala/me/arcanis/ffxivbis/utils/Converters.scala b/src/test/scala/me/arcanis/ffxivbis/utils/Converters.scala index bb3ca13..1747252 100644 --- a/src/test/scala/me/arcanis/ffxivbis/utils/Converters.scala +++ b/src/test/scala/me/arcanis/ffxivbis/utils/Converters.scala @@ -7,5 +7,5 @@ import me.arcanis.ffxivbis.models.{Loot, Piece} import scala.language.implicitConversions object Converters { - implicit def pieceToLoot(piece: Piece): Loot = Loot(-1, piece, Instant.ofEpochMilli(0)) + implicit def pieceToLoot(piece: Piece): Loot = Loot(-1, piece, Instant.ofEpochMilli(0), isFreeLoot = false) }