diff --git a/libraries.sbt b/libraries.sbt
index 1e87d66..d1d2463 100644
--- a/libraries.sbt
+++ b/libraries.sbt
@@ -1,26 +1,26 @@
-val AkkaVersion = "2.6.18"
-val AkkaHttpVersion = "10.2.7"
-val ScalaTestVersion = "3.2.10"
+val AkkaVersion = "2.6.19"
+val AkkaHttpVersion = "10.2.9"
+val ScalaTestVersion = "3.2.12"
val SlickVersion = "3.3.3"
-libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.10"
-libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.4"
+libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.11"
+libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.5"
libraryDependencies += "com.typesafe.akka" %% "akka-http" % AkkaHttpVersion
libraryDependencies += "com.typesafe.akka" %% "akka-http-spray-json" % AkkaHttpVersion
libraryDependencies += "com.typesafe.akka" %% "akka-actor-typed" % AkkaVersion
libraryDependencies += "com.typesafe.akka" %% "akka-stream" % AkkaVersion
-libraryDependencies += "com.github.swagger-akka-http" %% "swagger-akka-http" % "2.6.0"
+libraryDependencies += "com.github.swagger-akka-http" %% "swagger-akka-http" % "2.7.0"
libraryDependencies += "jakarta.platform" % "jakarta.jakartaee-web-api" % "9.1.0"
-libraryDependencies += "ch.megard" %% "akka-http-cors" % "1.1.2"
+libraryDependencies += "ch.megard" %% "akka-http-cors" % "1.1.3"
libraryDependencies += "io.spray" %% "spray-json" % "1.3.6"
libraryDependencies += "org.playframework.anorm" %% "anorm" % "2.6.10"
libraryDependencies += "com.zaxxer" % "HikariCP" % "5.0.1" exclude("org.slf4j", "slf4j-api")
-libraryDependencies += "org.flywaydb" % "flyway-core" % "8.4.1"
+libraryDependencies += "org.flywaydb" % "flyway-core" % "8.5.12"
libraryDependencies += "org.xerial" % "sqlite-jdbc" % "3.36.0.3"
-libraryDependencies += "org.postgresql" % "postgresql" % "42.3.1"
+libraryDependencies += "org.postgresql" % "postgresql" % "42.3.6"
libraryDependencies += "org.mindrot" % "jbcrypt" % "0.4"
libraryDependencies += "com.google.guava" % "guava" % "31.0.1-jre"
diff --git a/src/main/resources/db/migration/postgresql/V8_0__Remove_special_characters.sql b/src/main/resources/db/migration/postgresql/V8_0__Remove_special_characters.sql
new file mode 100644
index 0000000..6535f12
--- /dev/null
+++ b/src/main/resources/db/migration/postgresql/V8_0__Remove_special_characters.sql
@@ -0,0 +1,3 @@
+update parties set party_alias = regexp_replace(party_alias, '[^A-Za-z0-9!@#$%^&*()\-_=+;:'',./? ]', '', 'g');
+update players set nick = regexp_replace(nick, '[^A-Za-z0-9!@#$%^&*()\-_=+;:'',./? ]', '', 'g');
+update users set username = regexp_replace(username, '[^A-Za-z0-9!@#$%^&*()\-_=+;:'',./? ]', '', 'g');
\ No newline at end of file
diff --git a/src/main/resources/html/bis.html b/src/main/resources/html/bis.html
index 1013e71..976f414 100644
--- a/src/main/resources/html/bis.html
+++ b/src/main/resources/html/bis.html
@@ -9,9 +9,9 @@
-
+
-
+
@@ -157,11 +157,11 @@
-
+
-
+
-
+
diff --git a/src/main/resources/html/index.html b/src/main/resources/html/index.html
index 9653836..14664bd 100644
--- a/src/main/resources/html/index.html
+++ b/src/main/resources/html/index.html
@@ -87,6 +87,7 @@
+
-
+
-
+
-
+
diff --git a/src/main/resources/html/party.html b/src/main/resources/html/party.html
index a5cbecd..0c5719f 100644
--- a/src/main/resources/html/party.html
+++ b/src/main/resources/html/party.html
@@ -9,9 +9,9 @@
-
+
-
+
@@ -147,11 +147,11 @@
-
+
-
+
-
+
diff --git a/src/main/resources/html/users.html b/src/main/resources/html/users.html
index ac930f8..2f2c33c 100644
--- a/src/main/resources/html/users.html
+++ b/src/main/resources/html/users.html
@@ -9,9 +9,9 @@
-
+
-
+
@@ -141,11 +141,11 @@
-
+
-
+
-
+
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/ValidatorHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/ValidatorHelper.scala
new file mode 100644
index 0000000..d9d4f5f
--- /dev/null
+++ b/src/main/scala/me/arcanis/ffxivbis/http/ValidatorHelper.scala
@@ -0,0 +1,16 @@
+package me.arcanis.ffxivbis.http
+
+import scala.collection.immutable.HashSet
+
+trait ValidatorHelper {
+
+ def isValidString(string: String): Boolean = string.nonEmpty && string.forall(isValidSymbol)
+
+ def isValidSymbol(char: Char): Boolean =
+ char.isLetterOrDigit || ValidatorHelper.VALID_CHARACTERS.contains(char)
+}
+
+object ValidatorHelper {
+
+ final val VALID_CHARACTERS = HashSet.from("!@#$%^&*()-_=+;:',./? ")
+}
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 e2f6f81..a725cc9 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
@@ -49,7 +49,12 @@ class BiSEndpoint(
summary = "create best in slot",
description = "Create the best in slot set",
parameters = Array(
- new Parameter(name = "partyId", in = ParameterIn.PATH, description = "unique party ID", example = "abcdefgh"),
+ new Parameter(
+ name = "partyId",
+ in = ParameterIn.PATH,
+ description = "unique party ID",
+ example = "o3KicHQPW5b0JcOm5yI3"
+ ),
),
requestBody = new RequestBody(
description = "player best in slot description",
@@ -105,7 +110,12 @@ class BiSEndpoint(
summary = "get best in slot",
description = "Return the best in slot items",
parameters = Array(
- new Parameter(name = "partyId", in = ParameterIn.PATH, description = "unique party ID", example = "abcdefgh"),
+ new Parameter(
+ name = "partyId",
+ in = ParameterIn.PATH,
+ description = "unique party ID",
+ example = "o3KicHQPW5b0JcOm5yI3"
+ ),
new Parameter(
name = "nick",
in = ParameterIn.QUERY,
@@ -167,7 +177,12 @@ class BiSEndpoint(
summary = "modify best in slot",
description = "Add or remove an item from the best in slot",
parameters = Array(
- new Parameter(name = "partyId", in = ParameterIn.PATH, description = "unique party ID", example = "abcdefgh"),
+ new Parameter(
+ name = "partyId",
+ in = ParameterIn.PATH,
+ description = "unique party ID",
+ example = "o3KicHQPW5b0JcOm5yI3"
+ ),
),
requestBody = new RequestBody(
description = "action and piece description",
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 c1bdbce..75cb5f5 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
@@ -46,7 +46,12 @@ class LootEndpoint(override val storage: ActorRef[Message], override val auth: A
summary = "get loot list",
description = "Return the looted items",
parameters = Array(
- new Parameter(name = "partyId", in = ParameterIn.PATH, description = "unique party ID", example = "abcdefgh"),
+ new Parameter(
+ name = "partyId",
+ in = ParameterIn.PATH,
+ description = "unique party ID",
+ example = "o3KicHQPW5b0JcOm5yI3"
+ ),
new Parameter(
name = "nick",
in = ParameterIn.QUERY,
@@ -107,7 +112,12 @@ class LootEndpoint(override val storage: ActorRef[Message], override val auth: A
summary = "modify loot list",
description = "Add or remove an item from the loot list",
parameters = Array(
- new Parameter(name = "partyId", in = ParameterIn.PATH, description = "unique party ID", example = "abcdefgh"),
+ new Parameter(
+ name = "partyId",
+ in = ParameterIn.PATH,
+ description = "unique party ID",
+ example = "o3KicHQPW5b0JcOm5yI3"
+ ),
),
requestBody = new RequestBody(
description = "action and piece description",
@@ -164,7 +174,12 @@ class LootEndpoint(override val storage: ActorRef[Message], override val auth: A
summary = "suggest loot",
description = "Suggest loot piece to party",
parameters = Array(
- new Parameter(name = "partyId", in = ParameterIn.PATH, description = "unique party ID", example = "abcdefgh"),
+ new Parameter(
+ name = "partyId",
+ in = ParameterIn.PATH,
+ description = "unique party ID",
+ example = "o3KicHQPW5b0JcOm5yI3"
+ ),
),
requestBody = new RequestBody(
description = "piece description",
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 6f7f86f..023c316 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
@@ -49,7 +49,12 @@ class PartyEndpoint(
summary = "get party description",
description = "Return the party description",
parameters = Array(
- new Parameter(name = "partyId", in = ParameterIn.PATH, description = "unique party ID", example = "abcdefgh"),
+ new Parameter(
+ name = "partyId",
+ in = ParameterIn.PATH,
+ description = "unique party ID",
+ example = "o3KicHQPW5b0JcOm5yI3"
+ ),
),
responses = Array(
new ApiResponse(
@@ -96,7 +101,12 @@ class PartyEndpoint(
summary = "modify party description",
description = "Edit party description",
parameters = Array(
- new Parameter(name = "partyId", in = ParameterIn.PATH, description = "unique party ID", example = "abcdefgh"),
+ new Parameter(
+ name = "partyId",
+ in = ParameterIn.PATH,
+ description = "unique party ID",
+ example = "o3KicHQPW5b0JcOm5yI3"
+ ),
),
requestBody = new RequestBody(
description = "new party description",
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 b2d37d0..5b9849e 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
@@ -50,7 +50,12 @@ class PlayerEndpoint(
summary = "get party",
description = "Return the players who belong to the party",
parameters = Array(
- new Parameter(name = "partyId", in = ParameterIn.PATH, description = "unique party ID", example = "abcdefgh"),
+ new Parameter(
+ name = "partyId",
+ in = ParameterIn.PATH,
+ description = "unique party ID",
+ example = "o3KicHQPW5b0JcOm5yI3"
+ ),
new Parameter(
name = "nick",
in = ParameterIn.QUERY,
@@ -111,7 +116,12 @@ class PlayerEndpoint(
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 = "partyId",
+ in = ParameterIn.PATH,
+ description = "unique party ID",
+ example = "o3KicHQPW5b0JcOm5yI3"
+ ),
new Parameter(
name = "nick",
in = ParameterIn.QUERY,
@@ -172,7 +182,12 @@ class PlayerEndpoint(
summary = "modify party",
description = "Add or remove a player from party list",
parameters = Array(
- new Parameter(name = "partyId", in = ParameterIn.PATH, description = "unique party ID", example = "abcdefgh"),
+ new Parameter(
+ name = "partyId",
+ in = ParameterIn.PATH,
+ description = "unique party ID",
+ example = "o3KicHQPW5b0JcOm5yI3"
+ ),
),
requestBody = new RequestBody(
description = "player description",
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 8b05666..6ce3011 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
@@ -96,7 +96,12 @@ class UserEndpoint(override val storage: ActorRef[Message], override val auth: A
summary = "create new user",
description = "Add an user to the specified party",
parameters = Array(
- new Parameter(name = "partyId", in = ParameterIn.PATH, description = "unique party ID", example = "abcdefgh"),
+ new Parameter(
+ name = "partyId",
+ in = ParameterIn.PATH,
+ description = "unique party ID",
+ example = "o3KicHQPW5b0JcOm5yI3"
+ ),
),
requestBody = new RequestBody(
description = "user description",
@@ -151,7 +156,12 @@ class UserEndpoint(override val storage: ActorRef[Message], override val auth: A
summary = "remove user",
description = "Remove an user from the specified party",
parameters = Array(
- new Parameter(name = "partyId", in = ParameterIn.PATH, description = "unique party ID", example = "abcdefgh"),
+ new Parameter(
+ name = "partyId",
+ in = ParameterIn.PATH,
+ description = "unique party ID",
+ example = "o3KicHQPW5b0JcOm5yI3"
+ ),
new Parameter(name = "username", in = ParameterIn.PATH, description = "username to remove", example = "siuan"),
),
responses = Array(
@@ -195,7 +205,12 @@ class UserEndpoint(override val storage: ActorRef[Message], override val auth: A
summary = "get users",
description = "Return the list of users belong to party",
parameters = Array(
- new Parameter(name = "partyId", in = ParameterIn.PATH, description = "unique party ID", example = "abcdefgh"),
+ new Parameter(
+ name = "partyId",
+ in = ParameterIn.PATH,
+ description = "unique party ID",
+ example = "o3KicHQPW5b0JcOm5yI3"
+ ),
),
responses = Array(
new ApiResponse(
@@ -246,7 +261,12 @@ class UserEndpoint(override val storage: ActorRef[Message], override val auth: A
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"),
+ new Parameter(
+ name = "partyId",
+ in = ParameterIn.PATH,
+ description = "unique party ID",
+ example = "o3KicHQPW5b0JcOm5yI3"
+ ),
),
responses = Array(
new ApiResponse(
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PartyDescriptionModel.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PartyDescriptionModel.scala
index bfb5b2b..26dd085 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PartyDescriptionModel.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PartyDescriptionModel.scala
@@ -12,9 +12,11 @@ import io.swagger.v3.oas.annotations.media.Schema
import me.arcanis.ffxivbis.models.PartyDescription
case class PartyDescriptionModel(
- @Schema(description = "party id", required = true, example = "abcdefgh") partyId: String,
+ @Schema(description = "party id", required = true, example = "o3KicHQPW5b0JcOm5yI3") partyId: String,
@Schema(description = "party name") partyAlias: Option[String]
-) {
+) extends Validator {
+
+ require(partyAlias.forall(isValidString), stringMatchError("Party alias"))
def toDescription: PartyDescription = PartyDescription(partyId, partyAlias)
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PartyIdModel.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PartyIdModel.scala
index 88fc028..7d5a435 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PartyIdModel.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PartyIdModel.scala
@@ -10,4 +10,6 @@ package me.arcanis.ffxivbis.http.api.v1.json
import io.swagger.v3.oas.annotations.media.Schema
-case class PartyIdModel(@Schema(description = "party id", required = true, example = "abcdefgh") partyId: String)
+case class PartyIdModel(
+ @Schema(description = "party id", required = true, example = "o3KicHQPW5b0JcOm5yI3") partyId: String
+)
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerBiSLinkModel.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerBiSLinkModel.scala
index 7dfc244..e62f366 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerBiSLinkModel.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerBiSLinkModel.scala
@@ -17,4 +17,7 @@ case class PlayerBiSLinkModel(
example = "https://ffxiv.ariyala.com/19V5R"
) link: String,
@Schema(description = "player description", required = true) playerId: PlayerIdModel
-)
+) extends Validator {
+
+ require(isValidString(link), stringMatchError("BiS link"))
+}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerIdModel.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerIdModel.scala
index efca7e6..8ee4b13 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerIdModel.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerIdModel.scala
@@ -12,10 +12,14 @@ import io.swagger.v3.oas.annotations.media.Schema
import me.arcanis.ffxivbis.models.{Job, PlayerId}
case class PlayerIdModel(
- @Schema(description = "unique party ID. Required in responses", example = "abcdefgh") partyId: Option[String],
+ @Schema(description = "unique party ID. Required in responses", example = "o3KicHQPW5b0JcOm5yI3") 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
-) {
+) extends Validator {
+
+ require(isValidString(nick), stringMatchError("Player name"))
def withPartyId(partyId: String): PlayerId =
PlayerId(partyId, Job.withName(job), nick)
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerIdWithCountersModel.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerIdWithCountersModel.scala
index df72789..1623ce7 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerIdWithCountersModel.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerIdWithCountersModel.scala
@@ -12,7 +12,7 @@ import io.swagger.v3.oas.annotations.media.Schema
import me.arcanis.ffxivbis.models.PlayerIdWithCounters
case class PlayerIdWithCountersModel(
- @Schema(description = "unique party ID", required = true, example = "abcdefgh") partyId: String,
+ @Schema(description = "unique party ID", required = true, example = "o3KicHQPW5b0JcOm5yI3") 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,
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerModel.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerModel.scala
index 515d17d..2d55972 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerModel.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PlayerModel.scala
@@ -12,7 +12,7 @@ import io.swagger.v3.oas.annotations.media.Schema
import me.arcanis.ffxivbis.models.{BiS, Job, Player}
case class PlayerModel(
- @Schema(description = "unique party ID", required = true, example = "abcdefgh") partyId: String,
+ @Schema(description = "unique party ID", required = true, example = "o3KicHQPW5b0JcOm5yI3") 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[PieceModel]],
@@ -24,7 +24,10 @@ case class PlayerModel(
`type` = "number"
) lootCountBiS: Option[Int],
@Schema(description = "total count of looted pieces", `type` = "number") lootCountTotal: Option[Int],
-) {
+) extends Validator {
+
+ require(isValidString(nick), stringMatchError("Player name"))
+ require(link.forall(isValidString), stringMatchError("BiS link"))
def toPlayer: Player =
Player(
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/UserModel.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/UserModel.scala
index 437d940..19306f2 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/UserModel.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/UserModel.scala
@@ -12,23 +12,30 @@ import io.swagger.v3.oas.annotations.media.Schema
import me.arcanis.ffxivbis.models.{Permission, User}
case class UserModel(
- @Schema(description = "unique party ID", required = true, example = "abcdefgh") partyId: String,
+ @Schema(description = "unique party ID", required = true, example = "o3KicHQPW5b0JcOm5yI3") 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 = "password to login to party, required for user editing", example = "pa55w0rd") password: Option[
+ String
+ ],
@Schema(
description = "user permission",
defaultValue = "get",
`type` = "string",
allowableValues = Array("get", "post", "admin")
) permission: Option[Permission.Value] = None
-) {
+) extends Validator {
+
+ require(isValidString(username), stringMatchError("Username"))
+ require(password.forall(_.nonEmpty), "Password must not be empty")
def toUser: User =
- User(partyId, username, password, permission.getOrElse(Permission.get))
+ password.fold(throw new IllegalArgumentException("Password must noot be empty"))(
+ User(partyId, username, _, permission.getOrElse(Permission.get))
+ )
}
object UserModel {
def fromUser(user: User): UserModel =
- UserModel(user.partyId, user.username, "", Some(user.permission))
+ UserModel(user.partyId, user.username, None, Some(user.permission))
}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/Validator.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/Validator.scala
new file mode 100644
index 0000000..fa68852
--- /dev/null
+++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/Validator.scala
@@ -0,0 +1,9 @@
+package me.arcanis.ffxivbis.http.api.v1.json
+
+import me.arcanis.ffxivbis.http.ValidatorHelper
+
+trait Validator extends ValidatorHelper {
+
+ def stringMatchError(what: String): String =
+ s"$what must contain only letters or digits or one of (${ValidatorHelper.VALID_CHARACTERS.mkString(", ")})"
+}
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/helpers/BiSHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/helpers/BiSHelper.scala
index 0cdcd1d..c4c2188 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/helpers/BiSHelper.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/helpers/BiSHelper.scala
@@ -45,13 +45,15 @@ trait BiSHelper extends BisProviderHelper {
timeout: Timeout,
scheduler: Scheduler
): Future[Unit] =
- storage.ask(RemovePiecesFromBiS(playerId, _)).flatMap { _ =>
- downloadBiS(link, playerId.job)
- .flatMap { bis =>
- Future.traverse(bis.pieces)(addPieceBiS(playerId, _))
- }
- .map(_ => ())
- }
+ storage
+ .ask(RemovePiecesFromBiS(playerId, _))
+ .flatMap { _ =>
+ downloadBiS(link, playerId.job)
+ .flatMap { bis =>
+ Future.traverse(bis.pieces)(addPieceBiS(playerId, _))
+ }
+ }
+ .flatMap(_ => storage.ask(UpdateBiSLink(playerId, link, _)))
def removePieceBiS(playerId: PlayerId, piece: Piece)(implicit timeout: Timeout, scheduler: Scheduler): Future[Unit] =
storage.ask(RemovePieceFromBiS(playerId, piece, _))
diff --git a/src/main/scala/me/arcanis/ffxivbis/http/helpers/PlayerHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/helpers/PlayerHelper.scala
index a19ad30..1047eae 100644
--- a/src/main/scala/me/arcanis/ffxivbis/http/helpers/PlayerHelper.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/http/helpers/PlayerHelper.scala
@@ -27,15 +27,15 @@ trait PlayerHelper extends BisProviderHelper {
)(implicit executionContext: ExecutionContext, timeout: Timeout, scheduler: Scheduler): Future[Unit] =
storage
.ask(ref => AddPlayer(player, ref))
- .map { res =>
+ .map { _ =>
player.link.map(_.trim).filter(_.nonEmpty) match {
case Some(link) =>
downloadBiS(link, player.job)
.map { bis =>
bis.pieces.map(piece => storage.ask(AddPieceToBis(player.playerId, piece, _)))
}
- .map(_ => res)
- case None => Future.successful(res)
+ .flatMap(_ => storage.ask(UpdateBiSLink(player.playerId, link, _)))
+ case None => Future.successful(())
}
}
.flatten
diff --git a/src/main/scala/me/arcanis/ffxivbis/messages/DatabaseMessage.scala b/src/main/scala/me/arcanis/ffxivbis/messages/DatabaseMessage.scala
index 33b5aeb..3f291c2 100644
--- a/src/main/scala/me/arcanis/ffxivbis/messages/DatabaseMessage.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/messages/DatabaseMessage.scala
@@ -95,6 +95,11 @@ object DatabaseMessage {
override val isReadOnly: Boolean = false
}
+ case class UpdateBiSLink(playerId: PlayerId, link: String, actorRef: ActorRef[Unit]) extends PartyDatabaseMessage {
+ override val partyId: String = playerId.partyId
+ override val isReadOnly: Boolean = false
+ }
+
case class UpdateParty(partyDescription: PartyDescription, replyTo: ActorRef[Unit]) extends PartyDatabaseMessage {
override val partyId: String = partyDescription.partyId
override val isReadOnly: Boolean = false
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 2e679bd..07afc0a 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
@@ -62,6 +62,10 @@ trait DatabasePartyHandler { this: Database =>
run(profile.deletePlayer(playerId))(_ => client ! ())
Behaviors.same
+ case UpdateBiSLink(playerId, link, client) =>
+ run(profile.updateBiSLink(playerId, link))(_ => client ! ())
+ Behaviors.same
+
case UpdateParty(description, client) =>
run(profile.insertPartyDescription(description))(_ => client ! ())
Behaviors.same
diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/BiSProfile.scala b/src/main/scala/me/arcanis/ffxivbis/storage/BiSProfile.scala
index 20c6676..7663c81 100644
--- a/src/main/scala/me/arcanis/ffxivbis/storage/BiSProfile.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/storage/BiSProfile.scala
@@ -53,12 +53,14 @@ trait BiSProfile extends DatabaseConnection {
def getPiecesBiSById(playerId: Long): Future[Seq[Loot]] = getPiecesBiSById(Seq(playerId))
def getPiecesBiSById(playerIds: Seq[Long]): Future[Seq[Loot]] =
- withConnection { implicit conn =>
- SQL("""select * from bis where player_id in ({player_ids})""")
- .on("player_ids" -> playerIds)
- .executeQuery()
- .as(loot.*)
- }
+ if (playerIds.isEmpty) Future.successful(Seq.empty)
+ else
+ withConnection { implicit conn =>
+ SQL("""select * from bis where player_id in ({player_ids})""")
+ .on("player_ids" -> playerIds)
+ .executeQuery()
+ .as(loot.*)
+ }
def insertPieceBiSById(piece: Piece)(playerId: Long): Future[Int] =
withConnection { implicit conn =>
diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/LootProfile.scala b/src/main/scala/me/arcanis/ffxivbis/storage/LootProfile.scala
index a873faa..4c8116d 100644
--- a/src/main/scala/me/arcanis/ffxivbis/storage/LootProfile.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/storage/LootProfile.scala
@@ -59,12 +59,14 @@ trait LootProfile extends DatabaseConnection {
def getPiecesById(playerId: Long): Future[Seq[Loot]] = getPiecesById(Seq(playerId))
def getPiecesById(playerIds: Seq[Long]): Future[Seq[Loot]] =
- withConnection { implicit conn =>
- SQL("""select * from loot where player_id in ({player_ids})""")
- .on("player_ids" -> playerIds)
- .executeQuery()
- .as(loot.*)
- }
+ if (playerIds.isEmpty) Future.successful(Seq.empty)
+ else
+ withConnection { implicit conn =>
+ SQL("""select * from loot where player_id in ({player_ids})""")
+ .on("player_ids" -> playerIds)
+ .executeQuery()
+ .as(loot.*)
+ }
def insertPieceById(loot: Loot)(playerId: Long): Future[Int] =
withConnection { implicit conn =>
diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/PlayersProfile.scala b/src/main/scala/me/arcanis/ffxivbis/storage/PlayersProfile.scala
index 4b0758c..4921fed 100644
--- a/src/main/scala/me/arcanis/ffxivbis/storage/PlayersProfile.scala
+++ b/src/main/scala/me/arcanis/ffxivbis/storage/PlayersProfile.scala
@@ -101,4 +101,18 @@ trait PlayersProfile extends DatabaseConnection {
.executeUpdate()
}
+ def updateBiSLink(playerId: PlayerId, link: String): Future[Int] =
+ withConnection { implicit conn =>
+ SQL("""update players
+ | set bis_link = {link}
+ | where party_id = {party_id} and nick = {nick} and job = {job}""".stripMargin)
+ .on(
+ "link" -> link,
+ "party_id" -> playerId.partyId,
+ "nick" -> playerId.nick,
+ "job" -> playerId.job.toString
+ )
+ .executeUpdate()
+ }
+
}
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 b00c6e1..2fe7ae5 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
@@ -48,7 +48,7 @@ class UserEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRoute
"create a party" in {
val uri = Uri(s"/party")
- val entity = UserModel.fromUser(Fixtures.userAdmin).copy(password = Fixtures.userPassword)
+ val entity = UserModel.fromUser(Fixtures.userAdmin).copy(password = Some(Fixtures.userPassword))
Post(uri, entity) ~> route ~> check {
status shouldEqual StatusCodes.OK
@@ -57,7 +57,7 @@ class UserEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRoute
}
"add user" in {
- val entity = UserModel.fromUser(Fixtures.userGet).copy(partyId = partyId, password = Fixtures.userPassword2)
+ val entity = UserModel.fromUser(Fixtures.userGet).copy(partyId = partyId, password = Some(Fixtures.userPassword2))
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.Accepted
diff --git a/src/test/scala/me/arcanis/ffxivbis/service/database/DatabasePartyHandlerTest.scala b/src/test/scala/me/arcanis/ffxivbis/service/database/DatabasePartyHandlerTest.scala
index aac8d1f..9fdf97e 100644
--- a/src/test/scala/me/arcanis/ffxivbis/service/database/DatabasePartyHandlerTest.scala
+++ b/src/test/scala/me/arcanis/ffxivbis/service/database/DatabasePartyHandlerTest.scala
@@ -66,6 +66,18 @@ class DatabasePartyHandlerTest extends ScalaTestWithActorTestKit(Settings.withRa
Compare.seqEquals(party.getPlayers, Seq(newPlayer)) shouldEqual true
}
+ "update bis link" in {
+ val updateProbe = testKit.createTestProbe[Unit]()
+ val newPlayer = Fixtures.playerEmpty.copy(priority = 2, link = Some("link"))
+
+ database ! UpdateBiSLink(Fixtures.playerEmpty.playerId, "link", updateProbe.ref)
+ updateProbe.expectMessage(askTimeout, ())
+
+ val probe = testKit.createTestProbe[Option[Player]]()
+ database ! GetPlayer(Fixtures.playerEmpty.playerId, probe.ref)
+ probe.expectMessage(askTimeout, Some(newPlayer))
+ }
+
"remove player" in {
val updateProbe = testKit.createTestProbe[Unit]()
database ! RemovePlayer(Fixtures.playerEmpty.playerId, updateProbe.ref)