mirror of
https://github.com/arcan1s/ffxivbis.git
synced 2025-04-24 17:27:17 +00:00
add bisview
This commit is contained in:
parent
49fd33fffc
commit
eea2f1b04b
@ -9,14 +9,15 @@ scalacOptions ++= Seq("-deprecation", "-feature")
|
|||||||
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3"
|
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3"
|
||||||
libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.2"
|
libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.2"
|
||||||
|
|
||||||
libraryDependencies += "io.spray" %% "spray-json" % "1.3.5"
|
|
||||||
|
|
||||||
libraryDependencies += "com.typesafe.akka" %% "akka-http" % "10.1.10"
|
libraryDependencies += "com.typesafe.akka" %% "akka-http" % "10.1.10"
|
||||||
libraryDependencies += "com.typesafe.akka" %% "akka-http-spray-json" % "10.1.10"
|
libraryDependencies += "com.typesafe.akka" %% "akka-http-spray-json" % "10.1.10"
|
||||||
libraryDependencies += "com.typesafe.akka" %% "akka-stream" % "2.5.23"
|
libraryDependencies += "com.typesafe.akka" %% "akka-stream" % "2.5.23"
|
||||||
libraryDependencies += "com.github.swagger-akka-http" %% "swagger-akka-http" % "2.0.4"
|
libraryDependencies += "com.github.swagger-akka-http" %% "swagger-akka-http" % "2.0.4"
|
||||||
libraryDependencies += "javax.ws.rs" % "javax.ws.rs-api" % "2.1.1"
|
libraryDependencies += "javax.ws.rs" % "javax.ws.rs-api" % "2.1.1"
|
||||||
|
|
||||||
|
libraryDependencies += "io.spray" %% "spray-json" % "1.3.5"
|
||||||
|
libraryDependencies += "com.lihaoyi" %% "scalatags" % "0.7.0"
|
||||||
|
|
||||||
libraryDependencies += "com.typesafe.slick" %% "slick" % "3.3.2"
|
libraryDependencies += "com.typesafe.slick" %% "slick" % "3.3.2"
|
||||||
libraryDependencies += "com.typesafe.slick" %% "slick-hikaricp" % "3.3.2"
|
libraryDependencies += "com.typesafe.slick" %% "slick-hikaricp" % "3.3.2"
|
||||||
libraryDependencies += "org.flywaydb" % "flyway-core" % "6.0.6"
|
libraryDependencies += "org.flywaydb" % "flyway-core" % "6.0.6"
|
||||||
|
0
src/main/resources/html/bis.html
Normal file
0
src/main/resources/html/bis.html
Normal file
277
src/main/resources/static/styles.css
Normal file
277
src/main/resources/static/styles.css
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
/* in-text images */
|
||||||
|
figure.img {
|
||||||
|
float: right;
|
||||||
|
border: 0px solid #333;
|
||||||
|
padding: 0px;
|
||||||
|
margin: 5px 0px 5px 10px;
|
||||||
|
}
|
||||||
|
figure.img img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
figure.img figcaption {
|
||||||
|
margin: 0px;
|
||||||
|
font-size: 90%;
|
||||||
|
font-style: italic;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 .octicon-link, h2 .octicon-link, h3 .octicon-link, h4 .octicon-link, h5 .octicon-link, h6 .octicon-link {
|
||||||
|
display: none;
|
||||||
|
color: #222222;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1:hover .anchor, h2:hover .anchor, h3:hover .anchor, h4:hover .anchor, h5:hover .anchor, h6:hover .anchor{
|
||||||
|
padding-left: 8px;
|
||||||
|
margin-left: -24px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1:hover .anchor .octicon-link, h2:hover .anchor .octicon-link, h3:hover .anchor .octicon-link, h4:hover .anchor .octicon-link, h5:hover .anchor .octicon-link, h6:hover .anchor .octicon-link {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
padding: 50px;
|
||||||
|
font: 14px/1.5 "Liberation Sans", Helvetica, Arial, sans-serif;
|
||||||
|
color: #555555;
|
||||||
|
background: #eaeaea
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
color: #222222;
|
||||||
|
margin: 0 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p, ul, ol, table, pre, dl {
|
||||||
|
margin: 0 0 20px;
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3 {
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #393939;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3, h4, h5, h6 {
|
||||||
|
color: #494949;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #3399cc;
|
||||||
|
font-weight: 350;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a small {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #777777;
|
||||||
|
margin-top: -0.6em;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
width: 80%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
border-left: 1px solid #ffffff;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 0 0 20px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
code, pre {
|
||||||
|
font-family: "Liberation Mono", Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal;
|
||||||
|
color: #222222;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
padding: 8px 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #e5e5e5;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, select{
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-bottom: 1px solid #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt {
|
||||||
|
color: #444444;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: left;
|
||||||
|
color: #444444;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
width: 20%;
|
||||||
|
float: left;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
header ul {
|
||||||
|
list-style: none;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0;
|
||||||
|
background: #eeeeee;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #d2d2d2;
|
||||||
|
box-shadow: inset #fff 0 1px 0, inset rgba(0,0,0,0.03) 0 -1px 0;
|
||||||
|
width: 15%;
|
||||||
|
}
|
||||||
|
|
||||||
|
header li {
|
||||||
|
width: 8%;
|
||||||
|
float: left;
|
||||||
|
border-right: 1px solid #d2d2d2;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header ul a {
|
||||||
|
line-height: 1;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #999999;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 6px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: #222222;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
header ul li + li {
|
||||||
|
width: 8%;
|
||||||
|
border-left: 1px solid #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
header ul li + li + li {
|
||||||
|
width: 8%;
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
header ul a strong {
|
||||||
|
font-size: 14px;
|
||||||
|
display: block;
|
||||||
|
color: #222222;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
width: 70%;
|
||||||
|
float: right;
|
||||||
|
padding-bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: 0;
|
||||||
|
background: #ffffff;
|
||||||
|
height: 1px;
|
||||||
|
margin: 0 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
width: 20%;
|
||||||
|
float: left;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print, screen and (max-width: 960px) {
|
||||||
|
div.wrapper {
|
||||||
|
width: auto;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
header, section, footer {
|
||||||
|
float: none;
|
||||||
|
position: static;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
header {
|
||||||
|
padding-right: 320px;
|
||||||
|
}
|
||||||
|
section {
|
||||||
|
border: 1px solid #e5e5e5;
|
||||||
|
border-width: 1px 0;
|
||||||
|
padding: 20px 0;
|
||||||
|
margin: 0 0 20px;
|
||||||
|
}
|
||||||
|
header a small {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
header ul {
|
||||||
|
position: absolute;
|
||||||
|
right: 50px;
|
||||||
|
top: 52px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print, screen and (max-width: 720px) {
|
||||||
|
body {
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
header {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
header ul, header p.view {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
pre, code {
|
||||||
|
word-wrap: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print, screen and (max-width: 480px) {
|
||||||
|
body {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
header ul {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
body {
|
||||||
|
padding: 0.4in;
|
||||||
|
font-size: 12pt;
|
||||||
|
color: #444444;
|
||||||
|
}
|
||||||
|
}
|
31
src/main/resources/static/table_export.js
Normal file
31
src/main/resources/static/table_export.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
function downloadCsv(csv, filename) {
|
||||||
|
var csvFile = new Blob([csv], {"type": "text/csv"});
|
||||||
|
|
||||||
|
var downloadLink = document.createElement("a");
|
||||||
|
downloadLink.download = filename;
|
||||||
|
downloadLink.href = window.URL.createObjectURL(csvFile);
|
||||||
|
downloadLink.style.display = "none";
|
||||||
|
|
||||||
|
document.body.appendChild(downloadLink);
|
||||||
|
downloadLink.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportTableToCsv(filename) {
|
||||||
|
var table = document.getElementById("result");
|
||||||
|
var rows = table.getElementsByTagName("tr");
|
||||||
|
|
||||||
|
var csv = [];
|
||||||
|
for (var i = 0; i < rows.length; i++) {
|
||||||
|
if (rows[i].style.display === "none")
|
||||||
|
continue
|
||||||
|
var cols = rows[i].querySelectorAll("td, th");
|
||||||
|
|
||||||
|
var row = [];
|
||||||
|
for (var j = 0; j < cols.length; j++)
|
||||||
|
row.push(cols[j].innerText);
|
||||||
|
|
||||||
|
csv.push(row.join(","));
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadCsv(csv.join("\n"), filename);
|
||||||
|
}
|
21
src/main/resources/static/table_search.js
Normal file
21
src/main/resources/static/table_search.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
function searchTable() {
|
||||||
|
var input = document.getElementById("search");
|
||||||
|
var filter = input.value.toLowerCase();
|
||||||
|
var table = document.getElementById("result");
|
||||||
|
var tr = table.getElementsByTagName("tr");
|
||||||
|
|
||||||
|
// from 1 coz of header
|
||||||
|
for (var i = 1; i < tr.length; i++) {
|
||||||
|
var td = tr[i].getElementsByClassName("include_search");
|
||||||
|
var display = "none";
|
||||||
|
for (var j = 0; j < td.length; j++) {
|
||||||
|
if (td[j].tagName.toLowerCase() === "td") {
|
||||||
|
if (td[j].innerHTML.toLowerCase().indexOf(filter) > -1) {
|
||||||
|
display = "";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tr[i].style.display = display;
|
||||||
|
}
|
||||||
|
}
|
@ -11,8 +11,8 @@ import scala.concurrent.{ExecutionContext, Future}
|
|||||||
class BiSHelper(storage: ActorRef, ariyala: ActorRef) extends AriyalaHelper(ariyala) {
|
class BiSHelper(storage: ActorRef, ariyala: ActorRef) extends AriyalaHelper(ariyala) {
|
||||||
|
|
||||||
def addPieceBiS(playerId: PlayerId, piece: Piece)
|
def addPieceBiS(playerId: PlayerId, piece: Piece)
|
||||||
(implicit executionContext: ExecutionContext): Future[Unit] =
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
||||||
Future { storage ! DatabaseBiSHandler.AddPieceToBis(playerId, piece) }
|
(storage ? DatabaseBiSHandler.AddPieceToBis(playerId, piece)).mapTo[Int]
|
||||||
|
|
||||||
def bis(partyId: String, playerId: Option[PlayerId])
|
def bis(partyId: String, playerId: Option[PlayerId])
|
||||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[Player]] =
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[Player]] =
|
||||||
@ -23,7 +23,7 @@ class BiSHelper(storage: ActorRef, ariyala: ActorRef) extends AriyalaHelper(ariy
|
|||||||
downloadBiS(link, playerId.job).map(_.pieces.map(addPieceBiS(playerId, _)))
|
downloadBiS(link, playerId.job).map(_.pieces.map(addPieceBiS(playerId, _)))
|
||||||
|
|
||||||
def removePieceBiS(playerId: PlayerId, piece: Piece)
|
def removePieceBiS(playerId: PlayerId, piece: Piece)
|
||||||
(implicit executionContext: ExecutionContext): Future[Unit] =
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
||||||
Future { storage ! DatabaseBiSHandler.RemovePieceFromBiS(playerId, piece) }
|
(storage ? DatabaseBiSHandler.RemovePieceFromBiS(playerId, piece)).mapTo[Int]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -12,16 +12,16 @@ import scala.concurrent.{ExecutionContext, Future}
|
|||||||
class LootHelper(storage: ActorRef) {
|
class LootHelper(storage: ActorRef) {
|
||||||
|
|
||||||
def addPieceLoot(playerId: PlayerId, piece: Piece)
|
def addPieceLoot(playerId: PlayerId, piece: Piece)
|
||||||
(implicit executionContext: ExecutionContext): Future[Unit] =
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
||||||
Future { storage ! DatabaseLootHandler.AddPieceTo(playerId, piece) }
|
(storage ? DatabaseLootHandler.AddPieceTo(playerId, piece)).mapTo[Int]
|
||||||
|
|
||||||
def loot(partyId: String, playerId: Option[PlayerId])
|
def loot(partyId: String, playerId: Option[PlayerId])
|
||||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[Player]] =
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[Player]] =
|
||||||
(storage ? DatabaseLootHandler.GetLoot(partyId, playerId)).mapTo[Seq[Player]]
|
(storage ? DatabaseLootHandler.GetLoot(partyId, playerId)).mapTo[Seq[Player]]
|
||||||
|
|
||||||
def removePieceLoot(playerId: PlayerId, piece: Piece)
|
def removePieceLoot(playerId: PlayerId, piece: Piece)
|
||||||
(implicit executionContext: ExecutionContext): Future[Unit] =
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
||||||
Future { storage ! DatabaseLootHandler.RemovePieceFrom(playerId, piece) }
|
(storage ? DatabaseLootHandler.RemovePieceFrom(playerId, piece)).mapTo[Int]
|
||||||
|
|
||||||
def suggestPiece(partyId: String, piece: Piece)
|
def suggestPiece(partyId: String, piece: Piece)
|
||||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[PlayerIdWithCounters]] =
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[PlayerIdWithCounters]] =
|
||||||
|
@ -13,15 +13,16 @@ import scala.util.{Failure, Success}
|
|||||||
class PlayerHelper(storage: ActorRef, ariyala: ActorRef) extends AriyalaHelper(ariyala) {
|
class PlayerHelper(storage: ActorRef, ariyala: ActorRef) extends AriyalaHelper(ariyala) {
|
||||||
|
|
||||||
def addPlayer(player: Player)
|
def addPlayer(player: Player)
|
||||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] =
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
||||||
Future { storage ! DatabasePartyHandler.AddPlayer(player) }.andThen {
|
(storage ? DatabasePartyHandler.AddPlayer(player)).mapTo[Int].map { res =>
|
||||||
case Success(_) if player.link.isDefined =>
|
player.link match {
|
||||||
downloadBiS(player.link.get, player.job).map { bis =>
|
case Some(link) =>
|
||||||
bis.pieces.map(storage ! DatabaseBiSHandler.AddPieceToBis(player.playerId, _))
|
downloadBiS(link, player.job).map { bis =>
|
||||||
}.map(_ => ())
|
bis.pieces.map(storage ? DatabaseBiSHandler.AddPieceToBis(player.playerId, _))
|
||||||
case Success(_) => Future.successful(())
|
}.map(_ => res)
|
||||||
case Failure(exception) => Future.failed(exception)
|
case None => Future.successful(res)
|
||||||
}
|
}
|
||||||
|
}.flatten
|
||||||
|
|
||||||
def getPlayers(partyId: String, maybePlayerId: Option[PlayerId])
|
def getPlayers(partyId: String, maybePlayerId: Option[PlayerId])
|
||||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[Player]] =
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[Player]] =
|
||||||
@ -32,6 +33,7 @@ class PlayerHelper(storage: ActorRef, ariyala: ActorRef) extends AriyalaHelper(a
|
|||||||
(storage ? DatabasePartyHandler.GetParty(partyId)).mapTo[Party].map(_.players.values.toSeq)
|
(storage ? DatabasePartyHandler.GetParty(partyId)).mapTo[Party].map(_.players.values.toSeq)
|
||||||
}
|
}
|
||||||
|
|
||||||
def removePlayer(playerId: PlayerId)(implicit executionContext: ExecutionContext): Future[Unit] =
|
def removePlayer(playerId: PlayerId)
|
||||||
Future { storage ! DatabasePartyHandler.RemovePlayer(playerId) }
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
||||||
|
(storage ? DatabasePartyHandler.RemovePlayer(playerId)).mapTo[Int]
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package me.arcanis.ffxivbis.http
|
package me.arcanis.ffxivbis.http
|
||||||
|
|
||||||
import akka.actor.{ActorRef, ActorSystem}
|
import akka.actor.{ActorRef, ActorSystem}
|
||||||
import akka.http.scaladsl.model._
|
|
||||||
import akka.http.scaladsl.server.Directives._
|
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 com.typesafe.scalalogging.StrictLogging
|
import com.typesafe.scalalogging.StrictLogging
|
||||||
import me.arcanis.ffxivbis.http.api.v1.ApiV1Endpoint
|
import me.arcanis.ffxivbis.http.api.v1.RootApiV1Endpoint
|
||||||
|
import me.arcanis.ffxivbis.http.view.RootView
|
||||||
|
|
||||||
class RootEndpoint(system: ActorSystem, storage: ActorRef, ariyala: ActorRef)
|
class RootEndpoint(system: ActorSystem, storage: ActorRef, ariyala: ActorRef)
|
||||||
extends StrictLogging {
|
extends StrictLogging {
|
||||||
@ -17,7 +17,8 @@ class RootEndpoint(system: ActorSystem, storage: ActorRef, ariyala: ActorRef)
|
|||||||
implicit val timeout: Timeout =
|
implicit val timeout: Timeout =
|
||||||
config.getDuration("me.arcanis.ffxivbis.settings.request-timeout")
|
config.getDuration("me.arcanis.ffxivbis.settings.request-timeout")
|
||||||
|
|
||||||
private val apiV1Endpoint: ApiV1Endpoint = new ApiV1Endpoint(storage, ariyala)
|
private val rootApiV1Endpoint: RootApiV1Endpoint = new RootApiV1Endpoint(storage, ariyala)
|
||||||
|
private val rootView: RootView = new RootView(storage, ariyala)
|
||||||
|
|
||||||
def route: Route = apiRoute ~ htmlRoute ~ Swagger.routes ~ swaggerUIRoute
|
def route: Route = apiRoute ~ htmlRoute ~ Swagger.routes ~ swaggerUIRoute
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ class RootEndpoint(system: ActorSystem, storage: ActorRef, ariyala: ActorRef)
|
|||||||
ignoreTrailingSlash {
|
ignoreTrailingSlash {
|
||||||
pathPrefix("api") {
|
pathPrefix("api") {
|
||||||
pathPrefix(Segment) {
|
pathPrefix(Segment) {
|
||||||
case "v1" => apiV1Endpoint.route
|
case "v1" => rootApiV1Endpoint.route
|
||||||
case _ => reject
|
case _ => reject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -33,9 +34,9 @@ class RootEndpoint(system: ActorSystem, storage: ActorRef, ariyala: ActorRef)
|
|||||||
|
|
||||||
private def htmlRoute: Route =
|
private def htmlRoute: Route =
|
||||||
ignoreTrailingSlash {
|
ignoreTrailingSlash {
|
||||||
pathEndOrSingleSlash {
|
pathPrefix("static") {
|
||||||
complete(StatusCodes.OK)
|
getFromResourceDirectory("static")
|
||||||
}
|
} ~ rootView.route
|
||||||
}
|
}
|
||||||
|
|
||||||
private def swaggerUIRoute: Route =
|
private def swaggerUIRoute: Route =
|
||||||
|
@ -11,8 +11,8 @@ import scala.concurrent.{ExecutionContext, Future}
|
|||||||
class UserHelper(storage: ActorRef) {
|
class UserHelper(storage: ActorRef) {
|
||||||
|
|
||||||
def addUser(user: User, isHashedPassword: Boolean)
|
def addUser(user: User, isHashedPassword: Boolean)
|
||||||
(implicit executionContext: ExecutionContext): Future[Unit] =
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
||||||
Future { storage ! DatabaseUserHandler.InsertUser(user, isHashedPassword) }
|
(storage ? DatabaseUserHandler.InsertUser(user, isHashedPassword)).mapTo[Int]
|
||||||
|
|
||||||
def user(partyId: String, username: String)
|
def user(partyId: String, username: String)
|
||||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Option[User]] =
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Option[User]] =
|
||||||
@ -23,6 +23,6 @@ class UserHelper(storage: ActorRef) {
|
|||||||
(storage ? DatabaseUserHandler.GetUsers(partyId)).mapTo[Seq[User]]
|
(storage ? DatabaseUserHandler.GetUsers(partyId)).mapTo[Seq[User]]
|
||||||
|
|
||||||
def removeUser(partyId: String, username: String)
|
def removeUser(partyId: String, username: String)
|
||||||
(implicit executionContext: ExecutionContext): Future[Unit] =
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
||||||
Future { storage ! DatabaseUserHandler.DeleteUser(partyId, username) }
|
(storage ? DatabaseUserHandler.DeleteUser(partyId, username)).mapTo[Int]
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package me.arcanis.ffxivbis.http.api.v1
|
package me.arcanis.ffxivbis.http.api.v1
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
import akka.actor.ActorRef
|
||||||
import akka.http.scaladsl.model.StatusCodes
|
import akka.http.scaladsl.model.{HttpEntity, StatusCodes}
|
||||||
import akka.http.scaladsl.server.Directives._
|
import akka.http.scaladsl.server.Directives._
|
||||||
import akka.http.scaladsl.server._
|
import akka.http.scaladsl.server._
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
@ -19,7 +19,6 @@ import me.arcanis.ffxivbis.models.PlayerId
|
|||||||
@Path("api/v1")
|
@Path("api/v1")
|
||||||
class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout)
|
class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout)
|
||||||
extends BiSHelper(storage, ariyala) with Authorization with JsonSupport {
|
extends BiSHelper(storage, ariyala) with Authorization with JsonSupport {
|
||||||
import spray.json.DefaultJsonProtocol._
|
|
||||||
|
|
||||||
def route: Route = createBiS ~ getBiS ~ modifyBiS
|
def route: Route = createBiS ~ getBiS ~ modifyBiS
|
||||||
|
|
||||||
@ -49,7 +48,7 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti
|
|||||||
put {
|
put {
|
||||||
entity(as[PlayerBiSLinkResponse]) { bisLink =>
|
entity(as[PlayerBiSLinkResponse]) { bisLink =>
|
||||||
val playerId = bisLink.playerId.withPartyId(partyId)
|
val playerId = bisLink.playerId.withPartyId(partyId)
|
||||||
complete(putBiS(playerId, bisLink.link).map(_ => StatusCodes.Created))
|
complete(putBiS(playerId, bisLink.link).map(_ => (StatusCodes.Created, HttpEntity.Empty)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,7 +121,7 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti
|
|||||||
case ApiAction.add => addPieceBiS(playerId, action.piece.toPiece)
|
case ApiAction.add => addPieceBiS(playerId, action.piece.toPiece)
|
||||||
case ApiAction.remove => removePieceBiS(playerId, action.piece.toPiece)
|
case ApiAction.remove => removePieceBiS(playerId, action.piece.toPiece)
|
||||||
}
|
}
|
||||||
result.map(_ => StatusCodes.Accepted)
|
result.map(_ => (StatusCodes.Accepted, HttpEntity.Empty))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package me.arcanis.ffxivbis.http.api.v1
|
package me.arcanis.ffxivbis.http.api.v1
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
import akka.actor.ActorRef
|
||||||
import akka.http.scaladsl.model.StatusCodes
|
import akka.http.scaladsl.model.{HttpEntity, StatusCodes}
|
||||||
import akka.http.scaladsl.server.Directives._
|
import akka.http.scaladsl.server.Directives._
|
||||||
import akka.http.scaladsl.server._
|
import akka.http.scaladsl.server._
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
@ -19,7 +19,6 @@ import me.arcanis.ffxivbis.models.PlayerId
|
|||||||
@Path("api/v1")
|
@Path("api/v1")
|
||||||
class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
||||||
extends LootHelper(storage) with Authorization with JsonSupport {
|
extends LootHelper(storage) with Authorization with JsonSupport {
|
||||||
import spray.json.DefaultJsonProtocol._
|
|
||||||
|
|
||||||
def route: Route = getLoot ~ modifyLoot
|
def route: Route = getLoot ~ modifyLoot
|
||||||
|
|
||||||
@ -89,7 +88,7 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
case ApiAction.add => addPieceLoot(playerId, action.piece.toPiece)
|
case ApiAction.add => addPieceLoot(playerId, action.piece.toPiece)
|
||||||
case ApiAction.remove => removePieceLoot(playerId, action.piece.toPiece)
|
case ApiAction.remove => removePieceLoot(playerId, action.piece.toPiece)
|
||||||
}
|
}
|
||||||
result.map(_ => StatusCodes.Accepted)
|
result.map(_ => (StatusCodes.Accepted, HttpEntity.Empty))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package me.arcanis.ffxivbis.http.api.v1
|
package me.arcanis.ffxivbis.http.api.v1
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
import akka.actor.ActorRef
|
||||||
import akka.http.scaladsl.model.StatusCodes
|
import akka.http.scaladsl.model.{HttpEntity, StatusCodes}
|
||||||
import akka.http.scaladsl.server.Directives._
|
import akka.http.scaladsl.server.Directives._
|
||||||
import akka.http.scaladsl.server._
|
import akka.http.scaladsl.server._
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
@ -19,7 +19,6 @@ import me.arcanis.ffxivbis.models.PlayerId
|
|||||||
@Path("api/v1")
|
@Path("api/v1")
|
||||||
class PlayerEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout)
|
class PlayerEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout)
|
||||||
extends PlayerHelper(storage, ariyala) with Authorization with JsonSupport {
|
extends PlayerHelper(storage, ariyala) with Authorization with JsonSupport {
|
||||||
import spray.json.DefaultJsonProtocol._
|
|
||||||
|
|
||||||
def route: Route = getParty ~ modifyParty
|
def route: Route = getParty ~ modifyParty
|
||||||
|
|
||||||
@ -88,7 +87,7 @@ class PlayerEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit
|
|||||||
case ApiAction.add => addPlayer(player)
|
case ApiAction.add => addPlayer(player)
|
||||||
case ApiAction.remove => removePlayer(player.playerId)
|
case ApiAction.remove => removePlayer(player.playerId)
|
||||||
}
|
}
|
||||||
result.map(_ => StatusCodes.Accepted)
|
result.map(_ => (StatusCodes.Accepted, HttpEntity.Empty))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,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
|
||||||
|
|
||||||
class ApiV1Endpoint(storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout) {
|
class RootApiV1Endpoint(storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout) {
|
||||||
|
|
||||||
private val biSEndpoint = new BiSEndpoint(storage, ariyala)
|
private val biSEndpoint = new BiSEndpoint(storage, ariyala)
|
||||||
private val lootEndpoint = new LootEndpoint(storage)
|
private val lootEndpoint = new LootEndpoint(storage)
|
@ -1,7 +1,7 @@
|
|||||||
package me.arcanis.ffxivbis.http.api.v1
|
package me.arcanis.ffxivbis.http.api.v1
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
import akka.actor.ActorRef
|
||||||
import akka.http.scaladsl.model.StatusCodes
|
import akka.http.scaladsl.model.{HttpEntity, StatusCodes}
|
||||||
import akka.http.scaladsl.server.Directives._
|
import akka.http.scaladsl.server.Directives._
|
||||||
import akka.http.scaladsl.server._
|
import akka.http.scaladsl.server._
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
@ -19,12 +19,11 @@ import me.arcanis.ffxivbis.models.Permission
|
|||||||
@Path("api/v1")
|
@Path("api/v1")
|
||||||
class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
||||||
extends UserHelper(storage) with Authorization with JsonSupport {
|
extends UserHelper(storage) with Authorization with JsonSupport {
|
||||||
import spray.json.DefaultJsonProtocol._
|
|
||||||
|
|
||||||
def route: Route = createParty ~ createUser ~ deleteUser ~ getUsers
|
def route: Route = createParty ~ createUser ~ deleteUser ~ getUsers
|
||||||
|
|
||||||
@PUT
|
@PUT
|
||||||
@Path("party/{partyId}")
|
@Path("party/{partyId}/create")
|
||||||
@Consumes(value = Array("application/json"))
|
@Consumes(value = Array("application/json"))
|
||||||
@Operation(summary = "create new party", description = "Create new party with specified ID",
|
@Operation(summary = "create new party", description = "Create new party with specified ID",
|
||||||
parameters = Array(
|
parameters = Array(
|
||||||
@ -41,13 +40,13 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
tags = Array("party"),
|
tags = Array("party"),
|
||||||
)
|
)
|
||||||
def createParty: Route =
|
def createParty: Route =
|
||||||
path("party" / Segment) { partyId: String =>
|
path("party" / Segment / "create") { partyId: String =>
|
||||||
extractExecutionContext { implicit executionContext =>
|
extractExecutionContext { implicit executionContext =>
|
||||||
put {
|
put {
|
||||||
entity(as[UserResponse]) { user =>
|
entity(as[UserResponse]) { user =>
|
||||||
val admin = user.toUser.copy(partyId = partyId, permission = Permission.admin)
|
val admin = user.toUser.copy(partyId = partyId, permission = Permission.admin)
|
||||||
complete {
|
complete {
|
||||||
addUser(admin, isHashedPassword = false).map(_ => StatusCodes.Created)
|
addUser(admin, isHashedPassword = false).map(_ => (StatusCodes.Created, HttpEntity.Empty))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,7 +80,7 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
entity(as[UserResponse]) { user =>
|
entity(as[UserResponse]) { user =>
|
||||||
val withPartyId = user.toUser.copy(partyId = partyId)
|
val withPartyId = user.toUser.copy(partyId = partyId)
|
||||||
complete {
|
complete {
|
||||||
addUser(withPartyId, isHashedPassword = false).map(_ => StatusCodes.Created)
|
addUser(withPartyId, isHashedPassword = false).map(_ => (StatusCodes.Created, HttpEntity.Empty))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,7 +110,7 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ =>
|
authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ =>
|
||||||
delete {
|
delete {
|
||||||
complete {
|
complete {
|
||||||
removeUser(partyId, username).map(_ => StatusCodes.Accepted)
|
removeUser(partyId, username).map(_ => (StatusCodes.Accepted, HttpEntity.Empty))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,7 @@ import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
|
|||||||
import me.arcanis.ffxivbis.models.Permission
|
import me.arcanis.ffxivbis.models.Permission
|
||||||
import spray.json._
|
import spray.json._
|
||||||
|
|
||||||
trait JsonSupport extends SprayJsonSupport {
|
trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
|
||||||
import DefaultJsonProtocol._
|
|
||||||
|
|
||||||
private def enumFormat[E <: Enumeration](enum: E): RootJsonFormat[E#Value] =
|
private def enumFormat[E <: Enumeration](enum: E): RootJsonFormat[E#Value] =
|
||||||
new RootJsonFormat[E#Value] {
|
new RootJsonFormat[E#Value] {
|
||||||
|
142
src/main/scala/me/arcanis/ffxivbis/http/view/BiSView.scala
Normal file
142
src/main/scala/me/arcanis/ffxivbis/http/view/BiSView.scala
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package me.arcanis.ffxivbis.http.view
|
||||||
|
|
||||||
|
import akka.actor.ActorRef
|
||||||
|
import akka.http.scaladsl.model.StatusCodes
|
||||||
|
import akka.http.scaladsl.server.Directives._
|
||||||
|
import akka.http.scaladsl.server._
|
||||||
|
import akka.util.Timeout
|
||||||
|
import me.arcanis.ffxivbis.http.{Authorization, BiSHelper}
|
||||||
|
import me.arcanis.ffxivbis.models.{Piece, Player, PlayerId}
|
||||||
|
|
||||||
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
|
import scala.util.Try
|
||||||
|
|
||||||
|
class BiSView(override val storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout)
|
||||||
|
extends BiSHelper(storage, ariyala) with Authorization {
|
||||||
|
|
||||||
|
def route: Route = getBiS ~ modifyBiS
|
||||||
|
|
||||||
|
def getBiS: Route =
|
||||||
|
path("party" / Segment / "bis") { partyId: String =>
|
||||||
|
extractExecutionContext { implicit executionContext =>
|
||||||
|
authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
|
||||||
|
get {
|
||||||
|
complete {
|
||||||
|
bis(partyId, None).map { players =>
|
||||||
|
BiSView.template(partyId, players, Piece.available, None)
|
||||||
|
}.map { text =>
|
||||||
|
(StatusCodes.OK, RootView.toHtml(text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def modifyBiS: Route =
|
||||||
|
path("party" / Segment / "bis") { partyId: String =>
|
||||||
|
extractExecutionContext { implicit executionContext =>
|
||||||
|
authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
|
||||||
|
post {
|
||||||
|
formFields("player".as[String], "piece".as[String].?, "is_tome".as[String].?, "link".as[String].?, "action".as[String]) {
|
||||||
|
(player, maybePiece, maybeIsTome, maybeLink, action) =>
|
||||||
|
onComplete(modifyBiSCall(partyId, player, maybePiece, maybeIsTome, maybeLink, action)) {
|
||||||
|
case _ => redirect(s"/party/$partyId/bis", StatusCodes.Found)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def modifyBiSCall(partyId: String, player: String,
|
||||||
|
maybePiece: Option[String], maybeIsTome: Option[String],
|
||||||
|
maybeLink: Option[String], action: String)
|
||||||
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = {
|
||||||
|
def getPiece(playerId: PlayerId, piece: String) =
|
||||||
|
Try(Piece(piece, maybeIsTome.isDefined, playerId.job)).toOption
|
||||||
|
|
||||||
|
PlayerId(partyId, player) match {
|
||||||
|
case Some(playerId) => (maybePiece, action, maybeLink) match {
|
||||||
|
case (Some(piece), "add", _) => getPiece(playerId, piece) match {
|
||||||
|
case Some(item) => addPieceBiS(playerId, item).map(_ => ())
|
||||||
|
case _ => Future.failed(new Error(s"Could not construct piece from `$piece`"))
|
||||||
|
}
|
||||||
|
case (Some(piece), "remove", _) => getPiece(playerId, piece) match {
|
||||||
|
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 construct player id from `$player`"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object BiSView {
|
||||||
|
import scalatags.Text.all._
|
||||||
|
|
||||||
|
def template(partyId: String, party: Seq[Player], pieces: Seq[String], error: Option[String]): String = {
|
||||||
|
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
|
||||||
|
html(lang:="en",
|
||||||
|
head(
|
||||||
|
title:="Best in slot",
|
||||||
|
link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css")
|
||||||
|
),
|
||||||
|
|
||||||
|
body(
|
||||||
|
h2("best in slot"),
|
||||||
|
|
||||||
|
ErrorView.template(error),
|
||||||
|
SearchLineView.template,
|
||||||
|
|
||||||
|
form(action:=s"/party/$partyId/bis", method:="post")(
|
||||||
|
select(name:="player", id:="player", title:="player")
|
||||||
|
(for (player <- party) yield option(player.playerId.toString)),
|
||||||
|
select(name:="piece", id:="piece", title:="piece")
|
||||||
|
(for (piece <- pieces) yield option(piece)),
|
||||||
|
input(name:="is_tome", id:="is_tome", title:="is tome", `type`:="checkbox"),
|
||||||
|
label(`for`:="is_tome")("is tome gear"),
|
||||||
|
input(name:="action", id:="action", `type`:="hidden", value:="add"),
|
||||||
|
input(name:="add", id:="add", `type`:="submit", value:="add")
|
||||||
|
),
|
||||||
|
|
||||||
|
form(action:="/bis", method:="post")(
|
||||||
|
select(name:="player", id:="player", title:="player")
|
||||||
|
(for (player <- party) yield option(player.playerId.toString)),
|
||||||
|
input(name:="link", id:="link", placeholder:="player bis link", title:="link", `type`:="text"),
|
||||||
|
input(name:="action", id:="action", `type`:="hidden", value:="create"),
|
||||||
|
input(name:="add", id:="add", `type`:="submit", value:="add")
|
||||||
|
),
|
||||||
|
|
||||||
|
table(
|
||||||
|
tr(
|
||||||
|
th("player"),
|
||||||
|
th("piece"),
|
||||||
|
th("is tome"),
|
||||||
|
th("")
|
||||||
|
//td(`class`:="include_search")
|
||||||
|
),
|
||||||
|
for (player <- party; piece <- player.bis.pieces) yield tr(
|
||||||
|
td(`class`:="include_search")(player.playerId.toString),
|
||||||
|
td(`class`:="include_search")(piece.piece),
|
||||||
|
td(piece.isTomeToString),
|
||||||
|
td(
|
||||||
|
form(action:=s"/party/$partyId/bis", method:="post")(
|
||||||
|
input(name:="player", id:="player", `type`:="hidden", value:=player.playerId.toString),
|
||||||
|
input(name:="piece", id:="piece", `type`:="hidden", value:=piece.piece),
|
||||||
|
input(name:="is_tome", id:="is_tome", `type`:="hidden", value:=piece.isTomeToString),
|
||||||
|
input(name:="action", id:="action", `type`:="hidden", value:="remove"),
|
||||||
|
input(name:="remove", id:="remove", `type`:="submit", value:="x")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
ExportToCSVView.template,
|
||||||
|
script(src:="/static/table_search.js", `type`:="text/javascript")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
11
src/main/scala/me/arcanis/ffxivbis/http/view/ErrorView.scala
Normal file
11
src/main/scala/me/arcanis/ffxivbis/http/view/ErrorView.scala
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package me.arcanis.ffxivbis.http.view
|
||||||
|
|
||||||
|
import scalatags.Text
|
||||||
|
import scalatags.Text.all._
|
||||||
|
|
||||||
|
object ErrorView {
|
||||||
|
def template(error: Option[String]): Text.TypedTag[String] = error match {
|
||||||
|
case Some(text) => p(id:="error", s"Error occurs: $text")
|
||||||
|
case None => p("")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package me.arcanis.ffxivbis.http.view
|
||||||
|
|
||||||
|
import scalatags.Text
|
||||||
|
import scalatags.Text.all._
|
||||||
|
|
||||||
|
object ExportToCSVView {
|
||||||
|
def template: Text.TypedTag[String] =
|
||||||
|
div(
|
||||||
|
button(onclick:="exportTableToCsv('result.csv')")("Export to CSV"),
|
||||||
|
script(src:="/static/table_export.js", `type`:="text/javascript")
|
||||||
|
)
|
||||||
|
}
|
20
src/main/scala/me/arcanis/ffxivbis/http/view/RootView.scala
Normal file
20
src/main/scala/me/arcanis/ffxivbis/http/view/RootView.scala
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package me.arcanis.ffxivbis.http.view
|
||||||
|
|
||||||
|
import akka.actor.ActorRef
|
||||||
|
import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
|
||||||
|
import akka.http.scaladsl.server.Directives._
|
||||||
|
import akka.http.scaladsl.server.Route
|
||||||
|
import akka.util.Timeout
|
||||||
|
|
||||||
|
class RootView(storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout) {
|
||||||
|
|
||||||
|
private val biSView = new BiSView(storage, ariyala)
|
||||||
|
|
||||||
|
def route: Route =
|
||||||
|
biSView.route
|
||||||
|
}
|
||||||
|
|
||||||
|
object RootView {
|
||||||
|
def toHtml(template: String): HttpEntity.Strict =
|
||||||
|
HttpEntity(ContentTypes.`text/html(UTF-8)`, template)
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package me.arcanis.ffxivbis.http.view
|
||||||
|
|
||||||
|
import scalatags.Text
|
||||||
|
import scalatags.Text.all._
|
||||||
|
|
||||||
|
object SearchLineView {
|
||||||
|
def template: Text.TypedTag[String] =
|
||||||
|
div(
|
||||||
|
input(
|
||||||
|
`type`:="text", id:="search", onkeyup:="searchTable()",
|
||||||
|
placeholder:="search for data", title:="search"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
@ -5,6 +5,7 @@ trait Piece {
|
|||||||
def job: Job.Job
|
def job: Job.Job
|
||||||
def piece: String
|
def piece: String
|
||||||
|
|
||||||
|
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 !isTome => None
|
||||||
case _: Waist => Some(AccessoryUpgrade)
|
case _: Waist => Some(AccessoryUpgrade)
|
||||||
@ -94,4 +95,8 @@ object Piece {
|
|||||||
case "weapon upgrade" => WeaponUpgrade
|
case "weapon upgrade" => WeaponUpgrade
|
||||||
case other => throw new Error(s"Unknown item type $other")
|
case other => throw new Error(s"Unknown item type $other")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def available: Seq[String] = Seq("weapon",
|
||||||
|
"head", "body", "hands", "waist", "legs", "feet",
|
||||||
|
"ears", "neck", "wrist", "leftRing", "rightRing")
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package me.arcanis.ffxivbis.models
|
package me.arcanis.ffxivbis.models
|
||||||
|
|
||||||
|
import scala.util.matching.Regex
|
||||||
|
|
||||||
trait PlayerIdBase {
|
trait PlayerIdBase {
|
||||||
def job: Job.Job
|
def job: Job.Job
|
||||||
def nick: String
|
def nick: String
|
||||||
@ -15,4 +17,10 @@ object PlayerId {
|
|||||||
case (Some(nick), Some(job)) => Some(PlayerId(partyId, Job.fromString(job), nick))
|
case (Some(nick), Some(job)) => Some(PlayerId(partyId, Job.fromString(job), nick))
|
||||||
case _ => None
|
case _ => None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val prettyPlayerIdRegex: Regex = "^(.*) \\(([A-Z]{3})\\)$".r
|
||||||
|
def apply(partyId: String, player: String): Option[PlayerId] = player match {
|
||||||
|
case s"${prettyPlayerIdRegex(nick, job)}" => Some(PlayerId(partyId, Job.fromString(job), nick))
|
||||||
|
case _ => None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,8 @@ trait DatabaseBiSHandler { this: Database =>
|
|||||||
|
|
||||||
def bisHandler: Receive = {
|
def bisHandler: Receive = {
|
||||||
case AddPieceToBis(playerId, piece) =>
|
case AddPieceToBis(playerId, piece) =>
|
||||||
profile.insertPieceBiS(playerId, piece)
|
val client = sender()
|
||||||
|
profile.insertPieceBiS(playerId, piece).pipeTo(client)
|
||||||
|
|
||||||
case GetBiS(partyId, maybePlayerId) =>
|
case GetBiS(partyId, maybePlayerId) =>
|
||||||
val client = sender()
|
val client = sender()
|
||||||
@ -18,7 +19,8 @@ trait DatabaseBiSHandler { this: Database =>
|
|||||||
.pipeTo(client)
|
.pipeTo(client)
|
||||||
|
|
||||||
case RemovePieceFromBiS(playerId, piece) =>
|
case RemovePieceFromBiS(playerId, piece) =>
|
||||||
profile.deletePieceBiS(playerId, piece)
|
val client = sender()
|
||||||
|
profile.deletePieceBiS(playerId, piece).pipeTo(client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,8 @@ trait DatabaseLootHandler { this: Database =>
|
|||||||
|
|
||||||
def lootHandler: Receive = {
|
def lootHandler: Receive = {
|
||||||
case AddPieceTo(playerId, piece) =>
|
case AddPieceTo(playerId, piece) =>
|
||||||
profile.insertPiece(playerId, piece)
|
val client = sender()
|
||||||
|
profile.insertPiece(playerId, piece).pipeTo(client)
|
||||||
|
|
||||||
case GetLoot(partyId, maybePlayerId) =>
|
case GetLoot(partyId, maybePlayerId) =>
|
||||||
val client = sender()
|
val client = sender()
|
||||||
@ -18,7 +19,8 @@ trait DatabaseLootHandler { this: Database =>
|
|||||||
.pipeTo(client)
|
.pipeTo(client)
|
||||||
|
|
||||||
case RemovePieceFrom(playerId, piece) =>
|
case RemovePieceFrom(playerId, piece) =>
|
||||||
profile.deletePiece(playerId, piece)
|
val client = sender()
|
||||||
|
profile.deletePiece(playerId, piece).pipeTo(client)
|
||||||
|
|
||||||
case SuggestLoot(partyId, piece) =>
|
case SuggestLoot(partyId, piece) =>
|
||||||
val client = sender()
|
val client = sender()
|
||||||
|
@ -11,7 +11,8 @@ trait DatabasePartyHandler { this: Actor with StrictLogging with Database =>
|
|||||||
|
|
||||||
def partyHandler: Receive = {
|
def partyHandler: Receive = {
|
||||||
case AddPlayer(player) =>
|
case AddPlayer(player) =>
|
||||||
profile.insertPlayer(player)
|
val client = sender()
|
||||||
|
profile.insertPlayer(player).pipeTo(client)
|
||||||
|
|
||||||
case GetParty(partyId) =>
|
case GetParty(partyId) =>
|
||||||
val client = sender()
|
val client = sender()
|
||||||
@ -27,7 +28,8 @@ trait DatabasePartyHandler { this: Actor with StrictLogging with Database =>
|
|||||||
player.pipeTo(client)
|
player.pipeTo(client)
|
||||||
|
|
||||||
case RemovePlayer(playerId) =>
|
case RemovePlayer(playerId) =>
|
||||||
profile.deletePlayer(playerId)
|
val client = sender()
|
||||||
|
profile.deletePlayer(playerId).pipeTo(client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,8 @@ trait DatabaseUserHandler { this: Database =>
|
|||||||
|
|
||||||
def userHandler: Receive = {
|
def userHandler: Receive = {
|
||||||
case DeleteUser(partyId, username) =>
|
case DeleteUser(partyId, username) =>
|
||||||
profile.deleteUser(partyId, username)
|
val client = sender()
|
||||||
|
profile.deleteUser(partyId, username).pipeTo(client)
|
||||||
|
|
||||||
case GetUser(partyId, username) =>
|
case GetUser(partyId, username) =>
|
||||||
val client = sender()
|
val client = sender()
|
||||||
@ -20,8 +21,9 @@ trait DatabaseUserHandler { this: Database =>
|
|||||||
profile.getUsers(partyId).pipeTo(client)
|
profile.getUsers(partyId).pipeTo(client)
|
||||||
|
|
||||||
case InsertUser(user, isHashedPassword) =>
|
case InsertUser(user, isHashedPassword) =>
|
||||||
|
val client = sender()
|
||||||
val toInsert = if (isHashedPassword) user else user.copy(password = user.hash)
|
val toInsert = if (isHashedPassword) user else user.copy(password = user.hash)
|
||||||
profile.insertUser(toInsert)
|
profile.insertUser(toInsert).pipeTo(client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package me.arcanis.ffxivbis.storage
|
package me.arcanis.ffxivbis.storage
|
||||||
|
|
||||||
import me.arcanis.ffxivbis.models.{Job, Loot, Piece}
|
import me.arcanis.ffxivbis.models.{Job, Loot, Piece}
|
||||||
import slick.lifted.{ForeignKeyQuery, Index}
|
import slick.lifted.{ForeignKeyQuery, Index, PrimaryKey}
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
|
|
||||||
@ -18,9 +18,9 @@ trait BiSProfile { this: DatabaseProfile =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
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")
|
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")
|
def piece: Rep[String] = column[String]("piece", O.PrimaryKey)
|
||||||
def isTome: Rep[Int] = column[Int]("is_tome")
|
def isTome: Rep[Int] = column[Int]("is_tome")
|
||||||
def job: Rep[String] = column[String]("job")
|
def job: Rep[String] = column[String]("job")
|
||||||
|
|
||||||
@ -29,8 +29,6 @@ trait BiSProfile { this: DatabaseProfile =>
|
|||||||
|
|
||||||
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)
|
||||||
def bisPiecePlayerIdIdx: Index =
|
|
||||||
index("bis_piece_player_id_idx", (playerId, piece), unique = true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def deletePieceBiSById(piece: Piece)(playerId: Long): Future[Int] =
|
def deletePieceBiSById(piece: Piece)(playerId: Long): Future[Int] =
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package me.arcanis.ffxivbis.storage
|
package me.arcanis.ffxivbis.storage
|
||||||
|
|
||||||
import me.arcanis.ffxivbis.models.{BiS, Job, Player, PlayerId}
|
import me.arcanis.ffxivbis.models.{BiS, Job, Player, PlayerId}
|
||||||
import slick.lifted.Index
|
import slick.lifted.{Index, PrimaryKey}
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
|
|
||||||
@ -14,8 +14,8 @@ trait PlayersProfile { this: DatabaseProfile =>
|
|||||||
Player(partyId, Job.fromString(job), nick, BiS(Seq.empty), List.empty, link, priority)
|
Player(partyId, Job.fromString(job), nick, BiS(Seq.empty), List.empty, link, priority)
|
||||||
}
|
}
|
||||||
object PlayerRep {
|
object PlayerRep {
|
||||||
def fromPlayer(player: Player): PlayerRep =
|
def fromPlayer(player: Player, id: Option[Long]): PlayerRep =
|
||||||
PlayerRep(player.partyId, None, DatabaseProfile.now, player.nick,
|
PlayerRep(player.partyId, id, DatabaseProfile.now, player.nick,
|
||||||
player.job.toString, player.link, player.priority)
|
player.job.toString, player.link, player.priority)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,11 +30,9 @@ trait PlayersProfile { this: DatabaseProfile =>
|
|||||||
|
|
||||||
def * =
|
def * =
|
||||||
(partyId, playerId.?, created, nick, job, bisLink, priority) <> ((PlayerRep.apply _).tupled, PlayerRep.unapply)
|
(partyId, playerId.?, created, nick, job, bisLink, priority) <> ((PlayerRep.apply _).tupled, PlayerRep.unapply)
|
||||||
|
|
||||||
def playersNickJobIdx: Index =
|
|
||||||
index("players_nick_job_idx", (partyId, nick, job), unique = true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def deletePlayer(playerId: PlayerId): Future[Int] = db.run(player(playerId).delete)
|
def deletePlayer(playerId: PlayerId): Future[Int] = db.run(player(playerId).delete)
|
||||||
def getParty(partyId: String): Future[Map[Long, Player]] =
|
def getParty(partyId: String): Future[Map[Long, Player]] =
|
||||||
db.run(players(partyId).result).map(_.foldLeft(Map.empty[Long, Player]) {
|
db.run(players(partyId).result).map(_.foldLeft(Map.empty[Long, Player]) {
|
||||||
@ -45,8 +43,11 @@ trait PlayersProfile { this: DatabaseProfile =>
|
|||||||
db.run(player(playerId).map(_.playerId).result.headOption)
|
db.run(player(playerId).map(_.playerId).result.headOption)
|
||||||
def getPlayers(partyId: String): Future[Seq[Long]] =
|
def getPlayers(partyId: String): Future[Seq[Long]] =
|
||||||
db.run(players(partyId).map(_.playerId).result)
|
db.run(players(partyId).map(_.playerId).result)
|
||||||
def insertPlayer(player: Player): Future[Int] =
|
def insertPlayer(playerObj: Player): Future[Int] =
|
||||||
db.run(playersTable.insertOrUpdate(PlayerRep.fromPlayer(player)))
|
getPlayer(playerObj.playerId).map {
|
||||||
|
case Some(id) => db.run(playersTable.update(PlayerRep.fromPlayer(playerObj, Some(id))))
|
||||||
|
case _ => db.run(playersTable.insertOrUpdate(PlayerRep.fromPlayer(playerObj, None)))
|
||||||
|
}.flatten
|
||||||
|
|
||||||
private def player(playerId: PlayerId) =
|
private def player(playerId: PlayerId) =
|
||||||
playersTable
|
playersTable
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package me.arcanis.ffxivbis.storage
|
package me.arcanis.ffxivbis.storage
|
||||||
|
|
||||||
import me.arcanis.ffxivbis.models.{Permission, User}
|
import me.arcanis.ffxivbis.models.{Permission, User}
|
||||||
import slick.lifted.Index
|
import slick.lifted.{Index, PrimaryKey}
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ trait UsersProfile { this: DatabaseProfile =>
|
|||||||
def toUser: User = User(partyId, username, password, Permission.withName(permission))
|
def toUser: User = User(partyId, username, password, Permission.withName(permission))
|
||||||
}
|
}
|
||||||
object UserRep {
|
object UserRep {
|
||||||
def fromUser(user: User): UserRep =
|
def fromUser(user: User, id: Option[Long]): UserRep =
|
||||||
UserRep(user.partyId, None, user.username, user.password, user.permission.toString)
|
UserRep(user.partyId, None, user.username, user.password, user.permission.toString)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,6 +27,7 @@ trait UsersProfile { this: DatabaseProfile =>
|
|||||||
def * =
|
def * =
|
||||||
(partyId, userId.?, username, password, permission) <> ((UserRep.apply _).tupled, UserRep.unapply)
|
(partyId, userId.?, username, password, permission) <> ((UserRep.apply _).tupled, UserRep.unapply)
|
||||||
|
|
||||||
|
def pk: PrimaryKey = primaryKey("users_username_idx", (partyId, username))
|
||||||
def usersUsernameIdx: Index =
|
def usersUsernameIdx: Index =
|
||||||
index("users_username_idx", (partyId, username), unique = true)
|
index("users_username_idx", (partyId, username), unique = true)
|
||||||
}
|
}
|
||||||
@ -37,9 +38,11 @@ trait UsersProfile { this: DatabaseProfile =>
|
|||||||
db.run(user(partyId, Some(username)).result.headOption).map(_.map(_.toUser))
|
db.run(user(partyId, Some(username)).result.headOption).map(_.map(_.toUser))
|
||||||
def getUsers(partyId: String): Future[Seq[User]] =
|
def getUsers(partyId: String): Future[Seq[User]] =
|
||||||
db.run(user(partyId, None).result).map(_.map(_.toUser))
|
db.run(user(partyId, None).result).map(_.map(_.toUser))
|
||||||
def insertUser(user: User): Future[Int] = {
|
def insertUser(userObj: User): Future[Int] =
|
||||||
db.run(usersTable.insertOrUpdate(UserRep.fromUser(user)))
|
db.run(user(userObj.partyId, Some(userObj.username)).result.headOption).map {
|
||||||
}
|
case Some(user) => db.run(usersTable.update(UserRep.fromUser(userObj, user.userId)))
|
||||||
|
case _ => db.run(usersTable.insertOrUpdate(UserRep.fromUser(userObj, None)))
|
||||||
|
}.flatten
|
||||||
|
|
||||||
private def user(partyId: String, username: Option[String]) =
|
private def user(partyId: String, username: Option[String]) =
|
||||||
usersTable
|
usersTable
|
||||||
|
Loading…
Reference in New Issue
Block a user