diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpoint.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpoint.scala index b59c67a..a7fda3e 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpoint.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpoint.scala @@ -42,7 +42,7 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti tags = Array("best in slot"), ) def createBiS: Route = - path("party" / Segment / "bis") { partyId: String => + path("party" / Segment / "bis") { partyId => extractExecutionContext { implicit executionContext => authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => put { @@ -77,7 +77,7 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti tags = Array("best in slot"), ) def getBiS: Route = - path("party" / Segment / "bis") { partyId: String => + path("party" / Segment / "bis") { partyId => extractExecutionContext { implicit executionContext => authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => get { @@ -110,7 +110,7 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti tags = Array("best in slot"), ) def modifyBiS: Route = - path("party" / Segment / "bis") { partyId: String => + path("party" / Segment / "bis") { partyId => extractExecutionContext { implicit executionContext => authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => post { diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpoint.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpoint.scala index 6b85b46..f1e60e1 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpoint.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpoint.scala @@ -44,7 +44,7 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) tags = Array("loot"), ) def getLoot: Route = - path("party" / Segment / "loot") { partyId: String => + path("party" / Segment / "loot") { partyId => extractExecutionContext { implicit executionContext => authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => get { @@ -77,7 +77,7 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) tags = Array("loot"), ) def modifyLoot: Route = - path("party" / Segment / "loot") { partyId: String => + path("party" / Segment / "loot") { partyId => extractExecutionContext { implicit executionContext => authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => post { @@ -120,7 +120,7 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) tags = Array("loot"), ) def suggestLoot: Route = - path("party" / Segment / "loot") { partyId: String => + path("party" / Segment / "loot") { partyId => extractExecutionContext { implicit executionContext => authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => put { diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpoint.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpoint.scala index fc474bb..ad3bd8b 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpoint.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpoint.scala @@ -44,7 +44,7 @@ class PlayerEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit tags = Array("party"), ) def getParty: Route = - path("party" / Segment) { partyId: String => + path("party" / Segment) { partyId => extractExecutionContext { implicit executionContext => authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => get { @@ -77,7 +77,7 @@ class PlayerEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit tags = Array("party"), ) def modifyParty: Route = - path("party" / Segment) { partyId: String => + path("party" / Segment) { partyId => extractExecutionContext { implicit executionContext => authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => entity(as[PlayerActionResponse]) { action => diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/UserEndpoint.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/UserEndpoint.scala index 9e93d6e..8c54e98 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/UserEndpoint.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/UserEndpoint.scala @@ -40,7 +40,7 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) tags = Array("party"), ) def createParty: Route = - path("party" / Segment / "create") { partyId: String => + path("party" / Segment / "create") { partyId => extractExecutionContext { implicit executionContext => put { entity(as[UserResponse]) { user => @@ -73,7 +73,7 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) tags = Array("users"), ) def createUser: Route = - path("party" / Segment / "users") { partyId: String => + path("party" / Segment / "users") { partyId => extractExecutionContext { implicit executionContext => authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ => post { @@ -105,7 +105,7 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) tags = Array("users"), ) def deleteUser: Route = - path("party" / Segment / "users" / Segment) { (partyId: String, username: String) => + path("party" / Segment / "users" / Segment) { (partyId, username) => extractExecutionContext { implicit executionContext => authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ => delete { @@ -137,7 +137,7 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) tags = Array("users"), ) def getUsers: Route = - path("party" / Segment / "users") { partyId: String => + path("party" / Segment / "users") { partyId => extractExecutionContext { implicit executionContext => authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ => get { diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/BasePartyView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/BasePartyView.scala new file mode 100644 index 0000000..1fc8b18 --- /dev/null +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/BasePartyView.scala @@ -0,0 +1,55 @@ +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 + +class BasePartyView(override val storage: ActorRef)(implicit timeout: Timeout) + extends Authorization { + + def route: Route = getIndex + + def getIndex: Route = + path("party" / Segment) { partyId => + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => + get { + complete { + (StatusCodes.OK, RootView.toHtml(BasePartyView.template(partyId))) + } + } + } + } + } +} + +object BasePartyView { + import scalatags.Text + import scalatags.Text.all._ + + def root(partyId: String): Text.TypedTag[String] = + a(href:=s"/party/$partyId", title:="root")("root") + + def template(partyId: String): String = + "" + + html(lang:="en", + head( + title:=s"Party $partyId", + link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css") + ), + + body( + h2(s"Party $partyId"), + br, + h2(a(href:=s"/party/$partyId/players", title:="party")("party")), + h2(a(href:=s"/party/$partyId/bis", title:="bis management")("best in slot")), + h2(a(href:=s"/party/$partyId/loot", title:="loot management")("loot")), + h2(a(href:=s"/party/$partyId/suggest", title:="suggest loot")("suggest")), + hr, + h2(a(href:=s"/party/$partyId/users", title:="user management")("users")) + ) + ) +} 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 a0aed54..a3e4c3e 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/view/BiSView.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/BiSView.scala @@ -136,6 +136,7 @@ object BiSView { ), ExportToCSVView.template, + BasePartyView.root(partyId), script(src:="/static/table_search.js", `type`:="text/javascript") ) ) diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/IndexView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/IndexView.scala new file mode 100644 index 0000000..38dcdb8 --- /dev/null +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/IndexView.scala @@ -0,0 +1,71 @@ +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.UserHelper +import me.arcanis.ffxivbis.models.{Permission, User} +import me.arcanis.ffxivbis.service.Party + +class IndexView(storage: ActorRef)(implicit timeout: Timeout) + extends UserHelper(storage) { + + def route: Route = createParty ~ getIndex + + def createParty: Route = + path("party" / Segment / "create") { partyId => + extractExecutionContext { implicit executionContext => + post { + formFields("username".as[String], "password".as[String]) { (username, password) => + val user = User(partyId, username, password, Permission.admin) + onComplete(addUser(user, isHashedPassword = false)) { + case _ => redirect(s"/party/$partyId", StatusCodes.Found) + } + } + } + } + } + + def getIndex: Route = + pathEndOrSingleSlash { + get { + parameters("partyId".as[String].?) { + case Some(partyId) => redirect(s"/party/$partyId", StatusCodes.Found) + case _ => complete((StatusCodes.OK, RootView.toHtml(IndexView.template))) + } + } + } +} + +object IndexView { + import scalatags.Text.all._ + + def template: String = + "" + + html( + head( + title:="FFXIV loot helper", + link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css") + ), + + body( + form(action:=s"party/${Party.randomPartyId}/create", method:="post")( + label("create a new party"), + input(name:="username", id:="username", placeholder:="username", title:="username", `type`:="text"), + input(name:="password", id:="password", placeholder:="password", title:="password", `type`:="password"), + input(name:="add", id:="add", `type`:="submit", value:="add") + ), + + br, + + form(action:="/", method:="get")( + label("already have party?"), + input(name:="partyId", id:="partyId", placeholder:="party id", title:="party id", `type`:="text"), + input(name:="go", id:="go", `type`:="submit", value:="go") + ) + ) + ) +} + diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/LootSuggestView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/LootSuggestView.scala index 0ec7a54..c45bf92 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/view/LootSuggestView.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/LootSuggestView.scala @@ -114,6 +114,7 @@ object LootSuggestView { ), ExportToCSVView.template, + BasePartyView.root(partyId), 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 index 5264413..db4cadc 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/view/LootView.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/LootView.scala @@ -121,6 +121,7 @@ object LootView { ), ExportToCSVView.template, + BasePartyView.root(partyId), script(src:="/static/table_search.js", `type`:="text/javascript") ) ) diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/PlayerView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/PlayerView.scala index 4581908..67d496a 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/view/PlayerView.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/PlayerView.scala @@ -16,7 +16,7 @@ class PlayerView(override val storage: ActorRef, ariyala: ActorRef)(implicit tim def route: Route = getParty ~ modifyParty def getParty: Route = - path("party" / Segment) { partyId: String => + path("party" / Segment / "players") { partyId: String => extractExecutionContext { implicit executionContext => authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => get { @@ -33,14 +33,14 @@ class PlayerView(override val storage: ActorRef, ariyala: ActorRef)(implicit tim } def modifyParty: Route = - path("party" / Segment) { partyId: String => + path("party" / Segment / "players") { partyId: String => extractExecutionContext { implicit executionContext => authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => post { formFields("nick".as[String], "job".as[String], "priority".as[Int].?, "link".as[String].?, "action".as[String]) { (nick, job, maybePriority, maybeLink, action) => onComplete(modifyPartyCall(partyId, nick, job, maybePriority, maybeLink, action)) { - case _ => redirect(s"/party/$partyId", StatusCodes.Found) + case _ => redirect(s"/party/$partyId/players", StatusCodes.Found) } } } @@ -81,7 +81,7 @@ object PlayerView { ErrorView.template(error), SearchLineView.template, - form(action:=s"/party/$partyId", method:="post")( + form(action:=s"/party/$partyId/players", method:="post")( input(name:="nick", id:="nick", placeholder:="nick", title:="nick", `type`:="nick"), select(name:="job", id:="job", title:="job") (for (job <- Job.groupAll) yield option(job.toString)), @@ -107,7 +107,7 @@ object PlayerView { td(player.lootCountTotal), td(player.priority), td( - form(action:=s"/party/$partyId", method:="post")( + form(action:=s"/party/$partyId/players", method:="post")( input(name:="nick", id:="nick", `type`:="hidden", value:=player.nick), input(name:="job", id:="job", `type`:="hidden", value:=player.job.toString), input(name:="action", id:="action", `type`:="hidden", value:="remove"), @@ -118,6 +118,7 @@ object PlayerView { ), ExportToCSVView.template, + BasePartyView.root(partyId), 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 4e4e47a..8da13cc 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/view/RootView.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/RootView.scala @@ -8,6 +8,9 @@ import akka.util.Timeout class RootView(storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout) { + private val basePartyView = new BasePartyView(storage) + private val indexView = new IndexView(storage) + private val biSView = new BiSView(storage, ariyala) private val lootView = new LootView(storage) private val lootSuggestView = new LootSuggestView(storage) @@ -15,6 +18,7 @@ class RootView(storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout) private val userView = new UserView(storage) def route: Route = + basePartyView.route ~ indexView.route ~ biSView.route ~ lootView.route ~ lootSuggestView.route ~ playerView.route ~ userView.route } diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/UserView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/UserView.scala index 087e9e0..1e6654c 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/view/UserView.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/UserView.scala @@ -112,6 +112,7 @@ object UserView { ), ExportToCSVView.template, + BasePartyView.root(partyId), script(src:="/static/table_search.js", `type`:="text/javascript") ) ) diff --git a/src/main/scala/me/arcanis/ffxivbis/service/Party.scala b/src/main/scala/me/arcanis/ffxivbis/service/Party.scala index 7463ed1..25d7f72 100644 --- a/src/main/scala/me/arcanis/ffxivbis/service/Party.scala +++ b/src/main/scala/me/arcanis/ffxivbis/service/Party.scala @@ -31,7 +31,7 @@ case class Party(partyId: String, config: Config, players: Map[PlayerId, Player] object Party { def apply(partyId: Option[String], config: Config): Party = - new Party(partyId.getOrElse(Random.alphanumeric.take(20).mkString), config, Map.empty) + new Party(partyId.getOrElse(randomPartyId), config, Map.empty) def apply(partyId: String, config: Config, players: Map[Long, Player], bis: Seq[Loot], loot: Seq[Loot]): Party = { @@ -45,4 +45,6 @@ object Party { } Party(partyId, config, playersWithItems) } + + def randomPartyId: String = Random.alphanumeric.take(20).mkString }