diff --git a/src/main/resources/db/migration/postgresql/V3_0__Party_aliases.sql b/src/main/resources/db/migration/postgresql/V3_0__Party_aliases.sql new file mode 100644 index 0000000..6b34246 --- /dev/null +++ b/src/main/resources/db/migration/postgresql/V3_0__Party_aliases.sql @@ -0,0 +1,5 @@ +create table parties ( + player_id bigserial unique, + party_name text not null, + party_alias text); +create unique index parties_party_name_idx on parties(party_name); \ No newline at end of file diff --git a/src/main/resources/db/migration/sqlite/V3_0__Party_aliases.sql b/src/main/resources/db/migration/sqlite/V3_0__Party_aliases.sql new file mode 100644 index 0000000..2606cba --- /dev/null +++ b/src/main/resources/db/migration/sqlite/V3_0__Party_aliases.sql @@ -0,0 +1,5 @@ +create table parties ( + player_id integer primary key autoincrement, + party_name text not null, + party_alias text); +create unique index parties_party_name_idx on parties(party_name); \ No newline at end of file diff --git a/src/main/scala/me/arcanis/ffxivbis/http/AriyalaHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/AriyalaHelper.scala index 2304f48..38dd0b6 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/AriyalaHelper.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/AriyalaHelper.scala @@ -16,7 +16,9 @@ import me.arcanis.ffxivbis.service.Ariyala import scala.concurrent.{ExecutionContext, Future} -class AriyalaHelper(ariyala: ActorRef) { +trait AriyalaHelper { + + def ariyala: ActorRef def downloadBiS(link: String, job: Job.Job) (implicit executionContext: ExecutionContext, timeout: Timeout): Future[BiS] = diff --git a/src/main/scala/me/arcanis/ffxivbis/http/BiSHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/BiSHelper.scala index 0bc9d96..2fdfb57 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/BiSHelper.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/BiSHelper.scala @@ -17,7 +17,9 @@ import me.arcanis.ffxivbis.service.impl.DatabaseBiSHandler import scala.concurrent.{ExecutionContext, Future} -class BiSHelper(storage: ActorRef, ariyala: ActorRef) extends AriyalaHelper(ariyala) { +trait BiSHelper extends AriyalaHelper { + + def storage: ActorRef def addPieceBiS(playerId: PlayerId, piece: Piece) (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = diff --git a/src/main/scala/me/arcanis/ffxivbis/http/LootHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/LootHelper.scala index 5a1a9c0..43cb31c 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/LootHelper.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/LootHelper.scala @@ -18,7 +18,9 @@ import me.arcanis.ffxivbis.service.impl.DatabaseLootHandler import scala.concurrent.{ExecutionContext, Future} -class LootHelper(storage: ActorRef) { +trait LootHelper { + + def storage: ActorRef def addPieceLoot(playerId: PlayerId, piece: Piece) (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = diff --git a/src/main/scala/me/arcanis/ffxivbis/http/PlayerHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/PlayerHelper.scala index 813ba7a..ba6c1c9 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/PlayerHelper.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/PlayerHelper.scala @@ -12,12 +12,14 @@ import akka.actor.ActorRef import akka.pattern.ask import akka.util.Timeout import me.arcanis.ffxivbis.http.api.v1.json.ApiAction -import me.arcanis.ffxivbis.models.{Party, Player, PlayerId} +import me.arcanis.ffxivbis.models.{Party, PartyDescription, Player, PlayerId} import me.arcanis.ffxivbis.service.impl.{DatabaseBiSHandler, DatabasePartyHandler} import scala.concurrent.{ExecutionContext, Future} -class PlayerHelper(storage: ActorRef, ariyala: ActorRef) extends AriyalaHelper(ariyala) { +trait PlayerHelper extends AriyalaHelper { + + def storage: ActorRef def addPlayer(player: Player) (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = @@ -38,6 +40,10 @@ class PlayerHelper(storage: ActorRef, ariyala: ActorRef) extends AriyalaHelper(a case ApiAction.remove => removePlayer(player.playerId) } + def getPartyDescription(partyId: String) + (implicit executionContext: ExecutionContext, timeout: Timeout): Future[PartyDescription] = + (storage ? DatabasePartyHandler.GetPartyDescription(partyId)).mapTo[PartyDescription] + def getPlayers(partyId: String, maybePlayerId: Option[PlayerId]) (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[Player]] = maybePlayerId match { @@ -50,4 +56,8 @@ class PlayerHelper(storage: ActorRef, ariyala: ActorRef) extends AriyalaHelper(a def removePlayer(playerId: PlayerId) (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = (storage ? DatabasePartyHandler.RemovePlayer(playerId)).mapTo[Int] + + def updateDescription(partyDescription: PartyDescription) + (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = + (storage ? DatabasePartyHandler.UpdateParty(partyDescription)).mapTo[Int] } diff --git a/src/main/scala/me/arcanis/ffxivbis/http/Swagger.scala b/src/main/scala/me/arcanis/ffxivbis/http/Swagger.scala index 1ad7581..f3ed15d 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/Swagger.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/Swagger.scala @@ -18,8 +18,8 @@ import scala.io.Source class Swagger(config: Config) extends SwaggerHttpService { override val apiClasses: Set[Class[_]] = Set( classOf[api.v1.BiSEndpoint], classOf[api.v1.LootEndpoint], - classOf[api.v1.PlayerEndpoint], classOf[api.v1.TypesEndpoint], - classOf[api.v1.UserEndpoint] + classOf[api.v1.PartyEndpoint], classOf[api.v1.PlayerEndpoint], + classOf[api.v1.TypesEndpoint], classOf[api.v1.UserEndpoint] ) override val info: Info = Info( diff --git a/src/main/scala/me/arcanis/ffxivbis/http/UserHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/UserHelper.scala index d2e38af..98aa939 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/UserHelper.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/UserHelper.scala @@ -17,7 +17,9 @@ import me.arcanis.ffxivbis.service.impl.DatabaseUserHandler import scala.concurrent.{ExecutionContext, Future} -class UserHelper(storage: ActorRef) { +trait UserHelper { + + def storage: ActorRef def addUser(user: User, isHashedPassword: Boolean) (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = 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 6bb4dc4..a95cd9d 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 @@ -27,8 +27,8 @@ import me.arcanis.ffxivbis.models.PlayerId import scala.util.{Failure, Success} @Path("api/v1") -class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout) - extends BiSHelper(storage, ariyala) with Authorization with JsonSupport { +class BiSEndpoint(override val storage: ActorRef, override val ariyala: ActorRef)(implicit timeout: Timeout) + extends BiSHelper with Authorization with JsonSupport { def route: Route = createBiS ~ getBiS ~ modifyBiS 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 95f1935..66e5ef7 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 @@ -28,7 +28,7 @@ import scala.util.{Failure, Success} @Path("api/v1") class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) - extends LootHelper(storage) with Authorization with JsonSupport with HttpHandler { + extends LootHelper with Authorization with JsonSupport with HttpHandler { def route: Route = getLoot ~ modifyLoot 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 new file mode 100644 index 0000000..1fd348f --- /dev/null +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/PartyEndpoint.scala @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2020 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 + +import akka.actor.ActorRef +import akka.http.scaladsl.model.{HttpEntity, StatusCodes} +import akka.http.scaladsl.server.Directives._ +import akka.http.scaladsl.server._ +import akka.util.Timeout +import io.swagger.v3.oas.annotations.enums.ParameterIn +import io.swagger.v3.oas.annotations.media.{Content, Schema} +import io.swagger.v3.oas.annotations.parameters.RequestBody +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.security.SecurityRequirement +import io.swagger.v3.oas.annotations.{Operation, Parameter} +import javax.ws.rs._ +import me.arcanis.ffxivbis.http.api.v1.json._ +import me.arcanis.ffxivbis.http.{Authorization, PlayerHelper} + +import scala.util.{Failure, Success} + +@Path("api/v1") +class PartyEndpoint(override val storage: ActorRef, override val ariyala: ActorRef)(implicit timeout: Timeout) + extends PlayerHelper with Authorization with JsonSupport with HttpHandler { + + def route: Route = getPartyDescription ~ modifyPartyDescription + + @GET + @Path("party/{partyId}/description") + @Produces(value = Array("application/json")) + @Operation(summary = "get party description", description = "Return the party description", + parameters = Array( + new Parameter(name = "partyId", in = ParameterIn.PATH, description = "unique party ID", example = "abcdefgh"), + ), + responses = Array( + new ApiResponse(responseCode = "200", description = "Party description", + content = Array(new Content(schema = new Schema(implementation = classOf[PartyDescriptionResponse])))), + new ApiResponse(responseCode = "401", description = "Supplied authorization is invalid", + content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))), + new ApiResponse(responseCode = "403", description = "Access is forbidden", + content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))), + new ApiResponse(responseCode = "500", description = "Internal server error", + content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))), + ), + security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("get"))), + tags = Array("party"), + ) + def getPartyDescription: Route = + path("party" / Segment / "description") { partyId => + 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 + } + } + } + } + } + + @POST + @Consumes(value = Array("application/json")) + @Path("party/{partyId}/description") + @Operation(summary = "modify party description", description = "Edit party description", + parameters = Array( + new Parameter(name = "partyId", in = ParameterIn.PATH, description = "unique party ID", example = "abcdefgh"), + ), + requestBody = new RequestBody(description = "new party description", required = true, + content = Array(new Content(schema = new Schema(implementation = classOf[PartyDescriptionResponse])))), + 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])))), + new ApiResponse(responseCode = "401", description = "Supplied authorization is invalid", + content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))), + new ApiResponse(responseCode = "403", description = "Access is forbidden", + content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))), + new ApiResponse(responseCode = "500", description = "Internal server error", + content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))), + ), + security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("post"))), + tags = Array("party"), + ) + def modifyPartyDescription: Route = + path("party" / Segment / "description") { partyId => + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => + post { + entity(as[PartyDescriptionResponse]) { partyDescription => + val description = partyDescription.copy(partyId = partyId) + onComplete(updateDescription(description.toDescription)) { + case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty) + case Failure(exception) => throw exception + } + } + } + } + } + } +} 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 26ae757..198af96 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 @@ -27,8 +27,8 @@ import me.arcanis.ffxivbis.models.PlayerId import scala.util.{Failure, Success} @Path("api/v1") -class PlayerEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout) - extends PlayerHelper(storage, ariyala) with Authorization with JsonSupport with HttpHandler { +class PlayerEndpoint(override val storage: ActorRef, override val ariyala: ActorRef)(implicit timeout: Timeout) + extends PlayerHelper with Authorization with JsonSupport with HttpHandler { def route: Route = getParty ~ modifyParty 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 7259d7c..26cd0d5 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 @@ -21,6 +21,7 @@ class RootApiV1Endpoint(storage: ActorRef, ariyala: ActorRef, config: Config) private val biSEndpoint = new BiSEndpoint(storage, ariyala) private val lootEndpoint = new LootEndpoint(storage) + private val partyEndpoint = new PartyEndpoint(storage, ariyala) private val playerEndpoint = new PlayerEndpoint(storage, ariyala) private val typesEndpoint = new TypesEndpoint(config) private val userEndpoint = new UserEndpoint(storage) @@ -28,8 +29,8 @@ class RootApiV1Endpoint(storage: ActorRef, ariyala: ActorRef, config: Config) def route: Route = handleExceptions(exceptionHandler) { handleRejections(rejectionHandler) { - biSEndpoint.route ~ lootEndpoint.route ~ playerEndpoint.route ~ - typesEndpoint.route ~ userEndpoint.route + biSEndpoint.route ~ lootEndpoint.route ~ partyEndpoint.route ~ + playerEndpoint.route ~ typesEndpoint.route ~ userEndpoint.route } } } 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 3df05a4..9b33c30 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 @@ -28,7 +28,7 @@ import scala.util.{Failure, Success} @Path("api/v1") class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) - extends UserHelper(storage) with Authorization with JsonSupport { + extends UserHelper with Authorization with JsonSupport { def route: Route = createParty ~ createUser ~ deleteUser ~ getUsers 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 92e6d90..27fd75f 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 @@ -42,6 +42,7 @@ trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol { implicit val partyIdFormat: RootJsonFormat[PartyIdResponse] = jsonFormat1(PartyIdResponse.apply) implicit val pieceFormat: RootJsonFormat[PieceResponse] = jsonFormat3(PieceResponse.apply) implicit val lootFormat: RootJsonFormat[LootResponse] = jsonFormat2(LootResponse.apply) + implicit val partyDescriptionFormat: RootJsonFormat[PartyDescriptionResponse] = jsonFormat2(PartyDescriptionResponse.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) 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/PartyDescriptionResponse.scala new file mode 100644 index 0000000..ba585c1 --- /dev/null +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/PartyDescriptionResponse.scala @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020 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 io.swagger.v3.oas.annotations.media.Schema +import me.arcanis.ffxivbis.models.PartyDescription + +case class PartyDescriptionResponse( + @Schema(description = "party id", required = true) partyId: String, + @Schema(description = "party name") partyAlias: Option[String]) { + def toDescription: PartyDescription = PartyDescription(partyId, partyAlias) +} + +object PartyDescriptionResponse { + def fromDescription(description: PartyDescription): PartyDescriptionResponse = + PartyDescriptionResponse(description.partyId, description.partyAlias) +} diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/BasePartyView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/BasePartyView.scala index e241c3c..810e2aa 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/view/BasePartyView.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/BasePartyView.scala @@ -13,10 +13,12 @@ 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 +import me.arcanis.ffxivbis.http.{Authorization, PlayerHelper} -class BasePartyView(override val storage: ActorRef)(implicit timeout: Timeout) - extends Authorization { +import scala.util.{Failure, Success} + +class BasePartyView(override val storage: ActorRef, override val ariyala: ActorRef)(implicit timeout: Timeout) + extends PlayerHelper with Authorization { def route: Route = getIndex @@ -25,8 +27,10 @@ class BasePartyView(override val storage: ActorRef)(implicit timeout: Timeout) extractExecutionContext { implicit executionContext => authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => get { - complete { - (StatusCodes.OK, RootView.toHtml(BasePartyView.template(partyId))) + onComplete(getPartyDescription(partyId)) { + case Success(description) => + complete(StatusCodes.OK, RootView.toHtml(BasePartyView.template(partyId, description.alias))) + case Failure(exception) => throw exception } } } @@ -42,16 +46,16 @@ object BasePartyView { def root(partyId: String): Text.TypedTag[String] = a(href:=s"/party/$partyId", title:="root")("root") - def template(partyId: String): String = + def template(partyId: String, alias: String): String = "" + html(lang:="en", head( - titleTag(s"Party $partyId"), + titleTag(s"Party $alias"), link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css") ), body( - h2(s"Party $partyId"), + 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")), diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/BiSView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/BiSView.scala index 2ddec08..12610f8 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/view/BiSView.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/BiSView.scala @@ -19,8 +19,8 @@ import me.arcanis.ffxivbis.models.{Piece, Player, PlayerId} import scala.concurrent.{ExecutionContext, Future} import scala.util.Try -class BiSView(override val storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout) - extends BiSHelper(storage, ariyala) with Authorization { +class BiSView(override val storage: ActorRef, override val ariyala: ActorRef)(implicit timeout: Timeout) + extends BiSHelper with Authorization { def route: Route = getBiS ~ modifyBiS diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/IndexView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/IndexView.scala index 03fcdd5..4997cef 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/view/IndexView.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/IndexView.scala @@ -13,13 +13,14 @@ import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server._ import akka.util.Timeout -import me.arcanis.ffxivbis.http.UserHelper -import me.arcanis.ffxivbis.models.{Permission, User} +import me.arcanis.ffxivbis.http.{PlayerHelper, UserHelper} +import me.arcanis.ffxivbis.models.{PartyDescription, Permission, User} +import scala.concurrent.Future import scala.util.{Failure, Success} -class IndexView(storage: ActorRef)(implicit timeout: Timeout) - extends UserHelper(storage) { +class IndexView(override val storage: ActorRef, override val ariyala: ActorRef)(implicit timeout: Timeout) + extends PlayerHelper with UserHelper { def route: Route = createParty ~ getIndex @@ -27,13 +28,17 @@ class IndexView(storage: ActorRef)(implicit timeout: Timeout) path("party") { extractExecutionContext { implicit executionContext => post { - formFields("username".as[String], "password".as[String]) { (username, password) => - onComplete(newPartyId) { - case Success(partyId) => + 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) - onComplete(addUser(user, isHashedPassword = false)) { - case _ => redirect(s"/party/$partyId", StatusCodes.Found) + 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 } } @@ -67,6 +72,7 @@ object IndexView { 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") diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/LootSuggestView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/LootSuggestView.scala index 8891f85..331ffa2 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/view/LootSuggestView.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/LootSuggestView.scala @@ -20,7 +20,7 @@ import scala.concurrent.{ExecutionContext, Future} import scala.util.{Failure, Success, Try} class LootSuggestView(override val storage: ActorRef)(implicit timeout: Timeout) - extends LootHelper(storage) with Authorization { + extends LootHelper with Authorization { def route: Route = getIndex ~ suggestLoot diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/LootView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/LootView.scala index 4ab84b8..0ed00a3 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/view/LootView.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/LootView.scala @@ -20,7 +20,7 @@ import scala.concurrent.{ExecutionContext, Future} import scala.util.Try class LootView (override val storage: ActorRef)(implicit timeout: Timeout) - extends LootHelper(storage) with Authorization { + extends LootHelper with Authorization { def route: Route = getLoot ~ modifyLoot diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/PlayerView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/PlayerView.scala index c47234f..ebc1bb0 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/view/PlayerView.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/PlayerView.scala @@ -18,8 +18,8 @@ import me.arcanis.ffxivbis.models._ import scala.concurrent.{ExecutionContext, Future} -class PlayerView(override val storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout) - extends PlayerHelper(storage, ariyala) with Authorization { +class PlayerView(override val storage: ActorRef, override val ariyala: ActorRef)(implicit timeout: Timeout) + extends PlayerHelper with Authorization { def route: Route = getParty ~ modifyParty 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 7bab573..a631f38 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/view/RootView.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/RootView.scala @@ -16,8 +16,8 @@ import akka.util.Timeout class RootView(storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout) { - private val basePartyView = new BasePartyView(storage) - private val indexView = new IndexView(storage) + private val basePartyView = new BasePartyView(storage, ariyala) + private val indexView = new IndexView(storage, ariyala) private val biSView = new BiSView(storage, ariyala) private val lootView = new LootView(storage) diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/UserView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/UserView.scala index fbb2188..98bdbea 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/view/UserView.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/UserView.scala @@ -20,7 +20,7 @@ import scala.concurrent.{ExecutionContext, Future} import scala.util.Try class UserView(override val storage: ActorRef)(implicit timeout: Timeout) - extends UserHelper(storage) with Authorization { + extends UserHelper with Authorization { def route: Route = getUsers ~ modifyUsers diff --git a/src/main/scala/me/arcanis/ffxivbis/models/Party.scala b/src/main/scala/me/arcanis/ffxivbis/models/Party.scala index 104b106..81894f4 100644 --- a/src/main/scala/me/arcanis/ffxivbis/models/Party.scala +++ b/src/main/scala/me/arcanis/ffxivbis/models/Party.scala @@ -15,15 +15,15 @@ import me.arcanis.ffxivbis.service.LootSelector import scala.jdk.CollectionConverters._ import scala.util.Random -case class Party(partyId: String, rules: Seq[String], players: Map[PlayerId, Player]) +case class Party(partyDescription: PartyDescription, rules: Seq[String], players: Map[PlayerId, Player]) extends StrictLogging { - require(players.keys.forall(_.partyId == partyId), "party id must be same") + 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 == partyId, "player must belong to this party") + require(player.partyId == partyDescription.partyId, "player must belong to this party") copy(players = players + (player.playerId -> player)) } catch { case exception: Exception => @@ -36,10 +36,7 @@ case class Party(partyId: String, rules: Seq[String], players: Map[PlayerId, Pla } object Party { - def apply(partyId: Option[String], config: Config): Party = - new Party(partyId.getOrElse(randomPartyId), getRules(config), Map.empty) - - def apply(partyId: String, config: Config, + def apply(party: PartyDescription, config: Config, players: Map[Long, Player], bis: Seq[Loot], loot: Seq[Loot]): Party = { val bisByPlayer = bis.groupBy(_.playerId).view.mapValues(piece => BiS(piece.map(_.piece))) val lootByPlayer = loot.groupBy(_.playerId).view @@ -49,7 +46,7 @@ object Party { .withBiS(bisByPlayer.get(playerId)) .withLoot(lootByPlayer.getOrElse(playerId, Seq.empty))) } - Party(partyId, getRules(config), playersWithItems) + Party(party, getRules(config), playersWithItems) } def getRules(config: Config): Seq[String] = diff --git a/src/main/scala/me/arcanis/ffxivbis/models/PartyDescription.scala b/src/main/scala/me/arcanis/ffxivbis/models/PartyDescription.scala new file mode 100644 index 0000000..8279560 --- /dev/null +++ b/src/main/scala/me/arcanis/ffxivbis/models/PartyDescription.scala @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2020 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 + +case class PartyDescription(partyId: String, partyAlias: Option[String]) { + def alias: String = partyAlias.getOrElse(partyId) +} + +object PartyDescription { + def empty(partyId: String): PartyDescription = PartyDescription(partyId, None) +} \ No newline at end of file diff --git a/src/main/scala/me/arcanis/ffxivbis/service/Database.scala b/src/main/scala/me/arcanis/ffxivbis/service/Database.scala index 6033885..356cdab 100644 --- a/src/main/scala/me/arcanis/ffxivbis/service/Database.scala +++ b/src/main/scala/me/arcanis/ffxivbis/service/Database.scala @@ -32,10 +32,11 @@ trait Database extends Actor with StrictLogging { def getParty(partyId: String, withBiS: Boolean, withLoot: Boolean): Future[Party] = for { + partyDescription <- profile.getPartyDescription(partyId) players <- profile.getParty(partyId) bis <- if (withBiS) profile.getPiecesBiS(partyId) else Future(Seq.empty) loot <- if (withLoot) profile.getPieces(partyId) else Future(Seq.empty) - } yield Party(partyId, context.system.settings.config, players, bis, loot) + } yield Party(partyDescription, context.system.settings.config, players, bis, loot) } object Database { diff --git a/src/main/scala/me/arcanis/ffxivbis/service/impl/DatabasePartyHandler.scala b/src/main/scala/me/arcanis/ffxivbis/service/impl/DatabasePartyHandler.scala index 3537573..bc13fd6 100644 --- a/src/main/scala/me/arcanis/ffxivbis/service/impl/DatabasePartyHandler.scala +++ b/src/main/scala/me/arcanis/ffxivbis/service/impl/DatabasePartyHandler.scala @@ -9,7 +9,7 @@ package me.arcanis.ffxivbis.service.impl import akka.pattern.pipe -import me.arcanis.ffxivbis.models.{BiS, Player, PlayerId} +import me.arcanis.ffxivbis.models.{BiS, PartyDescription, Player, PlayerId} import me.arcanis.ffxivbis.service.Database import scala.concurrent.Future @@ -26,6 +26,10 @@ trait DatabasePartyHandler { this: Database => val client = sender() getParty(partyId, withBiS = true, withLoot = true).pipeTo(client) + case GetPartyDescription(partyId) => + val client = sender() + profile.getPartyDescription(partyId).pipeTo(client) + case GetPlayer(playerId) => val client = sender() val player = profile.getPlayerFull(playerId).flatMap { maybePlayerData => @@ -43,6 +47,10 @@ trait DatabasePartyHandler { this: Database => case RemovePlayer(playerId) => val client = sender() profile.deletePlayer(playerId).pipeTo(client) + + case UpdateParty(description) => + val client = sender() + profile.insertPartyDescription(description).pipeTo(client) } } @@ -51,10 +59,14 @@ object DatabasePartyHandler { override def partyId: String = player.partyId } case class GetParty(partyId: String) extends Database.DatabaseRequest + case class GetPartyDescription(partyId: String) extends Database.DatabaseRequest case class GetPlayer(playerId: PlayerId) extends Database.DatabaseRequest { override def partyId: String = playerId.partyId } case class RemovePlayer(playerId: PlayerId) extends Database.DatabaseRequest { override def partyId: String = playerId.partyId } + case class UpdateParty(partyDescription: PartyDescription) extends Database.DatabaseRequest { + override def partyId: String = partyDescription.partyId + } } diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/DatabaseProfile.scala b/src/main/scala/me/arcanis/ffxivbis/storage/DatabaseProfile.scala index 9468349..7823820 100644 --- a/src/main/scala/me/arcanis/ffxivbis/storage/DatabaseProfile.scala +++ b/src/main/scala/me/arcanis/ffxivbis/storage/DatabaseProfile.scala @@ -18,7 +18,7 @@ import slick.jdbc.JdbcProfile import scala.concurrent.{ExecutionContext, Future} class DatabaseProfile(context: ExecutionContext, config: Config) - extends BiSProfile with LootProfile with PlayersProfile with UsersProfile { + extends BiSProfile with LootProfile with PartyProfile with PlayersProfile with UsersProfile { implicit val executionContext: ExecutionContext = context @@ -29,6 +29,7 @@ class DatabaseProfile(context: ExecutionContext, config: Config) val bisTable: TableQuery[BiSPieces] = TableQuery[BiSPieces] val lootTable: TableQuery[LootPieces] = TableQuery[LootPieces] + val partiesTable: TableQuery[Parties] = TableQuery[Parties] val playersTable: TableQuery[Players] = TableQuery[Players] val usersTable: TableQuery[Users] = TableQuery[Users] @@ -55,10 +56,10 @@ class DatabaseProfile(context: ExecutionContext, config: Config) private def byPartyId[T](partyId: String, callback: Seq[Long] => Future[T]): Future[T] = getPlayers(partyId).map(callback).flatten private def byPlayerId[T](playerId: PlayerId, callback: Long => Future[T]): Future[T] = - getPlayer(playerId).map { + getPlayer(playerId).flatMap { case Some(id) => callback(id) case None => Future.failed(new Error(s"Could not find player $playerId")) - }.flatten + } } object DatabaseProfile { diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/PartyProfile.scala b/src/main/scala/me/arcanis/ffxivbis/storage/PartyProfile.scala new file mode 100644 index 0000000..327fca5 --- /dev/null +++ b/src/main/scala/me/arcanis/ffxivbis/storage/PartyProfile.scala @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020 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.storage + +import me.arcanis.ffxivbis.models.PartyDescription + +import scala.concurrent.Future + +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) + } + + class Parties(tag: Tag) extends Table[PartyRep](tag, "parties") { + def partyId: Rep[Long] = column[Long]("party_id", O.AutoInc, O.PrimaryKey) + def partyName: Rep[String] = column[String]("party_name") + def partyAlias: Rep[Option[String]] = column[Option[String]]("party_alias") + + def * = + (partyId.?, partyName, partyAlias) <> ((PartyRep.apply _).tupled, PartyRep.unapply) + } + + def getPartyDescription(partyId: String): Future[PartyDescription] = + 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)))) + case _ => db.run(partiesTable.insertOrUpdate(PartyRep.fromDescription(partyDescription, None))) + } + + + private def partyDescription(partyId: String) = + partiesTable.filter(_.partyName === partyId) +} \ No newline at end of file diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/PlayersProfile.scala b/src/main/scala/me/arcanis/ffxivbis/storage/PlayersProfile.scala index 46f5cbc..8951d32 100644 --- a/src/main/scala/me/arcanis/ffxivbis/storage/PlayersProfile.scala +++ b/src/main/scala/me/arcanis/ffxivbis/storage/PlayersProfile.scala @@ -39,7 +39,6 @@ trait PlayersProfile { this: DatabaseProfile => (partyId, playerId.?, created, nick, job, bisLink, priority) <> ((PlayerRep.apply _).tupled, PlayerRep.unapply) } - 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]) { @@ -53,10 +52,10 @@ trait PlayersProfile { this: DatabaseProfile => def getPlayers(partyId: String): Future[Seq[Long]] = db.run(players(partyId).map(_.playerId).result) def insertPlayer(playerObj: Player): Future[Int] = - getPlayer(playerObj.playerId).map { + getPlayer(playerObj.playerId).flatMap { case Some(id) => db.run(playersTable.update(PlayerRep.fromPlayer(playerObj, Some(id)))) case _ => db.run(playersTable.insertOrUpdate(PlayerRep.fromPlayer(playerObj, None))) - }.flatten + } private def player(playerId: PlayerId) = playersTable diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/UsersProfile.scala b/src/main/scala/me/arcanis/ffxivbis/storage/UsersProfile.scala index 2f8ef43..e57c1c8 100644 --- a/src/main/scala/me/arcanis/ffxivbis/storage/UsersProfile.scala +++ b/src/main/scala/me/arcanis/ffxivbis/storage/UsersProfile.scala @@ -49,10 +49,10 @@ trait UsersProfile { this: DatabaseProfile => 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).map { + 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)))) case _ => db.run(usersTable.insertOrUpdate(UserRep.fromUser(userObj, None))) - }.flatten + } private def user(partyId: String, username: Option[String]) = usersTable 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 c17d3af..121095c 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 @@ -5,12 +5,13 @@ import akka.http.scaladsl.model.{StatusCodes, Uri} import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials} import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest} import akka.http.scaladsl.server._ -import akka.pattern.ask import akka.testkit.TestKit +import akka.pattern.ask import com.typesafe.config.Config import me.arcanis.ffxivbis.{Fixtures, Settings} import me.arcanis.ffxivbis.http.api.v1.json._ -import me.arcanis.ffxivbis.service.{Ariyala, PartyService, impl} +import me.arcanis.ffxivbis.models.PartyDescription +import me.arcanis.ffxivbis.service.{Ariyala, impl} import me.arcanis.ffxivbis.storage.Migration import org.scalatest.{Matchers, WordSpec} @@ -23,22 +24,19 @@ class PartyEndpointTest extends WordSpec private val auth: Authorization = Authorization(BasicHttpCredentials(Fixtures.userAdmin.username, Fixtures.userPassword)) - private val endpoint: Uri = Uri(s"/party/${Fixtures.partyId}") - private val playerId = PlayerIdResponse.fromPlayerId(Fixtures.playerEmpty.playerId) + private val endpoint: Uri = Uri(s"/party/${Fixtures.partyId}/description") private val timeout: FiniteDuration = 60 seconds implicit private val routeTimeout: RouteTestTimeout = RouteTestTimeout(timeout) private val storage: ActorRef = system.actorOf(impl.DatabaseImpl.props) private val ariyala: ActorRef = system.actorOf(Ariyala.props) - private val party: ActorRef = system.actorOf(PartyService.props(storage)) - private val route: Route = new PlayerEndpoint(party, ariyala)(timeout).route + private val route: Route = new PartyEndpoint(storage, ariyala)(timeout).route override def testConfig: Config = Settings.withRandomDatabase override def beforeAll: Unit = { Await.result(Migration(system.settings.config), timeout) Await.result((storage ? impl.DatabaseUserHandler.AddUser(Fixtures.userAdmin, isHashedPassword = true))(timeout).mapTo[Int], timeout) - Await.result((storage ? impl.DatabasePartyHandler.AddPlayer(Fixtures.playerEmpty))(timeout).mapTo[Int], timeout) } override def afterAll: Unit = { @@ -48,13 +46,23 @@ class PartyEndpointTest extends WordSpec "api v1 party endpoint" must { - "get users" in { - val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job))) - val response = Seq(PlayerResponse.fromPlayer(Fixtures.playerEmpty)) + "get empty party description" in { + Get(endpoint).withHeaders(auth) ~> route ~> check { + status shouldEqual StatusCodes.OK + responseAs[PartyDescriptionResponse].toDescription shouldEqual PartyDescription.empty(Fixtures.partyId) + } + } + + "update party description" in { + val entity = PartyDescriptionResponse(Fixtures.partyId, Some("random party name")) + + Post(endpoint, entity).withHeaders(auth) ~> route ~> check { + status shouldEqual StatusCodes.Accepted + } Get(endpoint).withHeaders(auth) ~> route ~> check { status shouldEqual StatusCodes.OK - responseAs[Seq[PlayerResponse]] shouldEqual response + responseAs[PartyDescriptionResponse].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 new file mode 100644 index 0000000..f9021c6 --- /dev/null +++ b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpointTest.scala @@ -0,0 +1,62 @@ +package me.arcanis.ffxivbis.http.api.v1 + +import akka.actor.ActorRef +import akka.http.scaladsl.model.{StatusCodes, Uri} +import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials} +import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest} +import akka.http.scaladsl.server._ +import akka.pattern.ask +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.{Ariyala, PartyService, impl} +import me.arcanis.ffxivbis.storage.Migration +import org.scalatest.{Matchers, WordSpec} + +import scala.concurrent.Await +import scala.concurrent.duration._ +import scala.language.postfixOps + +class PlayerEndpointTest extends WordSpec + with Matchers with ScalatestRouteTest with JsonSupport { + + private val auth: Authorization = + Authorization(BasicHttpCredentials(Fixtures.userAdmin.username, Fixtures.userPassword)) + private val endpoint: Uri = Uri(s"/party/${Fixtures.partyId}") + private val playerId = PlayerIdResponse.fromPlayerId(Fixtures.playerEmpty.playerId) + private val timeout: FiniteDuration = 60 seconds + implicit private val routeTimeout: RouteTestTimeout = RouteTestTimeout(timeout) + + private val storage: ActorRef = system.actorOf(impl.DatabaseImpl.props) + private val ariyala: ActorRef = system.actorOf(Ariyala.props) + private val party: ActorRef = system.actorOf(PartyService.props(storage)) + private val route: Route = new PlayerEndpoint(party, ariyala)(timeout).route + + override def testConfig: Config = Settings.withRandomDatabase + + override def beforeAll: Unit = { + Await.result(Migration(system.settings.config), timeout) + Await.result((storage ? impl.DatabaseUserHandler.AddUser(Fixtures.userAdmin, isHashedPassword = true))(timeout).mapTo[Int], timeout) + Await.result((storage ? impl.DatabasePartyHandler.AddPlayer(Fixtures.playerEmpty))(timeout).mapTo[Int], timeout) + } + + override def afterAll: Unit = { + TestKit.shutdownActorSystem(system) + Settings.clearDatabase(system.settings.config) + } + + "api v1 player endpoint" must { + + "get users" in { + val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job))) + val response = Seq(PlayerResponse.fromPlayer(Fixtures.playerEmpty)) + + Get(endpoint).withHeaders(auth) ~> route ~> check { + status shouldEqual StatusCodes.OK + responseAs[Seq[PlayerResponse]] shouldEqual response + } + } + + } +} diff --git a/src/test/scala/me/arcanis/ffxivbis/models/PartyTest.scala b/src/test/scala/me/arcanis/ffxivbis/models/PartyTest.scala index 695f5c9..88f6d5a 100644 --- a/src/test/scala/me/arcanis/ffxivbis/models/PartyTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/models/PartyTest.scala @@ -6,23 +6,24 @@ import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} class PartyTest extends WordSpecLike with Matchers with BeforeAndAfterAll { + private val partyDescription = PartyDescription.empty(Fixtures.partyId) private val party = - Party(Fixtures.partyId, Seq.empty, Map(Fixtures.playerEmpty.playerId -> Fixtures.playerEmpty)) + Party(partyDescription, Seq.empty, Map(Fixtures.playerEmpty.playerId -> Fixtures.playerEmpty)) "party model" must { "accept player with same party id" in { noException should be thrownBy - Party(Fixtures.partyId, Seq.empty, Map(Fixtures.playerEmpty.playerId -> Fixtures.playerEmpty)) + Party(partyDescription, Seq.empty, Map(Fixtures.playerEmpty.playerId -> Fixtures.playerEmpty)) } "fail on multiple party ids" in { val anotherPlayer = Fixtures.playerEmpty.copy(partyId = Fixtures.partyId2) an [IllegalArgumentException] should be thrownBy - Party(Fixtures.partyId, Seq.empty, Map(anotherPlayer.playerId -> anotherPlayer)) + Party(partyDescription, Seq.empty, Map(anotherPlayer.playerId -> anotherPlayer)) an [IllegalArgumentException] should be thrownBy - Party(Fixtures.partyId, Seq.empty, Map(Fixtures.playerEmpty.playerId -> Fixtures.playerEmpty, anotherPlayer.playerId -> anotherPlayer)) + Party(partyDescription, Seq.empty, Map(Fixtures.playerEmpty.playerId -> Fixtures.playerEmpty, anotherPlayer.playerId -> anotherPlayer)) } "return player list" in { @@ -38,7 +39,7 @@ class PartyTest extends WordSpecLike with Matchers with BeforeAndAfterAll { } "add new player" in { - val newParty = Party(Fixtures.partyId, Seq.empty, Map.empty) + val newParty = Party(partyDescription, Seq.empty, Map.empty) newParty.withPlayer(Fixtures.playerEmpty) 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 a771447..eb88bb4 100644 --- a/src/test/scala/me/arcanis/ffxivbis/service/LootSelectorTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/service/LootSelectorTest.scala @@ -16,7 +16,7 @@ class LootSelectorTest extends TestKit(ActorSystem("lootselector")) import me.arcanis.ffxivbis.utils.Converters._ - private var default: Party = Party(Some(Fixtures.partyId), Settings.config(Map.empty)) + private var default: Party = Party(PartyDescription.empty(Fixtures.partyId), Settings.config(Map.empty), Map.empty, Seq.empty, Seq.empty) private var dnc: Player = Player(-1, Fixtures.partyId, Job.DNC, "a nick", BiS(), Seq.empty, Some(Fixtures.link)) private var drg: Player = Player(-1, Fixtures.partyId, Job.DRG, "another nick", BiS(), Seq.empty, Some(Fixtures.link2)) private val timeout: FiniteDuration = 60 seconds