mirror of
https://github.com/arcan1s/ffxivbis.git
synced 2025-04-25 01:37:17 +00:00
crafted items support (#5)
This commit is contained in:
parent
16ce0bf61c
commit
10c107d2c2
@ -0,0 +1,17 @@
|
|||||||
|
-- loot
|
||||||
|
alter table loot add column piece_type text;
|
||||||
|
|
||||||
|
update loot set piece_type = 'Tome' where is_tome = 1;
|
||||||
|
update loot set piece_type = 'Savage' where is_tome = 0;
|
||||||
|
|
||||||
|
alter table loot alter column piece_type set not null;
|
||||||
|
alter table loot drop column is_tome;
|
||||||
|
|
||||||
|
-- bis
|
||||||
|
alter table bis add column piece_type text;
|
||||||
|
|
||||||
|
update bis set piece_type = 'Tome' where is_tome = 1;
|
||||||
|
update bis set piece_type = 'Savage' where is_tome = 0;
|
||||||
|
|
||||||
|
alter table bis alter column piece_type set not null;
|
||||||
|
alter table bis drop column is_tome;
|
@ -0,0 +1,42 @@
|
|||||||
|
-- loot
|
||||||
|
alter table loot add column piece_type text;
|
||||||
|
|
||||||
|
update loot set piece_type = 'Tome' where is_tome = 1;
|
||||||
|
update loot set piece_type = 'Savage' where is_tome = 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,
|
||||||
|
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 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);
|
||||||
|
|
||||||
|
-- bis
|
||||||
|
alter table bis add column piece_type text;
|
||||||
|
|
||||||
|
update bis set piece_type = 'Tome' where is_tome = 1;
|
||||||
|
update bis set piece_type = 'Savage' where is_tome = 0;
|
||||||
|
|
||||||
|
create table bis_new (
|
||||||
|
player_id integer not null,
|
||||||
|
created integer not null,
|
||||||
|
piece text not null,
|
||||||
|
piece_type text not null,
|
||||||
|
job text not null,
|
||||||
|
foreign key (player_id) references players(player_id) on delete cascade);
|
||||||
|
insert into bis_new select player_id, created, piece, piece_type, job from bis;
|
||||||
|
|
||||||
|
drop index bis_piece_player_id_idx;
|
||||||
|
drop table bis;
|
||||||
|
|
||||||
|
alter table bis_new rename to bis;
|
||||||
|
create unique index bis_piece_player_id_idx on bis(player_id, piece);
|
@ -16,12 +16,12 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse
|
|||||||
import io.swagger.v3.oas.annotations.Operation
|
import io.swagger.v3.oas.annotations.Operation
|
||||||
import javax.ws.rs._
|
import javax.ws.rs._
|
||||||
import me.arcanis.ffxivbis.http.api.v1.json._
|
import me.arcanis.ffxivbis.http.api.v1.json._
|
||||||
import me.arcanis.ffxivbis.models.{Job, Party, Permission, Piece}
|
import me.arcanis.ffxivbis.models.{Job, Party, Permission, Piece, PieceType}
|
||||||
|
|
||||||
@Path("api/v1")
|
@Path("api/v1")
|
||||||
class TypesEndpoint(config: Config) extends JsonSupport {
|
class TypesEndpoint(config: Config) extends JsonSupport {
|
||||||
|
|
||||||
def route: Route = getJobs ~ getPermissions ~ getPieces ~ getPriority
|
def route: Route = getJobs ~ getPermissions ~ getPieces ~ getPieceTypes ~ getPriority
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("types/jobs")
|
@Path("types/jobs")
|
||||||
@ -86,6 +86,27 @@ class TypesEndpoint(config: Config) extends JsonSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("types/pieces/types")
|
||||||
|
@Produces(value = Array("application/json"))
|
||||||
|
@Operation(summary = "piece types list", description = "Returns the available piece types",
|
||||||
|
responses = Array(
|
||||||
|
new ApiResponse(responseCode = "200", description = "List of available piece types",
|
||||||
|
content = Array(new Content(
|
||||||
|
array = new ArraySchema(schema = new Schema(implementation = classOf[String]))
|
||||||
|
))),
|
||||||
|
new ApiResponse(responseCode = "500", description = "Internal server error",
|
||||||
|
content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))),
|
||||||
|
),
|
||||||
|
tags = Array("types"),
|
||||||
|
)
|
||||||
|
def getPieceTypes: Route =
|
||||||
|
path("types" / "pieces" / "types") {
|
||||||
|
get {
|
||||||
|
complete(PieceType.available.map(_.toString))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("types/priority")
|
@Path("types/priority")
|
||||||
@Produces(value = Array("application/json"))
|
@Produces(value = Array("application/json"))
|
||||||
|
@ -9,16 +9,16 @@
|
|||||||
package me.arcanis.ffxivbis.http.api.v1.json
|
package me.arcanis.ffxivbis.http.api.v1.json
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema
|
import io.swagger.v3.oas.annotations.media.Schema
|
||||||
import me.arcanis.ffxivbis.models.{Job, Piece}
|
import me.arcanis.ffxivbis.models.{Job, Piece, PieceType}
|
||||||
|
|
||||||
case class PieceResponse(
|
case class PieceResponse(
|
||||||
@Schema(description = "is piece tome gear", required = true) isTome: Boolean,
|
@Schema(description = "piece type", required = true) pieceType: String,
|
||||||
@Schema(description = "job name to which piece belong or AnyJob", required = true, example = "DNC") job: String,
|
@Schema(description = "job name to which piece belong or AnyJob", required = true, example = "DNC") job: String,
|
||||||
@Schema(description = "piece name", required = true, example = "body") piece: String) {
|
@Schema(description = "piece name", required = true, example = "body") piece: String) {
|
||||||
def toPiece: Piece = Piece(piece, isTome, Job.withName(job))
|
def toPiece: Piece = Piece(piece, PieceType.withName(pieceType), Job.withName(job))
|
||||||
}
|
}
|
||||||
|
|
||||||
object PieceResponse {
|
object PieceResponse {
|
||||||
def fromPiece(piece: Piece): PieceResponse =
|
def fromPiece(piece: Piece): PieceResponse =
|
||||||
PieceResponse(piece.isTome, piece.job.toString, piece.piece)
|
PieceResponse(piece.pieceType.toString, piece.job.toString, piece.piece)
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ import akka.http.scaladsl.server.Directives._
|
|||||||
import akka.http.scaladsl.server._
|
import akka.http.scaladsl.server._
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
import me.arcanis.ffxivbis.http.{Authorization, BiSHelper}
|
import me.arcanis.ffxivbis.http.{Authorization, BiSHelper}
|
||||||
import me.arcanis.ffxivbis.models.{Piece, Player, PlayerId}
|
import me.arcanis.ffxivbis.models.{Piece, PieceType, Player, PlayerId}
|
||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
@ -46,10 +46,10 @@ class BiSView(override val storage: ActorRef, override val ariyala: ActorRef)(im
|
|||||||
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].?, "is_tome".as[String].?, "link".as[String].?, "action".as[String]) {
|
formFields("player".as[String], "piece".as[String].?, "piece_type".as[String].?, "link".as[String].?, "action".as[String]) {
|
||||||
(player, maybePiece, maybeIsTome, maybeLink, action) =>
|
(player, maybePiece, maybePieceType, maybeLink, action) =>
|
||||||
onComplete(modifyBiSCall(partyId, player, maybePiece, maybeIsTome, maybeLink, action)) {
|
onComplete(modifyBiSCall(partyId, player, maybePiece, maybePieceType, maybeLink, action)) { _ =>
|
||||||
case _ => redirect(s"/party/$partyId/bis", StatusCodes.Found)
|
redirect(s"/party/$partyId/bis", StatusCodes.Found)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,25 +58,25 @@ class BiSView(override val storage: ActorRef, override val ariyala: ActorRef)(im
|
|||||||
}
|
}
|
||||||
|
|
||||||
private def modifyBiSCall(partyId: String, player: String,
|
private def modifyBiSCall(partyId: String, player: String,
|
||||||
maybePiece: Option[String], maybeIsTome: Option[String],
|
maybePiece: Option[String], maybePieceType: Option[String],
|
||||||
maybeLink: Option[String], action: String)
|
maybeLink: 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, piece: String, pieceType: String) =
|
||||||
|
Try(Piece(piece, PieceType.withName(pieceType), playerId.job)).toOption
|
||||||
|
|
||||||
def getPiece(playerId: PlayerId, piece: String) =
|
def bisAction(playerId: PlayerId, piece: String, pieceType: String)(fn: Piece => Future[Int]) =
|
||||||
Try(Piece(piece, maybeIsTome, playerId.job)).toOption
|
getPiece(playerId, piece, pieceType) match {
|
||||||
|
case Some(item) => fn(item).map(_ => ())
|
||||||
|
case _ => Future.failed(new Error(s"Could not construct piece from `$piece ($pieceType)`"))
|
||||||
|
}
|
||||||
|
|
||||||
PlayerId(partyId, player) match {
|
PlayerId(partyId, player) match {
|
||||||
case Some(playerId) => (maybePiece, action, maybeLink) match {
|
case Some(playerId) => (maybePiece, maybePieceType, action, maybeLink) match {
|
||||||
case (Some(piece), "add", _) => getPiece(playerId, piece) match {
|
case (Some(piece), Some(pieceType), "add", _) =>
|
||||||
case Some(item) => addPieceBiS(playerId, item).map(_ => ())
|
bisAction(playerId, piece, pieceType) { item => addPieceBiS(playerId, item) }
|
||||||
case _ => Future.failed(new Error(s"Could not construct piece from `$piece`"))
|
case (Some(piece), Some(pieceType), "remove", _) =>
|
||||||
}
|
bisAction(playerId, piece, pieceType) { item => removePieceBiS(playerId, item) }
|
||||||
case (Some(piece), "remove", _) => getPiece(playerId, piece) match {
|
case (_, _, "create", Some(link)) => putBiS(playerId, link).map(_ => ())
|
||||||
case Some(item) => removePieceBiS(playerId, item).map(_ => ())
|
|
||||||
case _ => Future.failed(new Error(s"Could not construct piece from `$piece`"))
|
|
||||||
}
|
|
||||||
case (_, "create", Some(link)) => putBiS(playerId, link).map(_ => ())
|
|
||||||
case _ => Future.failed(new Error(s"Could not perform $action"))
|
case _ => Future.failed(new Error(s"Could not perform $action"))
|
||||||
}
|
}
|
||||||
case _ => Future.failed(new Error(s"Could not construct player id from `$player`"))
|
case _ => Future.failed(new Error(s"Could not construct player id from `$player`"))
|
||||||
@ -107,8 +107,8 @@ object BiSView {
|
|||||||
(for (player <- party) yield option(player.playerId.toString)),
|
(for (player <- party) yield option(player.playerId.toString)),
|
||||||
select(name:="piece", id:="piece", title:="piece")
|
select(name:="piece", id:="piece", title:="piece")
|
||||||
(for (piece <- Piece.available) yield option(piece)),
|
(for (piece <- Piece.available) yield option(piece)),
|
||||||
input(name:="is_tome", id:="is_tome", title:="is tome", `type`:="checkbox"),
|
select(name:="piece_type", id:="piece_type", title:="piece type")
|
||||||
label(`for`:="is_tome")("is tome gear"),
|
(for (pieceType <- PieceType.available) yield option(pieceType.toString)),
|
||||||
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")
|
||||||
),
|
),
|
||||||
@ -125,18 +125,18 @@ object BiSView {
|
|||||||
tr(
|
tr(
|
||||||
th("player"),
|
th("player"),
|
||||||
th("piece"),
|
th("piece"),
|
||||||
th("is tome"),
|
th("piece type"),
|
||||||
th("")
|
th("")
|
||||||
),
|
),
|
||||||
for (player <- party; piece <- player.bis.pieces) yield tr(
|
for (player <- party; piece <- player.bis.pieces) yield tr(
|
||||||
td(`class`:="include_search")(player.playerId.toString),
|
td(`class`:="include_search")(player.playerId.toString),
|
||||||
td(`class`:="include_search")(piece.piece),
|
td(`class`:="include_search")(piece.piece),
|
||||||
td(piece.isTomeToString),
|
td(piece.pieceType.toString),
|
||||||
td(
|
td(
|
||||||
form(action:=s"/party/$partyId/bis", method:="post")(
|
form(action:=s"/party/$partyId/bis", 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:=piece.piece),
|
input(name:="piece", id:="piece", `type`:="hidden", value:=piece.piece),
|
||||||
input(name:="is_tome", id:="is_tome", `type`:="hidden", value:=piece.isTomeToString),
|
input(name:="piece_type", id:="piece_type", `type`:="hidden", value:=piece.pieceType.toString),
|
||||||
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")
|
||||||
)
|
)
|
||||||
|
@ -14,7 +14,7 @@ import akka.http.scaladsl.server.Directives._
|
|||||||
import akka.http.scaladsl.server.Route
|
import akka.http.scaladsl.server.Route
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
import me.arcanis.ffxivbis.http.{Authorization, LootHelper}
|
import me.arcanis.ffxivbis.http.{Authorization, LootHelper}
|
||||||
import me.arcanis.ffxivbis.models.{Job, Piece, PlayerIdWithCounters}
|
import me.arcanis.ffxivbis.models.{Job, Piece, PieceType, PlayerIdWithCounters}
|
||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
import scala.util.{Failure, Success, Try}
|
import scala.util.{Failure, Success, Try}
|
||||||
@ -43,9 +43,8 @@ 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], "is_tome".as[String].?) { (piece, job, maybeTome) =>
|
formFields("piece".as[String], "job".as[String], "piece_type".as[String]) { (piece, job, pieceType) =>
|
||||||
import me.arcanis.ffxivbis.utils.Implicits._
|
val maybePiece = Try(Piece(piece, PieceType.withName(pieceType), Job.withName(job))).toOption
|
||||||
val maybePiece = Try(Piece(piece, maybeTome, Job.withName(job))).toOption
|
|
||||||
|
|
||||||
onComplete(suggestLootCall(partyId, maybePiece)) {
|
onComplete(suggestLootCall(partyId, maybePiece)) {
|
||||||
case Success(players) =>
|
case Success(players) =>
|
||||||
@ -92,8 +91,8 @@ object LootSuggestView {
|
|||||||
(for (piece <- Piece.available) yield option(piece)),
|
(for (piece <- Piece.available) yield option(piece)),
|
||||||
select(name:="job", id:="job", title:="job")
|
select(name:="job", id:="job", title:="job")
|
||||||
(for (job <- Job.availableWithAnyJob) yield option(job.toString)),
|
(for (job <- Job.availableWithAnyJob) yield option(job.toString)),
|
||||||
input(name:="is_tome", id:="is_tome", title:="is tome", `type`:="checkbox"),
|
select(name:="piece_type", id:="piece_type", title:="piece type")
|
||||||
label(`for`:="is_tome")("is tome gear"),
|
(for (pieceType <- PieceType.available) yield option(pieceType.toString)),
|
||||||
input(name:="suggest", id:="suggest", `type`:="submit", value:="suggest")
|
input(name:="suggest", id:="suggest", `type`:="submit", value:="suggest")
|
||||||
),
|
),
|
||||||
|
|
||||||
@ -116,7 +115,7 @@ object LootSuggestView {
|
|||||||
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:=piece.map(_.piece).getOrElse("")),
|
input(name:="piece", id:="piece", `type`:="hidden", value:=piece.map(_.piece).getOrElse("")),
|
||||||
input(name:="is_tome", id:="is_tome", `type`:="hidden", value:=piece.map(_.isTomeToString).getOrElse("")),
|
input(name:="piece_type", id:="piece_type", `type`:="hidden", value:=piece.map(_.pieceType.toString).getOrElse("")),
|
||||||
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")
|
||||||
)
|
)
|
||||||
|
@ -14,7 +14,7 @@ import akka.http.scaladsl.server.Directives._
|
|||||||
import akka.http.scaladsl.server.Route
|
import akka.http.scaladsl.server.Route
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
import me.arcanis.ffxivbis.http.{Authorization, LootHelper}
|
import me.arcanis.ffxivbis.http.{Authorization, LootHelper}
|
||||||
import me.arcanis.ffxivbis.models.{Piece, Player, PlayerId}
|
import me.arcanis.ffxivbis.models.{Piece, PieceType, Player, PlayerId}
|
||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
@ -46,10 +46,10 @@ 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], "is_tome".as[String].?, "action".as[String]) {
|
formFields("player".as[String], "piece".as[String], "piece_type".as[String], "action".as[String]) {
|
||||||
(player, maybePiece, maybeIsTome, action) =>
|
(player, piece, pieceType, action) =>
|
||||||
onComplete(modifyLootCall(partyId, player, maybePiece, maybeIsTome, action)) {
|
onComplete(modifyLootCall(partyId, player, piece, pieceType, action)) { _ =>
|
||||||
case _ => redirect(s"/party/$partyId/loot", StatusCodes.Found)
|
redirect(s"/party/$partyId/loot", StatusCodes.Found)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,20 +57,17 @@ class LootView (override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def modifyLootCall(partyId: String, player: String,
|
private def modifyLootCall(partyId: String, player: String, maybePiece: String,
|
||||||
maybePiece: String, maybeIsTome: Option[String],
|
maybePieceType: String, action: 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, maybeIsTome, 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).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`"))
|
case _ => Future.failed(new Error(s"Could not construct piece from `$maybePiece ($maybePieceType)`"))
|
||||||
}
|
}
|
||||||
case _ => Future.failed(new Error(s"Could not construct player id from `$player`"))
|
case _ => Future.failed(new Error(s"Could not construct player id from `$player`"))
|
||||||
}
|
}
|
||||||
@ -100,8 +97,8 @@ object LootView {
|
|||||||
(for (player <- party) yield option(player.playerId.toString)),
|
(for (player <- party) yield option(player.playerId.toString)),
|
||||||
select(name:="piece", id:="piece", title:="piece")
|
select(name:="piece", id:="piece", title:="piece")
|
||||||
(for (piece <- Piece.available) yield option(piece)),
|
(for (piece <- Piece.available) yield option(piece)),
|
||||||
input(name:="is_tome", id:="is_tome", title:="is tome", `type`:="checkbox"),
|
select(name:="piece_type", id:="piece_type", title:="piece type")
|
||||||
label(`for`:="is_tome")("is tome gear"),
|
(for (pieceType <- PieceType.available) yield option(pieceType.toString)),
|
||||||
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")
|
||||||
),
|
),
|
||||||
@ -110,20 +107,20 @@ object LootView {
|
|||||||
tr(
|
tr(
|
||||||
th("player"),
|
th("player"),
|
||||||
th("piece"),
|
th("piece"),
|
||||||
th("is tome"),
|
th("piece type"),
|
||||||
th("timestamp"),
|
th("timestamp"),
|
||||||
th("")
|
th("")
|
||||||
),
|
),
|
||||||
for (player <- party; loot <- player.loot) yield tr(
|
for (player <- party; loot <- player.loot) yield tr(
|
||||||
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.isTomeToString),
|
td(loot.piece.pieceType.toString),
|
||||||
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:="is_tome", id:="is_tome", `type`:="hidden", value:=loot.piece.isTomeToString),
|
input(name:="piece_type", id:="piece_type", `type`:="hidden", value:=loot.piece.pieceType.toString),
|
||||||
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")
|
||||||
)
|
)
|
||||||
|
@ -101,9 +101,9 @@ object Job {
|
|||||||
lazy val availableWithAnyJob: Seq[Job] = available.prepended(AnyJob)
|
lazy val availableWithAnyJob: Seq[Job] = available.prepended(AnyJob)
|
||||||
|
|
||||||
def withName(job: String): Job.Job =
|
def withName(job: String): Job.Job =
|
||||||
availableWithAnyJob.find(_.toString.equalsIgnoreCase(job.toUpperCase)) match {
|
availableWithAnyJob.find(_.toString.equalsIgnoreCase(job)) match {
|
||||||
case Some(value) => value
|
case Some(value) => value
|
||||||
case None if job.isEmpty => AnyJob
|
case None if job.isEmpty => AnyJob
|
||||||
case _ => throw new IllegalArgumentException("Invalid or unknown job")
|
case _ => throw new IllegalArgumentException(s"Invalid or unknown job $job")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,15 +9,14 @@
|
|||||||
package me.arcanis.ffxivbis.models
|
package me.arcanis.ffxivbis.models
|
||||||
|
|
||||||
sealed trait Piece {
|
sealed trait Piece {
|
||||||
def isTome: Boolean
|
def pieceType: PieceType.PieceType
|
||||||
def job: Job.Job
|
def job: Job.Job
|
||||||
def piece: String
|
def piece: String
|
||||||
|
|
||||||
def withJob(other: Job.Job): Piece
|
def withJob(other: Job.Job): Piece
|
||||||
|
|
||||||
def isTomeToString: String = if (isTome) "yes" else "no"
|
|
||||||
def upgrade: Option[PieceUpgrade] = this match {
|
def upgrade: Option[PieceUpgrade] = this match {
|
||||||
case _ if !isTome => None
|
case _ if pieceType != PieceType.Tome => None
|
||||||
case _: Waist => Some(AccessoryUpgrade)
|
case _: Waist => Some(AccessoryUpgrade)
|
||||||
case _: PieceAccessory => Some(AccessoryUpgrade)
|
case _: PieceAccessory => Some(AccessoryUpgrade)
|
||||||
case _: PieceBody => Some(BodyUpgrade)
|
case _: PieceBody => Some(BodyUpgrade)
|
||||||
@ -28,59 +27,59 @@ sealed trait Piece {
|
|||||||
trait PieceAccessory extends Piece
|
trait PieceAccessory extends Piece
|
||||||
trait PieceBody extends Piece
|
trait PieceBody extends Piece
|
||||||
trait PieceUpgrade extends Piece {
|
trait PieceUpgrade extends Piece {
|
||||||
val isTome: Boolean = true
|
val pieceType: PieceType.PieceType = PieceType.Tome
|
||||||
val job: Job.Job = Job.AnyJob
|
val job: Job.Job = Job.AnyJob
|
||||||
def withJob(other: Job.Job): Piece = this
|
def withJob(other: Job.Job): Piece = this
|
||||||
}
|
}
|
||||||
trait PieceWeapon extends Piece
|
trait PieceWeapon extends Piece
|
||||||
|
|
||||||
case class Weapon(override val isTome: Boolean, override val job: Job.Job) extends PieceWeapon {
|
case class Weapon(override val pieceType: PieceType.PieceType, override val job: Job.Job) extends PieceWeapon {
|
||||||
val piece: String = "weapon"
|
val piece: String = "weapon"
|
||||||
def withJob(other: Job.Job): Piece = copy(job = other)
|
def withJob(other: Job.Job): Piece = copy(job = other)
|
||||||
}
|
}
|
||||||
|
|
||||||
case class Head(override val isTome: Boolean, override val job: Job.Job) extends PieceBody {
|
case class Head(override val pieceType: PieceType.PieceType, override val job: Job.Job) extends PieceBody {
|
||||||
val piece: String = "head"
|
val piece: String = "head"
|
||||||
def withJob(other: Job.Job): Piece = copy(job = other)
|
def withJob(other: Job.Job): Piece = copy(job = other)
|
||||||
}
|
}
|
||||||
case class Body(override val isTome: Boolean, override val job: Job.Job) extends PieceBody {
|
case class Body(override val pieceType: PieceType.PieceType, override val job: Job.Job) extends PieceBody {
|
||||||
val piece: String = "body"
|
val piece: String = "body"
|
||||||
def withJob(other: Job.Job): Piece = copy(job = other)
|
def withJob(other: Job.Job): Piece = copy(job = other)
|
||||||
}
|
}
|
||||||
case class Hands(override val isTome: Boolean, override val job: Job.Job) extends PieceBody {
|
case class Hands(override val pieceType: PieceType.PieceType, override val job: Job.Job) extends PieceBody {
|
||||||
val piece: String = "hands"
|
val piece: String = "hands"
|
||||||
def withJob(other: Job.Job): Piece = copy(job = other)
|
def withJob(other: Job.Job): Piece = copy(job = other)
|
||||||
}
|
}
|
||||||
case class Waist(override val isTome: Boolean, override val job: Job.Job) extends PieceBody {
|
case class Waist(override val pieceType: PieceType.PieceType, override val job: Job.Job) extends PieceBody {
|
||||||
val piece: String = "waist"
|
val piece: String = "waist"
|
||||||
def withJob(other: Job.Job): Piece = copy(job = other)
|
def withJob(other: Job.Job): Piece = copy(job = other)
|
||||||
}
|
}
|
||||||
case class Legs(override val isTome: Boolean, override val job: Job.Job) extends PieceBody {
|
case class Legs(override val pieceType: PieceType.PieceType, override val job: Job.Job) extends PieceBody {
|
||||||
val piece: String = "legs"
|
val piece: String = "legs"
|
||||||
def withJob(other: Job.Job): Piece = copy(job = other)
|
def withJob(other: Job.Job): Piece = copy(job = other)
|
||||||
}
|
}
|
||||||
case class Feet(override val isTome: Boolean, override val job: Job.Job) extends PieceBody {
|
case class Feet(override val pieceType: PieceType.PieceType, override val job: Job.Job) extends PieceBody {
|
||||||
val piece: String = "feet"
|
val piece: String = "feet"
|
||||||
def withJob(other: Job.Job): Piece = copy(job = other)
|
def withJob(other: Job.Job): Piece = copy(job = other)
|
||||||
}
|
}
|
||||||
|
|
||||||
case class Ears(override val isTome: Boolean, override val job: Job.Job) extends PieceAccessory {
|
case class Ears(override val pieceType: PieceType.PieceType, override val job: Job.Job) extends PieceAccessory {
|
||||||
val piece: String = "ears"
|
val piece: String = "ears"
|
||||||
def withJob(other: Job.Job): Piece = copy(job = other)
|
def withJob(other: Job.Job): Piece = copy(job = other)
|
||||||
}
|
}
|
||||||
case class Neck(override val isTome: Boolean, override val job: Job.Job) extends PieceAccessory {
|
case class Neck(override val pieceType: PieceType.PieceType, override val job: Job.Job) extends PieceAccessory {
|
||||||
val piece: String = "neck"
|
val piece: String = "neck"
|
||||||
def withJob(other: Job.Job): Piece = copy(job = other)
|
def withJob(other: Job.Job): Piece = copy(job = other)
|
||||||
}
|
}
|
||||||
case class Wrist(override val isTome: Boolean, override val job: Job.Job) extends PieceAccessory {
|
case class Wrist(override val pieceType: PieceType.PieceType, override val job: Job.Job) extends PieceAccessory {
|
||||||
val piece: String = "wrist"
|
val piece: String = "wrist"
|
||||||
def withJob(other: Job.Job): Piece = copy(job = other)
|
def withJob(other: Job.Job): Piece = copy(job = other)
|
||||||
}
|
}
|
||||||
case class Ring(override val isTome: Boolean, 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(thatIsTome, thatJob, _) => (thatIsTome == isTome) && (thatJob == job)
|
case Ring(thatPieceType, thatJob, _) => (thatPieceType == pieceType) && (thatJob == job)
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,19 +95,19 @@ case object WeaponUpgrade extends PieceUpgrade {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object Piece {
|
object Piece {
|
||||||
def apply(piece: String, isTome: Boolean, job: Job.Job = Job.AnyJob): Piece =
|
def apply(piece: String, pieceType: PieceType.PieceType, job: Job.Job = Job.AnyJob): Piece =
|
||||||
piece.toLowerCase match {
|
piece.toLowerCase match {
|
||||||
case "weapon" => Weapon(isTome, job)
|
case "weapon" => Weapon(pieceType, job)
|
||||||
case "head" => Head(isTome, job)
|
case "head" => Head(pieceType, job)
|
||||||
case "body" => Body(isTome, job)
|
case "body" => Body(pieceType, job)
|
||||||
case "hands" => Hands(isTome, job)
|
case "hands" => Hands(pieceType, job)
|
||||||
case "waist" => Waist(isTome, job)
|
case "waist" => Waist(pieceType, job)
|
||||||
case "legs" => Legs(isTome, job)
|
case "legs" => Legs(pieceType, job)
|
||||||
case "feet" => Feet(isTome, job)
|
case "feet" => Feet(pieceType, job)
|
||||||
case "ears" => Ears(isTome, job)
|
case "ears" => Ears(pieceType, job)
|
||||||
case "neck" => Neck(isTome, job)
|
case "neck" => Neck(pieceType, job)
|
||||||
case "wrist" => Wrist(isTome, job)
|
case "wrist" => Wrist(pieceType, job)
|
||||||
case ring @ ("ring" | "left ring" | "right ring") => Ring(isTome, job, ring)
|
case ring @ ("ring" | "left ring" | "right ring") => Ring(pieceType, job, ring)
|
||||||
case "accessory upgrade" => AccessoryUpgrade
|
case "accessory upgrade" => AccessoryUpgrade
|
||||||
case "body upgrade" => BodyUpgrade
|
case "body upgrade" => BodyUpgrade
|
||||||
case "weapon upgrade" => WeaponUpgrade
|
case "weapon upgrade" => WeaponUpgrade
|
||||||
|
18
src/main/scala/me/arcanis/ffxivbis/models/PieceType.scala
Normal file
18
src/main/scala/me/arcanis/ffxivbis/models/PieceType.scala
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package me.arcanis.ffxivbis.models
|
||||||
|
|
||||||
|
object PieceType {
|
||||||
|
sealed trait PieceType
|
||||||
|
|
||||||
|
case object Crafted extends PieceType
|
||||||
|
case object Tome extends PieceType
|
||||||
|
case object Savage extends PieceType
|
||||||
|
|
||||||
|
lazy val available: Seq[PieceType] =
|
||||||
|
Seq(Crafted, Tome, Savage)
|
||||||
|
|
||||||
|
def withName(pieceType: String): PieceType =
|
||||||
|
available.find(_.toString.equalsIgnoreCase(pieceType)) match {
|
||||||
|
case Some(value) => value
|
||||||
|
case _ => throw new IllegalArgumentException(s"Invalid or unknown piece type $pieceType")
|
||||||
|
}
|
||||||
|
}
|
@ -46,7 +46,7 @@ case class Player(id: Long,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def bisCountTotal(piece: Option[Piece]): Int = bis.pieces.count(!_.isTome)
|
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(_.piece == p)
|
||||||
case None => lootCountTotal(piece)
|
case None => lootCountTotal(piece)
|
||||||
|
@ -18,7 +18,7 @@ import akka.stream.ActorMaterializer
|
|||||||
import akka.stream.scaladsl.{Keep, Sink}
|
import akka.stream.scaladsl.{Keep, Sink}
|
||||||
import akka.util.ByteString
|
import akka.util.ByteString
|
||||||
import com.typesafe.scalalogging.StrictLogging
|
import com.typesafe.scalalogging.StrictLogging
|
||||||
import me.arcanis.ffxivbis.models.{BiS, Job, Piece}
|
import me.arcanis.ffxivbis.models.{BiS, Job, Piece, PieceType}
|
||||||
import spray.json._
|
import spray.json._
|
||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
@ -54,10 +54,10 @@ class Ariyala extends Actor with StrictLogging {
|
|||||||
.withPath(Uri.Path / "store.app")
|
.withPath(Uri.Path / "store.app")
|
||||||
.withQuery(Uri.Query(Map("identifier" -> id)))
|
.withQuery(Uri.Query(Map("identifier" -> id)))
|
||||||
|
|
||||||
sendRequest(uri, Ariyala.parseAriyalaJsonToPieces(job, getIsTome))
|
sendRequest(uri, Ariyala.parseAriyalaJsonToPieces(job, getPieceType))
|
||||||
}
|
}
|
||||||
|
|
||||||
private def getIsTome(itemIds: Seq[Long]): Future[Map[Long, Boolean]] = {
|
private def getPieceType(itemIds: Seq[Long]): Future[Map[Long, PieceType.PieceType]] = {
|
||||||
val uriForItems = Uri(xivapiUrl)
|
val uriForItems = Uri(xivapiUrl)
|
||||||
.withPath(Uri.Path / "item")
|
.withPath(Uri.Path / "item")
|
||||||
.withQuery(Uri.Query(Map(
|
.withQuery(Uri.Query(Map(
|
||||||
@ -77,7 +77,7 @@ class Ariyala extends Actor with StrictLogging {
|
|||||||
"private_key" -> xivapiKey.getOrElse("")
|
"private_key" -> xivapiKey.getOrElse("")
|
||||||
)))
|
)))
|
||||||
|
|
||||||
sendRequest(uriForShops, Ariyala.parseXivapiJsonToTome(shops))
|
sendRequest(uriForShops, Ariyala.parseXivapiJsonToType(shops))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,28 +120,34 @@ object Ariyala {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def parseAriyalaJsonToPieces(job: Job.Job, isTome: Seq[Long] => Future[Map[Long, Boolean]])(js: JsObject)
|
private def parseAriyalaJsonToPieces(job: Job.Job, pieceTypes: Seq[Long] => Future[Map[Long, PieceType.PieceType]])
|
||||||
|
(js: JsObject)
|
||||||
(implicit executionContext: ExecutionContext): Future[Seq[Piece]] =
|
(implicit executionContext: ExecutionContext): Future[Seq[Piece]] =
|
||||||
parseAriyalaJson(job)(js).flatMap { pieces =>
|
parseAriyalaJson(job)(js).flatMap { pieces =>
|
||||||
isTome(pieces.values.toSeq).map { tomePieces =>
|
pieceTypes(pieces.values.toSeq).map { types =>
|
||||||
pieces.view.mapValues(tomePieces).map {
|
pieces.view.mapValues(types).map {
|
||||||
case (piece, isTomePiece) => Piece(piece, isTomePiece, job)
|
case (piece, pieceType) => Piece(piece, pieceType, job)
|
||||||
}.toSeq
|
}.toSeq
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def parseXivapiJsonToShop(js: JsObject)
|
private def parseXivapiJsonToShop(js: JsObject)
|
||||||
(implicit executionContext: ExecutionContext): Future[Map[Long, (String, Long)]] = {
|
(implicit executionContext: ExecutionContext): Future[Map[Long, (String, Long)]] = {
|
||||||
def extractTraderId(js: JsObject) =
|
def extractTraderId(js: JsObject) = {
|
||||||
js.fields("SpecialShop").asJsObject
|
js.fields
|
||||||
.fields.collectFirst {
|
.get("Recipe").map(_ => "crafted" -> -1L) // you can craft this item
|
||||||
case (shopName, JsArray(array)) if shopName.startsWith("ItemReceive") =>
|
.orElse { // lets try shop items
|
||||||
val shopId = array.head match {
|
js.fields("SpecialShop").asJsObject
|
||||||
case JsNumber(id) => id.toLong
|
.fields.collectFirst {
|
||||||
case other => throw deserializationError(s"Could not parse $other")
|
case (shopName, JsArray(array)) if shopName.startsWith("ItemReceive") =>
|
||||||
|
val shopId = array.head match {
|
||||||
|
case JsNumber(id) => id.toLong
|
||||||
|
case other => throw deserializationError(s"Could not parse $other")
|
||||||
|
}
|
||||||
|
shopName.replace("ItemReceive", "") -> shopId
|
||||||
}
|
}
|
||||||
(shopName.replace("ItemReceive", ""), shopId)
|
}.getOrElse(throw deserializationError(s"Could not parse $js"))
|
||||||
}.getOrElse(throw deserializationError(s"Could not parse $js"))
|
}
|
||||||
|
|
||||||
Future {
|
Future {
|
||||||
js.fields("Results") match {
|
js.fields("Results") match {
|
||||||
@ -155,8 +161,8 @@ object Ariyala {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def parseXivapiJsonToTome(shops: Map[Long, (String, Long)])(js: JsObject)
|
private def parseXivapiJsonToType(shops: Map[Long, (String, Long)])(js: JsObject)
|
||||||
(implicit executionContext: ExecutionContext): Future[Map[Long, Boolean]] =
|
(implicit executionContext: ExecutionContext): Future[Map[Long, PieceType.PieceType]] =
|
||||||
Future {
|
Future {
|
||||||
val shopMap = js.fields("Results") match {
|
val shopMap = js.fields("Results") match {
|
||||||
case array: JsArray =>
|
case array: JsArray =>
|
||||||
@ -170,14 +176,20 @@ object Ariyala {
|
|||||||
}
|
}
|
||||||
|
|
||||||
shops.map { case (itemId, (index, shopId)) =>
|
shops.map { case (itemId, (index, shopId)) =>
|
||||||
val isTome = Try(shopMap(shopId).fields(s"ItemCost$index").asJsObject).toOption.getOrElse(throw new Exception(s"${shopMap(shopId).fields(s"ItemCost$index")}, $index"))
|
val pieceType =
|
||||||
.getFields("IsUnique", "StackSize") match {
|
if (index == "crafted" && shopId == -1) PieceType.Crafted
|
||||||
case Seq(JsNumber(isUnique), JsNumber(stackSize)) =>
|
else {
|
||||||
// either upgraded gear or tomes found
|
Try(shopMap(shopId).fields(s"ItemCost$index").asJsObject)
|
||||||
isUnique == 1 || stackSize.toLong == 2000
|
.toOption
|
||||||
case other => throw deserializationError(s"Could not parse $other")
|
.getOrElse(throw new Exception(s"${shopMap(shopId).fields(s"ItemCost$index")}, $index"))
|
||||||
}
|
.getFields("IsUnique", "StackSize") match {
|
||||||
itemId -> isTome
|
case Seq(JsNumber(isUnique), JsNumber(stackSize)) =>
|
||||||
|
if (isUnique == 1 || stackSize.toLong == 2000) PieceType.Tome // either upgraded gear or tomes found
|
||||||
|
else PieceType.Savage
|
||||||
|
case other => throw deserializationError(s"Could not parse $other")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
itemId -> pieceType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ package me.arcanis.ffxivbis.storage
|
|||||||
|
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
import me.arcanis.ffxivbis.models.{Job, Loot, Piece}
|
import me.arcanis.ffxivbis.models.{Job, Loot, Piece, PieceType}
|
||||||
import slick.lifted.ForeignKeyQuery
|
import slick.lifted.ForeignKeyQuery
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
@ -18,24 +18,27 @@ import scala.concurrent.Future
|
|||||||
trait BiSProfile { this: DatabaseProfile =>
|
trait BiSProfile { this: DatabaseProfile =>
|
||||||
import dbConfig.profile.api._
|
import dbConfig.profile.api._
|
||||||
|
|
||||||
case class BiSRep(playerId: Long, created: Long, piece: String, isTome: Int, job: String) {
|
case class BiSRep(playerId: Long, created: Long, piece: String,
|
||||||
def toLoot: Loot = Loot(playerId, Piece(piece, isTome == 1, Job.withName(job)), Instant.ofEpochMilli(created))
|
pieceType: String, job: String) {
|
||||||
|
def toLoot: Loot = Loot(
|
||||||
|
playerId, Piece(piece, PieceType.withName(pieceType), Job.withName(job)),
|
||||||
|
Instant.ofEpochMilli(created))
|
||||||
}
|
}
|
||||||
object BiSRep {
|
object BiSRep {
|
||||||
def fromPiece(playerId: Long, piece: Piece) =
|
def fromPiece(playerId: Long, piece: Piece): BiSRep =
|
||||||
BiSRep(playerId, DatabaseProfile.now, piece.piece, if (piece.isTome) 1 else 0,
|
BiSRep(playerId, DatabaseProfile.now, piece.piece,
|
||||||
piece.job.toString)
|
piece.pieceType.toString, piece.job.toString)
|
||||||
}
|
}
|
||||||
|
|
||||||
class BiSPieces(tag: Tag) extends Table[BiSRep](tag, "bis") {
|
class BiSPieces(tag: Tag) extends Table[BiSRep](tag, "bis") {
|
||||||
def playerId: Rep[Long] = column[Long]("player_id", O.PrimaryKey)
|
def playerId: Rep[Long] = column[Long]("player_id", O.PrimaryKey)
|
||||||
def created: Rep[Long] = column[Long]("created")
|
def created: Rep[Long] = column[Long]("created")
|
||||||
def piece: Rep[String] = column[String]("piece", O.PrimaryKey)
|
def piece: Rep[String] = column[String]("piece", O.PrimaryKey)
|
||||||
def isTome: Rep[Int] = column[Int]("is_tome")
|
def pieceType: Rep[String] = column[String]("piece_type")
|
||||||
def job: Rep[String] = column[String]("job")
|
def job: Rep[String] = column[String]("job")
|
||||||
|
|
||||||
def * =
|
def * =
|
||||||
(playerId, created, piece, isTome, job) <> ((BiSRep.apply _).tupled, BiSRep.unapply)
|
(playerId, created, piece, pieceType, job) <> ((BiSRep.apply _).tupled, BiSRep.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)
|
||||||
|
@ -10,7 +10,7 @@ package me.arcanis.ffxivbis.storage
|
|||||||
|
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
import me.arcanis.ffxivbis.models.{Job, Loot, Piece}
|
import me.arcanis.ffxivbis.models.{Job, Loot, Piece, PieceType}
|
||||||
import slick.lifted.{ForeignKeyQuery, Index}
|
import slick.lifted.{ForeignKeyQuery, Index}
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
@ -18,14 +18,17 @@ import scala.concurrent.Future
|
|||||||
trait LootProfile { this: DatabaseProfile =>
|
trait LootProfile { this: DatabaseProfile =>
|
||||||
import dbConfig.profile.api._
|
import dbConfig.profile.api._
|
||||||
|
|
||||||
case class LootRep(lootId: Option[Long], playerId: Long, created: Long, piece: String,
|
case class LootRep(lootId: Option[Long], playerId: Long, created: Long,
|
||||||
isTome: Int, job: String) {
|
piece: String, pieceType: String, job: String) {
|
||||||
def toLoot: Loot = Loot(playerId, Piece(piece, isTome == 1, Job.withName(job)), Instant.ofEpochMilli(created))
|
def toLoot: Loot = Loot(
|
||||||
|
playerId,
|
||||||
|
Piece(piece, PieceType.withName(pieceType), Job.withName(job)),
|
||||||
|
Instant.ofEpochMilli(created))
|
||||||
}
|
}
|
||||||
object LootRep {
|
object LootRep {
|
||||||
def fromPiece(playerId: Long, piece: Piece) =
|
def fromPiece(playerId: Long, piece: Piece): LootRep =
|
||||||
LootRep(None, playerId, DatabaseProfile.now, piece.piece, if (piece.isTome) 1 else 0,
|
LootRep(None, playerId, DatabaseProfile.now, piece.piece,
|
||||||
piece.job.toString)
|
piece.pieceType.toString, piece.job.toString)
|
||||||
}
|
}
|
||||||
|
|
||||||
class LootPieces(tag: Tag) extends Table[LootRep](tag, "loot") {
|
class LootPieces(tag: Tag) extends Table[LootRep](tag, "loot") {
|
||||||
@ -33,11 +36,11 @@ trait LootProfile { this: DatabaseProfile =>
|
|||||||
def playerId: Rep[Long] = column[Long]("player_id")
|
def playerId: Rep[Long] = column[Long]("player_id")
|
||||||
def created: Rep[Long] = column[Long]("created")
|
def created: Rep[Long] = column[Long]("created")
|
||||||
def piece: Rep[String] = column[String]("piece")
|
def piece: Rep[String] = column[String]("piece")
|
||||||
def isTome: Rep[Int] = column[Int]("is_tome")
|
def pieceType: Rep[String] = column[String]("piece_type")
|
||||||
def job: Rep[String] = column[String]("job")
|
def job: Rep[String] = column[String]("job")
|
||||||
|
|
||||||
def * =
|
def * =
|
||||||
(lootId.?, playerId, created, piece, isTome, job) <> ((LootRep.apply _).tupled, LootRep.unapply)
|
(lootId.?, playerId, created, piece, pieceType, job) <> ((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)
|
||||||
|
@ -15,7 +15,8 @@ import scala.concurrent.Future
|
|||||||
trait PartyProfile { this: DatabaseProfile =>
|
trait PartyProfile { this: DatabaseProfile =>
|
||||||
import dbConfig.profile.api._
|
import dbConfig.profile.api._
|
||||||
|
|
||||||
case class PartyRep(partyId: Option[Long], partyName: String, partyAlias: Option[String]) {
|
case class PartyRep(partyId: Option[Long], partyName: String,
|
||||||
|
partyAlias: Option[String]) {
|
||||||
def toDescription: PartyDescription = PartyDescription(partyName, partyAlias)
|
def toDescription: PartyDescription = PartyDescription(partyName, partyAlias)
|
||||||
}
|
}
|
||||||
object PartyRep {
|
object PartyRep {
|
||||||
|
@ -15,10 +15,11 @@ import scala.concurrent.Future
|
|||||||
trait PlayersProfile { this: DatabaseProfile =>
|
trait PlayersProfile { this: DatabaseProfile =>
|
||||||
import dbConfig.profile.api._
|
import dbConfig.profile.api._
|
||||||
|
|
||||||
case class PlayerRep(partyId: String, playerId: Option[Long], created: Long, nick: String,
|
case class PlayerRep(partyId: String, playerId: Option[Long], created: Long,
|
||||||
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, BiS(Seq.empty), List.empty, link, priority)
|
Player(playerId.getOrElse(-1), partyId, Job.withName(job), nick,
|
||||||
|
BiS(Seq.empty), List.empty, link, priority)
|
||||||
}
|
}
|
||||||
object PlayerRep {
|
object PlayerRep {
|
||||||
def fromPlayer(player: Player, id: Option[Long]): PlayerRep =
|
def fromPlayer(player: Player, id: Option[Long]): PlayerRep =
|
||||||
|
@ -16,8 +16,8 @@ import scala.concurrent.Future
|
|||||||
trait UsersProfile { this: DatabaseProfile =>
|
trait UsersProfile { this: DatabaseProfile =>
|
||||||
import dbConfig.profile.api._
|
import dbConfig.profile.api._
|
||||||
|
|
||||||
case class UserRep(partyId: String, userId: Option[Long], username: String, password: String,
|
case class UserRep(partyId: String, userId: Option[Long], username: String,
|
||||||
permission: String) {
|
password: String, permission: String) {
|
||||||
def toUser: User = User(partyId, username, password, Permission.withName(permission))
|
def toUser: User = User(partyId, username, password, Permission.withName(permission))
|
||||||
}
|
}
|
||||||
object UserRep {
|
object UserRep {
|
||||||
|
@ -5,33 +5,33 @@ import me.arcanis.ffxivbis.models._
|
|||||||
object Fixtures {
|
object Fixtures {
|
||||||
lazy val bis: BiS = BiS(
|
lazy val bis: BiS = BiS(
|
||||||
Seq(
|
Seq(
|
||||||
Weapon(isTome = false ,Job.DNC),
|
Weapon(pieceType = PieceType.Savage ,Job.DNC),
|
||||||
Head(isTome = false, Job.DNC),
|
Head(pieceType = PieceType.Savage, Job.DNC),
|
||||||
Body(isTome = false, Job.DNC),
|
Body(pieceType = PieceType.Savage, Job.DNC),
|
||||||
Hands(isTome = true, Job.DNC),
|
Hands(pieceType = PieceType.Tome, Job.DNC),
|
||||||
Waist(isTome = true, Job.DNC),
|
Waist(pieceType = PieceType.Tome, Job.DNC),
|
||||||
Legs(isTome = true, Job.DNC),
|
Legs(pieceType = PieceType.Tome, Job.DNC),
|
||||||
Feet(isTome = false, Job.DNC),
|
Feet(pieceType = PieceType.Savage, Job.DNC),
|
||||||
Ears(isTome = false, Job.DNC),
|
Ears(pieceType = PieceType.Savage, Job.DNC),
|
||||||
Neck(isTome = true, Job.DNC),
|
Neck(pieceType = PieceType.Tome, Job.DNC),
|
||||||
Wrist(isTome = false, Job.DNC),
|
Wrist(pieceType = PieceType.Savage, Job.DNC),
|
||||||
Ring(isTome = true, Job.DNC, "left ring"),
|
Ring(pieceType = PieceType.Tome, Job.DNC, "left ring"),
|
||||||
Ring(isTome = true, Job.DNC, "right ring")
|
Ring(pieceType = PieceType.Tome, Job.DNC, "right ring")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
lazy val link: String = "https://ffxiv.ariyala.com/19V5R"
|
lazy val link: String = "https://ffxiv.ariyala.com/19V5R"
|
||||||
lazy val link2: String = "https://ffxiv.ariyala.com/1A0WM"
|
lazy val link2: String = "https://ffxiv.ariyala.com/1A0WM"
|
||||||
|
|
||||||
lazy val lootWeapon: Piece = Weapon(isTome = true, Job.AnyJob)
|
lazy val lootWeapon: Piece = Weapon(pieceType = PieceType.Tome, Job.AnyJob)
|
||||||
lazy val lootBody: Piece = Body(isTome = false, Job.AnyJob)
|
lazy val lootBody: Piece = Body(pieceType = PieceType.Savage, Job.AnyJob)
|
||||||
lazy val lootHands: Piece = Hands(isTome = true, Job.AnyJob)
|
lazy val lootHands: Piece = Hands(pieceType = PieceType.Tome, Job.AnyJob)
|
||||||
lazy val lootWaist: Piece = Waist(isTome = true, Job.AnyJob)
|
lazy val lootWaist: Piece = Waist(pieceType = PieceType.Tome, Job.AnyJob)
|
||||||
lazy val lootLegs: Piece = Legs(isTome = false, Job.AnyJob)
|
lazy val lootLegs: Piece = Legs(pieceType = PieceType.Savage, Job.AnyJob)
|
||||||
lazy val lootEars: Piece = Ears(isTome = false, Job.AnyJob)
|
lazy val lootEars: Piece = Ears(pieceType = PieceType.Savage, Job.AnyJob)
|
||||||
lazy val lootRing: Piece = Ring(isTome = true, Job.AnyJob)
|
lazy val lootRing: Piece = Ring(pieceType = PieceType.Tome, Job.AnyJob)
|
||||||
lazy val lootLeftRing: Piece = Ring(isTome = true, Job.AnyJob, "left ring")
|
lazy val lootLeftRing: Piece = Ring(pieceType = PieceType.Tome, Job.AnyJob, "left ring")
|
||||||
lazy val lootRightRing: Piece = Ring(isTome = true, Job.AnyJob, "right ring")
|
lazy val lootRightRing: Piece = Ring(pieceType = PieceType.Tome, Job.AnyJob, "right ring")
|
||||||
lazy val lootUpgrade: Piece = BodyUpgrade
|
lazy val lootUpgrade: Piece = BodyUpgrade
|
||||||
lazy val loot: Seq[Piece] = Seq(lootBody, lootHands, lootLegs, lootUpgrade)
|
lazy val loot: Seq[Piece] = Seq(lootBody, lootHands, lootLegs, lootUpgrade)
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import akka.http.scaladsl.server._
|
|||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
import me.arcanis.ffxivbis.Settings
|
import me.arcanis.ffxivbis.Settings
|
||||||
import me.arcanis.ffxivbis.http.api.v1.json._
|
import me.arcanis.ffxivbis.http.api.v1.json._
|
||||||
import me.arcanis.ffxivbis.models.{Job, Party, Permission, Piece}
|
import me.arcanis.ffxivbis.models.{Job, Party, Permission, Piece, PieceType}
|
||||||
import org.scalatest.{Matchers, WordSpec}
|
import org.scalatest.{Matchers, WordSpec}
|
||||||
|
|
||||||
import scala.language.postfixOps
|
import scala.language.postfixOps
|
||||||
@ -41,6 +41,13 @@ class TypesEndpointTest extends WordSpec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"return all available piece types" in {
|
||||||
|
Get("/types/pieces/types") ~> route ~> check {
|
||||||
|
status shouldEqual StatusCodes.OK
|
||||||
|
responseAs[Seq[String]] shouldEqual PieceType.available.map(_.toString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
"return current priority" in {
|
"return current priority" in {
|
||||||
Get("/types/priority") ~> route ~> check {
|
Get("/types/priority") ~> route ~> check {
|
||||||
status shouldEqual StatusCodes.OK
|
status shouldEqual StatusCodes.OK
|
||||||
|
@ -7,11 +7,6 @@ class PieceTest extends WordSpecLike with Matchers with BeforeAndAfterAll {
|
|||||||
|
|
||||||
"piece model" must {
|
"piece model" must {
|
||||||
|
|
||||||
"convert `isTome` property to string" in {
|
|
||||||
Fixtures.lootBody.isTomeToString shouldEqual "no"
|
|
||||||
Fixtures.lootHands.isTomeToString shouldEqual "yes"
|
|
||||||
}
|
|
||||||
|
|
||||||
"return upgrade" in {
|
"return upgrade" in {
|
||||||
Fixtures.lootWeapon.upgrade shouldEqual Some(WeaponUpgrade)
|
Fixtures.lootWeapon.upgrade shouldEqual Some(WeaponUpgrade)
|
||||||
Fixtures.lootBody.upgrade shouldEqual None
|
Fixtures.lootBody.upgrade shouldEqual None
|
||||||
@ -25,7 +20,7 @@ class PieceTest extends WordSpecLike with Matchers with BeforeAndAfterAll {
|
|||||||
|
|
||||||
"build piece from string" in {
|
"build piece from string" in {
|
||||||
Fixtures.bis.pieces.foreach { piece =>
|
Fixtures.bis.pieces.foreach { piece =>
|
||||||
Piece(piece.piece, piece.isTome, piece.job) shouldEqual piece
|
Piece(piece.piece, piece.pieceType, piece.job) shouldEqual piece
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package me.arcanis.ffxivbis.models
|
||||||
|
|
||||||
|
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
|
||||||
|
|
||||||
|
class PieceTypeTest extends WordSpecLike with Matchers with BeforeAndAfterAll {
|
||||||
|
|
||||||
|
"piece type model" must {
|
||||||
|
|
||||||
|
"create piece type from string" in {
|
||||||
|
PieceType.available.foreach { pieceType =>
|
||||||
|
PieceType.withName(pieceType.toString) shouldEqual pieceType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"fail on unknown piece type" in {
|
||||||
|
an [IllegalArgumentException] should be thrownBy PieceType.withName("random string")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -65,7 +65,7 @@ class DatabaseBiSHandlerTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
"update piece in bis set" in {
|
"update piece in bis set" in {
|
||||||
val newPiece = Hands(isTome = false, Job.DNC)
|
val newPiece = Hands(pieceType = PieceType.Savage, Job.DNC)
|
||||||
|
|
||||||
database ! impl.DatabaseBiSHandler.AddPieceToBis(Fixtures.playerEmpty.playerId, newPiece)
|
database ! impl.DatabaseBiSHandler.AddPieceToBis(Fixtures.playerEmpty.playerId, newPiece)
|
||||||
expectMsg(timeout, 1)
|
expectMsg(timeout, 1)
|
||||||
|
@ -37,11 +37,11 @@ class LootSelectorTest extends TestKit(ActorSystem("lootselector"))
|
|||||||
"loot selector" must {
|
"loot selector" must {
|
||||||
|
|
||||||
"suggest loot by isRequired" in {
|
"suggest loot by isRequired" in {
|
||||||
toPlayerId(default.suggestLoot(Head(isTome = false, Job.AnyJob))) shouldEqual Seq(dnc.playerId, drg.playerId)
|
toPlayerId(default.suggestLoot(Head(pieceType = PieceType.Savage, Job.AnyJob))) shouldEqual Seq(dnc.playerId, drg.playerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
"suggest loot if a player already have it" in {
|
"suggest loot if a player already have it" in {
|
||||||
val piece = Body(isTome = false, Job.AnyJob)
|
val piece = Body(pieceType = PieceType.Savage, Job.AnyJob)
|
||||||
val party = default.withPlayer(dnc.withLoot(piece))
|
val party = default.withPlayer(dnc.withLoot(piece))
|
||||||
|
|
||||||
toPlayerId(party.suggestLoot(piece)) shouldEqual Seq(drg.playerId, dnc.playerId)
|
toPlayerId(party.suggestLoot(piece)) shouldEqual Seq(drg.playerId, dnc.playerId)
|
||||||
@ -50,7 +50,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(isTome = true, Job.DNC))))
|
Some(dnc.bis.copy(weapon = Some(Weapon(pieceType = PieceType.Tome, Job.DNC))))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -60,24 +60,24 @@ class LootSelectorTest extends TestKit(ActorSystem("lootselector"))
|
|||||||
"suggest loot by priority" in {
|
"suggest loot by priority" in {
|
||||||
val party = default.withPlayer(dnc.copy(priority = 2))
|
val party = default.withPlayer(dnc.copy(priority = 2))
|
||||||
|
|
||||||
toPlayerId(party.suggestLoot(Body(isTome = false, Job.AnyJob))) shouldEqual Seq(drg.playerId, dnc.playerId)
|
toPlayerId(party.suggestLoot(Body(pieceType = PieceType.Savage, Job.AnyJob))) shouldEqual Seq(drg.playerId, dnc.playerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
"suggest loot by bis pieces got" in {
|
"suggest loot by bis pieces got" in {
|
||||||
val party = default.withPlayer(dnc.withLoot(Head(isTome = false, Job.AnyJob)))
|
val party = default.withPlayer(dnc.withLoot(Head(pieceType = PieceType.Savage, Job.AnyJob)))
|
||||||
|
|
||||||
toPlayerId(party.suggestLoot(Body(isTome = false, Job.AnyJob))) shouldEqual Seq(drg.playerId, dnc.playerId)
|
toPlayerId(party.suggestLoot(Body(pieceType = PieceType.Savage, Job.AnyJob))) shouldEqual Seq(drg.playerId, dnc.playerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
"suggest loot by this piece got" in {
|
"suggest loot by this piece got" in {
|
||||||
val piece = Body(isTome = true, Job.AnyJob)
|
val piece = Body(pieceType = PieceType.Tome, Job.AnyJob)
|
||||||
val party = default.withPlayer(dnc.withLoot(piece))
|
val party = default.withPlayer(dnc.withLoot(piece))
|
||||||
|
|
||||||
toPlayerId(party.suggestLoot(piece)) shouldEqual Seq(drg.playerId, dnc.playerId)
|
toPlayerId(party.suggestLoot(piece)) shouldEqual Seq(drg.playerId, dnc.playerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
"suggest loot by total piece got" in {
|
"suggest loot by total piece got" in {
|
||||||
val piece = Body(isTome = true, Job.AnyJob)
|
val piece = Body(pieceType = PieceType.Tome, Job.AnyJob)
|
||||||
val party = default
|
val party = default
|
||||||
.withPlayer(dnc.withLoot(Seq(piece, piece).map(pieceToLoot)))
|
.withPlayer(dnc.withLoot(Seq(piece, piece).map(pieceToLoot)))
|
||||||
.withPlayer(drg.withLoot(piece))
|
.withPlayer(drg.withLoot(piece))
|
||||||
|
Loading…
Reference in New Issue
Block a user