improve api

This commit is contained in:
Evgenii Alekseev 2019-10-29 01:59:35 +03:00
parent f84b9cbaba
commit a84b947862
10 changed files with 173 additions and 88 deletions

View File

@ -11,6 +11,7 @@ package me.arcanis.ffxivbis.http
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.{Piece, Player, PlayerId}
import me.arcanis.ffxivbis.service.impl.DatabaseBiSHandler
@ -26,6 +27,13 @@ class BiSHelper(storage: ActorRef, ariyala: ActorRef) extends AriyalaHelper(ariy
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[Player]] =
(storage ? DatabaseBiSHandler.GetBiS(partyId, playerId)).mapTo[Seq[Player]]
def doModifyBiS(action: ApiAction.Value, playerId: PlayerId, piece: Piece)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
action match {
case ApiAction.add => addPieceBiS(playerId, piece)
case ApiAction.remove => removePieceBiS(playerId, piece)
}
def putBiS(playerId: PlayerId, link: String)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] =
downloadBiS(link, playerId.job).flatMap { bis =>

View File

@ -11,6 +11,7 @@ package me.arcanis.ffxivbis.http
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.{Piece, Player, PlayerId, PlayerIdWithCounters}
import me.arcanis.ffxivbis.service.LootSelector.LootSelectorResult
import me.arcanis.ffxivbis.service.impl.DatabaseLootHandler
@ -23,6 +24,13 @@ class LootHelper(storage: ActorRef) {
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
(storage ? DatabaseLootHandler.AddPieceTo(playerId, piece)).mapTo[Int]
def doModifyLoot(action: ApiAction.Value, playerId: PlayerId, piece: Piece)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
action match {
case ApiAction.add => addPieceLoot(playerId, piece)
case ApiAction.remove => removePieceLoot(playerId, piece)
}
def loot(partyId: String, playerId: Option[PlayerId])
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[Player]] =
(storage ? DatabaseLootHandler.GetLoot(partyId, playerId)).mapTo[Seq[Player]]

View File

@ -11,11 +11,11 @@ package me.arcanis.ffxivbis.http
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.service.impl.{DatabaseBiSHandler, DatabasePartyHandler}
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success}
class PlayerHelper(storage: ActorRef, ariyala: ActorRef) extends AriyalaHelper(ariyala) {
@ -31,6 +31,13 @@ class PlayerHelper(storage: ActorRef, ariyala: ActorRef) extends AriyalaHelper(a
}
}.flatten
def doModifyPlayer(action: ApiAction.Value, player: Player)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
action match {
case ApiAction.add => addPlayer(player)
case ApiAction.remove => removePlayer(player.playerId)
}
def getPlayers(partyId: String, maybePlayerId: Option[PlayerId])
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[Player]] =
maybePlayerId match {

View File

@ -13,6 +13,7 @@ import akka.http.scaladsl.model.{HttpEntity, StatusCodes}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server._
import akka.util.Timeout
import com.typesafe.scalalogging.StrictLogging
import io.swagger.v3.oas.annotations.enums.ParameterIn
import io.swagger.v3.oas.annotations.media.{ArraySchema, Content, Schema}
import io.swagger.v3.oas.annotations.responses.ApiResponse
@ -24,9 +25,11 @@ import me.arcanis.ffxivbis.http.{Authorization, BiSHelper}
import me.arcanis.ffxivbis.http.api.v1.json._
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 {
extends BiSHelper(storage, ariyala) with Authorization with JsonSupport with HttpExceptionsHandler {
def route: Route = createBiS ~ getBiS ~ modifyBiS
@ -51,12 +54,17 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti
)
def createBiS: Route =
path("party" / Segment / "bis") { partyId =>
handleExceptions(exceptionHandler) {
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
put {
entity(as[PlayerBiSLinkResponse]) { bisLink =>
val playerId = bisLink.playerId.withPartyId(partyId)
complete(putBiS(playerId, bisLink.link).map(_ => (StatusCodes.Created, HttpEntity.Empty)))
onComplete(putBiS(playerId, bisLink.link)) {
case Success(_) => complete(StatusCodes.Created, HttpEntity.Empty)
case Failure(exception) => throw exception
}
}
}
}
}
@ -86,12 +94,17 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti
)
def getBiS: Route =
path("party" / Segment / "bis") { partyId =>
handleExceptions(exceptionHandler) {
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)
complete(bis(partyId, playerId).map(_.map(PlayerResponse.fromPlayer)))
onComplete(bis(partyId, playerId)) {
case Success(response) => complete(response.map(PlayerResponse.fromPlayer))
case Failure(exception) => throw exception
}
}
}
}
}
@ -119,17 +132,16 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti
)
def modifyBiS: Route =
path("party" / Segment / "bis") { partyId =>
handleExceptions(exceptionHandler) {
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
post {
entity(as[PieceActionResponse]) { action =>
val playerId = action.playerIdResponse.withPartyId(partyId)
complete {
val result = action.action match {
case ApiAction.add => addPieceBiS(playerId, action.piece.toPiece)
case ApiAction.remove => removePieceBiS(playerId, action.piece.toPiece)
}
result.map(_ => (StatusCodes.Accepted, HttpEntity.Empty))
onComplete(doModifyBiS(action.action, playerId, action.piece.toPiece)) {
case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
case Failure(exception) => throw exception
}
}
}
}

View File

@ -0,0 +1,16 @@
package me.arcanis.ffxivbis.http.api.v1
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server._
import com.typesafe.scalalogging.StrictLogging
import me.arcanis.ffxivbis.http.api.v1.json._
trait HttpExceptionsHandler extends StrictLogging { this: JsonSupport =>
def exceptionHandler: ExceptionHandler = ExceptionHandler {
case other: Exception =>
logger.error("exception during request completion", other)
complete(StatusCodes.InternalServerError, ErrorResponse("unknown server error"))
}
}

View File

@ -24,9 +24,11 @@ import me.arcanis.ffxivbis.http.{Authorization, LootHelper}
import me.arcanis.ffxivbis.http.api.v1.json._
import me.arcanis.ffxivbis.models.PlayerId
import scala.util.{Failure, Success}
@Path("api/v1")
class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
extends LootHelper(storage) with Authorization with JsonSupport {
extends LootHelper(storage) with Authorization with JsonSupport with HttpExceptionsHandler {
def route: Route = getLoot ~ modifyLoot
@ -53,12 +55,17 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
)
def getLoot: Route =
path("party" / Segment / "loot") { partyId =>
handleExceptions(exceptionHandler) {
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)
complete(loot(partyId, playerId).map(_.map(PlayerResponse.fromPlayer)))
onComplete(loot(partyId, playerId)) {
case Success(response) => complete(response.map(PlayerResponse.fromPlayer))
case Failure(exception) => throw exception
}
}
}
}
}
@ -86,17 +93,16 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
)
def modifyLoot: Route =
path("party" / Segment / "loot") { partyId =>
handleExceptions(exceptionHandler) {
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
post {
entity(as[PieceActionResponse]) { action =>
val playerId = action.playerIdResponse.withPartyId(partyId)
complete {
val result = action.action match {
case ApiAction.add => addPieceLoot(playerId, action.piece.toPiece)
case ApiAction.remove => removePieceLoot(playerId, action.piece.toPiece)
onComplete(doModifyLoot(action.action, playerId, action.piece.toPiece)) {
case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
case Failure(exception) => throw exception
}
result.map(_ => (StatusCodes.Accepted, HttpEntity.Empty))
}
}
}
@ -129,13 +135,14 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
)
def suggestLoot: Route =
path("party" / Segment / "loot") { partyId =>
handleExceptions(exceptionHandler) {
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
put {
entity(as[PieceResponse]) { piece =>
complete {
suggestPiece(partyId, piece.toPiece).map { players =>
players.map(PlayerIdWithCountersResponse.fromPlayerId)
onComplete(suggestPiece(partyId, piece.toPiece)) {
case Success(response) => complete(response.map(PlayerIdWithCountersResponse.fromPlayerId))
case Failure(exception) => throw exception
}
}
}

View File

@ -24,9 +24,11 @@ import me.arcanis.ffxivbis.http.{Authorization, PlayerHelper}
import me.arcanis.ffxivbis.http.api.v1.json._
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 {
extends PlayerHelper(storage, ariyala) with Authorization with JsonSupport with HttpExceptionsHandler {
def route: Route = getParty ~ modifyParty
@ -53,12 +55,17 @@ class PlayerEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit
)
def getParty: Route =
path("party" / Segment) { partyId =>
handleExceptions(exceptionHandler) {
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)
complete(getPlayers(partyId, playerId).map(_.map(PlayerResponse.fromPlayer)))
onComplete(getPlayers(partyId, playerId)) {
case Success(response) => complete(response.map(PlayerResponse.fromPlayer))
case Failure(exception) => throw exception
}
}
}
}
}
@ -86,16 +93,15 @@ class PlayerEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit
)
def modifyParty: Route =
path("party" / Segment) { partyId =>
handleExceptions(exceptionHandler) {
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
entity(as[PlayerActionResponse]) { action =>
val player = action.playerIdResponse.toPlayer.copy(partyId = partyId)
complete {
val result = action.action match {
case ApiAction.add => addPlayer(player)
case ApiAction.remove => removePlayer(player.playerId)
}
result.map(_ => (StatusCodes.Accepted, HttpEntity.Empty))
onComplete(doModifyPlayer(action.action, player)) {
case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
case Failure(exception) => throw exception
}
}
}
}

View File

@ -24,9 +24,11 @@ import me.arcanis.ffxivbis.http.{Authorization, UserHelper}
import me.arcanis.ffxivbis.http.api.v1.json._
import me.arcanis.ffxivbis.models.Permission
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(storage) with Authorization with JsonSupport with HttpExceptionsHandler {
def route: Route = createParty ~ createUser ~ deleteUser ~ getUsers
@ -49,12 +51,15 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
)
def createParty: Route =
path("party" / Segment / "create") { partyId =>
handleExceptions(exceptionHandler) {
extractExecutionContext { implicit executionContext =>
put {
entity(as[UserResponse]) { user =>
val admin = user.toUser.copy(partyId = partyId, permission = Permission.admin)
complete {
addUser(admin, isHashedPassword = false).map(_ => (StatusCodes.Created, HttpEntity.Empty))
onComplete(addUser(admin, isHashedPassword = false)) {
case Success(_) => complete(StatusCodes.Created, HttpEntity.Empty)
case Failure(exception) => throw exception
}
}
}
}
@ -82,13 +87,16 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
)
def createUser: Route =
path("party" / Segment / "users") { partyId =>
handleExceptions(exceptionHandler) {
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ =>
post {
entity(as[UserResponse]) { user =>
val withPartyId = user.toUser.copy(partyId = partyId)
complete {
addUser(withPartyId, isHashedPassword = false).map(_ => (StatusCodes.Accepted, HttpEntity.Empty))
onComplete(addUser(withPartyId, isHashedPassword = false)) {
case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
case Failure(exception) => throw exception
}
}
}
}
@ -114,11 +122,14 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
)
def deleteUser: Route =
path("party" / Segment / "users" / Segment) { (partyId, username) =>
handleExceptions(exceptionHandler) {
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ =>
delete {
complete {
removeUser(partyId, username).map(_ => (StatusCodes.Accepted, HttpEntity.Empty))
onComplete(removeUser(partyId, username)) {
case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
case Failure(exception) => throw exception
}
}
}
}
@ -146,11 +157,14 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
)
def getUsers: Route =
path("party" / Segment / "users") { partyId =>
handleExceptions(exceptionHandler) {
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ =>
get {
complete {
users(partyId).map(_.map(UserResponse.fromUser))
onComplete(users(partyId)) {
case Success(response) => complete(response.map(UserResponse.fromUser))
case Failure(exception) => throw exception
}
}
}
}

View File

@ -0,0 +1,6 @@
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)

View File

@ -27,6 +27,7 @@ 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 pieceFormat: RootJsonFormat[PieceResponse] = jsonFormat3(PieceResponse.apply)
implicit val playerFormat: RootJsonFormat[PlayerResponse] = jsonFormat7(PlayerResponse.apply)
implicit val playerActionFormat: RootJsonFormat[PlayerActionResponse] = jsonFormat2(PlayerActionResponse.apply)