From d5233361e52c027a6169d32871c3f2c7655f1658 Mon Sep 17 00:00:00 2001 From: Evgeniy Alekseev Date: Thu, 17 Oct 2019 02:01:04 +0300 Subject: [PATCH] add some views --- .../arcanis/ffxivbis/http/view/BiSView.scala | 106 ++++++++------- .../arcanis/ffxivbis/http/view/LootView.scala | 126 ++++++++++++++++++ .../arcanis/ffxivbis/http/view/RootView.scala | 4 +- .../arcanis/ffxivbis/http/view/UserView.scala | 118 ++++++++++++++++ 4 files changed, 299 insertions(+), 55 deletions(-) create mode 100644 src/main/scala/me/arcanis/ffxivbis/http/view/LootView.scala create mode 100644 src/main/scala/me/arcanis/ffxivbis/http/view/UserView.scala diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/BiSView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/BiSView.scala index e7dd2b0..6d971f5 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/view/BiSView.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/BiSView.scala @@ -36,7 +36,7 @@ class BiSView(override val storage: ActorRef, ariyala: ActorRef)(implicit timeou def modifyBiS: Route = path("party" / Segment / "bis") { partyId: String => extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => + authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => post { formFields("player".as[String], "piece".as[String].?, "is_tome".as[String].?, "link".as[String].?, "action".as[String]) { (player, maybePiece, maybeIsTome, maybeLink, action) => @@ -77,66 +77,64 @@ class BiSView(override val storage: ActorRef, ariyala: ActorRef)(implicit timeou object BiSView { import scalatags.Text.all._ - def template(partyId: String, party: Seq[Player], pieces: Seq[String], error: Option[String]): String = { + def template(partyId: String, party: Seq[Player], pieces: Seq[String], error: Option[String]): String = "" + - 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") + html(lang:="en", + head( + title:="Best in slot", + link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css") ), - 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") - ), + body( + h2("Best in slot"), - table( - tr( - th("player"), - th("piece"), - th("is tome"), - th("") - //td(`class`:="include_search") + 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") ), - 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") + + 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(id:="result")( + tr( + th("player"), + th("piece"), + th("is tome"), + th("") + ), + 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") + ExportToCSVView.template, + script(src:="/static/table_search.js", `type`:="text/javascript") + ) ) - ) - } } diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/LootView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/LootView.scala new file mode 100644 index 0000000..744e69b --- /dev/null +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/LootView.scala @@ -0,0 +1,126 @@ +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.Route +import akka.util.Timeout +import me.arcanis.ffxivbis.http.{Authorization, LootHelper} +import me.arcanis.ffxivbis.models.{Piece, Player, PlayerId} + +import scala.concurrent.{ExecutionContext, Future} +import scala.util.Try + +class LootView (override val storage: ActorRef)(implicit timeout: Timeout) + extends LootHelper(storage) with Authorization { + + def route: Route = getLoot ~ modifyLoot + + def getLoot: Route = + path("party" / Segment / "loot") { partyId: String => + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => + get { + complete { + loot(partyId, None).map { players => + LootView.template(partyId, players, Piece.available, None) + }.map { text => + (StatusCodes.OK, RootView.toHtml(text)) + } + } + } + } + } + } + + def modifyLoot: Route = + path("party" / Segment / "loot") { partyId: String => + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => + post { + formFields("player".as[String], "piece".as[String], "is_tome".as[String].?, "action".as[String]) { + (player, maybePiece, maybeIsTome, action) => + onComplete(modifyLootCall(partyId, player, maybePiece, maybeIsTome, action)) { + case _ => redirect(s"/party/$partyId/loot", StatusCodes.Found) + } + } + } + } + } + } + + private def modifyLootCall(partyId: String, player: String, + maybePiece: String, maybeIsTome: Option[String], + action: String) + (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = { + def getPiece(playerId: PlayerId) = + Try(Piece(maybePiece, maybeIsTome.isDefined, playerId.job)).toOption + + PlayerId(partyId, player) match { + case Some(playerId) => (getPiece(playerId), action) match { + case (Some(piece), "add") => addPieceLoot(playerId, piece).map(_ => ()) + case (Some(piece), "remove") => removePieceLoot(playerId, piece).map(_ => ()) + case _ => Future.failed(new Error(s"Could not construct piece from `$maybePiece`")) + } + case _ => Future.failed(new Error(s"Could not construct player id from `$player`")) + } + } +} + +object LootView { + import scalatags.Text.all._ + + def template(partyId: String, party: Seq[Player], pieces: Seq[String], error: Option[String]): String = + "" + + html(lang:="en", + head( + title:="Loot", + link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css") + ), + + body( + h2("Loot"), + + ErrorView.template(error), + SearchLineView.template, + + form(action:=s"/party/$partyId/loot", 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") + ), + + table(id:="result")( + tr( + th("player"), + th("piece"), + th("is tome"), + th("") + ), + 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/loot", 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") + ) + ) + +} diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/RootView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/RootView.scala index 12cbaa1..8745847 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/view/RootView.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/RootView.scala @@ -9,9 +9,11 @@ import akka.util.Timeout class RootView(storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout) { private val biSView = new BiSView(storage, ariyala) + private val lootView = new LootView(storage) + private val userView = new UserView(storage) def route: Route = - biSView.route + biSView.route ~ lootView.route ~ userView.route } object RootView { diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/UserView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/UserView.scala new file mode 100644 index 0000000..3db6e01 --- /dev/null +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/UserView.scala @@ -0,0 +1,118 @@ +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.Route +import akka.util.Timeout +import me.arcanis.ffxivbis.http.{Authorization, UserHelper} +import me.arcanis.ffxivbis.models.{Permission, User} + +import scala.concurrent.{ExecutionContext, Future} +import scala.util.Try + +class UserView(override val storage: ActorRef)(implicit timeout: Timeout) + extends UserHelper(storage) with Authorization { + + def route: Route = getUsers + + def getUsers: Route = + path("party" / Segment / "users") { partyId: String => + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ => + get { + complete { + users(partyId).map { users => + UserView.template(partyId, users, None) + }.map { text => + (StatusCodes.OK, RootView.toHtml(text)) + } + } + } + } + } + } + + def modifyUsers: Route = + path("party" / Segment / "users") { partyId: String => + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ => + post { + formFields("username".as[String], "password".as[String].?, "permission".as[String].?, "action".as[String]) { + (username, maybePassword, maybePermission, action) => + onComplete(modifyUsersCall(partyId, username, maybePassword, maybePermission, action)) { + case _ => redirect(s"/party/$partyId/users", StatusCodes.Found) + } + } + } + } + } + } + + private def modifyUsersCall(partyId: String, username: String, + maybePassword: Option[String], maybePermission: Option[String], + action: String) + (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = { + def permission: Option[Permission.Value] = + maybePermission.flatMap(p => Try(Permission.withName(p)).toOption) + + action match { + case "add" => (maybePassword, permission) match { + case (Some(password), Some(permission)) => addUser(User(partyId, username, password, permission), isHashedPassword = false).map(_ => ()) + case _ => Future.failed(new Error(s"Could not construct permission/password from `$maybePermission`/`$maybePassword`")) + } + case "remove" => removeUser(partyId, username).map(_ => ()) + case _ => Future.failed(new Error(s"Could not perform $action")) + } + } +} + +object UserView { + import scalatags.Text.all._ + + def template(partyId: String, users: Seq[User], error: Option[String]) = + "" + + html(lang:="en", + head( + title:="Users", + link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css") + ), + + body( + h2("Users"), + + ErrorView.template(error), + SearchLineView.template, + + form(action:=s"/party/$partyId/users", method:="post")( + input(name:="username", id:="username", title:="username", placeholder:="username", `type`:="text"), + input(name:="password", id:="password", title:="password", placeholder:="password", `type`:="password"), + select(name:="permission", id:="permission", title:="permission")(option("get"), option("post")), + input(name:="action", id:="action", `type`:="hidden", value:="add"), + input(name:="add", id:="add", `type`:="submit", value:="add") + ), + + table(id:="result")( + tr( + th("username"), + th("permission"), + th("") + ), + for (user <- users) yield tr( + td(`class`:="include_search")(user.username), + td(user.permission.toString), + td( + form(action:=s"/party/$partyId/users", method:="post")( + input(name:="username", id:="username", `type`:="hidden", value:=user.username.toString), + 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") + ) + ) +}