mirror of
https://github.com/arcan1s/ffxivbis.git
synced 2025-04-25 01:37:17 +00:00
demo with optional loot support
This commit is contained in:
parent
10c107d2c2
commit
0171b229a1
@ -0,0 +1 @@
|
|||||||
|
alter table loot add column is_free_loot integer not null default 0;
|
20
src/main/resources/db/migration/sqlite/V5_0__Free_loot.sql
Normal file
20
src/main/resources/db/migration/sqlite/V5_0__Free_loot.sql
Normal file
@ -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);
|
@ -22,15 +22,16 @@ trait LootHelper {
|
|||||||
|
|
||||||
def storage: ActorRef
|
def storage: ActorRef
|
||||||
|
|
||||||
def addPieceLoot(playerId: PlayerId, piece: Piece)
|
def addPieceLoot(playerId: PlayerId, piece: Piece, isFreeLoot: Boolean)
|
||||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
(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] =
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
||||||
action match {
|
(action, maybeFree) match {
|
||||||
case ApiAction.add => addPieceLoot(playerId, piece)
|
case (ApiAction.add, Some(isFreeLoot)) => addPieceLoot(playerId, piece, isFreeLoot)
|
||||||
case ApiAction.remove => removePieceLoot(playerId, piece)
|
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])
|
def loot(partyId: String, playerId: Option[PlayerId])
|
||||||
|
@ -103,7 +103,7 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
post {
|
post {
|
||||||
entity(as[PieceActionResponse]) { action =>
|
entity(as[PieceActionResponse]) { action =>
|
||||||
val playerId = action.playerId.withPartyId(partyId)
|
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 Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
|
||||||
case Failure(exception) => throw exception
|
case Failure(exception) => throw exception
|
||||||
}
|
}
|
||||||
|
@ -41,12 +41,12 @@ trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
|
|||||||
implicit val errorFormat: RootJsonFormat[ErrorResponse] = jsonFormat1(ErrorResponse.apply)
|
implicit val errorFormat: RootJsonFormat[ErrorResponse] = jsonFormat1(ErrorResponse.apply)
|
||||||
implicit val partyIdFormat: RootJsonFormat[PartyIdResponse] = jsonFormat1(PartyIdResponse.apply)
|
implicit val partyIdFormat: RootJsonFormat[PartyIdResponse] = jsonFormat1(PartyIdResponse.apply)
|
||||||
implicit val pieceFormat: RootJsonFormat[PieceResponse] = jsonFormat3(PieceResponse.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 partyDescriptionFormat: RootJsonFormat[PartyDescriptionResponse] = jsonFormat2(PartyDescriptionResponse.apply)
|
||||||
implicit val playerFormat: RootJsonFormat[PlayerResponse] = jsonFormat7(PlayerResponse.apply)
|
implicit val playerFormat: RootJsonFormat[PlayerResponse] = jsonFormat7(PlayerResponse.apply)
|
||||||
implicit val playerActionFormat: RootJsonFormat[PlayerActionResponse] = jsonFormat2(PlayerActionResponse.apply)
|
implicit val playerActionFormat: RootJsonFormat[PlayerActionResponse] = jsonFormat2(PlayerActionResponse.apply)
|
||||||
implicit val playerIdFormat: RootJsonFormat[PlayerIdResponse] = jsonFormat3(PlayerIdResponse.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 playerBiSLinkFormat: RootJsonFormat[PlayerBiSLinkResponse] = jsonFormat2(PlayerBiSLinkResponse.apply)
|
||||||
implicit val playerIdWithCountersFormat: RootJsonFormat[PlayerIdWithCountersResponse] =
|
implicit val playerIdWithCountersFormat: RootJsonFormat[PlayerIdWithCountersResponse] =
|
||||||
jsonFormat9(PlayerIdWithCountersResponse.apply)
|
jsonFormat9(PlayerIdWithCountersResponse.apply)
|
||||||
|
@ -7,11 +7,12 @@ import me.arcanis.ffxivbis.models.Loot
|
|||||||
|
|
||||||
case class LootResponse(
|
case class LootResponse(
|
||||||
@Schema(description = "looted piece", required = true) piece: PieceResponse,
|
@Schema(description = "looted piece", required = true) piece: PieceResponse,
|
||||||
@Schema(description = "loot timestamp", required = true) timestamp: Instant) {
|
@Schema(description = "loot timestamp", required = true) timestamp: Instant,
|
||||||
def toLoot: Loot = Loot(-1, piece.toPiece, timestamp)
|
@Schema(description = "is loot free for all", required = true) isFreeLoot: Boolean) {
|
||||||
|
def toLoot: Loot = Loot(-1, piece.toPiece, timestamp, isFreeLoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
object LootResponse {
|
object LootResponse {
|
||||||
def fromLoot(loot: Loot): LootResponse =
|
def fromLoot(loot: Loot): LootResponse =
|
||||||
LootResponse(PieceResponse.fromPiece(loot.piece), loot.timestamp)
|
LootResponse(PieceResponse.fromPiece(loot.piece), loot.timestamp, loot.isFreeLoot)
|
||||||
}
|
}
|
@ -13,4 +13,5 @@ import io.swagger.v3.oas.annotations.media.Schema
|
|||||||
case class PieceActionResponse(
|
case class PieceActionResponse(
|
||||||
@Schema(description = "action to perform", required = true, `type` = "string", allowableValues = Array("add", "remove")) action: ApiAction.Value,
|
@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 = "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])
|
||||||
|
@ -30,7 +30,7 @@ class LootSuggestView(override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
|
authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
|
||||||
get {
|
get {
|
||||||
complete {
|
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))
|
(StatusCodes.OK, RootView.toHtml(text))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -43,17 +43,20 @@ class LootSuggestView(override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
extractExecutionContext { implicit executionContext =>
|
extractExecutionContext { implicit executionContext =>
|
||||||
authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
|
authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
|
||||||
post {
|
post {
|
||||||
formFields("piece".as[String], "job".as[String], "piece_type".as[String]) { (piece, job, pieceType) =>
|
formFields("piece".as[String], "job".as[String], "piece_type".as[String], "free_loot".as[String].?) {
|
||||||
val maybePiece = Try(Piece(piece, PieceType.withName(pieceType), Job.withName(job))).toOption
|
(piece, job, pieceType, maybeFreeLoot) =>
|
||||||
|
import me.arcanis.ffxivbis.utils.Implicits._
|
||||||
|
|
||||||
onComplete(suggestLootCall(partyId, maybePiece)) {
|
val maybePiece = Try(Piece(piece, PieceType.withName(pieceType), Job.withName(job))).toOption
|
||||||
case Success(players) =>
|
|
||||||
val text = LootSuggestView.template(partyId, players, maybePiece, None)
|
onComplete(suggestLootCall(partyId, maybePiece)) {
|
||||||
complete(StatusCodes.OK, RootView.toHtml(text))
|
case Success(players) =>
|
||||||
case Failure(exception) =>
|
val text = LootSuggestView.template(partyId, players, maybePiece, maybeFreeLoot, None)
|
||||||
val text = LootSuggestView.template(partyId, Seq.empty, maybePiece, Some(exception.getMessage))
|
complete(StatusCodes.OK, RootView.toHtml(text))
|
||||||
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.all._
|
||||||
import scalatags.Text.tags2.{title => titleTag}
|
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 =
|
||||||
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
|
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
|
||||||
html(lang:="en",
|
html(lang:="en",
|
||||||
head(
|
head(
|
||||||
@ -93,6 +97,8 @@ object LootSuggestView {
|
|||||||
(for (job <- Job.availableWithAnyJob) yield option(job.toString)),
|
(for (job <- Job.availableWithAnyJob) yield option(job.toString)),
|
||||||
select(name:="piece_type", id:="piece_type", title:="piece type")
|
select(name:="piece_type", id:="piece_type", title:="piece type")
|
||||||
(for (pieceType <- PieceType.available) yield option(pieceType.toString)),
|
(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")
|
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:="player", id:="player", `type`:="hidden", value:=player.playerId.toString),
|
||||||
input(name:="piece", id:="piece", `type`:="hidden", value:=piece.map(_.piece).getOrElse("")),
|
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:="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:="action", id:="action", `type`:="hidden", value:="add"),
|
||||||
input(name:="add", id:="add", `type`:="submit", value:="add")
|
input(name:="add", id:="add", `type`:="submit", value:="add")
|
||||||
)
|
)
|
||||||
|
@ -46,9 +46,9 @@ class LootView (override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
extractExecutionContext { implicit executionContext =>
|
extractExecutionContext { implicit executionContext =>
|
||||||
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
|
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
|
||||||
post {
|
post {
|
||||||
formFields("player".as[String], "piece".as[String], "piece_type".as[String], "action".as[String]) {
|
formFields("player".as[String], "piece".as[String], "piece_type".as[String], "action".as[String], "free_loot".as[String].?) {
|
||||||
(player, piece, pieceType, action) =>
|
(player, piece, pieceType, action, isFreeLoot) =>
|
||||||
onComplete(modifyLootCall(partyId, player, piece, pieceType, action)) { _ =>
|
onComplete(modifyLootCall(partyId, player, piece, pieceType, isFreeLoot, action)) { _ =>
|
||||||
redirect(s"/party/$partyId/loot", StatusCodes.Found)
|
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,
|
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] = {
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = {
|
||||||
|
import me.arcanis.ffxivbis.utils.Implicits._
|
||||||
|
|
||||||
def getPiece(playerId: PlayerId) =
|
def getPiece(playerId: PlayerId) =
|
||||||
Try(Piece(maybePiece, PieceType.withName(maybePieceType), playerId.job)).toOption
|
Try(Piece(maybePiece, PieceType.withName(maybePieceType), playerId.job)).toOption
|
||||||
|
|
||||||
PlayerId(partyId, player) match {
|
PlayerId(partyId, player) match {
|
||||||
case Some(playerId) => (getPiece(playerId), action) 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 (Some(piece), "remove") => removePieceLoot(playerId, piece).map(_ => ())
|
||||||
case _ => Future.failed(new Error(s"Could not construct piece from `$maybePiece ($maybePieceType)`"))
|
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)),
|
(for (piece <- Piece.available) yield option(piece)),
|
||||||
select(name:="piece_type", id:="piece_type", title:="piece type")
|
select(name:="piece_type", id:="piece_type", title:="piece type")
|
||||||
(for (pieceType <- PieceType.available) yield option(pieceType.toString)),
|
(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:="action", id:="action", `type`:="hidden", value:="add"),
|
||||||
input(name:="add", id:="add", `type`:="submit", value:="add")
|
input(name:="add", id:="add", `type`:="submit", value:="add")
|
||||||
),
|
),
|
||||||
@ -108,6 +113,7 @@ object LootView {
|
|||||||
th("player"),
|
th("player"),
|
||||||
th("piece"),
|
th("piece"),
|
||||||
th("piece type"),
|
th("piece type"),
|
||||||
|
th("is free loot"),
|
||||||
th("timestamp"),
|
th("timestamp"),
|
||||||
th("")
|
th("")
|
||||||
),
|
),
|
||||||
@ -115,12 +121,14 @@ object LootView {
|
|||||||
td(`class`:="include_search")(player.playerId.toString),
|
td(`class`:="include_search")(player.playerId.toString),
|
||||||
td(`class`:="include_search")(loot.piece.piece),
|
td(`class`:="include_search")(loot.piece.piece),
|
||||||
td(loot.piece.pieceType.toString),
|
td(loot.piece.pieceType.toString),
|
||||||
|
td(loot.isFreeLootToString),
|
||||||
td(loot.timestamp.toString),
|
td(loot.timestamp.toString),
|
||||||
td(
|
td(
|
||||||
form(action:=s"/party/$partyId/loot", method:="post")(
|
form(action:=s"/party/$partyId/loot", method:="post")(
|
||||||
input(name:="player", id:="player", `type`:="hidden", value:=player.playerId.toString),
|
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", id:="piece", `type`:="hidden", value:=loot.piece.piece),
|
||||||
input(name:="piece_type", id:="piece_type", `type`:="hidden", value:=loot.piece.pieceType.toString),
|
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:="action", id:="action", `type`:="hidden", value:="remove"),
|
||||||
input(name:="remove", id:="remove", `type`:="submit", value:="x")
|
input(name:="remove", id:="remove", `type`:="submit", value:="x")
|
||||||
)
|
)
|
||||||
|
@ -10,4 +10,6 @@ package me.arcanis.ffxivbis.models
|
|||||||
|
|
||||||
import java.time.Instant
|
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"
|
||||||
|
}
|
||||||
|
@ -31,10 +31,7 @@ case class Player(id: Long,
|
|||||||
def withLoot(piece: Loot): Player = withLoot(Seq(piece))
|
def withLoot(piece: Loot): Player = withLoot(Seq(piece))
|
||||||
def withLoot(list: Seq[Loot]): Player = {
|
def withLoot(list: Seq[Loot]): Player = {
|
||||||
require(loot.forall(_.playerId == id), "player id must be same")
|
require(loot.forall(_.playerId == id), "player id must be same")
|
||||||
list match {
|
copy(loot = loot ++ list)
|
||||||
case Nil => this
|
|
||||||
case _ => copy(loot = list)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def isRequired(piece: Option[Piece]): Boolean = {
|
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 bisCountTotal(piece: Option[Piece]): Int = bis.pieces.count(_.pieceType == PieceType.Savage)
|
||||||
def lootCount(piece: Option[Piece]): Int = piece match {
|
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)
|
case None => lootCountTotal(piece)
|
||||||
}
|
}
|
||||||
def lootCountBiS(piece: Option[Piece]): Int = loot.map(_.piece).count(bis.hasPiece)
|
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
|
def lootPriority(piece: Piece): Int = priority
|
||||||
}
|
}
|
||||||
|
@ -8,17 +8,20 @@
|
|||||||
*/
|
*/
|
||||||
package me.arcanis.ffxivbis.service.impl
|
package me.arcanis.ffxivbis.service.impl
|
||||||
|
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
import akka.pattern.pipe
|
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
|
import me.arcanis.ffxivbis.service.Database
|
||||||
|
|
||||||
trait DatabaseLootHandler { this: Database =>
|
trait DatabaseLootHandler { this: Database =>
|
||||||
import DatabaseLootHandler._
|
import DatabaseLootHandler._
|
||||||
|
|
||||||
def lootHandler: Receive = {
|
def lootHandler: Receive = {
|
||||||
case AddPieceTo(playerId, piece) =>
|
case AddPieceTo(playerId, piece, isFreeLoot) =>
|
||||||
val client = sender()
|
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) =>
|
case GetLoot(partyId, maybePlayerId) =>
|
||||||
val client = sender()
|
val client = sender()
|
||||||
@ -37,7 +40,7 @@ trait DatabaseLootHandler { this: Database =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
object DatabaseLootHandler {
|
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
|
override def partyId: String = playerId.partyId
|
||||||
}
|
}
|
||||||
case class GetLoot(partyId: String, playerId: Option[PlayerId]) extends Database.DatabaseRequest
|
case class GetLoot(partyId: String, playerId: Option[PlayerId]) extends Database.DatabaseRequest
|
||||||
|
@ -22,7 +22,7 @@ trait BiSProfile { this: DatabaseProfile =>
|
|||||||
pieceType: String, job: String) {
|
pieceType: String, job: String) {
|
||||||
def toLoot: Loot = Loot(
|
def toLoot: Loot = Loot(
|
||||||
playerId, Piece(piece, PieceType.withName(pieceType), Job.withName(job)),
|
playerId, Piece(piece, PieceType.withName(pieceType), Job.withName(job)),
|
||||||
Instant.ofEpochMilli(created))
|
Instant.ofEpochMilli(created), isFreeLoot = false)
|
||||||
}
|
}
|
||||||
object BiSRep {
|
object BiSRep {
|
||||||
def fromPiece(playerId: Long, piece: Piece): BiSRep =
|
def fromPiece(playerId: Long, piece: Piece): BiSRep =
|
||||||
|
@ -44,14 +44,17 @@ 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): Future[Int] = {
|
||||||
byPlayerId(playerId, deletePieceById(piece))
|
// 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]] =
|
def getPieces(playerId: PlayerId): Future[Seq[Loot]] =
|
||||||
byPlayerId(playerId, getPiecesById)
|
byPlayerId(playerId, getPiecesById)
|
||||||
def getPieces(partyId: String): Future[Seq[Loot]] =
|
def getPieces(partyId: String): Future[Seq[Loot]] =
|
||||||
byPartyId(partyId, getPiecesById)
|
byPartyId(partyId, getPiecesById)
|
||||||
def insertPiece(playerId: PlayerId, piece: Piece): Future[Int] =
|
def insertPiece(playerId: PlayerId, loot: Loot): Future[Int] =
|
||||||
byPlayerId(playerId, insertPieceById(piece))
|
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).map(callback).flatten
|
||||||
|
@ -19,16 +19,18 @@ trait LootProfile { this: DatabaseProfile =>
|
|||||||
import dbConfig.profile.api._
|
import dbConfig.profile.api._
|
||||||
|
|
||||||
case class LootRep(lootId: Option[Long], playerId: Long, created: Long,
|
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(
|
def toLoot: Loot = Loot(
|
||||||
playerId,
|
playerId,
|
||||||
Piece(piece, PieceType.withName(pieceType), Job.withName(job)),
|
Piece(piece, PieceType.withName(pieceType), Job.withName(job)),
|
||||||
Instant.ofEpochMilli(created))
|
Instant.ofEpochMilli(created), isFreeLoot == 1)
|
||||||
}
|
}
|
||||||
object LootRep {
|
object LootRep {
|
||||||
def fromPiece(playerId: Long, piece: Piece): LootRep =
|
def fromLoot(playerId: Long, loot: Loot): LootRep =
|
||||||
LootRep(None, playerId, DatabaseProfile.now, piece.piece,
|
LootRep(None, playerId, loot.timestamp.toEpochMilli, loot.piece.piece,
|
||||||
piece.pieceType.toString, piece.job.toString)
|
loot.piece.pieceType.toString, loot.piece.job.toString,
|
||||||
|
if (loot.isFreeLoot) 1 else 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
class LootPieces(tag: Tag) extends Table[LootRep](tag, "loot") {
|
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 piece: Rep[String] = column[String]("piece")
|
||||||
def pieceType: Rep[String] = column[String]("piece_type")
|
def pieceType: Rep[String] = column[String]("piece_type")
|
||||||
def job: Rep[String] = column[String]("job")
|
def job: Rep[String] = column[String]("job")
|
||||||
|
def isFreeLoot: Rep[Int] = column[Int]("is_free_loot")
|
||||||
|
|
||||||
def * =
|
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] =
|
def fkPlayerId: ForeignKeyQuery[Players, PlayerRep] =
|
||||||
foreignKey("player_id", playerId, playersTable)(_.playerId, onDelete = ForeignKeyAction.Cascade)
|
foreignKey("player_id", playerId, playersTable)(_.playerId, onDelete = ForeignKeyAction.Cascade)
|
||||||
@ -48,16 +51,16 @@ trait LootProfile { this: DatabaseProfile =>
|
|||||||
index("loot_owner_idx", (playerId), unique = false)
|
index("loot_owner_idx", (playerId), unique = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
def deletePieceById(piece: Piece)(playerId: Long): Future[Int] =
|
def deletePieceById(loot: Loot)(playerId: Long): Future[Int] =
|
||||||
db.run(pieceLoot(LootRep.fromPiece(playerId, piece)).map(_.lootId).max.result).flatMap {
|
db.run(pieceLoot(LootRep.fromLoot(playerId, loot)).map(_.lootId).max.result).flatMap {
|
||||||
case Some(id) => db.run(lootTable.filter(_.lootId === id).delete)
|
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(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))
|
db.run(piecesLoot(playerIds).result).map(_.map(_.toLoot))
|
||||||
def insertPieceById(piece: Piece)(playerId: Long): Future[Int] =
|
def insertPieceById(loot: Loot)(playerId: Long): Future[Int] =
|
||||||
db.run(lootTable.insertOrUpdate(LootRep.fromPiece(playerId, piece)))
|
db.run(lootTable.insertOrUpdate(LootRep.fromLoot(playerId, loot)))
|
||||||
|
|
||||||
private def pieceLoot(piece: LootRep) =
|
private def pieceLoot(piece: LootRep) =
|
||||||
piecesLoot(Seq(piece.playerId)).filter(_.piece === piece.piece)
|
piecesLoot(Seq(piece.playerId)).filter(_.piece === piece.piece)
|
||||||
|
@ -70,7 +70,7 @@ class BiSEndpointTest extends WordSpec
|
|||||||
|
|
||||||
"remove item from best in slot set" in {
|
"remove item from best in slot set" in {
|
||||||
val piece = PieceResponse.fromPiece(Fixtures.lootBody)
|
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 {
|
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
|
||||||
status shouldEqual StatusCodes.Accepted
|
status shouldEqual StatusCodes.Accepted
|
||||||
@ -89,7 +89,7 @@ class BiSEndpointTest extends WordSpec
|
|||||||
|
|
||||||
"add item to best in slot set" in {
|
"add item to best in slot set" in {
|
||||||
val piece = PieceResponse.fromPiece(Fixtures.lootBody)
|
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 {
|
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
|
||||||
status shouldEqual StatusCodes.Accepted
|
status shouldEqual StatusCodes.Accepted
|
||||||
|
@ -51,7 +51,7 @@ class LootEndpointTest extends WordSpec
|
|||||||
|
|
||||||
"add item to loot" in {
|
"add item to loot" in {
|
||||||
val piece = PieceResponse.fromPiece(Fixtures.lootBody)
|
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 {
|
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
|
||||||
status shouldEqual StatusCodes.Accepted
|
status shouldEqual StatusCodes.Accepted
|
||||||
@ -76,7 +76,7 @@ class LootEndpointTest extends WordSpec
|
|||||||
|
|
||||||
"remove item from loot" in {
|
"remove item from loot" in {
|
||||||
val piece = PieceResponse.fromPiece(Fixtures.lootBody)
|
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 {
|
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
|
||||||
status shouldEqual StatusCodes.Accepted
|
status shouldEqual StatusCodes.Accepted
|
||||||
|
@ -34,7 +34,7 @@ class DatabaseLootHandlerTest
|
|||||||
|
|
||||||
"add loot" in {
|
"add loot" in {
|
||||||
Fixtures.loot.foreach { piece =>
|
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)
|
expectMsg(timeout, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,11 +66,11 @@ class DatabaseLootHandlerTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
"add same loot" in {
|
"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)
|
expectMsg(timeout, 1)
|
||||||
|
|
||||||
Fixtures.loot.foreach { piece =>
|
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)
|
expectMsg(timeout, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,5 +7,5 @@ import me.arcanis.ffxivbis.models.{Loot, Piece}
|
|||||||
import scala.language.implicitConversions
|
import scala.language.implicitConversions
|
||||||
|
|
||||||
object Converters {
|
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)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user