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 += "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-spray-json" % "10.1.10"
|
||||
libraryDependencies += "com.typesafe.akka" %% "akka-stream" % "2.5.23"
|
||||
libraryDependencies += "com.github.swagger-akka-http" %% "swagger-akka-http" % "2.0.4"
|
||||
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-hikaricp" % "3.3.2"
|
||||
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) {
|
||||
|
||||
def addPieceBiS(playerId: PlayerId, piece: Piece)
|
||||
(implicit executionContext: ExecutionContext): Future[Unit] =
|
||||
Future { storage ! DatabaseBiSHandler.AddPieceToBis(playerId, piece) }
|
||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
||||
(storage ? DatabaseBiSHandler.AddPieceToBis(playerId, piece)).mapTo[Int]
|
||||
|
||||
def bis(partyId: String, playerId: Option[PlayerId])
|
||||
(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, _)))
|
||||
|
||||
def removePieceBiS(playerId: PlayerId, piece: Piece)
|
||||
(implicit executionContext: ExecutionContext): Future[Unit] =
|
||||
Future { storage ! DatabaseBiSHandler.RemovePieceFromBiS(playerId, piece) }
|
||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
||||
(storage ? DatabaseBiSHandler.RemovePieceFromBiS(playerId, piece)).mapTo[Int]
|
||||
|
||||
}
|
||||
|
@ -12,16 +12,16 @@ import scala.concurrent.{ExecutionContext, Future}
|
||||
class LootHelper(storage: ActorRef) {
|
||||
|
||||
def addPieceLoot(playerId: PlayerId, piece: Piece)
|
||||
(implicit executionContext: ExecutionContext): Future[Unit] =
|
||||
Future { storage ! DatabaseLootHandler.AddPieceTo(playerId, piece) }
|
||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
||||
(storage ? DatabaseLootHandler.AddPieceTo(playerId, piece)).mapTo[Int]
|
||||
|
||||
def loot(partyId: String, playerId: Option[PlayerId])
|
||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[Player]] =
|
||||
(storage ? DatabaseLootHandler.GetLoot(partyId, playerId)).mapTo[Seq[Player]]
|
||||
|
||||
def removePieceLoot(playerId: PlayerId, piece: Piece)
|
||||
(implicit executionContext: ExecutionContext): Future[Unit] =
|
||||
Future { storage ! DatabaseLootHandler.RemovePieceFrom(playerId, piece) }
|
||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
||||
(storage ? DatabaseLootHandler.RemovePieceFrom(playerId, piece)).mapTo[Int]
|
||||
|
||||
def suggestPiece(partyId: String, piece: Piece)
|
||||
(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) {
|
||||
|
||||
def addPlayer(player: Player)
|
||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] =
|
||||
Future { storage ! DatabasePartyHandler.AddPlayer(player) }.andThen {
|
||||
case Success(_) if player.link.isDefined =>
|
||||
downloadBiS(player.link.get, player.job).map { bis =>
|
||||
bis.pieces.map(storage ! DatabaseBiSHandler.AddPieceToBis(player.playerId, _))
|
||||
}.map(_ => ())
|
||||
case Success(_) => Future.successful(())
|
||||
case Failure(exception) => Future.failed(exception)
|
||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
||||
(storage ? DatabasePartyHandler.AddPlayer(player)).mapTo[Int].map { res =>
|
||||
player.link match {
|
||||
case Some(link) =>
|
||||
downloadBiS(link, player.job).map { bis =>
|
||||
bis.pieces.map(storage ? DatabaseBiSHandler.AddPieceToBis(player.playerId, _))
|
||||
}.map(_ => res)
|
||||
case None => Future.successful(res)
|
||||
}
|
||||
}.flatten
|
||||
|
||||
def getPlayers(partyId: String, maybePlayerId: Option[PlayerId])
|
||||
(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)
|
||||
}
|
||||
|
||||
def removePlayer(playerId: PlayerId)(implicit executionContext: ExecutionContext): Future[Unit] =
|
||||
Future { storage ! DatabasePartyHandler.RemovePlayer(playerId) }
|
||||
def removePlayer(playerId: PlayerId)
|
||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
||||
(storage ? DatabasePartyHandler.RemovePlayer(playerId)).mapTo[Int]
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
package me.arcanis.ffxivbis.http
|
||||
|
||||
import akka.actor.{ActorRef, ActorSystem}
|
||||
import akka.http.scaladsl.model._
|
||||
import akka.http.scaladsl.server.Directives._
|
||||
import akka.http.scaladsl.server._
|
||||
import akka.util.Timeout
|
||||
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)
|
||||
extends StrictLogging {
|
||||
@ -17,7 +17,8 @@ class RootEndpoint(system: ActorSystem, storage: ActorRef, ariyala: ActorRef)
|
||||
implicit val timeout: 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
|
||||
|
||||
@ -25,7 +26,7 @@ class RootEndpoint(system: ActorSystem, storage: ActorRef, ariyala: ActorRef)
|
||||
ignoreTrailingSlash {
|
||||
pathPrefix("api") {
|
||||
pathPrefix(Segment) {
|
||||
case "v1" => apiV1Endpoint.route
|
||||
case "v1" => rootApiV1Endpoint.route
|
||||
case _ => reject
|
||||
}
|
||||
}
|
||||
@ -33,9 +34,9 @@ class RootEndpoint(system: ActorSystem, storage: ActorRef, ariyala: ActorRef)
|
||||
|
||||
private def htmlRoute: Route =
|
||||
ignoreTrailingSlash {
|
||||
pathEndOrSingleSlash {
|
||||
complete(StatusCodes.OK)
|
||||
}
|
||||
pathPrefix("static") {
|
||||
getFromResourceDirectory("static")
|
||||
} ~ rootView.route
|
||||
}
|
||||
|
||||
private def swaggerUIRoute: Route =
|
||||
|
@ -11,8 +11,8 @@ import scala.concurrent.{ExecutionContext, Future}
|
||||
class UserHelper(storage: ActorRef) {
|
||||
|
||||
def addUser(user: User, isHashedPassword: Boolean)
|
||||
(implicit executionContext: ExecutionContext): Future[Unit] =
|
||||
Future { storage ! DatabaseUserHandler.InsertUser(user, isHashedPassword) }
|
||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
||||
(storage ? DatabaseUserHandler.InsertUser(user, isHashedPassword)).mapTo[Int]
|
||||
|
||||
def user(partyId: String, username: String)
|
||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Option[User]] =
|
||||
@ -23,6 +23,6 @@ class UserHelper(storage: ActorRef) {
|
||||
(storage ? DatabaseUserHandler.GetUsers(partyId)).mapTo[Seq[User]]
|
||||
|
||||
def removeUser(partyId: String, username: String)
|
||||
(implicit executionContext: ExecutionContext): Future[Unit] =
|
||||
Future { storage ! DatabaseUserHandler.DeleteUser(partyId, username) }
|
||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
||||
(storage ? DatabaseUserHandler.DeleteUser(partyId, username)).mapTo[Int]
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package me.arcanis.ffxivbis.http.api.v1
|
||||
|
||||
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._
|
||||
import akka.util.Timeout
|
||||
@ -19,7 +19,6 @@ import me.arcanis.ffxivbis.models.PlayerId
|
||||
@Path("api/v1")
|
||||
class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout)
|
||||
extends BiSHelper(storage, ariyala) with Authorization with JsonSupport {
|
||||
import spray.json.DefaultJsonProtocol._
|
||||
|
||||
def route: Route = createBiS ~ getBiS ~ modifyBiS
|
||||
|
||||
@ -49,7 +48,7 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti
|
||||
put {
|
||||
entity(as[PlayerBiSLinkResponse]) { bisLink =>
|
||||
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.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
|
||||
|
||||
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._
|
||||
import akka.util.Timeout
|
||||
@ -19,7 +19,6 @@ import me.arcanis.ffxivbis.models.PlayerId
|
||||
@Path("api/v1")
|
||||
class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
||||
extends LootHelper(storage) with Authorization with JsonSupport {
|
||||
import spray.json.DefaultJsonProtocol._
|
||||
|
||||
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.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
|
||||
|
||||
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._
|
||||
import akka.util.Timeout
|
||||
@ -19,7 +19,6 @@ import me.arcanis.ffxivbis.models.PlayerId
|
||||
@Path("api/v1")
|
||||
class PlayerEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout)
|
||||
extends PlayerHelper(storage, ariyala) with Authorization with JsonSupport {
|
||||
import spray.json.DefaultJsonProtocol._
|
||||
|
||||
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.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.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 lootEndpoint = new LootEndpoint(storage)
|
@ -1,7 +1,7 @@
|
||||
package me.arcanis.ffxivbis.http.api.v1
|
||||
|
||||
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._
|
||||
import akka.util.Timeout
|
||||
@ -19,12 +19,11 @@ import me.arcanis.ffxivbis.models.Permission
|
||||
@Path("api/v1")
|
||||
class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
||||
extends UserHelper(storage) with Authorization with JsonSupport {
|
||||
import spray.json.DefaultJsonProtocol._
|
||||
|
||||
def route: Route = createParty ~ createUser ~ deleteUser ~ getUsers
|
||||
|
||||
@PUT
|
||||
@Path("party/{partyId}")
|
||||
@Path("party/{partyId}/create")
|
||||
@Consumes(value = Array("application/json"))
|
||||
@Operation(summary = "create new party", description = "Create new party with specified ID",
|
||||
parameters = Array(
|
||||
@ -41,13 +40,13 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
||||
tags = Array("party"),
|
||||
)
|
||||
def createParty: Route =
|
||||
path("party" / Segment) { partyId: String =>
|
||||
path("party" / Segment / "create") { partyId: String =>
|
||||
extractExecutionContext { implicit executionContext =>
|
||||
put {
|
||||
entity(as[UserResponse]) { user =>
|
||||
val admin = user.toUser.copy(partyId = partyId, permission = Permission.admin)
|
||||
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 =>
|
||||
val withPartyId = user.toUser.copy(partyId = partyId)
|
||||
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)) { _ =>
|
||||
delete {
|
||||
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 spray.json._
|
||||
|
||||
trait JsonSupport extends SprayJsonSupport {
|
||||
import DefaultJsonProtocol._
|
||||
trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
|
||||
|
||||
private def enumFormat[E <: Enumeration](enum: E): 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 piece: String
|
||||
|
||||
def isTomeToString: String = if (isTome) "yes" else "no"
|
||||
def upgrade: Option[PieceUpgrade] = this match {
|
||||
case _ if !isTome => None
|
||||
case _: Waist => Some(AccessoryUpgrade)
|
||||
@ -94,4 +95,8 @@ object Piece {
|
||||
case "weapon upgrade" => WeaponUpgrade
|
||||
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
|
||||
|
||||
import scala.util.matching.Regex
|
||||
|
||||
trait PlayerIdBase {
|
||||
def job: Job.Job
|
||||
def nick: String
|
||||
@ -15,4 +17,10 @@ object PlayerId {
|
||||
case (Some(nick), Some(job)) => Some(PlayerId(partyId, Job.fromString(job), nick))
|
||||
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 = {
|
||||
case AddPieceToBis(playerId, piece) =>
|
||||
profile.insertPieceBiS(playerId, piece)
|
||||
val client = sender()
|
||||
profile.insertPieceBiS(playerId, piece).pipeTo(client)
|
||||
|
||||
case GetBiS(partyId, maybePlayerId) =>
|
||||
val client = sender()
|
||||
@ -18,7 +19,8 @@ trait DatabaseBiSHandler { this: Database =>
|
||||
.pipeTo(client)
|
||||
|
||||
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 = {
|
||||
case AddPieceTo(playerId, piece) =>
|
||||
profile.insertPiece(playerId, piece)
|
||||
val client = sender()
|
||||
profile.insertPiece(playerId, piece).pipeTo(client)
|
||||
|
||||
case GetLoot(partyId, maybePlayerId) =>
|
||||
val client = sender()
|
||||
@ -18,7 +19,8 @@ trait DatabaseLootHandler { this: Database =>
|
||||
.pipeTo(client)
|
||||
|
||||
case RemovePieceFrom(playerId, piece) =>
|
||||
profile.deletePiece(playerId, piece)
|
||||
val client = sender()
|
||||
profile.deletePiece(playerId, piece).pipeTo(client)
|
||||
|
||||
case SuggestLoot(partyId, piece) =>
|
||||
val client = sender()
|
||||
|
@ -11,7 +11,8 @@ trait DatabasePartyHandler { this: Actor with StrictLogging with Database =>
|
||||
|
||||
def partyHandler: Receive = {
|
||||
case AddPlayer(player) =>
|
||||
profile.insertPlayer(player)
|
||||
val client = sender()
|
||||
profile.insertPlayer(player).pipeTo(client)
|
||||
|
||||
case GetParty(partyId) =>
|
||||
val client = sender()
|
||||
@ -27,7 +28,8 @@ trait DatabasePartyHandler { this: Actor with StrictLogging with Database =>
|
||||
player.pipeTo(client)
|
||||
|
||||
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 = {
|
||||
case DeleteUser(partyId, username) =>
|
||||
profile.deleteUser(partyId, username)
|
||||
val client = sender()
|
||||
profile.deleteUser(partyId, username).pipeTo(client)
|
||||
|
||||
case GetUser(partyId, username) =>
|
||||
val client = sender()
|
||||
@ -20,8 +21,9 @@ trait DatabaseUserHandler { this: Database =>
|
||||
profile.getUsers(partyId).pipeTo(client)
|
||||
|
||||
case InsertUser(user, isHashedPassword) =>
|
||||
val client = sender()
|
||||
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
|
||||
|
||||
import me.arcanis.ffxivbis.models.{Job, Loot, Piece}
|
||||
import slick.lifted.{ForeignKeyQuery, Index}
|
||||
import slick.lifted.{ForeignKeyQuery, Index, PrimaryKey}
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
@ -18,9 +18,9 @@ trait BiSProfile { this: DatabaseProfile =>
|
||||
}
|
||||
|
||||
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 piece: Rep[String] = column[String]("piece")
|
||||
def piece: Rep[String] = column[String]("piece", O.PrimaryKey)
|
||||
def isTome: Rep[Int] = column[Int]("is_tome")
|
||||
def job: Rep[String] = column[String]("job")
|
||||
|
||||
@ -29,8 +29,6 @@ trait BiSProfile { this: DatabaseProfile =>
|
||||
|
||||
def fkPlayerId: ForeignKeyQuery[Players, PlayerRep] =
|
||||
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] =
|
||||
|
@ -1,7 +1,7 @@
|
||||
package me.arcanis.ffxivbis.storage
|
||||
|
||||
import me.arcanis.ffxivbis.models.{BiS, Job, Player, PlayerId}
|
||||
import slick.lifted.Index
|
||||
import slick.lifted.{Index, PrimaryKey}
|
||||
|
||||
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)
|
||||
}
|
||||
object PlayerRep {
|
||||
def fromPlayer(player: Player): PlayerRep =
|
||||
PlayerRep(player.partyId, None, DatabaseProfile.now, player.nick,
|
||||
def fromPlayer(player: Player, id: Option[Long]): PlayerRep =
|
||||
PlayerRep(player.partyId, id, DatabaseProfile.now, player.nick,
|
||||
player.job.toString, player.link, player.priority)
|
||||
}
|
||||
|
||||
@ -30,11 +30,9 @@ trait PlayersProfile { this: DatabaseProfile =>
|
||||
|
||||
def * =
|
||||
(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 getParty(partyId: String): Future[Map[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)
|
||||
def getPlayers(partyId: String): Future[Seq[Long]] =
|
||||
db.run(players(partyId).map(_.playerId).result)
|
||||
def insertPlayer(player: Player): Future[Int] =
|
||||
db.run(playersTable.insertOrUpdate(PlayerRep.fromPlayer(player)))
|
||||
def insertPlayer(playerObj: Player): Future[Int] =
|
||||
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) =
|
||||
playersTable
|
||||
|
@ -1,7 +1,7 @@
|
||||
package me.arcanis.ffxivbis.storage
|
||||
|
||||
import me.arcanis.ffxivbis.models.{Permission, User}
|
||||
import slick.lifted.Index
|
||||
import slick.lifted.{Index, PrimaryKey}
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
@ -13,7 +13,7 @@ trait UsersProfile { this: DatabaseProfile =>
|
||||
def toUser: User = User(partyId, username, password, Permission.withName(permission))
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ trait UsersProfile { this: DatabaseProfile =>
|
||||
def * =
|
||||
(partyId, userId.?, username, password, permission) <> ((UserRep.apply _).tupled, UserRep.unapply)
|
||||
|
||||
def pk: PrimaryKey = primaryKey("users_username_idx", (partyId, username))
|
||||
def usersUsernameIdx: Index =
|
||||
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))
|
||||
def getUsers(partyId: String): Future[Seq[User]] =
|
||||
db.run(user(partyId, None).result).map(_.map(_.toUser))
|
||||
def insertUser(user: User): Future[Int] = {
|
||||
db.run(usersTable.insertOrUpdate(UserRep.fromUser(user)))
|
||||
}
|
||||
def insertUser(userObj: User): Future[Int] =
|
||||
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]) =
|
||||
usersTable
|
||||
|
Loading…
Reference in New Issue
Block a user