diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index e21274e..85c0f89 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -25,9 +25,9 @@ jobs:
uses: actions/setup-java@v2
with:
distribution: temurin
- java-version: 8
+ java-version: 17
- name: create dist
- run: sbt -v dist
+ run: make dist
- name: release
uses: softprops/action-gh-release@v1
with:
diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index d83ea63..bae636a 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -17,6 +17,6 @@ jobs:
uses: actions/setup-java@v2
with:
distribution: temurin
- java-version: 8
+ java-version: 17
- name: run tests
- run: sbt -v +test
\ No newline at end of file
+ run: make tests
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..f2eee08
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,35 @@
+.PHONY: check clean compile dist push tests version
+.DEFAULT_GOAL := compile
+
+PROJECT := ffxivbis
+
+check:
+ sbt scalafmtCheck
+
+clean:
+ sbt clean
+
+compile: clean
+ sbt compile
+
+format:
+ sbt scalafmt
+
+dist: tests version
+ sbt dist
+
+push: dist
+ git add version.sbt
+ git commit -m "Release $(VERSION)"
+ git tag "$(VERSION)"
+ git push
+ git push --tags
+
+tests: compile check
+ sbt test
+
+version:
+ifndef VERSION
+ $(error VERSION is required, but not set)
+endif
+ sed -i '/version := "[0-9.]*/s/[^"][^)]*/version := "$(VERSION)"/' version.sbt
diff --git a/libraries.sbt b/libraries.sbt
index 7f38267..cc09bee 100644
--- a/libraries.sbt
+++ b/libraries.sbt
@@ -14,7 +14,6 @@ libraryDependencies += "com.github.swagger-akka-http" %% "swagger-akka-http" % "
libraryDependencies += "jakarta.platform" % "jakarta.jakartaee-web-api" % "9.1.0"
libraryDependencies += "io.spray" %% "spray-json" % "1.3.6"
-libraryDependencies += "com.lihaoyi" %% "scalatags" % "0.9.2"
libraryDependencies += "com.typesafe.slick" %% "slick" % SlickVersion
libraryDependencies += "com.typesafe.slick" %% "slick-hikaricp" % SlickVersion
@@ -23,6 +22,8 @@ libraryDependencies += "org.xerial" % "sqlite-jdbc" % "3.36.0.3"
libraryDependencies += "org.postgresql" % "postgresql" % "42.3.1"
libraryDependencies += "org.mindrot" % "jbcrypt" % "0.4"
+libraryDependencies += "com.google.guava" % "guava" % "31.0.1-jre"
+
// testing
libraryDependencies += "org.scalactic" %% "scalactic" % ScalaTestVersion % "test"
diff --git a/src/main/resources/html/bis.html b/src/main/resources/html/bis.html
new file mode 100644
index 0000000..28032d5
--- /dev/null
+++ b/src/main/resources/html/bis.html
@@ -0,0 +1,349 @@
+
+
+
+
+ Best in slot
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Best in slot
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ nick |
+ job |
+ piece |
+ piece type |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/html/index.html b/src/main/resources/html/index.html
new file mode 100644
index 0000000..c524f14
--- /dev/null
+++ b/src/main/resources/html/index.html
@@ -0,0 +1,183 @@
+
+
+
+
+ FFXIV loot helper
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/html/loot.html b/src/main/resources/html/loot.html
new file mode 100644
index 0000000..8996a2b
--- /dev/null
+++ b/src/main/resources/html/loot.html
@@ -0,0 +1,338 @@
+
+
+
+
+ Loot table
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Looted items
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ nick |
+ job |
+ piece |
+ piece type |
+ is free loot |
+ date |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/html/party.html b/src/main/resources/html/party.html
new file mode 100644
index 0000000..b151f36
--- /dev/null
+++ b/src/main/resources/html/party.html
@@ -0,0 +1,258 @@
+
+
+
+
+ FFXIV loot helper
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ nick |
+ job |
+ best in slot link |
+ total bis pieces looted |
+ total pieces looted |
+ priority |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/html/redoc.html b/src/main/resources/html/redoc.html
new file mode 100644
index 0000000..6b67c4d
--- /dev/null
+++ b/src/main/resources/html/redoc.html
@@ -0,0 +1,19 @@
+
+
+
+
+ FFXIV loot helper API
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/html/swagger.html b/src/main/resources/html/swagger.html
deleted file mode 100644
index cdee79d..0000000
--- a/src/main/resources/html/swagger.html
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
- FFXIV loot tracker API
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main/resources/html/users.html b/src/main/resources/html/users.html
new file mode 100644
index 0000000..3e28c9f
--- /dev/null
+++ b/src/main/resources/html/users.html
@@ -0,0 +1,230 @@
+
+
+
+
+ User management
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Users
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ username |
+ permission |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf
index fc8f9f1..7701121 100644
--- a/src/main/resources/reference.conf
+++ b/src/main/resources/reference.conf
@@ -1,62 +1,71 @@
me.arcanis.ffxivbis {
- bis-provider {
- include "item_data.json"
+ bis-provider {
+ include "item_data.json"
- # xivapi base url, string, required
- xivapi-url = "https://xivapi.com"
- # xivapi developer key, string, optional
- #xivapi-key = "abcdef"
- }
-
- database {
- # database section. Section must be declared inside
- # for more detailed section descriptions refer to slick documentation
- mode = "sqlite"
-
- sqlite {
- profile = "slick.jdbc.SQLiteProfile$"
- db {
- url = "jdbc:sqlite:ffxivbis.db"
- user = "user"
- password = "password"
- }
- numThreads = 10
+ # xivapi base url, string, required
+ xivapi-url = "https://xivapi.com"
+ # xivapi developer key, string, optional
+ #xivapi-key = "abcdef"
}
- postgresql {
- profile = "slick.jdbc.PostgresProfile$"
- db {
- url = "jdbc:postgresql://localhost/ffxivbis"
- user = "ffxivbis"
- password = "ffxivbis"
+ database {
+ # database section. Section must be declared inside
+ # for more detailed section descriptions refer to slick documentation
+ mode = "sqlite"
- connectionPool = disabled
- keepAliveConnection = yes
- }
- numThreads = 10
+ sqlite {
+ profile = "slick.jdbc.SQLiteProfile$"
+ db {
+ url = "jdbc:sqlite:ffxivbis.db"
+ #user = "user"
+ #password = "password"
+ }
+ numThreads = 10
+ }
+
+ postgresql {
+ profile = "slick.jdbc.PostgresProfile$"
+ db {
+ url = "jdbc:postgresql://localhost/ffxivbis"
+ #user = "ffxivbis"
+ #password = "ffxivbis"
+
+ connectionPool = disabled
+ keepAliveConnection = yes
+ }
+ numThreads = 10
+ }
}
- }
- settings {
- # counters of Player class which will be called to sort players for loot priority
- # list of strings, required
- priority = [
- "isRequired", "lootCountBiS", "priority", "lootCount", "lootCountTotal"
- ]
- # general request timeout, duratin, required
- request-timeout = 10s
- # party in-memory storage lifetime
- cache-timeout = 1m
- }
+ settings {
+ # counters of Player class which will be called to sort players for loot priority
+ # list of strings, required
+ priority = [
+ "isRequired", "lootCountBiS", "priority", "lootCount", "lootCountTotal"
+ ]
+ # general request timeout, duratin, required
+ request-timeout = 10s
+ # party in-memory storage lifetime
+ cache-timeout = 1m
+ }
- web {
- # address to bind, string, required
- host = "127.0.0.1"
- # port to bind, int, required
- port = 8000
- # hostname to use in docs, if not set host:port will be used
- #hostname = "127.0.0.1:8000"
+ web {
+ # address to bind, string, required
+ host = "127.0.0.1"
+ # port to bind, int, required
+ port = 8000
+ # hostname to use in docs, if not set host:port will be used
+ #hostname = "127.0.0.1:8000"
+ # enable head requests for GET requests
+ enable-head-requests = yes
+
+ authorization-cache {
+ # maximum amount of cached logins
+ cache-size = 1024
+ # ttl of cached logins
+ cache-timeout = 1m
+ }
}
default-dispatcher {
diff --git a/src/main/resources/static/favicon.ico b/src/main/resources/static/favicon.ico
new file mode 100644
index 0000000..78d37d6
Binary files /dev/null and b/src/main/resources/static/favicon.ico differ
diff --git a/src/main/resources/static/load.js b/src/main/resources/static/load.js
new file mode 100644
index 0000000..264148f
--- /dev/null
+++ b/src/main/resources/static/load.js
@@ -0,0 +1,54 @@
+function loadHeader(partyId) {
+ const title = $("#navbar-title");
+
+ // because I don't know how to handle relative url if current does not end with slash
+ title.attr("href", `/party/${partyId}`);
+ $("#navbar-bis").attr("href", `/party/${partyId}/bis`);
+ $("#navbar-loot").attr("href", `/party/${partyId}/loot`);
+ $("#navbar-users").attr("href", `/party/${partyId}/users`);
+
+ $.ajax({
+ url: `/api/v1/party/${partyId}/description`,
+ type: "GET",
+ dataType: "json",
+ success: function (resp) {
+ title.text(safe(resp.partyAlias || partyId));
+ },
+ error: function (jqXHR, _, errorThrown) { requestAlert(jqXHR, errorThrown); },
+ });
+}
+
+function loadTypes(url, selector) {
+ $.ajax({
+ url: url,
+ type: "GET",
+ dataType: "json",
+ success: function (data) {
+ const options = data.map(function (name) {
+ const option = document.createElement("option");
+ option.value = name;
+ option.innerText = name;
+ return option;
+ });
+ selector.empty().append(options);
+ },
+ error: function (jqXHR, _, errorThrown) { requestAlert(jqXHR, errorThrown); },
+ });
+}
+
+function setupFormClear(dialog, reset) {
+ dialog.on("shown.bs.modal", function () {
+ $(this).find("form").trigger("reset");
+ $(this).find("table").bootstrapTable("removeAll");
+ if (reset) {
+ reset();
+ }
+ });
+}
+
+function setupRemoveButton(table, removeButton) {
+ table.on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table",
+ function () {
+ removeButton.prop("disabled", !table.bootstrapTable("getSelections").length);
+ });
+}
diff --git a/src/main/resources/static/styles.css b/src/main/resources/static/styles.css
index a24324d..e69de29 100644
--- a/src/main/resources/static/styles.css
+++ b/src/main/resources/static/styles.css
@@ -1,277 +0,0 @@
-/* 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;
- }
-}
diff --git a/src/main/resources/static/table_export.js b/src/main/resources/static/table_export.js
deleted file mode 100644
index ea019df..0000000
--- a/src/main/resources/static/table_export.js
+++ /dev/null
@@ -1,31 +0,0 @@
-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);
-}
diff --git a/src/main/resources/static/table_search.js b/src/main/resources/static/table_search.js
deleted file mode 100644
index a2eec3b..0000000
--- a/src/main/resources/static/table_search.js
+++ /dev/null
@@ -1,21 +0,0 @@
-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;
- }
-}
diff --git a/src/main/resources/static/utils.js b/src/main/resources/static/utils.js
new file mode 100644
index 0000000..63bc91c
--- /dev/null
+++ b/src/main/resources/static/utils.js
@@ -0,0 +1,44 @@
+function createAlert(message, placeholder) {
+ const wrapper = document.createElement('div');
+ wrapper.innerHTML = `${safe(message)}
`;
+ placeholder.append(wrapper);
+}
+
+function formatPlayerId(obj) {
+ return `${obj.nick} (${obj.job})`;
+}
+
+function getCurrentOption(select) {
+ return select.find(":selected")[0];
+}
+
+function getPartyId() {
+ const request = new XMLHttpRequest();
+ request.open("HEAD", document.location, false);
+ request.send(null);
+
+ // tuple lol
+ return [
+ request.getResponseHeader("X-Party-Id"),
+ request.getResponseHeader("X-User-Permission") === "get",
+ ]
+}
+
+function requestAlert(jqXHR, errorThrown) {
+ let message;
+ try {
+ message = $.parseJSON(jqXHR.responseText).message;
+ } catch (_) {
+ message = errorThrown;
+ }
+ const alert = $("#alert-placeholder");
+ createAlert(`Error during request: ${message}`, alert);
+}
+
+function safe(string) {
+ return String(string)
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """);
+}
diff --git a/src/main/resources/swagger-info/description.md b/src/main/resources/swagger-info/description.md
index 39647dc..a2b184f 100644
--- a/src/main/resources/swagger-info/description.md
+++ b/src/main/resources/swagger-info/description.md
@@ -11,6 +11,8 @@ REST json API description to interact with FFXIVBiS service.
# Limitations
+No limitations for the API so far.
+
# Authentication
For the most party utils service requires user to be authenticated. User permission can be one of `get`, `post` or `admin`.
diff --git a/src/main/scala/me/arcanis/ffxivbis/Application.scala b/src/main/scala/me/arcanis/ffxivbis/Application.scala
index d91737c..951b91d 100644
--- a/src/main/scala/me/arcanis/ffxivbis/Application.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/Application.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -8,16 +8,16 @@
*/
package me.arcanis.ffxivbis
-import akka.actor.typed.{Behavior, PostStop, Signal}
import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors}
+import akka.actor.typed.{Behavior, PostStop, Signal}
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Route
import akka.stream.Materializer
import com.typesafe.scalalogging.StrictLogging
import me.arcanis.ffxivbis.http.RootEndpoint
+import me.arcanis.ffxivbis.service.PartyService
import me.arcanis.ffxivbis.service.bis.BisProvider
import me.arcanis.ffxivbis.service.database.Database
-import me.arcanis.ffxivbis.service.PartyService
import me.arcanis.ffxivbis.storage.Migration
import scala.concurrent.ExecutionContext
diff --git a/src/main/scala/me/arcanis/ffxivbis/Configuration.scala b/src/main/scala/me/arcanis/ffxivbis/Configuration.scala
new file mode 100644
index 0000000..f92bf8e
--- /dev/null
+++ b/src/main/scala/me/arcanis/ffxivbis/Configuration.scala
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
+ *
+ * This file is part of ffxivbis
+ * (see https://github.com/arcan1s/ffxivbis).
+ *
+ * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
+ */
+package me.arcanis.ffxivbis
+
+import com.typesafe.config.{Config, ConfigFactory, ConfigValueFactory}
+
+object Configuration {
+
+ def load(): Config = {
+ val root = ConfigFactory.load()
+ root
+ .withValue(
+ "akka.http.server.transparent-head-requests",
+ ConfigValueFactory.fromAnyRef(root.getBoolean("me.arcanis.ffxivbis.web.enable-head-requests"))
+ )
+ }
+}
diff --git a/src/main/scala/me/arcanis/ffxivbis/ffxivbis.scala b/src/main/scala/me/arcanis/ffxivbis/ffxivbis.scala
index 6ad9732..15db400 100644
--- a/src/main/scala/me/arcanis/ffxivbis/ffxivbis.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/ffxivbis.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -9,12 +9,9 @@
package me.arcanis.ffxivbis
import akka.actor.typed.ActorSystem
-import com.typesafe.config.ConfigFactory
object ffxivbis {
- def main(args: Array[String]): Unit = {
- val config = ConfigFactory.load()
- ActorSystem[Nothing](Application(), "ffxivbis", config)
- }
+ def main(args: Array[String]): Unit =
+ ActorSystem[Nothing](Application(), "ffxivbis", Configuration.load())
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/Authorization.scala b/src/main/scala/me/arcanis/ffxivbis/http/Authorization.scala
index 356887d..971b869 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/Authorization.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/Authorization.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -8,22 +8,18 @@
*/
package me.arcanis.ffxivbis.http
-import akka.actor.typed.scaladsl.AskPattern.Askable
-import akka.actor.typed.{ActorRef, Scheduler}
import akka.http.scaladsl.model.headers._
import akka.http.scaladsl.server.AuthenticationFailedRejection._
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server._
-import akka.util.Timeout
-import me.arcanis.ffxivbis.messages.{GetUser, Message}
-import me.arcanis.ffxivbis.models.Permission
+import me.arcanis.ffxivbis.models.{Permission, User}
import scala.concurrent.{ExecutionContext, Future}
// idea comes from https://synkre.com/bcrypt-for-akka-http-password-encryption/
trait Authorization {
- def storage: ActorRef[Message]
+ def auth: AuthorizationProvider
def authenticateBasicBCrypt[T](realm: String, authenticate: (String, String) => Future[Option[T]]): Directive1[T] = {
def challenge = HttpChallenges.basic(realm)
@@ -38,34 +34,26 @@ trait Authorization {
}
}
- def authenticator(scope: Permission.Value, partyId: String)(username: String, password: String)(implicit
- executionContext: ExecutionContext,
- timeout: Timeout,
- scheduler: Scheduler
- ): Future[Option[String]] =
- storage.ask(GetUser(partyId, username, _)).map {
- case Some(user) if user.verify(password) && user.verityScope(scope) => Some(username)
- case _ => None
- }
-
def authAdmin(partyId: String)(username: String, password: String)(implicit
- executionContext: ExecutionContext,
- timeout: Timeout,
- scheduler: Scheduler
- ): Future[Option[String]] =
+ executionContext: ExecutionContext
+ ): Future[Option[User]] =
authenticator(Permission.admin, partyId)(username, password)
def authGet(partyId: String)(username: String, password: String)(implicit
- executionContext: ExecutionContext,
- timeout: Timeout,
- scheduler: Scheduler
- ): Future[Option[String]] =
+ executionContext: ExecutionContext
+ ): Future[Option[User]] =
authenticator(Permission.get, partyId)(username, password)
def authPost(partyId: String)(username: String, password: String)(implicit
- executionContext: ExecutionContext,
- timeout: Timeout,
- scheduler: Scheduler
- ): Future[Option[String]] =
+ executionContext: ExecutionContext
+ ): Future[Option[User]] =
authenticator(Permission.post, partyId)(username, password)
+
+ private def authenticator(scope: Permission.Value, partyId: String)(username: String, password: String)(implicit
+ executionContext: ExecutionContext
+ ): Future[Option[User]] =
+ auth.get(partyId, username).map {
+ case Some(user) if user.verify(password) && user.verityScope(scope) => Some(user)
+ case _ => None
+ }
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/AuthorizationProvider.scala b/src/main/scala/me/arcanis/ffxivbis/http/AuthorizationProvider.scala
new file mode 100644
index 0000000..46cf1ff
--- /dev/null
+++ b/src/main/scala/me/arcanis/ffxivbis/http/AuthorizationProvider.scala
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
+ *
+ * This file is part of ffxivbis
+ * (see https://github.com/arcan1s/ffxivbis).
+ *
+ * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
+ */
+package me.arcanis.ffxivbis.http
+
+import akka.actor.typed.scaladsl.AskPattern.Askable
+import akka.actor.typed.{ActorRef, Scheduler}
+import akka.util.Timeout
+import com.google.common.cache.{CacheBuilder, CacheLoader, LoadingCache}
+import com.typesafe.config.Config
+import me.arcanis.ffxivbis.messages.{GetUser, Message}
+import me.arcanis.ffxivbis.models.User
+
+import java.util.concurrent.TimeUnit
+import scala.concurrent.Future
+
+trait AuthorizationProvider {
+
+ def get(partyId: String, username: String): Future[Option[User]]
+}
+
+object AuthorizationProvider {
+
+ def apply(config: Config, storage: ActorRef[Message], timeout: Timeout, scheduler: Scheduler): AuthorizationProvider =
+ new AuthorizationProvider {
+ private val cacheSize = config.getInt("me.arcanis.ffxivbis.web.authorization-cache.cache-size")
+ private val cacheTimeout =
+ config.getDuration("me.arcanis.ffxivbis.web.authorization-cache.cache-timeout", TimeUnit.MILLISECONDS)
+
+ private val cache: LoadingCache[(String, String), Future[Option[User]]] = CacheBuilder
+ .newBuilder()
+ .expireAfterWrite(cacheTimeout, TimeUnit.MILLISECONDS)
+ .maximumSize(cacheSize)
+ .build(
+ new CacheLoader[(String, String), Future[Option[User]]] {
+ override def load(key: (String, String)): Future[Option[User]] = {
+ val (partyId, username) = key
+ storage.ask(GetUser(partyId, username, _))(timeout, scheduler)
+ }
+ }
+ )
+
+ override def get(partyId: String, username: String): Future[Option[User]] =
+ cache.get((partyId, username))
+ }
+}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/HttpLog.scala b/src/main/scala/me/arcanis/ffxivbis/http/HttpLog.scala
new file mode 100644
index 0000000..0e8b4b4
--- /dev/null
+++ b/src/main/scala/me/arcanis/ffxivbis/http/HttpLog.scala
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
+ *
+ * This file is part of ffxivbis
+ * (see https://github.com/arcan1s/ffxivbis).
+ *
+ * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
+ */
+package me.arcanis.ffxivbis.http
+
+import akka.http.scaladsl.model.headers.{`User-Agent`, Authorization, BasicHttpCredentials, Referer}
+import akka.http.scaladsl.server.Directive0
+import akka.http.scaladsl.server.Directives.{extractClientIP, extractRequestContext, mapResponse, optionalHeaderValueByType}
+import com.typesafe.scalalogging.Logger
+
+import java.time.{Instant, ZoneId}
+import java.time.format.DateTimeFormatter
+import java.util.Locale
+
+trait HttpLog {
+
+ private val httpLogger = Logger("http")
+
+ def withHttpLog: Directive0 =
+ extractRequestContext.flatMap { context =>
+ val request = s"${context.request.method.name()} ${context.request.uri.path}"
+
+ extractClientIP.flatMap { maybeRemoteAddr =>
+ val remoteAddr = maybeRemoteAddr.toIP.getOrElse("-")
+
+ optionalHeaderValueByType(Referer).flatMap { maybeReferer =>
+ val referer = maybeReferer.map(_.uri).getOrElse("-")
+
+ optionalHeaderValueByType(`User-Agent`).flatMap { maybeUserAgent =>
+ val userAgent = maybeUserAgent.map(_.products.map(_.toString()).mkString(" ")).getOrElse("-")
+
+ optionalHeaderValueByType(Authorization).flatMap { maybeAuth =>
+ val remoteUser = maybeAuth
+ .map(_.credentials)
+ .collect { case BasicHttpCredentials(username, _) =>
+ username
+ }
+ .getOrElse("-")
+
+ val start = Instant.now.toEpochMilli
+ val timeLocal = HttpLog.httpLogDatetimeFormatter.format(Instant.now)
+
+ mapResponse { response =>
+ val time = (Instant.now.toEpochMilli - start) / 1000.0
+
+ val status = response.status.intValue()
+ val bytesSent = response.entity.getContentLengthOption.getAsLong
+
+ httpLogger.debug(
+ s"""$remoteAddr - $remoteUser [$timeLocal] "$request" $status $bytesSent "$referer" "$userAgent" $time"""
+ )
+ response
+ }
+ }
+ }
+ }
+ }
+ }
+
+}
+
+object HttpLog {
+
+ val httpLogDatetimeFormatter: DateTimeFormatter =
+ DateTimeFormatter
+ .ofPattern("dd/MMM/uuuu:HH:mm:ss xx ")
+ .withLocale(Locale.UK)
+ .withZone(ZoneId.systemDefault())
+}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/RootEndpoint.scala b/src/main/scala/me/arcanis/ffxivbis/http/RootEndpoint.scala
index fdc0b76..a81d910 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/RootEndpoint.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/RootEndpoint.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -8,44 +8,30 @@
*/
package me.arcanis.ffxivbis.http
-import java.time.Instant
-
import akka.actor.typed.{ActorRef, ActorSystem, Scheduler}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server._
import akka.util.Timeout
-import com.typesafe.scalalogging.{Logger, StrictLogging}
+import com.typesafe.scalalogging.StrictLogging
import me.arcanis.ffxivbis.http.api.v1.RootApiV1Endpoint
import me.arcanis.ffxivbis.http.view.RootView
import me.arcanis.ffxivbis.messages.{BiSProviderMessage, Message}
class RootEndpoint(system: ActorSystem[Nothing], storage: ActorRef[Message], provider: ActorRef[BiSProviderMessage])
- extends StrictLogging {
+ extends StrictLogging
+ with HttpLog {
import me.arcanis.ffxivbis.utils.Implicits._
private val config = system.settings.config
implicit val scheduler: Scheduler = system.scheduler
- implicit val timeout: Timeout =
- config.getDuration("me.arcanis.ffxivbis.settings.request-timeout")
+ implicit val timeout: Timeout = config.getDuration("me.arcanis.ffxivbis.settings.request-timeout")
- private val rootApiV1Endpoint: RootApiV1Endpoint = new RootApiV1Endpoint(storage, provider, config)
- private val rootView: RootView = new RootView(storage, provider)
- private val swagger: Swagger = new Swagger(config)
- private val httpLogger = Logger("http")
+ private val auth = AuthorizationProvider(config, storage, timeout, scheduler)
- private val withHttpLog: Directive0 =
- extractRequestContext.flatMap { context =>
- val start = Instant.now.toEpochMilli
- mapResponse { response =>
- val time = (Instant.now.toEpochMilli - start) / 1000.0
- httpLogger.debug(
- s"""- - [${Instant.now}] "${context.request.method.name()} ${context.request.uri.path}" ${response.status
- .intValue()} ${response.entity.getContentLengthOption.getAsLong} $time"""
- )
- response
- }
- }
+ private val rootApiV1Endpoint = new RootApiV1Endpoint(storage, auth, provider, config)
+ private val rootView = new RootView(auth)
+ private val swagger = new Swagger(config)
def route: Route =
withHttpLog {
@@ -68,7 +54,7 @@ class RootEndpoint(system: ActorSystem[Nothing], storage: ActorRef[Message], pro
} ~ rootView.route
private def swaggerUIRoute: Route =
- path("swagger") {
- getFromResource("html/swagger.html")
+ path("api-docs") {
+ getFromResource("html/redoc.html")
}
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/Swagger.scala b/src/main/scala/me/arcanis/ffxivbis/http/Swagger.scala
index 0c6dc4c..c4da73e 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/Swagger.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/Swagger.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
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 723c3c1..24f7b28 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
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -21,14 +21,19 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement
import io.swagger.v3.oas.annotations.{Operation, Parameter}
import jakarta.ws.rs._
import me.arcanis.ffxivbis.http.api.v1.json._
-import me.arcanis.ffxivbis.http.{Authorization, BiSHelper}
+import me.arcanis.ffxivbis.http.helpers.BiSHelper
+import me.arcanis.ffxivbis.http.{Authorization, AuthorizationProvider}
import me.arcanis.ffxivbis.messages.{BiSProviderMessage, Message}
import me.arcanis.ffxivbis.models.PlayerId
import scala.util.{Failure, Success}
@Path("/api/v1")
-class BiSEndpoint(override val storage: ActorRef[Message], override val provider: ActorRef[BiSProviderMessage])(implicit
+class BiSEndpoint(
+ override val storage: ActorRef[Message],
+ override val provider: ActorRef[BiSProviderMessage],
+ override val auth: AuthorizationProvider
+)(implicit
timeout: Timeout,
scheduler: Scheduler
) extends BiSHelper
@@ -49,29 +54,29 @@ class BiSEndpoint(override val storage: ActorRef[Message], override val provider
requestBody = new RequestBody(
description = "player best in slot description",
required = true,
- content = Array(new Content(schema = new Schema(implementation = classOf[PlayerBiSLinkResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[PlayerBiSLinkModel])))
),
responses = Array(
new ApiResponse(responseCode = "201", description = "Best in slot set has been created"),
new ApiResponse(
responseCode = "400",
description = "Invalid parameters were supplied",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "401",
description = "Supplied authorization is invalid",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "403",
description = "Access is forbidden",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "500",
description = "Internal server error",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
),
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("post"))),
@@ -82,11 +87,10 @@ class BiSEndpoint(override val storage: ActorRef[Message], override val provider
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
put {
- entity(as[PlayerBiSLinkResponse]) { bisLink =>
+ entity(as[PlayerBiSLinkModel]) { bisLink =>
val playerId = bisLink.playerId.withPartyId(partyId)
- onComplete(putBiS(playerId, bisLink.link)) {
- case Success(_) => complete(StatusCodes.Created, HttpEntity.Empty)
- case Failure(exception) => throw exception
+ onSuccess(putBiS(playerId, bisLink.link)) {
+ complete(StatusCodes.Created, HttpEntity.Empty)
}
}
}
@@ -116,24 +120,24 @@ class BiSEndpoint(override val storage: ActorRef[Message], override val provider
description = "Best in slot",
content = Array(
new Content(
- array = new ArraySchema(schema = new Schema(implementation = classOf[PlayerResponse]))
+ array = new ArraySchema(schema = new Schema(implementation = classOf[PlayerModel]))
)
)
),
new ApiResponse(
responseCode = "401",
description = "Supplied authorization is invalid",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "403",
description = "Access is forbidden",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "500",
description = "Internal server error",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
),
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("get"))),
@@ -146,9 +150,8 @@ class BiSEndpoint(override val storage: ActorRef[Message], override val provider
get {
parameters("nick".as[String].?, "job".as[String].?) { (maybeNick, maybeJob) =>
val playerId = PlayerId(partyId, maybeNick, maybeJob)
- onComplete(bis(partyId, playerId)) {
- case Success(response) => complete(response.map(PlayerResponse.fromPlayer))
- case Failure(exception) => throw exception
+ onSuccess(bis(partyId, playerId)) { response =>
+ complete(response.map(PlayerModel.fromPlayer))
}
}
}
@@ -169,29 +172,29 @@ class BiSEndpoint(override val storage: ActorRef[Message], override val provider
requestBody = new RequestBody(
description = "action and piece description",
required = true,
- content = Array(new Content(schema = new Schema(implementation = classOf[PieceActionResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[PieceActionModel])))
),
responses = Array(
new ApiResponse(responseCode = "202", description = "Best in slot set has been modified"),
new ApiResponse(
responseCode = "400",
description = "Invalid parameters were supplied",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "401",
description = "Supplied authorization is invalid",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "403",
description = "Access is forbidden",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "500",
description = "Internal server error",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
),
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("post"))),
@@ -202,11 +205,10 @@ class BiSEndpoint(override val storage: ActorRef[Message], override val provider
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
post {
- entity(as[PieceActionResponse]) { action =>
+ entity(as[PieceActionModel]) { action =>
val playerId = action.playerId.withPartyId(partyId)
- onComplete(doModifyBiS(action.action, playerId, action.piece.toPiece)) {
- case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
- case Failure(exception) => throw exception
+ onSuccess(doModifyBiS(action.action, playerId, action.piece.toPiece)) {
+ complete(StatusCodes.Accepted, HttpEntity.Empty)
}
}
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/HttpHandler.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/HttpHandler.scala
index 6a85c11..dc4b2b1 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/HttpHandler.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/HttpHandler.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -19,18 +19,18 @@ trait HttpHandler extends StrictLogging { this: JsonSupport =>
def exceptionHandler: ExceptionHandler = ExceptionHandler {
case ex: IllegalArgumentException =>
- complete(StatusCodes.BadRequest, ErrorResponse(ex.getMessage))
+ complete(StatusCodes.BadRequest, ErrorModel(ex.getMessage))
case other: Exception =>
logger.error("exception during request completion", other)
- complete(StatusCodes.InternalServerError, ErrorResponse("unknown server error"))
+ complete(StatusCodes.InternalServerError, ErrorModel("unknown server error"))
}
def rejectionHandler: RejectionHandler =
RejectionHandler.default
.mapRejectionResponse {
case response @ HttpResponse(_, _, entity: HttpEntity.Strict, _) =>
- val message = ErrorResponse(entity.data.utf8String).toJson
+ val message = ErrorModel(entity.data.utf8String).toJson
response.withEntity(HttpEntity(ContentTypes.`application/json`, message.compactPrint))
case other => other
}
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 f5f695a..62a7c03 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
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -21,20 +21,23 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement
import io.swagger.v3.oas.annotations.{Operation, Parameter}
import jakarta.ws.rs._
import me.arcanis.ffxivbis.http.api.v1.json._
-import me.arcanis.ffxivbis.http.{Authorization, LootHelper}
+import me.arcanis.ffxivbis.http.helpers.LootHelper
+import me.arcanis.ffxivbis.http.{Authorization, AuthorizationProvider}
import me.arcanis.ffxivbis.messages.Message
import me.arcanis.ffxivbis.models.PlayerId
import scala.util.{Failure, Success}
@Path("/api/v1")
-class LootEndpoint(override val storage: ActorRef[Message])(implicit timeout: Timeout, scheduler: Scheduler)
- extends LootHelper
+class LootEndpoint(override val storage: ActorRef[Message], override val auth: AuthorizationProvider)(implicit
+ timeout: Timeout,
+ scheduler: Scheduler
+) extends LootHelper
with Authorization
with JsonSupport
with HttpHandler {
- def route: Route = getLoot ~ modifyLoot
+ def route: Route = getLoot ~ modifyLoot ~ suggestLoot
@GET
@Path("party/{partyId}/loot")
@@ -58,24 +61,24 @@ class LootEndpoint(override val storage: ActorRef[Message])(implicit timeout: Ti
description = "Loot list",
content = Array(
new Content(
- array = new ArraySchema(schema = new Schema(implementation = classOf[PlayerResponse]))
+ array = new ArraySchema(schema = new Schema(implementation = classOf[PlayerModel]))
)
)
),
new ApiResponse(
responseCode = "401",
description = "Supplied authorization is invalid",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "403",
description = "Access is forbidden",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "500",
description = "Internal server error",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
),
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("get"))),
@@ -88,9 +91,8 @@ class LootEndpoint(override val storage: ActorRef[Message])(implicit timeout: Ti
get {
parameters("nick".as[String].?, "job".as[String].?) { (maybeNick, maybeJob) =>
val playerId = PlayerId(partyId, maybeNick, maybeJob)
- onComplete(loot(partyId, playerId)) {
- case Success(response) => complete(response.map(PlayerResponse.fromPlayer))
- case Failure(exception) => throw exception
+ onSuccess(loot(partyId, playerId)) { response =>
+ complete(response.map(PlayerModel.fromPlayer))
}
}
}
@@ -110,29 +112,29 @@ class LootEndpoint(override val storage: ActorRef[Message])(implicit timeout: Ti
requestBody = new RequestBody(
description = "action and piece description",
required = true,
- content = Array(new Content(schema = new Schema(implementation = classOf[PieceActionResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[PieceActionModel])))
),
responses = Array(
new ApiResponse(responseCode = "202", description = "Loot list has been modified"),
new ApiResponse(
responseCode = "400",
description = "Invalid parameters were supplied",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "401",
description = "Supplied authorization is invalid",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "403",
description = "Access is forbidden",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "500",
description = "Internal server error",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
),
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("post"))),
@@ -143,11 +145,10 @@ class LootEndpoint(override val storage: ActorRef[Message])(implicit timeout: Ti
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
post {
- entity(as[PieceActionResponse]) { action =>
+ entity(as[PieceActionModel]) { action =>
val playerId = action.playerId.withPartyId(partyId)
- onComplete(doModifyLoot(action.action, playerId, action.piece.toPiece, action.isFreeLoot)) {
- case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
- case Failure(exception) => throw exception
+ onSuccess(doModifyLoot(action.action, playerId, action.piece.toPiece, action.isFreeLoot)) {
+ complete(StatusCodes.Accepted, HttpEntity.Empty)
}
}
}
@@ -168,7 +169,7 @@ class LootEndpoint(override val storage: ActorRef[Message])(implicit timeout: Ti
requestBody = new RequestBody(
description = "piece description",
required = true,
- content = Array(new Content(schema = new Schema(implementation = classOf[PieceResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[PieceModel])))
),
responses = Array(
new ApiResponse(
@@ -176,29 +177,29 @@ class LootEndpoint(override val storage: ActorRef[Message])(implicit timeout: Ti
description = "Players with counters ordered by priority to get this item",
content = Array(
new Content(
- array = new ArraySchema(schema = new Schema(implementation = classOf[PlayerIdWithCountersResponse])),
+ array = new ArraySchema(schema = new Schema(implementation = classOf[PlayerIdWithCountersModel])),
)
)
),
new ApiResponse(
responseCode = "400",
description = "Invalid parameters were supplied",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "401",
description = "Supplied authorization is invalid",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "403",
description = "Access is forbidden",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "500",
description = "Internal server error",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
),
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("get"))),
@@ -209,10 +210,9 @@ class LootEndpoint(override val storage: ActorRef[Message])(implicit timeout: Ti
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
put {
- entity(as[PieceResponse]) { piece =>
- onComplete(suggestPiece(partyId, piece.toPiece)) {
- case Success(response) => complete(response.map(PlayerIdWithCountersResponse.fromPlayerId))
- case Failure(exception) => throw exception
+ entity(as[PieceModel]) { piece =>
+ onSuccess(suggestPiece(partyId, piece.toPiece)) { response =>
+ complete(response.map(PlayerIdWithCountersModel.fromPlayerId))
}
}
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/PartyEndpoint.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/PartyEndpoint.scala
index 3174882..2ce7dff 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/PartyEndpoint.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/PartyEndpoint.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -21,14 +21,18 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement
import io.swagger.v3.oas.annotations.{Operation, Parameter}
import jakarta.ws.rs._
import me.arcanis.ffxivbis.http.api.v1.json._
-import me.arcanis.ffxivbis.http.{Authorization, PlayerHelper}
+import me.arcanis.ffxivbis.http.helpers.PlayerHelper
+import me.arcanis.ffxivbis.http.{Authorization, AuthorizationProvider}
import me.arcanis.ffxivbis.messages.{BiSProviderMessage, Message}
import scala.util.{Failure, Success}
@Path("/api/v1")
-class PartyEndpoint(override val storage: ActorRef[Message], override val provider: ActorRef[BiSProviderMessage])(
- implicit
+class PartyEndpoint(
+ override val storage: ActorRef[Message],
+ override val provider: ActorRef[BiSProviderMessage],
+ override val auth: AuthorizationProvider
+)(implicit
timeout: Timeout,
scheduler: Scheduler
) extends PlayerHelper
@@ -51,22 +55,22 @@ class PartyEndpoint(override val storage: ActorRef[Message], override val provid
new ApiResponse(
responseCode = "200",
description = "Party description",
- content = Array(new Content(schema = new Schema(implementation = classOf[PartyDescriptionResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[PartyDescriptionModel])))
),
new ApiResponse(
responseCode = "401",
description = "Supplied authorization is invalid",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "403",
description = "Access is forbidden",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "500",
description = "Internal server error",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
),
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("get"))),
@@ -77,9 +81,8 @@ class PartyEndpoint(override val storage: ActorRef[Message], override val provid
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
get {
- onComplete(getPartyDescription(partyId)) {
- case Success(response) => complete(PartyDescriptionResponse.fromDescription(response))
- case Failure(exception) => throw exception
+ onSuccess(getPartyDescription(partyId)) { response =>
+ complete(PartyDescriptionModel.fromDescription(response))
}
}
}
@@ -98,29 +101,29 @@ class PartyEndpoint(override val storage: ActorRef[Message], override val provid
requestBody = new RequestBody(
description = "new party description",
required = true,
- content = Array(new Content(schema = new Schema(implementation = classOf[PartyDescriptionResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[PartyDescriptionModel])))
),
responses = Array(
new ApiResponse(responseCode = "202", description = "Party description has been modified"),
new ApiResponse(
responseCode = "400",
description = "Invalid parameters were supplied",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "401",
description = "Supplied authorization is invalid",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "403",
description = "Access is forbidden",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "500",
description = "Internal server error",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
),
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("post"))),
@@ -131,11 +134,10 @@ class PartyEndpoint(override val storage: ActorRef[Message], override val provid
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
post {
- entity(as[PartyDescriptionResponse]) { partyDescription =>
+ entity(as[PartyDescriptionModel]) { partyDescription =>
val description = partyDescription.copy(partyId = partyId)
- onComplete(updateDescription(description.toDescription)) {
- case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
- case Failure(exception) => throw exception
+ onSuccess(updateDescription(description.toDescription)) {
+ complete(StatusCodes.Accepted, HttpEntity.Empty)
}
}
}
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 5b1d930..5f0e449 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
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -21,15 +21,19 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement
import io.swagger.v3.oas.annotations.{Operation, Parameter}
import jakarta.ws.rs._
import me.arcanis.ffxivbis.http.api.v1.json._
-import me.arcanis.ffxivbis.http.{Authorization, PlayerHelper}
+import me.arcanis.ffxivbis.http.helpers.PlayerHelper
+import me.arcanis.ffxivbis.http.{Authorization, AuthorizationProvider}
import me.arcanis.ffxivbis.messages.{BiSProviderMessage, Message}
import me.arcanis.ffxivbis.models.PlayerId
import scala.util.{Failure, Success}
@Path("/api/v1")
-class PlayerEndpoint(override val storage: ActorRef[Message], override val provider: ActorRef[BiSProviderMessage])(
- implicit
+class PlayerEndpoint(
+ override val storage: ActorRef[Message],
+ override val provider: ActorRef[BiSProviderMessage],
+ override val auth: AuthorizationProvider
+)(implicit
timeout: Timeout,
scheduler: Scheduler
) extends PlayerHelper
@@ -37,7 +41,7 @@ class PlayerEndpoint(override val storage: ActorRef[Message], override val provi
with JsonSupport
with HttpHandler {
- def route: Route = getParty ~ modifyParty
+ def route: Route = getParty ~ getPartyStats ~ modifyParty
@GET
@Path("party/{partyId}")
@@ -61,24 +65,24 @@ class PlayerEndpoint(override val storage: ActorRef[Message], override val provi
description = "Players list",
content = Array(
new Content(
- array = new ArraySchema(schema = new Schema(implementation = classOf[PlayerResponse])),
+ array = new ArraySchema(schema = new Schema(implementation = classOf[PlayerModel])),
)
)
),
new ApiResponse(
responseCode = "401",
description = "Supplied authorization is invalid",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "403",
description = "Access is forbidden",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "500",
description = "Internal server error",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
),
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("get"))),
@@ -91,9 +95,69 @@ class PlayerEndpoint(override val storage: ActorRef[Message], override val provi
get {
parameters("nick".as[String].?, "job".as[String].?) { (maybeNick, maybeJob) =>
val playerId = PlayerId(partyId, maybeNick, maybeJob)
- onComplete(getPlayers(partyId, playerId)) {
- case Success(response) => complete(response.map(PlayerResponse.fromPlayer))
- case Failure(exception) => throw exception
+ onSuccess(getPlayers(partyId, playerId)) { response =>
+ complete(response.map(PlayerModel.fromPlayer))
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @GET
+ @Path("party/{partyId}/stats")
+ @Produces(value = Array("application/json"))
+ @Operation(
+ summary = "get party statistics",
+ description = "Return the party statistics",
+ parameters = Array(
+ new Parameter(name = "partyId", in = ParameterIn.PATH, description = "unique party ID", example = "abcdefgh"),
+ new Parameter(
+ name = "nick",
+ in = ParameterIn.QUERY,
+ description = "player nick name to filter",
+ example = "Siuan Sanche"
+ ),
+ new Parameter(name = "job", in = ParameterIn.QUERY, description = "player job to filter", example = "DNC"),
+ ),
+ responses = Array(
+ new ApiResponse(
+ responseCode = "200",
+ description = "Party loot statistics",
+ content = Array(
+ new Content(
+ array = new ArraySchema(schema = new Schema(implementation = classOf[PlayerIdWithCountersModel])),
+ )
+ )
+ ),
+ new ApiResponse(
+ responseCode = "401",
+ description = "Supplied authorization is invalid",
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
+ ),
+ new ApiResponse(
+ responseCode = "403",
+ description = "Access is forbidden",
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
+ ),
+ new ApiResponse(
+ responseCode = "500",
+ description = "Internal server error",
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
+ ),
+ ),
+ security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("get"))),
+ tags = Array("party"),
+ )
+ def getPartyStats: Route =
+ path("party" / Segment / "stats") { partyId =>
+ extractExecutionContext { implicit executionContext =>
+ authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
+ get {
+ parameters("nick".as[String].?, "job".as[String].?) { (maybeNick, maybeJob) =>
+ val playerId = PlayerId(partyId, maybeNick, maybeJob)
+ onSuccess(getPlayers(partyId, playerId)) { response =>
+ complete(response.map(player => PlayerIdWithCountersModel.fromPlayerId(player.withCounters(None))))
}
}
}
@@ -113,29 +177,29 @@ class PlayerEndpoint(override val storage: ActorRef[Message], override val provi
requestBody = new RequestBody(
description = "player description",
required = true,
- content = Array(new Content(schema = new Schema(implementation = classOf[PlayerActionResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[PlayerActionModel])))
),
responses = Array(
new ApiResponse(responseCode = "202", description = "Party has been modified"),
new ApiResponse(
responseCode = "400",
description = "Invalid parameters were supplied",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "401",
description = "Supplied authorization is invalid",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "403",
description = "Access is forbidden",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "500",
description = "Internal server error",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
),
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("post"))),
@@ -145,11 +209,12 @@ class PlayerEndpoint(override val storage: ActorRef[Message], override val provi
path("party" / Segment) { partyId =>
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
- entity(as[PlayerActionResponse]) { action =>
- val player = action.playerId.toPlayer.copy(partyId = partyId)
- onComplete(doModifyPlayer(action.action, player)) {
- case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
- case Failure(exception) => throw exception
+ post {
+ entity(as[PlayerActionModel]) { action =>
+ val player = action.playerId.toPlayer.copy(partyId = partyId)
+ onSuccess(doModifyPlayer(action.action, player)) {
+ complete(StatusCodes.Accepted, HttpEntity.Empty)
+ }
}
}
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/RootApiV1Endpoint.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/RootApiV1Endpoint.scala
index a33c547..0fecf00 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/RootApiV1Endpoint.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/RootApiV1Endpoint.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -13,21 +13,27 @@ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.util.Timeout
import com.typesafe.config.Config
+import me.arcanis.ffxivbis.http.AuthorizationProvider
import me.arcanis.ffxivbis.http.api.v1.json.JsonSupport
import me.arcanis.ffxivbis.messages.{BiSProviderMessage, Message}
-class RootApiV1Endpoint(storage: ActorRef[Message], provider: ActorRef[BiSProviderMessage], config: Config)(implicit
+class RootApiV1Endpoint(
+ storage: ActorRef[Message],
+ auth: AuthorizationProvider,
+ provider: ActorRef[BiSProviderMessage],
+ config: Config
+)(implicit
timeout: Timeout,
scheduler: Scheduler
) extends JsonSupport
with HttpHandler {
- private val biSEndpoint = new BiSEndpoint(storage, provider)
- private val lootEndpoint = new LootEndpoint(storage)
- private val partyEndpoint = new PartyEndpoint(storage, provider)
- private val playerEndpoint = new PlayerEndpoint(storage, provider)
+ private val biSEndpoint = new BiSEndpoint(storage, provider, auth)
+ private val lootEndpoint = new LootEndpoint(storage, auth)
+ private val partyEndpoint = new PartyEndpoint(storage, provider, auth)
+ private val playerEndpoint = new PlayerEndpoint(storage, provider, auth)
private val typesEndpoint = new TypesEndpoint(config)
- private val userEndpoint = new UserEndpoint(storage)
+ private val userEndpoint = new UserEndpoint(storage, auth)
def route: Route =
handleExceptions(exceptionHandler) {
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/TypesEndpoint.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/TypesEndpoint.scala
index 1df51c2..a1dc408 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/TypesEndpoint.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/TypesEndpoint.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -11,17 +11,48 @@ package me.arcanis.ffxivbis.http.api.v1
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server._
import com.typesafe.config.Config
+import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.media.{ArraySchema, Content, Schema}
import io.swagger.v3.oas.annotations.responses.ApiResponse
-import io.swagger.v3.oas.annotations.Operation
import jakarta.ws.rs._
import me.arcanis.ffxivbis.http.api.v1.json._
-import me.arcanis.ffxivbis.models.{Job, Party, Permission, Piece, PieceType}
+import me.arcanis.ffxivbis.models._
@Path("/api/v1")
class TypesEndpoint(config: Config) extends JsonSupport {
- def route: Route = getJobs ~ getPermissions ~ getPieces ~ getPieceTypes ~ getPriority
+ def route: Route = getAllJobs ~ getJobs ~ getPermissions ~ getPieces ~ getPieceTypes ~ getPriority
+
+ @GET
+ @Path("types/jobs/all")
+ @Produces(value = Array("application/json"))
+ @Operation(
+ summary = "full jobs list",
+ description = "Returns the available jobs including any job",
+ responses = Array(
+ new ApiResponse(
+ responseCode = "200",
+ description = "List of available jobs with AnyJob",
+ content = Array(
+ new Content(
+ array = new ArraySchema(schema = new Schema(implementation = classOf[String]))
+ )
+ )
+ ),
+ new ApiResponse(
+ responseCode = "500",
+ description = "Internal server error",
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
+ ),
+ ),
+ tags = Array("types"),
+ )
+ def getAllJobs: Route =
+ path("types" / "jobs" / "all") {
+ get {
+ complete(Job.availableWithAnyJob.map(_.toString))
+ }
+ }
@GET
@Path("types/jobs")
@@ -42,7 +73,7 @@ class TypesEndpoint(config: Config) extends JsonSupport {
new ApiResponse(
responseCode = "500",
description = "Internal server error",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
),
tags = Array("types"),
@@ -50,7 +81,7 @@ class TypesEndpoint(config: Config) extends JsonSupport {
def getJobs: Route =
path("types" / "jobs") {
get {
- complete(Job.availableWithAnyJob.map(_.toString))
+ complete(Job.available.map(_.toString))
}
}
@@ -73,7 +104,7 @@ class TypesEndpoint(config: Config) extends JsonSupport {
new ApiResponse(
responseCode = "500",
description = "Internal server error",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
),
tags = Array("types"),
@@ -104,7 +135,7 @@ class TypesEndpoint(config: Config) extends JsonSupport {
new ApiResponse(
responseCode = "500",
description = "Internal server error",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
),
tags = Array("types"),
@@ -135,7 +166,7 @@ class TypesEndpoint(config: Config) extends JsonSupport {
new ApiResponse(
responseCode = "500",
description = "Internal server error",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
),
tags = Array("types"),
@@ -166,7 +197,7 @@ class TypesEndpoint(config: Config) extends JsonSupport {
new ApiResponse(
responseCode = "500",
description = "Internal server error",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
),
tags = Array("types"),
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 9726566..e2d9232 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
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -21,19 +21,22 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement
import io.swagger.v3.oas.annotations.{Operation, Parameter}
import jakarta.ws.rs._
import me.arcanis.ffxivbis.http.api.v1.json._
-import me.arcanis.ffxivbis.http.{Authorization, UserHelper}
+import me.arcanis.ffxivbis.http.helpers.UserHelper
+import me.arcanis.ffxivbis.http.{Authorization, AuthorizationProvider}
import me.arcanis.ffxivbis.messages.Message
import me.arcanis.ffxivbis.models.Permission
import scala.util.{Failure, Success}
@Path("/api/v1")
-class UserEndpoint(override val storage: ActorRef[Message])(implicit timeout: Timeout, scheduler: Scheduler)
- extends UserHelper
+class UserEndpoint(override val storage: ActorRef[Message], override val auth: AuthorizationProvider)(implicit
+ timeout: Timeout,
+ scheduler: Scheduler
+) extends UserHelper
with Authorization
with JsonSupport {
- def route: Route = createParty ~ createUser ~ deleteUser ~ getUsers
+ def route: Route = createParty ~ createUser ~ deleteUser ~ getUsers ~ getUsersCurrent
@PUT
@Path("party")
@@ -44,24 +47,28 @@ class UserEndpoint(override val storage: ActorRef[Message])(implicit timeout: Ti
requestBody = new RequestBody(
description = "party administrator description",
required = true,
- content = Array(new Content(schema = new Schema(implementation = classOf[UserResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[UserModel])))
),
responses = Array(
- new ApiResponse(responseCode = "200", description = "Party has been created"),
+ new ApiResponse(
+ responseCode = "200",
+ description = "Party has been created",
+ content = Array(new Content(schema = new Schema(implementation = classOf[PartyIdModel])))
+ ),
new ApiResponse(
responseCode = "400",
description = "Invalid parameters were supplied",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "406",
description = "Party with the specified ID already exists",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "500",
description = "Internal server error",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
),
tags = Array("party"),
@@ -70,15 +77,12 @@ class UserEndpoint(override val storage: ActorRef[Message])(implicit timeout: Ti
path("party") {
extractExecutionContext { implicit executionContext =>
put {
- entity(as[UserResponse]) { user =>
- onComplete(newPartyId) {
- case Success(partyId) =>
- val admin = user.toUser.copy(partyId = partyId, permission = Permission.admin)
- onComplete(addUser(admin, isHashedPassword = false)) {
- case Success(_) => complete(PartyIdResponse(partyId))
- case Failure(exception) => throw exception
- }
- case Failure(exception) => throw exception
+ entity(as[UserModel]) { user =>
+ onSuccess(newPartyId) { partyId =>
+ val admin = user.toUser.copy(partyId = partyId, permission = Permission.admin)
+ onSuccess(addUser(admin, isHashedPassword = false)) {
+ complete(PartyIdModel(partyId))
+ }
}
}
}
@@ -97,29 +101,29 @@ class UserEndpoint(override val storage: ActorRef[Message])(implicit timeout: Ti
requestBody = new RequestBody(
description = "user description",
required = true,
- content = Array(new Content(schema = new Schema(implementation = classOf[UserResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[UserModel])))
),
responses = Array(
new ApiResponse(responseCode = "201", description = "User has been created"),
new ApiResponse(
responseCode = "400",
description = "Invalid parameters were supplied",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "401",
description = "Supplied authorization is invalid",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "403",
description = "Access is forbidden",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "500",
description = "Internal server error",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
),
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("admin"))),
@@ -130,11 +134,10 @@ class UserEndpoint(override val storage: ActorRef[Message])(implicit timeout: Ti
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ =>
post {
- entity(as[UserResponse]) { user =>
+ entity(as[UserModel]) { user =>
val withPartyId = user.toUser.copy(partyId = partyId)
- onComplete(addUser(withPartyId, isHashedPassword = false)) {
- case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
- case Failure(exception) => throw exception
+ onSuccess(addUser(withPartyId, isHashedPassword = false)) {
+ complete(StatusCodes.Accepted, HttpEntity.Empty)
}
}
}
@@ -156,17 +159,17 @@ class UserEndpoint(override val storage: ActorRef[Message])(implicit timeout: Ti
new ApiResponse(
responseCode = "401",
description = "Supplied authorization is invalid",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "403",
description = "Access is forbidden",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "500",
description = "Internal server error",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
),
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("admin"))),
@@ -177,9 +180,8 @@ class UserEndpoint(override val storage: ActorRef[Message])(implicit timeout: Ti
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ =>
delete {
- onComplete(removeUser(partyId, username)) {
- case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
- case Failure(exception) => throw exception
+ onSuccess(removeUser(partyId, username)) {
+ complete(StatusCodes.Accepted, HttpEntity.Empty)
}
}
}
@@ -201,27 +203,27 @@ class UserEndpoint(override val storage: ActorRef[Message])(implicit timeout: Ti
description = "Users list",
content = Array(
new Content(
- array = new ArraySchema(schema = new Schema(implementation = classOf[UserResponse])),
+ array = new ArraySchema(schema = new Schema(implementation = classOf[UserModel])),
)
)
),
new ApiResponse(
responseCode = "401",
description = "Supplied authorization is invalid",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "403",
description = "Access is forbidden",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
new ApiResponse(
responseCode = "500",
description = "Internal server error",
- content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
),
),
- security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("admin"))),
+ security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("get"))),
tags = Array("users"),
)
def getUsers: Route =
@@ -229,12 +231,56 @@ class UserEndpoint(override val storage: ActorRef[Message])(implicit timeout: Ti
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ =>
get {
- onComplete(users(partyId)) {
- case Success(response) => complete(response.map(UserResponse.fromUser))
- case Failure(exception) => throw exception
+ onSuccess(users(partyId)) { response =>
+ complete(response.map(UserModel.fromUser))
}
}
}
}
}
+
+ @GET
+ @Path("party/{partyId}/users/current")
+ @Produces(value = Array("application/json"))
+ @Operation(
+ summary = "get current user",
+ description = "Return the current user descriptor",
+ parameters = Array(
+ new Parameter(name = "partyId", in = ParameterIn.PATH, description = "unique party ID", example = "abcdefgh"),
+ ),
+ responses = Array(
+ new ApiResponse(
+ responseCode = "200",
+ description = "User descriptor",
+ content = Array(new Content(schema = new Schema(implementation = classOf[UserModel])))
+ ),
+ new ApiResponse(
+ responseCode = "401",
+ description = "Supplied authorization is invalid",
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
+ ),
+ new ApiResponse(
+ responseCode = "403",
+ description = "Access is forbidden",
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
+ ),
+ new ApiResponse(
+ responseCode = "500",
+ description = "Internal server error",
+ content = Array(new Content(schema = new Schema(implementation = classOf[ErrorModel])))
+ ),
+ ),
+ security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("admin"))),
+ tags = Array("users"),
+ )
+ def getUsersCurrent: Route =
+ path("party" / Segment / "users" / "current") { partyId =>
+ extractExecutionContext { implicit executionContext =>
+ authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { user =>
+ get {
+ complete(UserModel.fromUser(user))
+ }
+ }
+ }
+ }
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/ApiAction.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/ApiAction.scala
index 3a8d3af..6051720 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/ApiAction.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/ApiAction.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PartyIdResponse.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/ErrorModel.scala
similarity index 65%
rename from src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PartyIdResponse.scala
rename to src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/ErrorModel.scala
index 81bfcff..8de54cb 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PartyIdResponse.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/ErrorModel.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -10,4 +10,4 @@ package me.arcanis.ffxivbis.http.api.v1.json
import io.swagger.v3.oas.annotations.media.Schema
-case class PartyIdResponse(@Schema(description = "party id", required = true) partyId: String)
+case class ErrorModel(@Schema(description = "error message", required = true) message: String)
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/JsonSupport.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/JsonSupport.scala
index 4486244..85e6bbc 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/JsonSupport.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/JsonSupport.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -8,12 +8,12 @@
*/
package me.arcanis.ffxivbis.http.api.v1.json
-import java.time.Instant
-
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import me.arcanis.ffxivbis.models.Permission
import spray.json._
+import java.time.Instant
+
trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
private def enumFormat[E <: Enumeration](enum: E): RootJsonFormat[E#Value] =
@@ -38,19 +38,19 @@ trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
implicit val actionFormat: RootJsonFormat[ApiAction.Value] = enumFormat(ApiAction)
implicit val permissionFormat: RootJsonFormat[Permission.Value] = enumFormat(Permission)
- implicit val errorFormat: RootJsonFormat[ErrorResponse] = jsonFormat1(ErrorResponse.apply)
- implicit val partyIdFormat: RootJsonFormat[PartyIdResponse] = jsonFormat1(PartyIdResponse.apply)
- implicit val pieceFormat: RootJsonFormat[PieceResponse] = jsonFormat3(PieceResponse.apply)
- implicit val lootFormat: RootJsonFormat[LootResponse] = jsonFormat3(LootResponse.apply)
- implicit val partyDescriptionFormat: RootJsonFormat[PartyDescriptionResponse] = jsonFormat2(
- PartyDescriptionResponse.apply
+ implicit val errorFormat: RootJsonFormat[ErrorModel] = jsonFormat1(ErrorModel.apply)
+ implicit val partyIdFormat: RootJsonFormat[PartyIdModel] = jsonFormat1(PartyIdModel.apply)
+ implicit val pieceFormat: RootJsonFormat[PieceModel] = jsonFormat3(PieceModel.apply)
+ implicit val lootFormat: RootJsonFormat[LootModel] = jsonFormat3(LootModel.apply)
+ implicit val partyDescriptionFormat: RootJsonFormat[PartyDescriptionModel] = jsonFormat2(
+ PartyDescriptionModel.apply
)
- implicit val playerFormat: RootJsonFormat[PlayerResponse] = jsonFormat7(PlayerResponse.apply)
- implicit val playerActionFormat: RootJsonFormat[PlayerActionResponse] = jsonFormat2(PlayerActionResponse.apply)
- implicit val playerIdFormat: RootJsonFormat[PlayerIdResponse] = jsonFormat3(PlayerIdResponse.apply)
- implicit val pieceActionFormat: RootJsonFormat[PieceActionResponse] = jsonFormat4(PieceActionResponse.apply)
- implicit val playerBiSLinkFormat: RootJsonFormat[PlayerBiSLinkResponse] = jsonFormat2(PlayerBiSLinkResponse.apply)
- implicit val playerIdWithCountersFormat: RootJsonFormat[PlayerIdWithCountersResponse] =
- jsonFormat9(PlayerIdWithCountersResponse.apply)
- implicit val userFormat: RootJsonFormat[UserResponse] = jsonFormat4(UserResponse.apply)
+ implicit val playerFormat: RootJsonFormat[PlayerModel] = jsonFormat9(PlayerModel.apply)
+ implicit val playerActionFormat: RootJsonFormat[PlayerActionModel] = jsonFormat2(PlayerActionModel.apply)
+ implicit val playerIdFormat: RootJsonFormat[PlayerIdModel] = jsonFormat3(PlayerIdModel.apply)
+ implicit val pieceActionFormat: RootJsonFormat[PieceActionModel] = jsonFormat4(PieceActionModel.apply)
+ implicit val playerBiSLinkFormat: RootJsonFormat[PlayerBiSLinkModel] = jsonFormat2(PlayerBiSLinkModel.apply)
+ implicit val playerIdWithCountersFormat: RootJsonFormat[PlayerIdWithCountersModel] =
+ jsonFormat9(PlayerIdWithCountersModel.apply)
+ implicit val userFormat: RootJsonFormat[UserModel] = jsonFormat4(UserModel.apply)
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/LootResponse.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/LootModel.scala
similarity index 54%
rename from src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/LootResponse.scala
rename to src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/LootModel.scala
index 0bf0d42..8f72c85 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/LootResponse.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/LootModel.scala
@@ -1,12 +1,20 @@
+/*
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
+ *
+ * This file is part of ffxivbis
+ * (see https://github.com/arcan1s/ffxivbis).
+ *
+ * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
+ */
package me.arcanis.ffxivbis.http.api.v1.json
-import java.time.Instant
-
import io.swagger.v3.oas.annotations.media.Schema
import me.arcanis.ffxivbis.models.Loot
-case class LootResponse(
- @Schema(description = "looted piece", required = true) piece: PieceResponse,
+import java.time.Instant
+
+case class LootModel(
+ @Schema(description = "looted piece", required = true) piece: PieceModel,
@Schema(description = "loot timestamp", required = true) timestamp: Instant,
@Schema(description = "is loot free for all", required = true) isFreeLoot: Boolean
) {
@@ -14,8 +22,8 @@ case class LootResponse(
def toLoot: Loot = Loot(-1, piece.toPiece, timestamp, isFreeLoot)
}
-object LootResponse {
+object LootModel {
- def fromLoot(loot: Loot): LootResponse =
- LootResponse(PieceResponse.fromPiece(loot.piece), loot.timestamp, loot.isFreeLoot)
+ def fromLoot(loot: Loot): LootModel =
+ LootModel(PieceModel.fromPiece(loot.piece), loot.timestamp, loot.isFreeLoot)
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PartyDescriptionResponse.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PartyDescriptionModel.scala
similarity index 65%
rename from src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PartyDescriptionResponse.scala
rename to src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PartyDescriptionModel.scala
index 7111c14..bfb5b2b 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PartyDescriptionResponse.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PartyDescriptionModel.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -11,16 +11,16 @@ package me.arcanis.ffxivbis.http.api.v1.json
import io.swagger.v3.oas.annotations.media.Schema
import me.arcanis.ffxivbis.models.PartyDescription
-case class PartyDescriptionResponse(
- @Schema(description = "party id", required = true) partyId: String,
+case class PartyDescriptionModel(
+ @Schema(description = "party id", required = true, example = "abcdefgh") partyId: String,
@Schema(description = "party name") partyAlias: Option[String]
) {
def toDescription: PartyDescription = PartyDescription(partyId, partyAlias)
}
-object PartyDescriptionResponse {
+object PartyDescriptionModel {
- def fromDescription(description: PartyDescription): PartyDescriptionResponse =
- PartyDescriptionResponse(description.partyId, description.partyAlias)
+ def fromDescription(description: PartyDescription): PartyDescriptionModel =
+ PartyDescriptionModel(description.partyId, description.partyAlias)
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/ErrorResponse.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PartyIdModel.scala
similarity index 62%
rename from src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/ErrorResponse.scala
rename to src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PartyIdModel.scala
index ba3e7a0..88fc028 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/ErrorResponse.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PartyIdModel.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -10,4 +10,4 @@ package me.arcanis.ffxivbis.http.api.v1.json
import io.swagger.v3.oas.annotations.media.Schema
-case class ErrorResponse(@Schema(description = "error message", required = true) message: String)
+case class PartyIdModel(@Schema(description = "party id", required = true, example = "abcdefgh") partyId: String)
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PieceActionResponse.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PieceActionModel.scala
similarity index 71%
rename from src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PieceActionResponse.scala
rename to src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PieceActionModel.scala
index b90026c..0487f22 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PieceActionResponse.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PieceActionModel.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -10,14 +10,14 @@ package me.arcanis.ffxivbis.http.api.v1.json
import io.swagger.v3.oas.annotations.media.Schema
-case class PieceActionResponse(
+case class PieceActionModel(
@Schema(
description = "action to perform",
required = true,
`type` = "string",
allowableValues = Array("add", "remove")
) action: ApiAction.Value,
- @Schema(description = "piece description", required = true) piece: PieceResponse,
- @Schema(description = "player description", required = true) playerId: PlayerIdResponse,
- @Schema(description = "is piece free to roll or not") isFreeLoot: Option[Boolean]
+ @Schema(description = "piece description", required = true) piece: PieceModel,
+ @Schema(description = "player description", required = true) playerId: PlayerIdModel,
+ @Schema(description = "is piece free to roll or not", `type` = "boolean") isFreeLoot: Option[Boolean]
)
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PieceResponse.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PieceModel.scala
similarity index 67%
rename from src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PieceResponse.scala
rename to src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PieceModel.scala
index 9a7e172..b67cb5a 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PieceResponse.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PieceModel.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -11,8 +11,8 @@ package me.arcanis.ffxivbis.http.api.v1.json
import io.swagger.v3.oas.annotations.media.Schema
import me.arcanis.ffxivbis.models.{Job, Piece, PieceType}
-case class PieceResponse(
- @Schema(description = "piece type", required = true) pieceType: String,
+case class PieceModel(
+ @Schema(description = "piece type", required = true, example = "Savage") pieceType: String,
@Schema(description = "job name to which piece belong or AnyJob", required = true, example = "DNC") job: String,
@Schema(description = "piece name", required = true, example = "body") piece: String
) {
@@ -20,8 +20,8 @@ case class PieceResponse(
def toPiece: Piece = Piece(piece, PieceType.withName(pieceType), Job.withName(job))
}
-object PieceResponse {
+object PieceModel {
- def fromPiece(piece: Piece): PieceResponse =
- PieceResponse(piece.pieceType.toString, piece.job.toString, piece.piece)
+ def fromPiece(piece: Piece): PieceModel =
+ PieceModel(piece.pieceType.toString, piece.job.toString, piece.piece)
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerActionResponse.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerActionModel.scala
similarity index 84%
rename from src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerActionResponse.scala
rename to src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerActionModel.scala
index f79ca93..7b240ff 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerActionResponse.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerActionModel.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -10,7 +10,7 @@ package me.arcanis.ffxivbis.http.api.v1.json
import io.swagger.v3.oas.annotations.media.Schema
-case class PlayerActionResponse(
+case class PlayerActionModel(
@Schema(
description = "action to perform",
required = true,
@@ -18,5 +18,5 @@ case class PlayerActionResponse(
allowableValues = Array("add", "remove"),
example = "add"
) action: ApiAction.Value,
- @Schema(description = "player description", required = true) playerId: PlayerResponse
+ @Schema(description = "player description", required = true) playerId: PlayerModel
)
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerBiSLinkResponse.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerBiSLinkModel.scala
similarity index 82%
rename from src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerBiSLinkResponse.scala
rename to src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerBiSLinkModel.scala
index b4625ff..7dfc244 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerBiSLinkResponse.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerBiSLinkModel.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -10,11 +10,11 @@ package me.arcanis.ffxivbis.http.api.v1.json
import io.swagger.v3.oas.annotations.media.Schema
-case class PlayerBiSLinkResponse(
+case class PlayerBiSLinkModel(
@Schema(
description = "link to player best in slot",
required = true,
example = "https://ffxiv.ariyala.com/19V5R"
) link: String,
- @Schema(description = "player description", required = true) playerId: PlayerIdResponse
+ @Schema(description = "player description", required = true) playerId: PlayerIdModel
)
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerIdResponse.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerIdModel.scala
similarity index 75%
rename from src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerIdResponse.scala
rename to src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerIdModel.scala
index 2b54925..efca7e6 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerIdResponse.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerIdModel.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -11,7 +11,7 @@ package me.arcanis.ffxivbis.http.api.v1.json
import io.swagger.v3.oas.annotations.media.Schema
import me.arcanis.ffxivbis.models.{Job, PlayerId}
-case class PlayerIdResponse(
+case class PlayerIdModel(
@Schema(description = "unique party ID. Required in responses", example = "abcdefgh") partyId: Option[String],
@Schema(description = "job name", required = true, example = "DNC") job: String,
@Schema(description = "player nick name", required = true, example = "Siuan Sanche") nick: String
@@ -21,8 +21,8 @@ case class PlayerIdResponse(
PlayerId(partyId, Job.withName(job), nick)
}
-object PlayerIdResponse {
+object PlayerIdModel {
- def fromPlayerId(playerId: PlayerId): PlayerIdResponse =
- PlayerIdResponse(Some(playerId.partyId), playerId.job.toString, playerId.nick)
+ def fromPlayerId(playerId: PlayerId): PlayerIdModel =
+ PlayerIdModel(Some(playerId.partyId), playerId.job.toString, playerId.nick)
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerIdWithCountersResponse.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerIdWithCountersModel.scala
similarity index 84%
rename from src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerIdWithCountersResponse.scala
rename to src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerIdWithCountersModel.scala
index 8485102..df72789 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerIdWithCountersResponse.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerIdWithCountersModel.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -11,22 +11,22 @@ package me.arcanis.ffxivbis.http.api.v1.json
import io.swagger.v3.oas.annotations.media.Schema
import me.arcanis.ffxivbis.models.PlayerIdWithCounters
-case class PlayerIdWithCountersResponse(
+case class PlayerIdWithCountersModel(
@Schema(description = "unique party ID", required = true, example = "abcdefgh") partyId: String,
@Schema(description = "job name", required = true, example = "DNC") job: String,
@Schema(description = "player nick name", required = true, example = "Siuan Sanche") nick: String,
@Schema(description = "is piece required by player or not", required = true) isRequired: Boolean,
@Schema(description = "player loot priority", required = true) priority: Int,
@Schema(description = "count of savage pieces in best in slot", required = true) bisCountTotal: Int,
- @Schema(description = "count of looted pieces", required = true) lootCount: Int,
+ @Schema(description = "count of looted pieces of this type", required = true) lootCount: Int,
@Schema(description = "count of looted pieces which are parts of best in slot", required = true) lootCountBiS: Int,
@Schema(description = "total count of looted pieces", required = true) lootCountTotal: Int
)
-object PlayerIdWithCountersResponse {
+object PlayerIdWithCountersModel {
- def fromPlayerId(playerIdWithCounters: PlayerIdWithCounters): PlayerIdWithCountersResponse =
- PlayerIdWithCountersResponse(
+ def fromPlayerId(playerIdWithCounters: PlayerIdWithCounters): PlayerIdWithCountersModel =
+ PlayerIdWithCountersModel(
playerIdWithCounters.partyId,
playerIdWithCounters.job.toString,
playerIdWithCounters.nick,
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerResponse.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerModel.scala
similarity index 63%
rename from src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerResponse.scala
rename to src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerModel.scala
index 2a4b987..62457fb 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerResponse.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerModel.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -11,14 +11,16 @@ package me.arcanis.ffxivbis.http.api.v1.json
import io.swagger.v3.oas.annotations.media.Schema
import me.arcanis.ffxivbis.models.{BiS, Job, Player}
-case class PlayerResponse(
+case class PlayerModel(
@Schema(description = "unique party ID", required = true, example = "abcdefgh") partyId: String,
@Schema(description = "job name", required = true, example = "DNC") job: String,
@Schema(description = "player nick name", required = true, example = "Siuan Sanche") nick: String,
- @Schema(description = "pieces in best in slot") bis: Option[Seq[PieceResponse]],
- @Schema(description = "looted pieces") loot: Option[Seq[LootResponse]],
+ @Schema(description = "pieces in best in slot") bis: Option[Seq[PieceModel]],
+ @Schema(description = "looted pieces") loot: Option[Seq[LootModel]],
@Schema(description = "link to best in slot", example = "https://ffxiv.ariyala.com/19V5R") link: Option[String],
- @Schema(description = "player loot priority", `type` = "number") priority: Option[Int]
+ @Schema(description = "player loot priority", `type` = "number") priority: Option[Int],
+ @Schema(description = "count of looted pieces which are parts of best in slot") lootCountBiS: Option[Int],
+ @Schema(description = "total count of looted pieces", `type` = "number") lootCountTotal: Option[Int],
) {
def toPlayer: Player =
@@ -34,16 +36,18 @@ case class PlayerResponse(
)
}
-object PlayerResponse {
+object PlayerModel {
- def fromPlayer(player: Player): PlayerResponse =
- PlayerResponse(
+ def fromPlayer(player: Player): PlayerModel =
+ PlayerModel(
player.partyId,
player.job.toString,
player.nick,
- Some(player.bis.pieces.map(PieceResponse.fromPiece)),
- Some(player.loot.map(LootResponse.fromLoot)),
+ Some(player.bis.pieces.map(PieceModel.fromPiece)),
+ Some(player.loot.map(LootModel.fromLoot)),
player.link,
- Some(player.priority)
+ Some(player.priority),
+ Some(player.lootCountBiS),
+ Some(player.lootCountTotal),
)
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/UserResponse.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/UserModel.scala
similarity index 80%
rename from src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/UserResponse.scala
rename to src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/UserModel.scala
index 6786140..437d940 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/UserResponse.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/UserModel.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -11,13 +11,14 @@ package me.arcanis.ffxivbis.http.api.v1.json
import io.swagger.v3.oas.annotations.media.Schema
import me.arcanis.ffxivbis.models.{Permission, User}
-case class UserResponse(
+case class UserModel(
@Schema(description = "unique party ID", required = true, example = "abcdefgh") partyId: String,
@Schema(description = "username to login to party", required = true, example = "siuan") username: String,
@Schema(description = "password to login to party", required = true, example = "pa55w0rd") password: String,
@Schema(
description = "user permission",
defaultValue = "get",
+ `type` = "string",
allowableValues = Array("get", "post", "admin")
) permission: Option[Permission.Value] = None
) {
@@ -26,8 +27,8 @@ case class UserResponse(
User(partyId, username, password, permission.getOrElse(Permission.get))
}
-object UserResponse {
+object UserModel {
- def fromUser(user: User): UserResponse =
- UserResponse(user.partyId, user.username, "", Some(user.permission))
+ def fromUser(user: User): UserModel =
+ UserModel(user.partyId, user.username, "", Some(user.permission))
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/BiSHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/helpers/BiSHelper.scala
similarity index 82%
rename from src/main/scala/me/arcanis/ffxivbis/http/BiSHelper.scala
rename to src/main/scala/me/arcanis/ffxivbis/http/helpers/BiSHelper.scala
index f1a5fce..5089dbe 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/BiSHelper.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/helpers/BiSHelper.scala
@@ -1,18 +1,10 @@
-/*
- * Copyright (c) 2019 Evgeniy Alekseev.
- *
- * This file is part of ffxivbis
- * (see https://github.com/arcan1s/ffxivbis).
- *
- * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
- */
-package me.arcanis.ffxivbis.http
+package me.arcanis.ffxivbis.http.helpers
import akka.actor.typed.scaladsl.AskPattern.Askable
import akka.actor.typed.{ActorRef, Scheduler}
import akka.util.Timeout
import me.arcanis.ffxivbis.http.api.v1.json.ApiAction
-import me.arcanis.ffxivbis.messages.{AddPieceToBis, GetBiS, Message, RemovePieceFromBiS, RemovePiecesFromBiS}
+import me.arcanis.ffxivbis.messages._
import me.arcanis.ffxivbis.models.{Piece, Player, PlayerId}
import scala.concurrent.{ExecutionContext, Future}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/BisProviderHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/helpers/BisProviderHelper.scala
similarity index 67%
rename from src/main/scala/me/arcanis/ffxivbis/http/BisProviderHelper.scala
rename to src/main/scala/me/arcanis/ffxivbis/http/helpers/BisProviderHelper.scala
index 3ddc56e..2a28b0a 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/BisProviderHelper.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/helpers/BisProviderHelper.scala
@@ -1,15 +1,7 @@
-/*
- * Copyright (c) 2019 Evgeniy Alekseev.
- *
- * This file is part of ffxivbis
- * (see https://github.com/arcan1s/ffxivbis).
- *
- * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
- */
-package me.arcanis.ffxivbis.http
+package me.arcanis.ffxivbis.http.helpers
-import akka.actor.typed.{ActorRef, Scheduler}
import akka.actor.typed.scaladsl.AskPattern.Askable
+import akka.actor.typed.{ActorRef, Scheduler}
import akka.util.Timeout
import me.arcanis.ffxivbis.messages.{BiSProviderMessage, DownloadBiS}
import me.arcanis.ffxivbis.models.{BiS, Job}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/LootHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/helpers/LootHelper.scala
similarity index 83%
rename from src/main/scala/me/arcanis/ffxivbis/http/LootHelper.scala
rename to src/main/scala/me/arcanis/ffxivbis/http/helpers/LootHelper.scala
index 57b1351..ad65a72 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/LootHelper.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/helpers/LootHelper.scala
@@ -1,18 +1,10 @@
-/*
- * Copyright (c) 2019 Evgeniy Alekseev.
- *
- * This file is part of ffxivbis
- * (see https://github.com/arcan1s/ffxivbis).
- *
- * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
- */
-package me.arcanis.ffxivbis.http
+package me.arcanis.ffxivbis.http.helpers
import akka.actor.typed.scaladsl.AskPattern.Askable
import akka.actor.typed.{ActorRef, Scheduler}
import akka.util.Timeout
import me.arcanis.ffxivbis.http.api.v1.json.ApiAction
-import me.arcanis.ffxivbis.messages.{AddPieceTo, GetLoot, Message, RemovePieceFrom, SuggestLoot}
+import me.arcanis.ffxivbis.messages._
import me.arcanis.ffxivbis.models.{Piece, Player, PlayerId, PlayerIdWithCounters}
import scala.concurrent.{ExecutionContext, Future}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/PlayerHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/helpers/PlayerHelper.scala
similarity index 85%
rename from src/main/scala/me/arcanis/ffxivbis/http/PlayerHelper.scala
rename to src/main/scala/me/arcanis/ffxivbis/http/helpers/PlayerHelper.scala
index f10e7b2..80a3647 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/PlayerHelper.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/helpers/PlayerHelper.scala
@@ -1,18 +1,10 @@
-/*
- * Copyright (c) 2019 Evgeniy Alekseev.
- *
- * This file is part of ffxivbis
- * (see https://github.com/arcan1s/ffxivbis).
- *
- * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
- */
-package me.arcanis.ffxivbis.http
+package me.arcanis.ffxivbis.http.helpers
-import akka.actor.typed.{ActorRef, Scheduler}
import akka.actor.typed.scaladsl.AskPattern.Askable
+import akka.actor.typed.{ActorRef, Scheduler}
import akka.util.Timeout
import me.arcanis.ffxivbis.http.api.v1.json.ApiAction
-import me.arcanis.ffxivbis.messages.{AddPieceToBis, AddPlayer, GetParty, GetPartyDescription, GetPlayer, Message, RemovePlayer, UpdateParty}
+import me.arcanis.ffxivbis.messages._
import me.arcanis.ffxivbis.models.{PartyDescription, Player, PlayerId}
import scala.concurrent.{ExecutionContext, Future}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/UserHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/helpers/UserHelper.scala
similarity index 74%
rename from src/main/scala/me/arcanis/ffxivbis/http/UserHelper.scala
rename to src/main/scala/me/arcanis/ffxivbis/http/helpers/UserHelper.scala
index dc6c202..a2b6be0 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/UserHelper.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/helpers/UserHelper.scala
@@ -1,17 +1,9 @@
-/*
- * Copyright (c) 2019 Evgeniy Alekseev.
- *
- * This file is part of ffxivbis
- * (see https://github.com/arcan1s/ffxivbis).
- *
- * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
- */
-package me.arcanis.ffxivbis.http
+package me.arcanis.ffxivbis.http.helpers
import akka.actor.typed.scaladsl.AskPattern.Askable
import akka.actor.typed.{ActorRef, Scheduler}
import akka.util.Timeout
-import me.arcanis.ffxivbis.messages.{AddUser, DeleteUser, GetNewPartyId, GetUser, GetUsers, Message}
+import me.arcanis.ffxivbis.messages._
import me.arcanis.ffxivbis.models.User
import scala.concurrent.Future
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/BasePartyView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/BasePartyView.scala
deleted file mode 100644
index 5a9d049..0000000
--- a/src/main/scala/me/arcanis/ffxivbis/http/view/BasePartyView.scala
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (c) 2019 Evgeniy Alekseev.
- *
- * This file is part of ffxivbis
- * (see https://github.com/arcan1s/ffxivbis).
- *
- * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
- */
-package me.arcanis.ffxivbis.http.view
-
-import akka.actor.typed.{ActorRef, Scheduler}
-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, PlayerHelper}
-import me.arcanis.ffxivbis.messages.{BiSProviderMessage, Message}
-
-import scala.util.{Failure, Success}
-
-class BasePartyView(override val storage: ActorRef[Message], override val provider: ActorRef[BiSProviderMessage])(
- implicit
- timeout: Timeout,
- scheduler: Scheduler
-) extends PlayerHelper
- with Authorization {
-
- def route: Route = getIndex
-
- def getIndex: Route =
- path("party" / Segment) { partyId =>
- extractExecutionContext { implicit executionContext =>
- authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
- get {
- onComplete(getPartyDescription(partyId)) {
- case Success(description) =>
- complete(StatusCodes.OK, RootView.toHtml(BasePartyView.template(partyId, description.alias)))
- case Failure(exception) => throw exception
- }
- }
- }
- }
- }
-}
-
-object BasePartyView {
- import scalatags.Text
- import scalatags.Text.all._
- import scalatags.Text.tags2.{title => titleTag}
-
- def root(partyId: String): Text.TypedTag[String] =
- a(href := s"/party/$partyId", title := "root")("root")
-
- def template(partyId: String, alias: String): String =
- "" +
- html(
- lang := "en",
- head(
- titleTag(s"Party $alias"),
- link(rel := "stylesheet", `type` := "text/css", href := "/static/styles.css")
- ),
- body(
- h2(s"Party $alias"),
- 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
deleted file mode 100644
index 57a5c42..0000000
--- a/src/main/scala/me/arcanis/ffxivbis/http/view/BiSView.scala
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (c) 2019 Evgeniy Alekseev.
- *
- * This file is part of ffxivbis
- * (see https://github.com/arcan1s/ffxivbis).
- *
- * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
- */
-package me.arcanis.ffxivbis.http.view
-
-import akka.actor.typed.{ActorRef, Scheduler}
-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.messages.{BiSProviderMessage, Message}
-import me.arcanis.ffxivbis.models.{Piece, PieceType, Player, PlayerId}
-
-import scala.concurrent.{ExecutionContext, Future}
-import scala.util.Try
-
-class BiSView(override val storage: ActorRef[Message], override val provider: ActorRef[BiSProviderMessage])(implicit
- timeout: Timeout,
- scheduler: Scheduler
-) extends BiSHelper
- 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, None)
- }
- .map { text =>
- (StatusCodes.OK, RootView.toHtml(text))
- }
- }
- }
- }
- }
- }
-
- def modifyBiS: Route =
- path("party" / Segment / "bis") { partyId: String =>
- extractExecutionContext { implicit executionContext =>
- authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
- post {
- formFields(
- "player".as[String],
- "piece".as[String].?,
- "piece_type".as[String].?,
- "link".as[String].?,
- "action".as[String]
- ) { (player, maybePiece, maybePieceType, maybeLink, action) =>
- onComplete(modifyBiSCall(partyId, player, maybePiece, maybePieceType, maybeLink, action)) { _ =>
- redirect(s"/party/$partyId/bis", StatusCodes.Found)
- }
- }
- }
- }
- }
- }
-
- private def modifyBiSCall(
- partyId: String,
- player: String,
- maybePiece: Option[String],
- maybePieceType: Option[String],
- maybeLink: Option[String],
- action: String
- )(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = {
- def getPiece(playerId: PlayerId, piece: String, pieceType: String) =
- Try(Piece(piece, PieceType.withName(pieceType), playerId.job)).toOption
-
- def bisAction(playerId: PlayerId, piece: String, pieceType: String)(fn: Piece => Future[Unit]) =
- getPiece(playerId, piece, pieceType) match {
- case Some(item) => fn(item)
- case _ => Future.failed(new Error(s"Could not construct piece from `$piece ($pieceType)`"))
- }
-
- PlayerId(partyId, player) match {
- case Some(playerId) =>
- (maybePiece, maybePieceType, action, maybeLink.map(_.trim).filter(_.nonEmpty)) match {
- case (Some(piece), Some(pieceType), "add", _) =>
- bisAction(playerId, piece, pieceType)(addPieceBiS(playerId, _))
- case (Some(piece), Some(pieceType), "remove", _) =>
- bisAction(playerId, piece, pieceType)(removePieceBiS(playerId, _))
- case (_, _, "create", Some(link)) => putBiS(playerId, link)
- 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._
- import scalatags.Text.tags2.{title => titleTag}
-
- def template(partyId: String, party: Seq[Player], error: Option[String]): String =
- "" +
- html(
- lang := "en",
- head(
- titleTag("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 <- Piece.available) yield option(piece)
- ),
- select(name := "piece_type", id := "piece_type", title := "piece type")(
- for (pieceType <- PieceType.available) yield option(pieceType.toString)
- ),
- input(name := "action", id := "action", `type` := "hidden", value := "add"),
- input(name := "add", id := "add", `type` := "submit", value := "add")
- ),
- form(action := s"/party/$partyId/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("piece type"),
- 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.pieceType.toString),
- 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 := "piece_type",
- id := "piece_type",
- `type` := "hidden",
- value := piece.pieceType.toString
- ),
- input(name := "action", id := "action", `type` := "hidden", value := "remove"),
- input(name := "remove", id := "remove", `type` := "submit", value := "x")
- )
- )
- )
- ),
- 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/ErrorView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/ErrorView.scala
deleted file mode 100644
index 9a35e81..0000000
--- a/src/main/scala/me/arcanis/ffxivbis/http/view/ErrorView.scala
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (c) 2019 Evgeniy Alekseev.
- *
- * This file is part of ffxivbis
- * (see https://github.com/arcan1s/ffxivbis).
- *
- * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
- */
-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("")
- }
-}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/ExportToCSVView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/ExportToCSVView.scala
deleted file mode 100644
index 0b76606..0000000
--- a/src/main/scala/me/arcanis/ffxivbis/http/view/ExportToCSVView.scala
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (c) 2019 Evgeniy Alekseev.
- *
- * This file is part of ffxivbis
- * (see https://github.com/arcan1s/ffxivbis).
- *
- * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
- */
-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")
- )
-}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/IndexView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/IndexView.scala
deleted file mode 100644
index 97d772f..0000000
--- a/src/main/scala/me/arcanis/ffxivbis/http/view/IndexView.scala
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (c) 2019 Evgeniy Alekseev.
- *
- * This file is part of ffxivbis
- * (see https://github.com/arcan1s/ffxivbis).
- *
- * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
- */
-package me.arcanis.ffxivbis.http.view
-
-import akka.actor.typed.{ActorRef, Scheduler}
-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.{PlayerHelper, UserHelper}
-import me.arcanis.ffxivbis.messages.{BiSProviderMessage, Message}
-import me.arcanis.ffxivbis.models.{PartyDescription, Permission, User}
-
-import scala.concurrent.Future
-import scala.util.{Failure, Success}
-
-class IndexView(override val storage: ActorRef[Message], override val provider: ActorRef[BiSProviderMessage])(implicit
- timeout: Timeout,
- scheduler: Scheduler
-) extends PlayerHelper
- with UserHelper {
-
- def route: Route = createParty ~ getIndex
-
- def createParty: Route =
- path("party") {
- extractExecutionContext { implicit executionContext =>
- post {
- formFields("username".as[String], "password".as[String], "alias".as[String].?) {
- (username, password, maybeAlias) =>
- onComplete {
- newPartyId.flatMap { partyId =>
- val user = User(partyId, username, password, Permission.admin)
- addUser(user, isHashedPassword = false).flatMap { _ =>
- if (maybeAlias.getOrElse("").isEmpty) Future.successful(partyId)
- else updateDescription(PartyDescription(partyId, maybeAlias)).map(_ => partyId)
- }
- }
- } {
- case Success(partyId) => redirect(s"/party/$partyId", StatusCodes.Found)
- case Failure(exception) => throw exception
- }
- }
- }
- }
- }
-
- 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._
- import scalatags.Text.tags2.{title => titleTag}
-
- def template: String =
- "" +
- html(
- head(
- titleTag("FFXIV loot helper"),
- link(rel := "stylesheet", `type` := "text/css", href := "/static/styles.css")
- ),
- body(
- form(action := s"party", method := "post")(
- label("create a new party"),
- input(name := "alias", id := "alias", placeholder := "party alias", title := "alias", `type` := "text"),
- 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
deleted file mode 100644
index a15d023..0000000
--- a/src/main/scala/me/arcanis/ffxivbis/http/view/LootSuggestView.scala
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (c) 2019 Evgeniy Alekseev.
- *
- * This file is part of ffxivbis
- * (see https://github.com/arcan1s/ffxivbis).
- *
- * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
- */
-package me.arcanis.ffxivbis.http.view
-
-import akka.actor.typed.{ActorRef, Scheduler}
-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.messages.Message
-import me.arcanis.ffxivbis.models.{Job, Piece, PieceType, PlayerIdWithCounters}
-
-import scala.concurrent.{ExecutionContext, Future}
-import scala.util.{Failure, Success, Try}
-
-class LootSuggestView(override val storage: ActorRef[Message])(implicit timeout: Timeout, scheduler: Scheduler)
- extends LootHelper
- with Authorization {
-
- def route: Route = getIndex ~ suggestLoot
-
- def getIndex: Route =
- path("party" / Segment / "suggest") { partyId: String =>
- extractExecutionContext { implicit executionContext =>
- authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
- get {
- complete {
- val text = LootSuggestView.template(partyId, Seq.empty, None, false, None)
- (StatusCodes.OK, RootView.toHtml(text))
- }
- }
- }
- }
- }
-
- def suggestLoot: Route =
- path("party" / Segment / "suggest") { partyId: String =>
- extractExecutionContext { implicit executionContext =>
- authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
- post {
- formFields("piece".as[String], "job".as[String], "piece_type".as[String], "free_loot".as[String].?) {
- (piece, job, pieceType, maybeFreeLoot) =>
- import me.arcanis.ffxivbis.utils.Implicits._
-
- val maybePiece = Try(Piece(piece, PieceType.withName(pieceType), Job.withName(job))).toOption
-
- onComplete(suggestLootCall(partyId, maybePiece)) {
- case Success(players) =>
- val text = LootSuggestView.template(partyId, players, maybePiece, maybeFreeLoot, None)
- complete(StatusCodes.OK, RootView.toHtml(text))
- case Failure(exception) =>
- val text = LootSuggestView.template(partyId, Seq.empty, None, false, Some(exception.getMessage))
- complete(StatusCodes.OK, RootView.toHtml(text))
- }
- }
- }
- }
- }
- }
-
- private def suggestLootCall(partyId: String, maybePiece: Option[Piece])(implicit
- executionContext: ExecutionContext,
- timeout: Timeout
- ): Future[Seq[PlayerIdWithCounters]] =
- maybePiece match {
- case Some(piece) => suggestPiece(partyId, piece)
- case _ => Future.failed(new Error(s"Could not construct piece from `$maybePiece`"))
- }
-}
-
-object LootSuggestView {
- import scalatags.Text.all._
- import scalatags.Text.tags2.{title => titleTag}
-
- def template(
- partyId: String,
- party: Seq[PlayerIdWithCounters],
- piece: Option[Piece],
- isFreeLoot: Boolean,
- error: Option[String]
- ): String =
- "" +
- html(
- lang := "en",
- head(
- titleTag("Suggest loot"),
- link(rel := "stylesheet", `type` := "text/css", href := "/static/styles.css")
- ),
- body(
- h2("Suggest loot"),
- for (part <- piece) yield p(s"Piece ${part.piece} (${part.pieceType})"),
- ErrorView.template(error),
- SearchLineView.template,
- form(action := s"/party/$partyId/suggest", method := "post")(
- select(name := "piece", id := "piece", title := "piece")(
- for (piece <- Piece.available) yield option(piece)
- ),
- select(name := "job", id := "job", title := "job")(
- for (job <- Job.availableWithAnyJob) yield option(job.toString)
- ),
- select(name := "piece_type", id := "piece_type", title := "piece type")(
- for (pieceType <- PieceType.available) yield option(pieceType.toString)
- ),
- input(name := "free_loot", id := "free_loot", title := "is free loot", `type` := "checkbox"),
- label(`for` := "free_loot")("is free loot"),
- input(name := "suggest", id := "suggest", `type` := "submit", value := "suggest")
- ),
- table(id := "result")(
- tr(
- th("player"),
- th("is required"),
- th("these pieces looted"),
- th("total bis pieces looted"),
- th("total pieces looted"),
- th("")
- ),
- for (player <- party)
- yield tr(
- td(`class` := "include_search")(player.playerId.toString),
- td(player.isRequiredToString),
- td(player.lootCount),
- td(player.lootCountBiS),
- td(player.lootCountTotal),
- 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.map(_.piece).getOrElse("")
- ),
- input(
- name := "piece_type",
- id := "piece_type",
- `type` := "hidden",
- value := piece.map(_.pieceType.toString).getOrElse("")
- ),
- input(
- name := "free_loot",
- id := "free_loot",
- `type` := "hidden",
- value := (if (isFreeLoot) "yes" else "no")
- ),
- input(name := "action", id := "action", `type` := "hidden", value := "add"),
- input(name := "add", id := "add", `type` := "submit", value := "add")
- )
- )
- )
- ),
- 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
deleted file mode 100644
index a4d6ef2..0000000
--- a/src/main/scala/me/arcanis/ffxivbis/http/view/LootView.scala
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (c) 2019 Evgeniy Alekseev.
- *
- * This file is part of ffxivbis
- * (see https://github.com/arcan1s/ffxivbis).
- *
- * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
- */
-package me.arcanis.ffxivbis.http.view
-
-import akka.actor.typed.{ActorRef, Scheduler}
-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.messages.Message
-import me.arcanis.ffxivbis.models.{Piece, PieceType, Player, PlayerId}
-
-import scala.concurrent.{ExecutionContext, Future}
-import scala.util.Try
-
-class LootView(override val storage: ActorRef[Message])(implicit timeout: Timeout, scheduler: Scheduler)
- extends LootHelper
- 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, 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],
- "piece_type".as[String],
- "action".as[String],
- "free_loot".as[String].?
- ) { (player, piece, pieceType, action, isFreeLoot) =>
- onComplete(modifyLootCall(partyId, player, piece, pieceType, isFreeLoot, action)) { _ =>
- redirect(s"/party/$partyId/loot", StatusCodes.Found)
- }
- }
- }
- }
- }
- }
-
- private def modifyLootCall(
- partyId: String,
- player: String,
- maybePiece: String,
- maybePieceType: String,
- maybeFreeLoot: Option[String],
- action: String
- )(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = {
- import me.arcanis.ffxivbis.utils.Implicits._
-
- def getPiece(playerId: PlayerId) =
- Try(Piece(maybePiece, PieceType.withName(maybePieceType), playerId.job)).toOption
-
- PlayerId(partyId, player) match {
- case Some(playerId) =>
- (getPiece(playerId), action) match {
- case (Some(piece), "add") => addPieceLoot(playerId, piece, maybeFreeLoot)
- case (Some(piece), "remove") => removePieceLoot(playerId, piece)
- case _ => Future.failed(new Error(s"Could not construct piece from `$maybePiece ($maybePieceType)`"))
- }
- case _ => Future.failed(new Error(s"Could not construct player id from `$player`"))
- }
- }
-}
-
-object LootView {
- import scalatags.Text.all._
- import scalatags.Text.tags2.{title => titleTag}
-
- def template(partyId: String, party: Seq[Player], error: Option[String]): String =
- "" +
- html(
- lang := "en",
- head(
- titleTag("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 <- Piece.available) yield option(piece)
- ),
- select(name := "piece_type", id := "piece_type", title := "piece type")(
- for (pieceType <- PieceType.available) yield option(pieceType.toString)
- ),
- input(name := "free_loot", id := "free_loot", title := "is free loot", `type` := "checkbox"),
- label(`for` := "free_loot")("is free loot"),
- 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("piece type"),
- th("is free loot"),
- th("timestamp"),
- th("")
- ),
- for (player <- party; loot <- player.loot)
- yield tr(
- td(`class` := "include_search")(player.playerId.toString),
- td(`class` := "include_search")(loot.piece.piece),
- td(loot.piece.pieceType.toString),
- td(loot.isFreeLootToString),
- td(loot.timestamp.toString),
- 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 := loot.piece.piece),
- input(
- name := "piece_type",
- id := "piece_type",
- `type` := "hidden",
- value := loot.piece.pieceType.toString
- ),
- input(name := "free_loot", id := "free_loot", `type` := "hidden", value := loot.isFreeLootToString),
- input(name := "action", id := "action", `type` := "hidden", value := "remove"),
- input(name := "remove", id := "remove", `type` := "submit", value := "x")
- )
- )
- )
- ),
- 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
deleted file mode 100644
index 25c850d..0000000
--- a/src/main/scala/me/arcanis/ffxivbis/http/view/PlayerView.scala
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (c) 2019 Evgeniy Alekseev.
- *
- * This file is part of ffxivbis
- * (see https://github.com/arcan1s/ffxivbis).
- *
- * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
- */
-package me.arcanis.ffxivbis.http.view
-
-import akka.actor.typed.{ActorRef, Scheduler}
-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, PlayerHelper}
-import me.arcanis.ffxivbis.messages.{BiSProviderMessage, Message}
-import me.arcanis.ffxivbis.models._
-
-import scala.concurrent.{ExecutionContext, Future}
-
-class PlayerView(override val storage: ActorRef[Message], override val provider: ActorRef[BiSProviderMessage])(implicit
- timeout: Timeout,
- scheduler: Scheduler
-) extends PlayerHelper
- with Authorization {
-
- def route: Route = getParty ~ modifyParty
-
- def getParty: Route =
- path("party" / Segment / "players") { partyId: String =>
- extractExecutionContext { implicit executionContext =>
- authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
- get {
- complete {
- getPlayers(partyId, None)
- .map { players =>
- PlayerView.template(partyId, players.map(_.withCounters(None)), None)
- }
- .map { text =>
- (StatusCodes.OK, RootView.toHtml(text))
- }
- }
- }
- }
- }
- }
-
- def modifyParty: Route =
- 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)) { _ =>
- redirect(s"/party/$partyId/players", StatusCodes.Found)
- }
- }
- }
- }
- }
- }
-
- private def modifyPartyCall(
- partyId: String,
- nick: String,
- job: String,
- maybePriority: Option[Int],
- maybeLink: Option[String],
- action: String
- )(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = {
- def maybePlayerId = PlayerId(partyId, Some(nick), Some(job))
- def player(playerId: PlayerId) =
- Player(-1, partyId, playerId.job, playerId.nick, BiS.empty, Seq.empty, maybeLink, maybePriority.getOrElse(0))
-
- (action, maybePlayerId) match {
- case ("add", Some(playerId)) => addPlayer(player(playerId))
- case ("remove", Some(playerId)) => removePlayer(playerId)
- case _ => Future.failed(new Error(s"Could not perform $action with $nick ($job)"))
- }
- }
-}
-
-object PlayerView {
- import scalatags.Text.all._
- import scalatags.Text.tags2.{title => titleTag}
-
- def template(partyId: String, party: Seq[PlayerIdWithCounters], error: Option[String]): String =
- "" +
- html(
- lang := "en",
- head(
- titleTag("Party"),
- link(rel := "stylesheet", `type` := "text/css", href := "/static/styles.css")
- ),
- body(
- h2("Party"),
- ErrorView.template(error),
- SearchLineView.template,
- 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.available) yield option(job.toString)),
- input(name := "link", id := "link", placeholder := "player bis link", title := "link", `type` := "text"),
- input(
- name := "prioiry",
- id := "priority",
- placeholder := "priority",
- title := "priority",
- `type` := "number",
- value := "0"
- ),
- input(name := "action", id := "action", `type` := "hidden", value := "add"),
- input(name := "add", id := "add", `type` := "submit", value := "add")
- ),
- table(id := "result")(
- tr(
- th("nick"),
- th("job"),
- th("total bis pieces looted"),
- th("total pieces looted"),
- th("priority"),
- th("")
- ),
- for (player <- party)
- yield tr(
- td(`class` := "include_search")(player.nick),
- td(`class` := "include_search")(player.job.toString),
- td(player.lootCountBiS),
- td(player.lootCountTotal),
- td(player.priority),
- td(
- 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"),
- input(name := "remove", id := "remove", `type` := "submit", value := "x")
- )
- )
- )
- ),
- 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 62e3fed..afc3bca 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/view/RootView.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/view/RootView.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -8,34 +8,73 @@
*/
package me.arcanis.ffxivbis.http.view
-import akka.actor.typed.{ActorRef, Scheduler}
-import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
+import akka.http.scaladsl.model.headers.RawHeader
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
-import akka.util.Timeout
-import me.arcanis.ffxivbis.messages.{BiSProviderMessage, Message}
+import me.arcanis.ffxivbis.http.{Authorization, AuthorizationProvider}
-class RootView(storage: ActorRef[Message], provider: ActorRef[BiSProviderMessage])(implicit
- timeout: Timeout,
- scheduler: Scheduler
-) {
+class RootView(override val auth: AuthorizationProvider) extends Authorization {
- private val basePartyView = new BasePartyView(storage, provider)
- private val indexView = new IndexView(storage, provider)
+ def route: Route = getBiS ~ getIndex ~ getLoot ~ getParty ~ getUsers
- private val biSView = new BiSView(storage, provider)
- private val lootView = new LootView(storage)
- private val lootSuggestView = new LootSuggestView(storage)
- private val playerView = new PlayerView(storage, provider)
- private val userView = new UserView(storage)
+ def getBiS: Route =
+ path("party" / Segment / "bis") { partyId: String =>
+ extractExecutionContext { implicit executionContext =>
+ authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { user =>
+ respondWithHeaders(
+ RawHeader("X-Party-Id", partyId),
+ RawHeader("X-User-Permission", user.permission.toString)
+ ) {
+ getFromResource("html/bis.html")
+ }
+ }
+ }
+ }
- def route: Route =
- basePartyView.route ~ indexView.route ~
- biSView.route ~ lootView.route ~ lootSuggestView.route ~ playerView.route ~ userView.route
-}
-
-object RootView {
-
- def toHtml(template: String): HttpEntity.Strict =
- HttpEntity(ContentTypes.`text/html(UTF-8)`, template)
+ def getIndex: Route =
+ pathEndOrSingleSlash {
+ getFromResource("html/index.html")
+ }
+
+ def getLoot: Route =
+ path("party" / Segment / "loot") { partyId: String =>
+ extractExecutionContext { implicit executionContext =>
+ authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { user =>
+ respondWithHeaders(
+ RawHeader("X-Party-Id", partyId),
+ RawHeader("X-User-Permission", user.permission.toString)
+ ) {
+ getFromResource("html/loot.html")
+ }
+ }
+ }
+ }
+
+ def getParty: Route =
+ path("party" / Segment) { partyId: String =>
+ extractExecutionContext { implicit executionContext =>
+ authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { user =>
+ respondWithHeaders(
+ RawHeader("X-Party-Id", partyId),
+ RawHeader("X-User-Permission", user.permission.toString)
+ ) {
+ getFromResource("html/party.html")
+ }
+ }
+ }
+ }
+
+ def getUsers: Route =
+ path("party" / Segment / "users") { partyId: String =>
+ extractExecutionContext { implicit executionContext =>
+ authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { user =>
+ respondWithHeaders(
+ RawHeader("X-Party-Id", partyId),
+ RawHeader("X-User-Permission", user.permission.toString)
+ ) {
+ getFromResource("html/users.html")
+ }
+ }
+ }
+ }
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/SearchLineView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/SearchLineView.scala
deleted file mode 100644
index ef2274d..0000000
--- a/src/main/scala/me/arcanis/ffxivbis/http/view/SearchLineView.scala
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (c) 2019 Evgeniy Alekseev.
- *
- * This file is part of ffxivbis
- * (see https://github.com/arcan1s/ffxivbis).
- *
- * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
- */
-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"
- )
- )
-}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/UserView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/UserView.scala
deleted file mode 100644
index 68c7536..0000000
--- a/src/main/scala/me/arcanis/ffxivbis/http/view/UserView.scala
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (c) 2019 Evgeniy Alekseev.
- *
- * This file is part of ffxivbis
- * (see https://github.com/arcan1s/ffxivbis).
- *
- * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
- */
-package me.arcanis.ffxivbis.http.view
-
-import akka.actor.typed.{ActorRef, Scheduler}
-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.messages.Message
-import me.arcanis.ffxivbis.models.{Permission, User}
-
-import scala.concurrent.{ExecutionContext, Future}
-import scala.util.Try
-
-class UserView(override val storage: ActorRef[Message])(implicit timeout: Timeout, scheduler: Scheduler)
- extends UserHelper
- with Authorization {
-
- def route: Route = getUsers ~ modifyUsers
-
- 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)
- case _ =>
- Future.failed(
- new Error(s"Could not construct permission/password from `$maybePermission`/`$maybePassword`")
- )
- }
- case "remove" => removeUser(partyId, username)
- case _ => Future.failed(new Error(s"Could not perform $action"))
- }
- }
-}
-
-object UserView {
- import scalatags.Text.all._
- import scalatags.Text.tags2.{title => titleTag}
-
- def template(partyId: String, users: Seq[User], error: Option[String]) =
- "" +
- html(
- lang := "en",
- head(
- titleTag("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",
- placeholder := "username",
- title := "username",
- `type` := "text"
- ),
- input(
- name := "password",
- id := "password",
- placeholder := "password",
- title := "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,
- BasePartyView.root(partyId),
- script(src := "/static/table_search.js", `type` := "text/javascript")
- )
- )
-}
diff --git a/src/main/scala/me/arcanis/ffxivbis/messages/BiSProviderMessage.scala b/src/main/scala/me/arcanis/ffxivbis/messages/BiSProviderMessage.scala
index daa5c48..703c9fc 100644
--- a/src/main/scala/me/arcanis/ffxivbis/messages/BiSProviderMessage.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/messages/BiSProviderMessage.scala
@@ -1,3 +1,11 @@
+/*
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
+ *
+ * This file is part of ffxivbis
+ * (see https://github.com/arcan1s/ffxivbis).
+ *
+ * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
+ */
package me.arcanis.ffxivbis.messages
import akka.actor.typed.ActorRef
diff --git a/src/main/scala/me/arcanis/ffxivbis/messages/ContolMessage.scala b/src/main/scala/me/arcanis/ffxivbis/messages/ContolMessage.scala
index 409d399..cdceda5 100644
--- a/src/main/scala/me/arcanis/ffxivbis/messages/ContolMessage.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/messages/ContolMessage.scala
@@ -1,3 +1,11 @@
+/*
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
+ *
+ * This file is part of ffxivbis
+ * (see https://github.com/arcan1s/ffxivbis).
+ *
+ * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
+ */
package me.arcanis.ffxivbis.messages
import akka.actor.typed.ActorRef
diff --git a/src/main/scala/me/arcanis/ffxivbis/messages/DatabaseMessage.scala b/src/main/scala/me/arcanis/ffxivbis/messages/DatabaseMessage.scala
index 07e16ed..72ff50e 100644
--- a/src/main/scala/me/arcanis/ffxivbis/messages/DatabaseMessage.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/messages/DatabaseMessage.scala
@@ -1,7 +1,15 @@
+/*
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
+ *
+ * This file is part of ffxivbis
+ * (see https://github.com/arcan1s/ffxivbis).
+ *
+ * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
+ */
package me.arcanis.ffxivbis.messages
import akka.actor.typed.{ActorRef, Behavior}
-import me.arcanis.ffxivbis.models.{Party, PartyDescription, Piece, Player, PlayerId, User}
+import me.arcanis.ffxivbis.models._
import me.arcanis.ffxivbis.service.LootSelector
sealed trait DatabaseMessage extends Message {
@@ -15,65 +23,75 @@ object DatabaseMessage {
}
// bis handler
-case class AddPieceToBis(playerId: PlayerId, piece: Piece, replyTo: ActorRef[Unit]) extends DatabaseMessage {
+trait BisDatabaseMessage extends DatabaseMessage
+
+case class AddPieceToBis(playerId: PlayerId, piece: Piece, replyTo: ActorRef[Unit]) extends BisDatabaseMessage {
override def partyId: String = playerId.partyId
}
-case class GetBiS(partyId: String, playerId: Option[PlayerId], replyTo: ActorRef[Seq[Player]]) extends DatabaseMessage
+case class GetBiS(partyId: String, playerId: Option[PlayerId], replyTo: ActorRef[Seq[Player]])
+ extends BisDatabaseMessage
-case class RemovePieceFromBiS(playerId: PlayerId, piece: Piece, replyTo: ActorRef[Unit]) extends DatabaseMessage {
+case class RemovePieceFromBiS(playerId: PlayerId, piece: Piece, replyTo: ActorRef[Unit]) extends BisDatabaseMessage {
override def partyId: String = playerId.partyId
}
-case class RemovePiecesFromBiS(playerId: PlayerId, replyTo: ActorRef[Unit]) extends DatabaseMessage {
+case class RemovePiecesFromBiS(playerId: PlayerId, replyTo: ActorRef[Unit]) extends BisDatabaseMessage {
override def partyId: String = playerId.partyId
}
// loot handler
+trait LootDatabaseMessage extends DatabaseMessage
+
case class AddPieceTo(playerId: PlayerId, piece: Piece, isFreeLoot: Boolean, replyTo: ActorRef[Unit])
- extends DatabaseMessage {
+ extends LootDatabaseMessage {
override def partyId: String = playerId.partyId
}
-case class GetLoot(partyId: String, playerId: Option[PlayerId], replyTo: ActorRef[Seq[Player]]) extends DatabaseMessage
+case class GetLoot(partyId: String, playerId: Option[PlayerId], replyTo: ActorRef[Seq[Player]])
+ extends LootDatabaseMessage
-case class RemovePieceFrom(playerId: PlayerId, piece: Piece, replyTo: ActorRef[Unit]) extends DatabaseMessage {
+case class RemovePieceFrom(playerId: PlayerId, piece: Piece, replyTo: ActorRef[Unit]) extends LootDatabaseMessage {
override def partyId: String = playerId.partyId
}
case class SuggestLoot(partyId: String, piece: Piece, replyTo: ActorRef[LootSelector.LootSelectorResult])
- extends DatabaseMessage
+ extends LootDatabaseMessage
// party handler
-case class AddPlayer(player: Player, replyTo: ActorRef[Unit]) extends DatabaseMessage {
+trait PartyDatabaseMessage extends DatabaseMessage
+
+case class AddPlayer(player: Player, replyTo: ActorRef[Unit]) extends PartyDatabaseMessage {
override def partyId: String = player.partyId
}
-case class GetParty(partyId: String, replyTo: ActorRef[Party]) extends DatabaseMessage
+case class GetParty(partyId: String, replyTo: ActorRef[Party]) extends PartyDatabaseMessage
-case class GetPartyDescription(partyId: String, replyTo: ActorRef[PartyDescription]) extends DatabaseMessage
+case class GetPartyDescription(partyId: String, replyTo: ActorRef[PartyDescription]) extends PartyDatabaseMessage
-case class GetPlayer(playerId: PlayerId, replyTo: ActorRef[Option[Player]]) extends DatabaseMessage {
+case class GetPlayer(playerId: PlayerId, replyTo: ActorRef[Option[Player]]) extends PartyDatabaseMessage {
override def partyId: String = playerId.partyId
}
-case class RemovePlayer(playerId: PlayerId, replyTo: ActorRef[Unit]) extends DatabaseMessage {
+case class RemovePlayer(playerId: PlayerId, replyTo: ActorRef[Unit]) extends PartyDatabaseMessage {
override def partyId: String = playerId.partyId
}
-case class UpdateParty(partyDescription: PartyDescription, replyTo: ActorRef[Unit]) extends DatabaseMessage {
+case class UpdateParty(partyDescription: PartyDescription, replyTo: ActorRef[Unit]) extends PartyDatabaseMessage {
override def partyId: String = partyDescription.partyId
}
// user handler
-case class AddUser(user: User, isHashedPassword: Boolean, replyTo: ActorRef[Unit]) extends DatabaseMessage {
+trait UserDatabaseMessage extends DatabaseMessage
+
+case class AddUser(user: User, isHashedPassword: Boolean, replyTo: ActorRef[Unit]) extends UserDatabaseMessage {
override def partyId: String = user.partyId
}
-case class DeleteUser(partyId: String, username: String, replyTo: ActorRef[Unit]) extends DatabaseMessage
+case class DeleteUser(partyId: String, username: String, replyTo: ActorRef[Unit]) extends UserDatabaseMessage
-case class Exists(partyId: String, replyTo: ActorRef[Boolean]) extends DatabaseMessage
+case class Exists(partyId: String, replyTo: ActorRef[Boolean]) extends UserDatabaseMessage
-case class GetUser(partyId: String, username: String, replyTo: ActorRef[Option[User]]) extends DatabaseMessage
+case class GetUser(partyId: String, username: String, replyTo: ActorRef[Option[User]]) extends UserDatabaseMessage
-case class GetUsers(partyId: String, replyTo: ActorRef[Seq[User]]) extends DatabaseMessage
+case class GetUsers(partyId: String, replyTo: ActorRef[Seq[User]]) extends UserDatabaseMessage
diff --git a/src/main/scala/me/arcanis/ffxivbis/messages/Message.scala b/src/main/scala/me/arcanis/ffxivbis/messages/Message.scala
index def973d..fe392f2 100644
--- a/src/main/scala/me/arcanis/ffxivbis/messages/Message.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/messages/Message.scala
@@ -1,3 +1,11 @@
+/*
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
+ *
+ * This file is part of ffxivbis
+ * (see https://github.com/arcan1s/ffxivbis).
+ *
+ * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
+ */
package me.arcanis.ffxivbis.messages
import akka.actor.typed.Behavior
@@ -5,5 +13,6 @@ import akka.actor.typed.Behavior
trait Message
object Message {
+
type Handler = PartialFunction[Message, Behavior[Message]]
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/models/BiS.scala b/src/main/scala/me/arcanis/ffxivbis/models/BiS.scala
index 9fd5d64..7eb3e29 100644
--- a/src/main/scala/me/arcanis/ffxivbis/models/BiS.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/models/BiS.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -25,6 +25,7 @@ case class BiS(pieces: Seq[Piece]) {
.withDefaultValue(0)
def withPiece(piece: Piece): BiS = copy(pieces :+ piece)
+
def withoutPiece(piece: Piece): BiS = copy(pieces.filterNot(_.strictEqual(piece)))
override def equals(obj: Any): Boolean = {
diff --git a/src/main/scala/me/arcanis/ffxivbis/models/Job.scala b/src/main/scala/me/arcanis/ffxivbis/models/Job.scala
index 2afb6fc..df4a4aa 100644
--- a/src/main/scala/me/arcanis/ffxivbis/models/Job.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/models/Job.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -29,6 +29,7 @@ object Job {
sealed trait Job extends Equals {
def leftSide: LeftSide
+
def rightSide: RightSide
// conversion to string to avoid recursion
diff --git a/src/main/scala/me/arcanis/ffxivbis/models/Loot.scala b/src/main/scala/me/arcanis/ffxivbis/models/Loot.scala
index 1f1af48..afcedd2 100644
--- a/src/main/scala/me/arcanis/ffxivbis/models/Loot.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/models/Loot.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
diff --git a/src/main/scala/me/arcanis/ffxivbis/models/Party.scala b/src/main/scala/me/arcanis/ffxivbis/models/Party.scala
index a2e5950..62a4330 100644
--- a/src/main/scala/me/arcanis/ffxivbis/models/Party.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/models/Party.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -17,10 +17,13 @@ import scala.util.Random
case class Party(partyDescription: PartyDescription, rules: Seq[String], players: Map[PlayerId, Player])
extends StrictLogging {
+
require(players.keys.forall(_.partyId == partyDescription.partyId), "party id must be same")
def getPlayers: Seq[Player] = players.values.toSeq
+
def player(playerId: PlayerId): Option[Player] = players.get(playerId)
+
def withPlayer(player: Player): Party =
try {
require(player.partyId == partyDescription.partyId, "player must belong to this party")
diff --git a/src/main/scala/me/arcanis/ffxivbis/models/PartyDescription.scala b/src/main/scala/me/arcanis/ffxivbis/models/PartyDescription.scala
index 9da1f4e..a138856 100644
--- a/src/main/scala/me/arcanis/ffxivbis/models/PartyDescription.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/models/PartyDescription.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
diff --git a/src/main/scala/me/arcanis/ffxivbis/models/Piece.scala b/src/main/scala/me/arcanis/ffxivbis/models/Piece.scala
index 7dd4603..87d3af0 100644
--- a/src/main/scala/me/arcanis/ffxivbis/models/Piece.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/models/Piece.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -11,7 +11,9 @@ package me.arcanis.ffxivbis.models
sealed trait Piece extends Equals {
def pieceType: PieceType.PieceType
+
def job: Job.Job
+
def piece: String
def withJob(other: Job.Job): Piece
diff --git a/src/main/scala/me/arcanis/ffxivbis/models/PieceType.scala b/src/main/scala/me/arcanis/ffxivbis/models/PieceType.scala
index 88cf4b4..bb1f7ef 100644
--- a/src/main/scala/me/arcanis/ffxivbis/models/PieceType.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/models/PieceType.scala
@@ -1,3 +1,11 @@
+/*
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
+ *
+ * This file is part of ffxivbis
+ * (see https://github.com/arcan1s/ffxivbis).
+ *
+ * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
+ */
package me.arcanis.ffxivbis.models
object PieceType {
diff --git a/src/main/scala/me/arcanis/ffxivbis/models/Player.scala b/src/main/scala/me/arcanis/ffxivbis/models/Player.scala
index 9821da1..6faeb31 100644
--- a/src/main/scala/me/arcanis/ffxivbis/models/Player.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/models/Player.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -21,10 +21,12 @@ case class Player(
require(job ne Job.AnyJob, "AnyJob is not allowed")
val playerId: PlayerId = PlayerId(partyId, job, nick)
+
def withBiS(set: Option[BiS]): Player = set match {
case Some(value) => copy(bis = value)
case None => this
}
+
def withCounters(piece: Option[Piece]): PlayerIdWithCounters =
PlayerIdWithCounters(
partyId,
@@ -32,12 +34,14 @@ case class Player(
nick,
isRequired(piece),
priority,
- bisCountTotal(piece),
+ bisCountTotal,
lootCount(piece),
- lootCountBiS(piece),
- lootCountTotal(piece)
+ lootCountBiS,
+ lootCountTotal
)
+
def withLoot(piece: Loot): Player = withLoot(Seq(piece))
+
def withLoot(list: Seq[Loot]): Player = {
require(loot.forall(_.playerId == id), "player id must be same")
copy(loot = loot ++ list)
@@ -51,12 +55,16 @@ case class Player(
case Some(_) => lootCount(piece) == 0
}
- def bisCountTotal(piece: Option[Piece]): Int = bis.pieces.count(_.pieceType == PieceType.Savage)
+ def bisCountTotal: Int = bis.pieces.count(_.pieceType == PieceType.Savage)
+
def lootCount(piece: Option[Piece]): Int = piece match {
case Some(p) => loot.count(item => !item.isFreeLoot && item.piece == p)
- case None => lootCountTotal(piece)
+ case None => lootCountTotal
}
- def lootCountBiS(piece: Option[Piece]): Int = loot.map(_.piece).count(bis.hasPiece)
- def lootCountTotal(piece: Option[Piece]): Int = loot.count(!_.isFreeLoot)
- def lootPriority(piece: Piece): Int = priority
+
+ def lootCountBiS: Int = loot.map(_.piece).count(bis.hasPiece)
+
+ def lootCountTotal: Int = loot.count(!_.isFreeLoot)
+
+ def lootPriority: Int = priority
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/models/PlayerId.scala b/src/main/scala/me/arcanis/ffxivbis/models/PlayerId.scala
index 6fbdf68..d7d1821 100644
--- a/src/main/scala/me/arcanis/ffxivbis/models/PlayerId.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/models/PlayerId.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -14,6 +14,7 @@ import scala.util.matching.Regex
trait PlayerIdBase {
def job: Job.Job
+
def nick: String
override def toString: String = s"$nick ($job)"
diff --git a/src/main/scala/me/arcanis/ffxivbis/models/PlayerIdWithCounters.scala b/src/main/scala/me/arcanis/ffxivbis/models/PlayerIdWithCounters.scala
index 14703bb..ba1a1a6 100644
--- a/src/main/scala/me/arcanis/ffxivbis/models/PlayerIdWithCounters.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/models/PlayerIdWithCounters.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -23,7 +23,9 @@ case class PlayerIdWithCounters(
def gt(that: PlayerIdWithCounters, orderBy: Seq[String]): Boolean =
withCounters(orderBy) > that.withCounters(orderBy)
+
def isRequiredToString: String = if (isRequired) "yes" else "no"
+
def playerId: PlayerId = PlayerId(partyId, job, nick)
private val counters: Map[String, Int] = Map(
@@ -42,6 +44,7 @@ case class PlayerIdWithCounters(
object PlayerIdWithCounters {
private case class PlayerCountersComparator(values: Int*) {
+
def >(that: PlayerCountersComparator): Boolean = {
@scala.annotation.tailrec
def compareLists(left: List[Int], right: List[Int]): Boolean =
diff --git a/src/main/scala/me/arcanis/ffxivbis/models/User.scala b/src/main/scala/me/arcanis/ffxivbis/models/User.scala
index 759b5e2..17caacc 100644
--- a/src/main/scala/me/arcanis/ffxivbis/models/User.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/models/User.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -17,7 +17,10 @@ object Permission extends Enumeration {
case class User(partyId: String, username: String, password: String, permission: Permission.Value) {
def hash: String = BCrypt.hashpw(password, BCrypt.gensalt)
+
def verify(plain: String): Boolean = BCrypt.checkpw(plain, password)
+
def verityScope(scope: Permission.Value): Boolean = permission >= scope
+
def withHashedPassword: User = copy(password = hash)
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/service/LootSelector.scala b/src/main/scala/me/arcanis/ffxivbis/service/LootSelector.scala
index e77be60..1943af8 100644
--- a/src/main/scala/me/arcanis/ffxivbis/service/LootSelector.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/service/LootSelector.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
diff --git a/src/main/scala/me/arcanis/ffxivbis/service/PartyService.scala b/src/main/scala/me/arcanis/ffxivbis/service/PartyService.scala
index dbea45a..b11a43e 100644
--- a/src/main/scala/me/arcanis/ffxivbis/service/PartyService.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/service/PartyService.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -9,11 +9,11 @@
package me.arcanis.ffxivbis.service
import akka.actor.typed.scaladsl.AskPattern.Askable
-import akka.actor.typed.{ActorRef, Behavior, DispatcherSelector, Scheduler}
import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors}
+import akka.actor.typed.{ActorRef, Behavior, DispatcherSelector, Scheduler}
import akka.util.Timeout
import com.typesafe.scalalogging.StrictLogging
-import me.arcanis.ffxivbis.messages.{DatabaseMessage, Exists, ForgetParty, GetNewPartyId, GetParty, Message, StoreParty}
+import me.arcanis.ffxivbis.messages._
import me.arcanis.ffxivbis.models.Party
import scala.concurrent.duration.FiniteDuration
diff --git a/src/main/scala/me/arcanis/ffxivbis/service/bis/BisProvider.scala b/src/main/scala/me/arcanis/ffxivbis/service/bis/BisProvider.scala
index edcfb55..d3e108e 100644
--- a/src/main/scala/me/arcanis/ffxivbis/service/bis/BisProvider.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/service/bis/BisProvider.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -8,10 +8,9 @@
*/
package me.arcanis.ffxivbis.service.bis
-import java.nio.file.Paths
import akka.actor.ClassicActorSystemProvider
-import akka.actor.typed.{Behavior, PostStop, Signal}
import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors}
+import akka.actor.typed.{Behavior, PostStop, Signal}
import akka.http.scaladsl.model._
import com.typesafe.scalalogging.StrictLogging
import me.arcanis.ffxivbis.messages.{BiSProviderMessage, DownloadBiS}
@@ -20,6 +19,7 @@ import me.arcanis.ffxivbis.service.bis.parser.Parser
import me.arcanis.ffxivbis.service.bis.parser.impl.{Ariyala, Etro}
import spray.json._
+import java.nio.file.Paths
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success}
diff --git a/src/main/scala/me/arcanis/ffxivbis/service/bis/RequestExecutor.scala b/src/main/scala/me/arcanis/ffxivbis/service/bis/RequestExecutor.scala
index d37dd35..4e224db 100644
--- a/src/main/scala/me/arcanis/ffxivbis/service/bis/RequestExecutor.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/service/bis/RequestExecutor.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
diff --git a/src/main/scala/me/arcanis/ffxivbis/service/bis/XivApi.scala b/src/main/scala/me/arcanis/ffxivbis/service/bis/XivApi.scala
index bf8af9c..2db60cd 100644
--- a/src/main/scala/me/arcanis/ffxivbis/service/bis/XivApi.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/service/bis/XivApi.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
diff --git a/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/Parser.scala b/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/Parser.scala
index 3d2b192..fcf4481 100644
--- a/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/Parser.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/Parser.scala
@@ -1,3 +1,11 @@
+/*
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
+ *
+ * This file is part of ffxivbis
+ * (see https://github.com/arcan1s/ffxivbis).
+ *
+ * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
+ */
package me.arcanis.ffxivbis.service.bis.parser
import akka.http.scaladsl.model.Uri
diff --git a/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/impl/Ariyala.scala b/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/impl/Ariyala.scala
index bbe2cda..eefae33 100644
--- a/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/impl/Ariyala.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/impl/Ariyala.scala
@@ -1,3 +1,11 @@
+/*
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
+ *
+ * This file is part of ffxivbis
+ * (see https://github.com/arcan1s/ffxivbis).
+ *
+ * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
+ */
package me.arcanis.ffxivbis.service.bis.parser.impl
import akka.http.scaladsl.model.Uri
diff --git a/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/impl/Etro.scala b/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/impl/Etro.scala
index 304a879..e4f357d 100644
--- a/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/impl/Etro.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/impl/Etro.scala
@@ -1,3 +1,11 @@
+/*
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
+ *
+ * This file is part of ffxivbis
+ * (see https://github.com/arcan1s/ffxivbis).
+ *
+ * License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
+ */
package me.arcanis.ffxivbis.service.bis.parser.impl
import akka.http.scaladsl.model.Uri
diff --git a/src/main/scala/me/arcanis/ffxivbis/service/database/Database.scala b/src/main/scala/me/arcanis/ffxivbis/service/database/Database.scala
index 89d81ba..45969cc 100644
--- a/src/main/scala/me/arcanis/ffxivbis/service/database/Database.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/service/database/Database.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
diff --git a/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseBiSHandler.scala b/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseBiSHandler.scala
index fc37c51..ba12d3a 100644
--- a/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseBiSHandler.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseBiSHandler.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -9,7 +9,7 @@
package me.arcanis.ffxivbis.service.database.impl
import akka.actor.typed.scaladsl.Behaviors
-import me.arcanis.ffxivbis.messages.{AddPieceToBis, DatabaseMessage, GetBiS, RemovePieceFromBiS, RemovePiecesFromBiS}
+import me.arcanis.ffxivbis.messages._
import me.arcanis.ffxivbis.service.database.Database
trait DatabaseBiSHandler { this: Database =>
diff --git a/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseImpl.scala b/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseImpl.scala
index 8d006c5..5fdefeb 100644
--- a/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseImpl.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseImpl.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -8,8 +8,8 @@
*/
package me.arcanis.ffxivbis.service.database.impl
-import akka.actor.typed.{Behavior, DispatcherSelector}
import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext}
+import akka.actor.typed.{Behavior, DispatcherSelector}
import com.typesafe.config.Config
import me.arcanis.ffxivbis.messages.DatabaseMessage
import me.arcanis.ffxivbis.service.database.Database
diff --git a/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseLootHandler.scala b/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseLootHandler.scala
index 755b21d..3ae0b98 100644
--- a/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseLootHandler.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseLootHandler.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -8,12 +8,13 @@
*/
package me.arcanis.ffxivbis.service.database.impl
-import java.time.Instant
import akka.actor.typed.scaladsl.Behaviors
-import me.arcanis.ffxivbis.messages.{AddPieceTo, DatabaseMessage, GetLoot, RemovePieceFrom, SuggestLoot}
+import me.arcanis.ffxivbis.messages._
import me.arcanis.ffxivbis.models.Loot
import me.arcanis.ffxivbis.service.database.Database
+import java.time.Instant
+
trait DatabaseLootHandler { this: Database =>
def lootHandler: DatabaseMessage.Handler = {
diff --git a/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabasePartyHandler.scala b/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabasePartyHandler.scala
index 49b147f..fbbef7a 100644
--- a/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabasePartyHandler.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabasePartyHandler.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -9,7 +9,7 @@
package me.arcanis.ffxivbis.service.database.impl
import akka.actor.typed.scaladsl.Behaviors
-import me.arcanis.ffxivbis.messages.{AddPlayer, DatabaseMessage, GetParty, GetPartyDescription, GetPlayer, RemovePlayer, UpdateParty}
+import me.arcanis.ffxivbis.messages._
import me.arcanis.ffxivbis.models.{BiS, Player}
import me.arcanis.ffxivbis.service.database.Database
diff --git a/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseUserHandler.scala b/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseUserHandler.scala
index 5131034..d7a2a08 100644
--- a/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseUserHandler.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseUserHandler.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -9,7 +9,7 @@
package me.arcanis.ffxivbis.service.database.impl
import akka.actor.typed.scaladsl.Behaviors
-import me.arcanis.ffxivbis.messages.{AddUser, DatabaseMessage, DeleteUser, Exists, GetUser, GetUsers}
+import me.arcanis.ffxivbis.messages._
import me.arcanis.ffxivbis.service.database.Database
trait DatabaseUserHandler { this: Database =>
diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/BiSProfile.scala b/src/main/scala/me/arcanis/ffxivbis/storage/BiSProfile.scala
index e8f3e05..ffbfa4b 100644
--- a/src/main/scala/me/arcanis/ffxivbis/storage/BiSProfile.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/storage/BiSProfile.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -8,17 +8,17 @@
*/
package me.arcanis.ffxivbis.storage
-import java.time.Instant
-
import me.arcanis.ffxivbis.models.{Job, Loot, Piece, PieceType}
import slick.lifted.ForeignKeyQuery
+import java.time.Instant
import scala.concurrent.Future
trait BiSProfile { this: DatabaseProfile =>
import dbConfig.profile.api._
case class BiSRep(playerId: Long, created: Long, piece: String, pieceType: String, job: String) {
+
def toLoot: Loot = Loot(
playerId,
Piece(piece, PieceType.withName(pieceType), Job.withName(job)),
@@ -26,7 +26,9 @@ trait BiSProfile { this: DatabaseProfile =>
isFreeLoot = false
)
}
+
object BiSRep {
+
def fromPiece(playerId: Long, piece: Piece): BiSRep =
BiSRep(playerId, DatabaseProfile.now, piece.piece, piece.pieceType.toString, piece.job.toString)
}
@@ -47,11 +49,15 @@ trait BiSProfile { this: DatabaseProfile =>
def deletePieceBiSById(piece: Piece)(playerId: Long): Future[Int] =
db.run(pieceBiS(BiSRep.fromPiece(playerId, piece)).delete)
+
def deletePiecesBiSById(playerId: Long): Future[Int] =
db.run(piecesBiS(Seq(playerId)).delete)
+
def getPiecesBiSById(playerId: Long): Future[Seq[Loot]] = getPiecesBiSById(Seq(playerId))
+
def getPiecesBiSById(playerIds: Seq[Long]): Future[Seq[Loot]] =
db.run(piecesBiS(playerIds).result).map(_.map(_.toLoot))
+
def insertPieceBiSById(piece: Piece)(playerId: Long): Future[Int] =
getPiecesBiSById(playerId).flatMap {
case pieces if pieces.exists(loot => loot.piece.strictEqual(piece)) => Future.successful(0)
diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/DatabaseProfile.scala b/src/main/scala/me/arcanis/ffxivbis/storage/DatabaseProfile.scala
index d287125..2ee01cc 100644
--- a/src/main/scala/me/arcanis/ffxivbis/storage/DatabaseProfile.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/storage/DatabaseProfile.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -8,13 +8,12 @@
*/
package me.arcanis.ffxivbis.storage
-import java.time.Instant
-
import com.typesafe.config.Config
import me.arcanis.ffxivbis.models.{Loot, Piece, PlayerId}
import slick.basic.DatabaseConfig
import slick.jdbc.JdbcProfile
+import java.time.Instant
import scala.concurrent.{ExecutionContext, Future}
class DatabaseProfile(context: ExecutionContext, config: Config)
@@ -40,12 +39,16 @@ class DatabaseProfile(context: ExecutionContext, config: Config)
// generic bis api
def deletePieceBiS(playerId: PlayerId, piece: Piece): Future[Int] =
byPlayerId(playerId, deletePieceBiSById(piece))
+
def deletePiecesBiS(playerId: PlayerId): Future[Int] =
byPlayerId(playerId, deletePiecesBiSById)
+
def getPiecesBiS(playerId: PlayerId): Future[Seq[Loot]] =
byPlayerId(playerId, getPiecesBiSById)
+
def getPiecesBiS(partyId: String): Future[Seq[Loot]] =
byPartyId(partyId, getPiecesBiSById)
+
def insertPieceBiS(playerId: PlayerId, piece: Piece): Future[Int] =
byPlayerId(playerId, insertPieceBiSById(piece))
@@ -55,15 +58,19 @@ class DatabaseProfile(context: ExecutionContext, config: Config)
val loot = Loot(-1, piece, Instant.now, isFreeLoot = false)
byPlayerId(playerId, deletePieceById(loot))
}
+
def getPieces(playerId: PlayerId): Future[Seq[Loot]] =
byPlayerId(playerId, getPiecesById)
+
def getPieces(partyId: String): Future[Seq[Loot]] =
byPartyId(partyId, getPiecesById)
+
def insertPiece(playerId: PlayerId, loot: Loot): Future[Int] =
byPlayerId(playerId, insertPieceById(loot))
private def byPartyId[T](partyId: String, callback: Seq[Long] => Future[T]): Future[T] =
getPlayers(partyId).flatMap(callback)
+
private def byPlayerId[T](playerId: PlayerId, callback: Long => Future[T]): Future[T] =
getPlayer(playerId).flatMap {
case Some(id) => callback(id)
@@ -74,6 +81,7 @@ class DatabaseProfile(context: ExecutionContext, config: Config)
object DatabaseProfile {
def now: Long = Instant.now.toEpochMilli
+
def getSection(config: Config): Config = {
val section = config.getString("me.arcanis.ffxivbis.database.mode")
config.getConfig("me.arcanis.ffxivbis.database").getConfig(section)
diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/LootProfile.scala b/src/main/scala/me/arcanis/ffxivbis/storage/LootProfile.scala
index 32c3165..5b7d775 100644
--- a/src/main/scala/me/arcanis/ffxivbis/storage/LootProfile.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/storage/LootProfile.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -8,11 +8,10 @@
*/
package me.arcanis.ffxivbis.storage
-import java.time.Instant
-
import me.arcanis.ffxivbis.models.{Job, Loot, Piece, PieceType}
import slick.lifted.{ForeignKeyQuery, Index}
+import java.time.Instant
import scala.concurrent.Future
trait LootProfile { this: DatabaseProfile =>
@@ -27,6 +26,7 @@ trait LootProfile { this: DatabaseProfile =>
job: String,
isFreeLoot: Int
) {
+
def toLoot: Loot = Loot(
playerId,
Piece(piece, PieceType.withName(pieceType), Job.withName(job)),
@@ -34,6 +34,7 @@ trait LootProfile { this: DatabaseProfile =>
isFreeLoot == 1
)
}
+
object LootRep {
def fromLoot(playerId: Long, loot: Loot): LootRep =
LootRep(
@@ -70,14 +71,18 @@ trait LootProfile { this: DatabaseProfile =>
case Some(id) => db.run(lootTable.filter(_.lootId === id).delete)
case _ => throw new IllegalArgumentException(s"Could not find piece $loot belong to $playerId")
}
+
def getPiecesById(playerId: Long): Future[Seq[Loot]] = getPiecesById(Seq(playerId))
+
def getPiecesById(playerIds: Seq[Long]): Future[Seq[Loot]] =
db.run(piecesLoot(playerIds).result).map(_.map(_.toLoot))
+
def insertPieceById(loot: Loot)(playerId: Long): Future[Int] =
db.run(lootTable.insertOrUpdate(LootRep.fromLoot(playerId, loot)))
private def pieceLoot(piece: LootRep) =
piecesLoot(Seq(piece.playerId)).filter(_.piece === piece.piece)
+
private def piecesLoot(playerIds: Seq[Long]) =
lootTable.filter(_.playerId.inSet(playerIds.toSet))
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/Migration.scala b/src/main/scala/me/arcanis/ffxivbis/storage/Migration.scala
index c2ab08e..8c2b782 100644
--- a/src/main/scala/me/arcanis/ffxivbis/storage/Migration.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/storage/Migration.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -21,8 +21,8 @@ class Migration(config: Config) {
val section = DatabaseProfile.getSection(config)
val url = section.getString("db.url")
- val username = section.getString("db.user")
- val password = section.getString("db.password")
+ val username = Try(section.getString("db.user")).toOption.filter(_.nonEmpty).orNull
+ val password = Try(section.getString("db.password")).toOption.filter(_.nonEmpty).orNull
val provider = url match {
case s"jdbc:$p:$_" => p
diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/PartyProfile.scala b/src/main/scala/me/arcanis/ffxivbis/storage/PartyProfile.scala
index d79bf48..7c8ae75 100644
--- a/src/main/scala/me/arcanis/ffxivbis/storage/PartyProfile.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/storage/PartyProfile.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -16,9 +16,12 @@ trait PartyProfile { this: DatabaseProfile =>
import dbConfig.profile.api._
case class PartyRep(partyId: Option[Long], partyName: String, partyAlias: Option[String]) {
+
def toDescription: PartyDescription = PartyDescription(partyName, partyAlias)
}
+
object PartyRep {
+
def fromDescription(party: PartyDescription, id: Option[Long]): PartyRep =
PartyRep(id, party.partyId, party.partyAlias)
}
@@ -36,8 +39,10 @@ trait PartyProfile { this: DatabaseProfile =>
db.run(
partyDescription(partyId).result.headOption.map(_.map(_.toDescription).getOrElse(PartyDescription.empty(partyId)))
)
+
def getUniquePartyId(partyId: String): Future[Option[Long]] =
db.run(partyDescription(partyId).map(_.partyId).result.headOption)
+
def insertPartyDescription(partyDescription: PartyDescription): Future[Int] =
getUniquePartyId(partyDescription.partyId).flatMap {
case Some(id) => db.run(partiesTable.update(PartyRep.fromDescription(partyDescription, Some(id))))
diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/PlayersProfile.scala b/src/main/scala/me/arcanis/ffxivbis/storage/PlayersProfile.scala
index 607a3f3..a660662 100644
--- a/src/main/scala/me/arcanis/ffxivbis/storage/PlayersProfile.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/storage/PlayersProfile.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -24,10 +24,13 @@ trait PlayersProfile { this: DatabaseProfile =>
link: Option[String],
priority: Int
) {
+
def toPlayer: Player =
Player(playerId.getOrElse(-1), partyId, Job.withName(job), nick, BiS.empty, Seq.empty, link, priority)
}
+
object PlayerRep {
+
def fromPlayer(player: Player, id: Option[Long]): PlayerRep =
PlayerRep(player.partyId, id, DatabaseProfile.now, player.nick, player.job.toString, player.link, player.priority)
}
@@ -46,18 +49,23 @@ trait PlayersProfile { this: DatabaseProfile =>
}
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]) {
case (acc, p @ PlayerRep(_, Some(id), _, _, _, _, _)) => acc + (id -> p.toPlayer)
case (acc, _) => acc
})
+
def getPlayer(playerId: PlayerId): Future[Option[Long]] =
db.run(player(playerId).map(_.playerId).result.headOption)
+
def getPlayerFull(playerId: PlayerId): Future[Option[Player]] =
db.run(player(playerId).result.headOption.map(_.map(_.toPlayer)))
+
def getPlayers(partyId: String): Future[Seq[Long]] =
db.run(players(partyId).map(_.playerId).result)
+
def insertPlayer(playerObj: Player): Future[Int] =
getPlayer(playerObj.playerId).flatMap {
case Some(id) => db.run(playersTable.update(PlayerRep.fromPlayer(playerObj, Some(id))))
@@ -69,6 +77,7 @@ trait PlayersProfile { this: DatabaseProfile =>
.filter(_.partyId === playerId.partyId)
.filter(_.job === playerId.job.toString)
.filter(_.nick === playerId.nick)
+
private def players(partyId: String) =
playersTable.filter(_.partyId === partyId)
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/UsersProfile.scala b/src/main/scala/me/arcanis/ffxivbis/storage/UsersProfile.scala
index 7a5a09a..788775d 100644
--- a/src/main/scala/me/arcanis/ffxivbis/storage/UsersProfile.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/storage/UsersProfile.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -17,9 +17,12 @@ trait UsersProfile { this: DatabaseProfile =>
import dbConfig.profile.api._
case class UserRep(partyId: String, userId: Option[Long], username: String, password: String, permission: String) {
+
def toUser: User = User(partyId, username, password, Permission.withName(permission))
}
+
object UserRep {
+
def fromUser(user: User, id: Option[Long]): UserRep =
UserRep(user.partyId, id, user.username, user.password, user.permission.toString)
}
@@ -40,13 +43,21 @@ trait UsersProfile { this: DatabaseProfile =>
}
def deleteUser(partyId: String, username: String): Future[Int] =
- db.run(user(partyId, Some(username)).delete)
+ db.run(
+ user(partyId, Some(username))
+ .filter(_.permission =!= Permission.admin.toString) // we do not allow to remove admins
+ .delete
+ )
+
def exists(partyId: String): Future[Boolean] =
db.run(user(partyId, None).exists.result)
+
def getUser(partyId: String, username: String): Future[Option[User]] =
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(userObj: User): Future[Int] =
db.run(user(userObj.partyId, Some(userObj.username)).map(_.userId).result.headOption).flatMap {
case Some(id) => db.run(usersTable.insertOrUpdate(UserRep.fromUser(userObj, Some(id))))
diff --git a/src/main/scala/me/arcanis/ffxivbis/utils/Implicits.scala b/src/main/scala/me/arcanis/ffxivbis/utils/Implicits.scala
index 0c01e61..e79b6ef 100644
--- a/src/main/scala/me/arcanis/ffxivbis/utils/Implicits.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/utils/Implicits.scala
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Evgeniy Alekseev.
+ * Copyright (c) 2019-2022 Evgeniy Alekseev.
*
* This file is part of ffxivbis
* (see https://github.com/arcan1s/ffxivbis).
@@ -8,11 +8,10 @@
*/
package me.arcanis.ffxivbis.utils
-import java.time.Duration
-import java.util.concurrent.TimeUnit
-
import akka.util.Timeout
+import java.time.Duration
+import java.util.concurrent.TimeUnit
import scala.concurrent.duration.FiniteDuration
import scala.language.implicitConversions
diff --git a/src/test/scala/me/arcanis/ffxivbis/Fixtures.scala b/src/test/scala/me/arcanis/ffxivbis/Fixtures.scala
index 042e793..0ae0cad 100644
--- a/src/test/scala/me/arcanis/ffxivbis/Fixtures.scala
+++ b/src/test/scala/me/arcanis/ffxivbis/Fixtures.scala
@@ -1,7 +1,10 @@
package me.arcanis.ffxivbis
+import me.arcanis.ffxivbis.http.AuthorizationProvider
import me.arcanis.ffxivbis.models._
+import scala.concurrent.Future
+
object Fixtures {
lazy val bis: BiS = BiS(
Seq(
@@ -79,4 +82,6 @@ object Fixtures {
lazy val userAdmin: User = User(partyId, "admin", userPassword, Permission.admin).withHashedPassword
lazy val userGet: User = User(partyId, "get", userPassword, Permission.get).withHashedPassword
lazy val users: Seq[User] = Seq(userAdmin, userGet)
+
+ lazy val authProvider: AuthorizationProvider = (_: String, _: String) => Future.successful(Some(userAdmin))
}
diff --git a/src/test/scala/me/arcanis/ffxivbis/Settings.scala b/src/test/scala/me/arcanis/ffxivbis/Settings.scala
index e5c67f6..cea846e 100644
--- a/src/test/scala/me/arcanis/ffxivbis/Settings.scala
+++ b/src/test/scala/me/arcanis/ffxivbis/Settings.scala
@@ -1,9 +1,9 @@
package me.arcanis.ffxivbis
-import java.io.File
-
import com.typesafe.config.{Config, ConfigFactory, ConfigValueFactory}
+import java.io.File
+
object Settings {
def config(values: Map[String, AnyRef]): Config = {
@scala.annotation.tailrec
diff --git a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpointTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpointTest.scala
index 9ca5a3b..2238443 100644
--- a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpointTest.scala
+++ b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpointTest.scala
@@ -2,20 +2,20 @@ package me.arcanis.ffxivbis.http.api.v1
import akka.actor.testkit.typed.scaladsl.ActorTestKit
import akka.actor.typed.scaladsl.AskPattern.Askable
-import akka.http.scaladsl.model.{StatusCodes, Uri}
import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials}
+import akka.http.scaladsl.model.{StatusCodes, Uri}
import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest}
import akka.testkit.TestKit
import com.typesafe.config.Config
-import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.http.api.v1.json._
import me.arcanis.ffxivbis.messages.{AddPlayer, AddUser}
import me.arcanis.ffxivbis.models.{BiS, Job}
+import me.arcanis.ffxivbis.service.PartyService
import me.arcanis.ffxivbis.service.bis.BisProvider
import me.arcanis.ffxivbis.service.database.Database
-import me.arcanis.ffxivbis.service.PartyService
import me.arcanis.ffxivbis.storage.Migration
import me.arcanis.ffxivbis.utils.Compare
+import me.arcanis.ffxivbis.{Fixtures, Settings}
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
@@ -31,14 +31,14 @@ class BiSEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRouteT
private val auth =
Authorization(BasicHttpCredentials(Fixtures.userAdmin.username, Fixtures.userPassword))
private val endpoint = Uri(s"/party/${Fixtures.partyId}/bis")
- private val playerId = PlayerIdResponse.fromPlayerId(Fixtures.playerEmpty.playerId)
+ private val playerId = PlayerIdModel.fromPlayerId(Fixtures.playerEmpty.playerId)
private val askTimeout = 60 seconds
implicit private val routeTimeout: RouteTestTimeout = RouteTestTimeout(askTimeout)
private val storage = testKit.spawn(Database())
private val provider = testKit.spawn(BisProvider())
private val party = testKit.spawn(PartyService(storage))
- private val route = new BiSEndpoint(party, provider)(askTimeout, testKit.scheduler).route
+ private val route = new BiSEndpoint(party, provider, Fixtures.authProvider)(askTimeout, testKit.scheduler).route
override def beforeAll(): Unit = {
super.beforeAll()
@@ -54,7 +54,7 @@ class BiSEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRouteT
super.afterAll()
}
- private def compareBiSResponse(actual: PlayerResponse, expected: PlayerResponse): Unit = {
+ private def compareBiSResponse(actual: PlayerModel, expected: PlayerModel): Unit = {
actual.partyId shouldEqual expected.partyId
actual.nick shouldEqual expected.nick
actual.job shouldEqual expected.job
@@ -66,7 +66,7 @@ class BiSEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRouteT
"api v1 bis endpoint" must {
"create best in slot set from ariyala" in {
- val entity = PlayerBiSLinkResponse(Fixtures.link, playerId)
+ val entity = PlayerBiSLinkModel(Fixtures.link, playerId)
Put(endpoint, entity).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.Created
@@ -76,19 +76,19 @@ class BiSEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRouteT
"return best in slot set" in {
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
- val response = PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))
+ val response = PlayerModel.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))
Get(uri).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.OK
- val actual = responseAs[Seq[PlayerResponse]]
+ val actual = responseAs[Seq[PlayerModel]]
actual.length shouldEqual 1
actual.foreach(compareBiSResponse(_, response))
}
}
"remove item from best in slot set" in {
- val piece = PieceResponse.fromPiece(Fixtures.lootBody)
- val entity = PieceActionResponse(ApiAction.remove, piece, playerId, None)
+ val piece = PieceModel.fromPiece(Fixtures.lootBody)
+ val entity = PieceActionModel(ApiAction.remove, piece, playerId, None)
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.Accepted
@@ -97,19 +97,19 @@ class BiSEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRouteT
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
val bis = BiS(Fixtures.bis.pieces.filterNot(_ == Fixtures.lootBody))
- val response = PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(bis)))
+ val response = PlayerModel.fromPlayer(Fixtures.playerEmpty.withBiS(Some(bis)))
Get(uri).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.OK
- val actual = responseAs[Seq[PlayerResponse]]
+ val actual = responseAs[Seq[PlayerModel]]
actual.length shouldEqual 1
actual.foreach(compareBiSResponse(_, response))
}
}
"add item to best in slot set" in {
- val piece = PieceResponse.fromPiece(Fixtures.lootBody)
- val entity = PieceActionResponse(ApiAction.add, piece, playerId, None)
+ val piece = PieceModel.fromPiece(Fixtures.lootBody)
+ val entity = PieceActionModel(ApiAction.add, piece, playerId, None)
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.Accepted
@@ -117,19 +117,19 @@ class BiSEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRouteT
}
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
- val response = PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))
+ val response = PlayerModel.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))
Get(uri).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.OK
- val actual = responseAs[Seq[PlayerResponse]]
+ val actual = responseAs[Seq[PlayerModel]]
actual.length shouldEqual 1
actual.foreach(compareBiSResponse(_, response))
}
}
"do not allow to add same item to best in slot set" in {
- val piece = PieceResponse.fromPiece(Fixtures.lootBody.withJob(Job.DNC))
- val entity = PieceActionResponse(ApiAction.add, piece, playerId, None)
+ val piece = PieceModel.fromPiece(Fixtures.lootBody.withJob(Job.DNC))
+ val entity = PieceActionModel(ApiAction.add, piece, playerId, None)
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.Accepted
@@ -137,19 +137,19 @@ class BiSEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRouteT
}
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
- val response = PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))
+ val response = PlayerModel.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))
Get(uri).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.OK
- val actual = responseAs[Seq[PlayerResponse]]
+ val actual = responseAs[Seq[PlayerModel]]
actual.length shouldEqual 1
actual.foreach(compareBiSResponse(_, response))
}
}
"allow to add item with another type to best in slot set" in {
- val piece = PieceResponse.fromPiece(Fixtures.lootBodyCrafted.withJob(Job.DNC))
- val entity = PieceActionResponse(ApiAction.add, piece, playerId, None)
+ val piece = PieceModel.fromPiece(Fixtures.lootBodyCrafted.withJob(Job.DNC))
+ val entity = PieceActionModel(ApiAction.add, piece, playerId, None)
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.Accepted
@@ -158,19 +158,19 @@ class BiSEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRouteT
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
val bis = Fixtures.bis.withPiece(piece.toPiece)
- val response = PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(bis)))
+ val response = PlayerModel.fromPlayer(Fixtures.playerEmpty.withBiS(Some(bis)))
Get(uri).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.OK
- val actual = responseAs[Seq[PlayerResponse]]
+ val actual = responseAs[Seq[PlayerModel]]
actual.length shouldEqual 1
actual.foreach(compareBiSResponse(_, response))
}
}
"remove only specific item from best in slot set" in {
- val piece = PieceResponse.fromPiece(Fixtures.lootBodyCrafted.withJob(Job.DNC))
- val entity = PieceActionResponse(ApiAction.remove, piece, playerId, None)
+ val piece = PieceModel.fromPiece(Fixtures.lootBodyCrafted.withJob(Job.DNC))
+ val entity = PieceActionModel(ApiAction.remove, piece, playerId, None)
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.Accepted
@@ -178,11 +178,11 @@ class BiSEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRouteT
}
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
- val response = PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))
+ val response = PlayerModel.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))
Get(uri).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.OK
- val actual = responseAs[Seq[PlayerResponse]]
+ val actual = responseAs[Seq[PlayerModel]]
actual.length shouldEqual 1
actual.foreach(compareBiSResponse(_, response))
}
@@ -190,15 +190,15 @@ class BiSEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRouteT
"totaly replace player bis" in {
// add random item first
- val piece = PieceResponse.fromPiece(Fixtures.lootBodyCrafted.withJob(Job.DNC))
- val entity = PieceActionResponse(ApiAction.add, piece, playerId, None)
+ val piece = PieceModel.fromPiece(Fixtures.lootBodyCrafted.withJob(Job.DNC))
+ val entity = PieceActionModel(ApiAction.add, piece, playerId, None)
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.Accepted
responseAs[String] shouldEqual ""
}
- val bisEntity = PlayerBiSLinkResponse(Fixtures.link, playerId)
+ val bisEntity = PlayerBiSLinkModel(Fixtures.link, playerId)
Put(endpoint, bisEntity).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.Created
@@ -206,11 +206,11 @@ class BiSEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRouteT
}
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
- val response = PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))
+ val response = PlayerModel.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))
Get(uri).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.OK
- val actual = responseAs[Seq[PlayerResponse]]
+ val actual = responseAs[Seq[PlayerModel]]
actual.length shouldEqual 1
actual.foreach(compareBiSResponse(_, response))
}
diff --git a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpointTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpointTest.scala
index 8a9e935..e45bf35 100644
--- a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpointTest.scala
+++ b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpointTest.scala
@@ -1,23 +1,22 @@
package me.arcanis.ffxivbis.http.api.v1
-import java.time.Instant
-
import akka.actor.testkit.typed.scaladsl.ActorTestKit
import akka.actor.typed.scaladsl.AskPattern.Askable
-import akka.http.scaladsl.model.{StatusCodes, Uri}
import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials}
+import akka.http.scaladsl.model.{StatusCodes, Uri}
import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest}
import akka.testkit.TestKit
import com.typesafe.config.Config
-import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.http.api.v1.json._
import me.arcanis.ffxivbis.messages.{AddPlayer, AddUser}
-import me.arcanis.ffxivbis.service.database.Database
import me.arcanis.ffxivbis.service.PartyService
+import me.arcanis.ffxivbis.service.database.Database
import me.arcanis.ffxivbis.storage.Migration
+import me.arcanis.ffxivbis.{Fixtures, Settings}
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
+import java.time.Instant
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.language.postfixOps
@@ -30,13 +29,13 @@ class LootEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRoute
private val auth =
Authorization(BasicHttpCredentials(Fixtures.userAdmin.username, Fixtures.userPassword))
private val endpoint = Uri(s"/party/${Fixtures.partyId}/loot")
- private val playerId = PlayerIdResponse.fromPlayerId(Fixtures.playerEmpty.playerId)
+ private val playerId = PlayerIdModel.fromPlayerId(Fixtures.playerEmpty.playerId)
private val askTimeout = 60 seconds
implicit private val routeTimeout: RouteTestTimeout = RouteTestTimeout(askTimeout)
private val storage = testKit.spawn(Database())
private val party = testKit.spawn(PartyService(storage))
- private val route = new LootEndpoint(party)(askTimeout, testKit.scheduler).route
+ private val route = new LootEndpoint(party, Fixtures.authProvider)(askTimeout, testKit.scheduler).route
override def beforeAll(): Unit = {
super.beforeAll()
@@ -55,8 +54,8 @@ class LootEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRoute
"api v1 loot endpoint" must {
"add item to loot" in {
- val piece = PieceResponse.fromPiece(Fixtures.lootBody)
- val entity = PieceActionResponse(ApiAction.add, piece, playerId, Some(false))
+ val piece = PieceModel.fromPiece(Fixtures.lootBody)
+ val entity = PieceActionModel(ApiAction.add, piece, playerId, Some(false))
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.Accepted
@@ -68,11 +67,11 @@ class LootEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRoute
import me.arcanis.ffxivbis.utils.Converters._
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
- val response = Seq(PlayerResponse.fromPlayer(Fixtures.playerEmpty.withLoot(Fixtures.lootBody)))
+ val response = Seq(PlayerModel.fromPlayer(Fixtures.playerEmpty.withLoot(Fixtures.lootBody)))
Get(uri).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.OK
- val withEmptyTimestamp = responseAs[Seq[PlayerResponse]].map { player =>
+ val withEmptyTimestamp = responseAs[Seq[PlayerModel]].map { player =>
player.copy(loot = player.loot.map(_.map(_.copy(timestamp = Instant.ofEpochMilli(0)))))
}
withEmptyTimestamp shouldEqual response
@@ -80,8 +79,8 @@ class LootEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRoute
}
"remove item from loot" in {
- val piece = PieceResponse.fromPiece(Fixtures.lootBody)
- val entity = PieceActionResponse(ApiAction.remove, piece, playerId, Some(false))
+ val piece = PieceModel.fromPiece(Fixtures.lootBody)
+ val entity = PieceActionModel(ApiAction.remove, piece, playerId, Some(false))
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.Accepted
@@ -89,11 +88,11 @@ class LootEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRoute
}
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
- val response = Seq(PlayerResponse.fromPlayer(Fixtures.playerEmpty))
+ val response = Seq(PlayerModel.fromPlayer(Fixtures.playerEmpty))
Get(uri).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.OK
- responseAs[Seq[PlayerResponse]] shouldEqual response
+ responseAs[Seq[PlayerModel]] shouldEqual response
}
}
diff --git a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/PartyEndpointTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/PartyEndpointTest.scala
index 6cbb3d9..9b4aae1 100644
--- a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/PartyEndpointTest.scala
+++ b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/PartyEndpointTest.scala
@@ -2,19 +2,19 @@ package me.arcanis.ffxivbis.http.api.v1
import akka.actor.testkit.typed.scaladsl.ActorTestKit
import akka.actor.typed.scaladsl.AskPattern.Askable
-import akka.http.scaladsl.model.{StatusCodes, Uri}
import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials}
+import akka.http.scaladsl.model.{StatusCodes, Uri}
import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest}
import akka.testkit.TestKit
import com.typesafe.config.Config
-import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.http.api.v1.json._
import me.arcanis.ffxivbis.messages.AddUser
import me.arcanis.ffxivbis.models.PartyDescription
+import me.arcanis.ffxivbis.service.PartyService
import me.arcanis.ffxivbis.service.bis.BisProvider
import me.arcanis.ffxivbis.service.database.Database
-import me.arcanis.ffxivbis.service.PartyService
import me.arcanis.ffxivbis.storage.Migration
+import me.arcanis.ffxivbis.{Fixtures, Settings}
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
@@ -36,7 +36,7 @@ class PartyEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRout
private val storage = testKit.spawn(Database())
private val provider = testKit.spawn(BisProvider())
private val party = testKit.spawn(PartyService(storage))
- private val route = new PartyEndpoint(party, provider)(askTimeout, testKit.scheduler).route
+ private val route = new PartyEndpoint(party, provider, Fixtures.authProvider)(askTimeout, testKit.scheduler).route
override def beforeAll(): Unit = {
super.beforeAll()
@@ -56,12 +56,12 @@ class PartyEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRout
"get empty party description" in {
Get(endpoint).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.OK
- responseAs[PartyDescriptionResponse].toDescription shouldEqual PartyDescription.empty(Fixtures.partyId)
+ responseAs[PartyDescriptionModel].toDescription shouldEqual PartyDescription.empty(Fixtures.partyId)
}
}
"update party description" in {
- val entity = PartyDescriptionResponse(Fixtures.partyId, Some("random party name"))
+ val entity = PartyDescriptionModel(Fixtures.partyId, Some("random party name"))
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.Accepted
@@ -69,7 +69,7 @@ class PartyEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRout
Get(endpoint).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.OK
- responseAs[PartyDescriptionResponse].toDescription shouldEqual entity.toDescription
+ responseAs[PartyDescriptionModel].toDescription shouldEqual entity.toDescription
}
}
diff --git a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpointTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpointTest.scala
index 3dd649f..cd1ccfd 100644
--- a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpointTest.scala
+++ b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpointTest.scala
@@ -2,18 +2,18 @@ package me.arcanis.ffxivbis.http.api.v1
import akka.actor.testkit.typed.scaladsl.ActorTestKit
import akka.actor.typed.scaladsl.AskPattern.Askable
-import akka.http.scaladsl.model.{StatusCodes, Uri}
import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials}
+import akka.http.scaladsl.model.{StatusCodes, Uri}
import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest}
import akka.testkit.TestKit
import com.typesafe.config.Config
-import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.http.api.v1.json._
import me.arcanis.ffxivbis.messages.{AddPlayer, AddUser}
+import me.arcanis.ffxivbis.service.PartyService
import me.arcanis.ffxivbis.service.bis.BisProvider
import me.arcanis.ffxivbis.service.database.Database
-import me.arcanis.ffxivbis.service.PartyService
import me.arcanis.ffxivbis.storage.Migration
+import me.arcanis.ffxivbis.{Fixtures, Settings}
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
@@ -35,7 +35,7 @@ class PlayerEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRou
private val storage = testKit.spawn(Database())
private val provider = testKit.spawn(BisProvider())
private val party = testKit.spawn(PartyService(storage))
- private val route = new PlayerEndpoint(party, provider)(askTimeout, testKit.scheduler).route
+ private val route = new PlayerEndpoint(party, provider, Fixtures.authProvider)(askTimeout, testKit.scheduler).route
override def beforeAll(): Unit = {
super.beforeAll()
@@ -54,11 +54,11 @@ class PlayerEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRou
"api v1 player endpoint" must {
"get users" in {
- val response = Seq(PlayerResponse.fromPlayer(Fixtures.playerEmpty))
+ val response = Seq(PlayerModel.fromPlayer(Fixtures.playerEmpty))
Get(endpoint).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.OK
- responseAs[Seq[PlayerResponse]] shouldEqual response
+ responseAs[Seq[PlayerModel]] shouldEqual response
}
}
diff --git a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/TypesEndpointTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/TypesEndpointTest.scala
index 05a1720..84520aa 100644
--- a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/TypesEndpointTest.scala
+++ b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/TypesEndpointTest.scala
@@ -5,7 +5,7 @@ import akka.http.scaladsl.testkit.ScalatestRouteTest
import com.typesafe.config.Config
import me.arcanis.ffxivbis.Settings
import me.arcanis.ffxivbis.http.api.v1.json._
-import me.arcanis.ffxivbis.models.{Job, Party, Permission, Piece, PieceType}
+import me.arcanis.ffxivbis.models._
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
@@ -22,6 +22,13 @@ class TypesEndpointTest extends AnyWordSpecLike
"return all available jobs" in {
Get("/types/jobs") ~> route ~> check {
+ status shouldEqual StatusCodes.OK
+ responseAs[Seq[String]] shouldEqual Job.available.map(_.toString)
+ }
+ }
+
+ "return all available jobs WITH ANY JOB ALIAS" in {
+ Get("/types/jobs/all") ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[Seq[String]] shouldEqual Job.availableWithAnyJob.map(_.toString)
}
diff --git a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/UserEndpointTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/UserEndpointTest.scala
index a965acf..26143e4 100644
--- a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/UserEndpointTest.scala
+++ b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/UserEndpointTest.scala
@@ -1,16 +1,16 @@
package me.arcanis.ffxivbis.http.api.v1
import akka.actor.testkit.typed.scaladsl.ActorTestKit
-import akka.http.scaladsl.model.{StatusCodes, Uri}
import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials}
+import akka.http.scaladsl.model.{StatusCodes, Uri}
import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest}
import akka.testkit.TestKit
import com.typesafe.config.Config
-import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.http.api.v1.json._
import me.arcanis.ffxivbis.service.PartyService
import me.arcanis.ffxivbis.service.database.Database
import me.arcanis.ffxivbis.storage.Migration
+import me.arcanis.ffxivbis.{Fixtures, Settings}
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
@@ -31,7 +31,7 @@ class UserEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRoute
private var partyId = Fixtures.partyId
private val storage = testKit.spawn(Database())
private val party = testKit.spawn(PartyService(storage))
- private val route = new UserEndpoint(party)(askTimeout, testKit.scheduler).route
+ private val route = new UserEndpoint(party, Fixtures.authProvider)(askTimeout, testKit.scheduler).route
override def beforeAll(): Unit = {
super.beforeAll()
@@ -49,16 +49,16 @@ class UserEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRoute
"create a party" in {
val uri = Uri(s"/party")
- val entity = UserResponse.fromUser(Fixtures.userAdmin).copy(password = Fixtures.userPassword)
+ val entity = UserModel.fromUser(Fixtures.userAdmin).copy(password = Fixtures.userPassword)
Put(uri, entity) ~> route ~> check {
status shouldEqual StatusCodes.OK
- partyId = responseAs[PartyIdResponse].partyId
+ partyId = responseAs[PartyIdModel].partyId
}
}
"add user" in {
- val entity = UserResponse.fromUser(Fixtures.userGet).copy(partyId = partyId, password = Fixtures.userPassword2)
+ val entity = UserModel.fromUser(Fixtures.userGet).copy(partyId = partyId, password = Fixtures.userPassword2)
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.Accepted
@@ -73,14 +73,24 @@ class UserEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRoute
Get(endpoint).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.OK
- val users = responseAs[Seq[UserResponse]]
+ val users = responseAs[Seq[UserModel]]
users.map(_.partyId).distinct shouldEqual Seq(partyId)
users.map(user => user.username -> user.permission).toMap shouldEqual party
}
}
+ "get current user" in {
+ Get(Uri(s"${endpoint.path}/current")).withHeaders(auth) ~> route ~> check {
+ status shouldEqual StatusCodes.OK
+
+ val user = responseAs[UserModel]
+ user.partyId shouldEqual Fixtures.partyId
+ user.username shouldEqual Fixtures.userAdmin.username
+ }
+ }
+
"remove user" in {
- val entity = UserResponse.fromUser(Fixtures.userGet).copy(partyId = partyId)
+ val entity = UserModel.fromUser(Fixtures.userGet).copy(partyId = partyId)
Delete(endpoint.toString + s"/${entity.username}").withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.Accepted
@@ -92,7 +102,7 @@ class UserEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRoute
Get(endpoint).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.OK
- val users = responseAs[Seq[UserResponse]]
+ val users = responseAs[Seq[UserModel]]
users.map(_.partyId).distinct shouldEqual Seq(partyId)
users.map(user => user.username -> user.permission).toMap shouldEqual party
}
diff --git a/src/test/scala/me/arcanis/ffxivbis/service/LootSelectorTest.scala b/src/test/scala/me/arcanis/ffxivbis/service/LootSelectorTest.scala
index 41e1aa1..fdeca76 100644
--- a/src/test/scala/me/arcanis/ffxivbis/service/LootSelectorTest.scala
+++ b/src/test/scala/me/arcanis/ffxivbis/service/LootSelectorTest.scala
@@ -3,9 +3,9 @@ package me.arcanis.ffxivbis.service
import akka.actor.testkit.typed.scaladsl.ActorTestKit
import akka.actor.typed.scaladsl.AskPattern.Askable
import me.arcanis.ffxivbis.messages.DownloadBiS
-import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.models._
import me.arcanis.ffxivbis.service.bis.BisProvider
+import me.arcanis.ffxivbis.{Fixtures, Settings}
import org.scalatest.BeforeAndAfterAll
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
diff --git a/src/test/scala/me/arcanis/ffxivbis/service/bis/BisProviderTest.scala b/src/test/scala/me/arcanis/ffxivbis/service/bis/BisProviderTest.scala
index 654c034..6e25354 100644
--- a/src/test/scala/me/arcanis/ffxivbis/service/bis/BisProviderTest.scala
+++ b/src/test/scala/me/arcanis/ffxivbis/service/bis/BisProviderTest.scala
@@ -2,8 +2,8 @@ package me.arcanis.ffxivbis.service.bis
import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit
import me.arcanis.ffxivbis.messages.DownloadBiS
-import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.models._
+import me.arcanis.ffxivbis.{Fixtures, Settings}
import org.scalatest.wordspec.AnyWordSpecLike
import scala.concurrent.duration._
diff --git a/src/test/scala/me/arcanis/ffxivbis/utils/Converters.scala b/src/test/scala/me/arcanis/ffxivbis/utils/Converters.scala
index 1747252..41ede69 100644
--- a/src/test/scala/me/arcanis/ffxivbis/utils/Converters.scala
+++ b/src/test/scala/me/arcanis/ffxivbis/utils/Converters.scala
@@ -1,9 +1,8 @@
package me.arcanis.ffxivbis.utils
-import java.time.Instant
-
import me.arcanis.ffxivbis.models.{Loot, Piece}
+import java.time.Instant
import scala.language.implicitConversions
object Converters {