mirror of
https://github.com/arcan1s/ffxivbis.git
synced 2025-04-24 17:27:17 +00:00
party cache impl, fix reject handling, correct party creation api
This commit is contained in:
parent
e03f8987b0
commit
50acecd97e
@ -11,6 +11,6 @@
|
||||
<logger name="http" level="DEBUG">
|
||||
<appender-ref ref="http" />
|
||||
</logger>
|
||||
<logger name="slick.jdbc.JdbcBackend.statement" level="DEBUG" />
|
||||
<logger name="slick" level="INFO" />
|
||||
|
||||
</configuration>
|
||||
|
@ -42,6 +42,8 @@ me.arcanis.ffxivbis {
|
||||
]
|
||||
# general request timeout, duratin, required
|
||||
request-timeout = 10s
|
||||
# party in-memory storage lifetime
|
||||
cache-timeout = 1m
|
||||
}
|
||||
|
||||
web {
|
||||
|
@ -13,7 +13,7 @@ import akka.http.scaladsl.Http
|
||||
import akka.stream.ActorMaterializer
|
||||
import com.typesafe.scalalogging.StrictLogging
|
||||
import me.arcanis.ffxivbis.http.RootEndpoint
|
||||
import me.arcanis.ffxivbis.service.Ariyala
|
||||
import me.arcanis.ffxivbis.service.{Ariyala, PartyService}
|
||||
import me.arcanis.ffxivbis.service.impl.DatabaseImpl
|
||||
import me.arcanis.ffxivbis.storage.Migration
|
||||
|
||||
@ -35,7 +35,8 @@ class Application extends Actor with StrictLogging {
|
||||
case Success(_) =>
|
||||
val ariyala = context.system.actorOf(Ariyala.props, "ariyala")
|
||||
val storage = context.system.actorOf(DatabaseImpl.props, "storage")
|
||||
val http = new RootEndpoint(context.system, storage, ariyala)
|
||||
val party = context.system.actorOf(PartyService.props(storage), "party")
|
||||
val http = new RootEndpoint(context.system, party, ariyala)
|
||||
|
||||
logger.info(s"start server at $host:$port")
|
||||
val bind = Http()(context.system).bindAndHandle(http.route, host, port)
|
||||
|
@ -12,6 +12,7 @@ import akka.actor.ActorRef
|
||||
import akka.pattern.ask
|
||||
import akka.util.Timeout
|
||||
import me.arcanis.ffxivbis.models.User
|
||||
import me.arcanis.ffxivbis.service.PartyService
|
||||
import me.arcanis.ffxivbis.service.impl.DatabaseUserHandler
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
@ -22,6 +23,9 @@ class UserHelper(storage: ActorRef) {
|
||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
||||
(storage ? DatabaseUserHandler.AddUser(user, isHashedPassword)).mapTo[Int]
|
||||
|
||||
def newPartyId(implicit executionContext: ExecutionContext, timeout: Timeout): Future[String] =
|
||||
(storage ? PartyService.GetNewPartyId).mapTo[String]
|
||||
|
||||
def user(partyId: String, username: String)
|
||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Option[User]] =
|
||||
(storage ? DatabaseUserHandler.GetUser(partyId, username)).mapTo[Option[User]]
|
||||
|
@ -29,7 +29,7 @@ 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 with HttpHandler {
|
||||
extends BiSHelper(storage, ariyala) with Authorization with JsonSupport {
|
||||
|
||||
def route: Route = createBiS ~ getBiS ~ modifyBiS
|
||||
|
||||
@ -54,18 +54,14 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti
|
||||
)
|
||||
def createBiS: Route =
|
||||
path("party" / Segment / "bis") { partyId =>
|
||||
handleExceptions(exceptionHandler) {
|
||||
handleRejections(rejectionHandler) {
|
||||
extractExecutionContext { implicit executionContext =>
|
||||
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
|
||||
put {
|
||||
entity(as[PlayerBiSLinkResponse]) { bisLink =>
|
||||
val playerId = bisLink.playerId.withPartyId(partyId)
|
||||
onComplete(putBiS(playerId, bisLink.link)) {
|
||||
case Success(_) => complete(StatusCodes.Created, HttpEntity.Empty)
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
extractExecutionContext { implicit executionContext =>
|
||||
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
|
||||
put {
|
||||
entity(as[PlayerBiSLinkResponse]) { bisLink =>
|
||||
val playerId = bisLink.playerId.withPartyId(partyId)
|
||||
onComplete(putBiS(playerId, bisLink.link)) {
|
||||
case Success(_) => complete(StatusCodes.Created, HttpEntity.Empty)
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -96,23 +92,19 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti
|
||||
)
|
||||
def getBiS: Route =
|
||||
path("party" / Segment / "bis") { partyId =>
|
||||
handleExceptions(exceptionHandler) {
|
||||
handleRejections(rejectionHandler) {
|
||||
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)
|
||||
onComplete(bis(partyId, playerId)) {
|
||||
case Success(response) => complete(response.map(PlayerResponse.fromPlayer))
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
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)
|
||||
onComplete(bis(partyId, playerId)) {
|
||||
case Success(response) => complete(response.map(PlayerResponse.fromPlayer))
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,21 +129,18 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti
|
||||
)
|
||||
def modifyBiS: Route =
|
||||
path("party" / Segment / "bis") { partyId =>
|
||||
handleExceptions(exceptionHandler) {
|
||||
handleRejections(rejectionHandler) {
|
||||
extractExecutionContext { implicit executionContext =>
|
||||
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
|
||||
post {
|
||||
entity(as[PieceActionResponse]) { action =>
|
||||
val playerId = action.playerIdResponse.withPartyId(partyId)
|
||||
onComplete(doModifyBiS(action.action, playerId, action.piece.toPiece)) {
|
||||
case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
extractExecutionContext { implicit executionContext =>
|
||||
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
|
||||
post {
|
||||
entity(as[PieceActionResponse]) { action =>
|
||||
val playerId = action.playerIdResponse.withPartyId(partyId)
|
||||
onComplete(doModifyBiS(action.action, playerId, action.piece.toPiece)) {
|
||||
case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,18 +55,14 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
||||
)
|
||||
def getLoot: Route =
|
||||
path("party" / Segment / "loot") { partyId =>
|
||||
handleExceptions(exceptionHandler) {
|
||||
handleRejections(rejectionHandler) {
|
||||
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)
|
||||
onComplete(loot(partyId, playerId)) {
|
||||
case Success(response) => complete(response.map(PlayerResponse.fromPlayer))
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
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)
|
||||
onComplete(loot(partyId, playerId)) {
|
||||
case Success(response) => complete(response.map(PlayerResponse.fromPlayer))
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -95,18 +91,14 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
||||
)
|
||||
def modifyLoot: Route =
|
||||
path("party" / Segment / "loot") { partyId =>
|
||||
handleExceptions(exceptionHandler) {
|
||||
handleRejections(rejectionHandler) {
|
||||
extractExecutionContext { implicit executionContext =>
|
||||
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
|
||||
post {
|
||||
entity(as[PieceActionResponse]) { action =>
|
||||
val playerId = action.playerIdResponse.withPartyId(partyId)
|
||||
onComplete(doModifyLoot(action.action, playerId, action.piece.toPiece)) {
|
||||
case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
extractExecutionContext { implicit executionContext =>
|
||||
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
|
||||
post {
|
||||
entity(as[PieceActionResponse]) { action =>
|
||||
val playerId = action.playerIdResponse.withPartyId(partyId)
|
||||
onComplete(doModifyLoot(action.action, playerId, action.piece.toPiece)) {
|
||||
case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -139,17 +131,13 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
||||
)
|
||||
def suggestLoot: Route =
|
||||
path("party" / Segment / "loot") { partyId =>
|
||||
handleExceptions(exceptionHandler) {
|
||||
handleRejections(rejectionHandler) {
|
||||
extractExecutionContext { implicit executionContext =>
|
||||
authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
|
||||
put {
|
||||
entity(as[PieceResponse]) { piece =>
|
||||
onComplete(suggestPiece(partyId, piece.toPiece)) {
|
||||
case Success(response) => complete(response.map(PlayerIdWithCountersResponse.fromPlayerId))
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
extractExecutionContext { implicit executionContext =>
|
||||
authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
|
||||
put {
|
||||
entity(as[PieceResponse]) { piece =>
|
||||
onComplete(suggestPiece(partyId, piece.toPiece)) {
|
||||
case Success(response) => complete(response.map(PlayerIdWithCountersResponse.fromPlayerId))
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,18 +55,14 @@ class PlayerEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit
|
||||
)
|
||||
def getParty: Route =
|
||||
path("party" / Segment) { partyId =>
|
||||
handleExceptions(exceptionHandler) {
|
||||
handleRejections(rejectionHandler) {
|
||||
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)
|
||||
onComplete(getPlayers(partyId, playerId)) {
|
||||
case Success(response) => complete(response.map(PlayerResponse.fromPlayer))
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
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)
|
||||
onComplete(getPlayers(partyId, playerId)) {
|
||||
case Success(response) => complete(response.map(PlayerResponse.fromPlayer))
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -95,17 +91,13 @@ class PlayerEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit
|
||||
)
|
||||
def modifyParty: Route =
|
||||
path("party" / Segment) { partyId =>
|
||||
handleExceptions(exceptionHandler) {
|
||||
handleRejections(rejectionHandler) {
|
||||
extractExecutionContext { implicit executionContext =>
|
||||
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
|
||||
entity(as[PlayerActionResponse]) { action =>
|
||||
val player = action.playerIdResponse.toPlayer.copy(partyId = partyId)
|
||||
onComplete(doModifyPlayer(action.action, player)) {
|
||||
case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
extractExecutionContext { implicit executionContext =>
|
||||
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
|
||||
entity(as[PlayerActionResponse]) { action =>
|
||||
val player = action.playerIdResponse.toPlayer.copy(partyId = partyId)
|
||||
onComplete(doModifyPlayer(action.action, player)) {
|
||||
case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,8 +12,11 @@ import akka.actor.ActorRef
|
||||
import akka.http.scaladsl.server.Directives._
|
||||
import akka.http.scaladsl.server.Route
|
||||
import akka.util.Timeout
|
||||
import me.arcanis.ffxivbis.http.api.v1.json.JsonSupport
|
||||
|
||||
class RootApiV1Endpoint(storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout) {
|
||||
class RootApiV1Endpoint(storage: ActorRef, ariyala: ActorRef)
|
||||
(implicit timeout: Timeout)
|
||||
extends JsonSupport with HttpHandler {
|
||||
|
||||
private val biSEndpoint = new BiSEndpoint(storage, ariyala)
|
||||
private val lootEndpoint = new LootEndpoint(storage)
|
||||
@ -21,5 +24,9 @@ class RootApiV1Endpoint(storage: ActorRef, ariyala: ActorRef)(implicit timeout:
|
||||
private val userEndpoint = new UserEndpoint(storage)
|
||||
|
||||
def route: Route =
|
||||
biSEndpoint.route ~ lootEndpoint.route ~ playerEndpoint.route ~ userEndpoint.route
|
||||
handleExceptions(exceptionHandler) {
|
||||
handleRejections(rejectionHandler) {
|
||||
biSEndpoint.route ~ lootEndpoint.route ~ playerEndpoint.route ~ userEndpoint.route
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,21 +28,18 @@ import scala.util.{Failure, Success}
|
||||
|
||||
@Path("api/v1")
|
||||
class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
||||
extends UserHelper(storage) with Authorization with JsonSupport with HttpHandler {
|
||||
extends UserHelper(storage) with Authorization with JsonSupport {
|
||||
|
||||
def route: Route = createParty ~ createUser ~ deleteUser ~ getUsers
|
||||
|
||||
@PUT
|
||||
@Path("party/{partyId}/create")
|
||||
@Path("party")
|
||||
@Consumes(value = Array("application/json"))
|
||||
@Operation(summary = "create new party", description = "Create new party with specified ID",
|
||||
parameters = Array(
|
||||
new Parameter(name = "partyId", in = ParameterIn.PATH, description = "unique party ID", example = "abcdefgh"),
|
||||
),
|
||||
requestBody = new RequestBody(description = "party administrator description", required = true,
|
||||
content = Array(new Content(schema = new Schema(implementation = classOf[UserResponse])))),
|
||||
responses = Array(
|
||||
new ApiResponse(responseCode = "201", description = "Party has been created"),
|
||||
new ApiResponse(responseCode = "200", description = "Party has been created"),
|
||||
new ApiResponse(responseCode = "400", description = "Invalid parameters were supplied"),
|
||||
new ApiResponse(responseCode = "406", description = "Party with the specified ID already exists"),
|
||||
new ApiResponse(responseCode = "500", description = "Internal server error"),
|
||||
@ -50,18 +47,18 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
||||
tags = Array("party"),
|
||||
)
|
||||
def createParty: Route =
|
||||
path("party" / Segment / "create") { partyId =>
|
||||
handleExceptions(exceptionHandler) {
|
||||
handleRejections(rejectionHandler) {
|
||||
extractExecutionContext { implicit executionContext =>
|
||||
put {
|
||||
entity(as[UserResponse]) { user =>
|
||||
path("party") {
|
||||
extractExecutionContext { implicit executionContext =>
|
||||
put {
|
||||
entity(as[UserResponse]) { user =>
|
||||
onComplete(newPartyId) {
|
||||
case Success(partyId) =>
|
||||
val admin = user.toUser.copy(partyId = partyId, permission = Permission.admin)
|
||||
onComplete(addUser(admin, isHashedPassword = false)) {
|
||||
case Success(_) => complete(StatusCodes.Created, HttpEntity.Empty)
|
||||
case Success(_) => complete(PartyIdResponse(partyId))
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -89,18 +86,14 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
||||
)
|
||||
def createUser: Route =
|
||||
path("party" / Segment / "users") { partyId =>
|
||||
handleExceptions(exceptionHandler) {
|
||||
handleRejections(rejectionHandler) {
|
||||
extractExecutionContext { implicit executionContext =>
|
||||
authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ =>
|
||||
post {
|
||||
entity(as[UserResponse]) { user =>
|
||||
val withPartyId = user.toUser.copy(partyId = partyId)
|
||||
onComplete(addUser(withPartyId, isHashedPassword = false)) {
|
||||
case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
extractExecutionContext { implicit executionContext =>
|
||||
authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ =>
|
||||
post {
|
||||
entity(as[UserResponse]) { user =>
|
||||
val withPartyId = user.toUser.copy(partyId = partyId)
|
||||
onComplete(addUser(withPartyId, isHashedPassword = false)) {
|
||||
case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -126,16 +119,12 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
||||
)
|
||||
def deleteUser: Route =
|
||||
path("party" / Segment / "users" / Segment) { (partyId, username) =>
|
||||
handleExceptions(exceptionHandler) {
|
||||
handleRejections(rejectionHandler) {
|
||||
extractExecutionContext { implicit executionContext =>
|
||||
authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ =>
|
||||
delete {
|
||||
onComplete(removeUser(partyId, username)) {
|
||||
case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
extractExecutionContext { implicit executionContext =>
|
||||
authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ =>
|
||||
delete {
|
||||
onComplete(removeUser(partyId, username)) {
|
||||
case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -163,16 +152,12 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
||||
)
|
||||
def getUsers: Route =
|
||||
path("party" / Segment / "users") { partyId =>
|
||||
handleExceptions(exceptionHandler) {
|
||||
handleRejections(rejectionHandler) {
|
||||
extractExecutionContext { implicit executionContext =>
|
||||
authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ =>
|
||||
get {
|
||||
onComplete(users(partyId)) {
|
||||
case Success(response) => complete(response.map(UserResponse.fromUser))
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
extractExecutionContext { implicit executionContext =>
|
||||
authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ =>
|
||||
get {
|
||||
onComplete(users(partyId)) {
|
||||
case Success(response) => complete(response.map(UserResponse.fromUser))
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
|
||||
implicit val permissionFormat: RootJsonFormat[Permission.Value] = enumFormat(Permission)
|
||||
|
||||
implicit val errorFormat: RootJsonFormat[ErrorResponse] = jsonFormat1(ErrorResponse.apply)
|
||||
implicit val partyIdFormat: RootJsonFormat[PartyIdResponse] = jsonFormat1(PartyIdResponse.apply)
|
||||
implicit val pieceFormat: RootJsonFormat[PieceResponse] = jsonFormat3(PieceResponse.apply)
|
||||
implicit val playerFormat: RootJsonFormat[PlayerResponse] = jsonFormat7(PlayerResponse.apply)
|
||||
implicit val playerActionFormat: RootJsonFormat[PlayerActionResponse] = jsonFormat2(PlayerActionResponse.apply)
|
||||
|
@ -0,0 +1,6 @@
|
||||
package me.arcanis.ffxivbis.http.api.v1.json
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema
|
||||
|
||||
case class PartyIdResponse(
|
||||
@Schema(description = "party id", required = true) partyId: String)
|
@ -16,19 +16,25 @@ import akka.util.Timeout
|
||||
import me.arcanis.ffxivbis.http.UserHelper
|
||||
import me.arcanis.ffxivbis.models.{Party, Permission, User}
|
||||
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
class IndexView(storage: ActorRef)(implicit timeout: Timeout)
|
||||
extends UserHelper(storage) {
|
||||
|
||||
def route: Route = createParty ~ getIndex
|
||||
|
||||
def createParty: Route =
|
||||
path("party" / Segment / "create") { partyId =>
|
||||
path("party") {
|
||||
extractExecutionContext { implicit executionContext =>
|
||||
post {
|
||||
formFields("username".as[String], "password".as[String]) { (username, password) =>
|
||||
val user = User(partyId, username, password, Permission.admin)
|
||||
onComplete(addUser(user, isHashedPassword = false)) {
|
||||
case _ => redirect(s"/party/$partyId", StatusCodes.Found)
|
||||
onComplete(newPartyId) {
|
||||
case Success(partyId) =>
|
||||
val user = User(partyId, username, password, Permission.admin)
|
||||
onComplete(addUser(user, isHashedPassword = false)) {
|
||||
case _ => redirect(s"/party/$partyId", StatusCodes.Found)
|
||||
}
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -40,7 +46,7 @@ class IndexView(storage: ActorRef)(implicit timeout: Timeout)
|
||||
get {
|
||||
parameters("partyId".as[String].?) {
|
||||
case Some(partyId) => redirect(s"/party/$partyId", StatusCodes.Found)
|
||||
case _ => complete((StatusCodes.OK, RootView.toHtml(IndexView.template)))
|
||||
case _ => complete(StatusCodes.OK, RootView.toHtml(IndexView.template))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -58,7 +64,7 @@ object IndexView {
|
||||
),
|
||||
|
||||
body(
|
||||
form(action:=s"party/${Party.randomPartyId}/create", method:="post")(
|
||||
form(action:=s"party", method:="post")(
|
||||
label("create a new party"),
|
||||
input(name:="username", id:="username", placeholder:="username", title:="username", `type`:="text"),
|
||||
input(name:="password", id:="password", placeholder:="password", title:="password", `type`:="password"),
|
||||
|
@ -22,7 +22,7 @@ import me.arcanis.ffxivbis.models.{BiS, Job, Piece}
|
||||
import spray.json._
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
import scala.util.{Failure, Success, Try}
|
||||
import scala.util.Try
|
||||
|
||||
class Ariyala extends Actor with StrictLogging {
|
||||
import Ariyala._
|
||||
|
@ -37,3 +37,9 @@ trait Database extends Actor with StrictLogging {
|
||||
loot <- if (withLoot) profile.getPieces(partyId) else Future(Seq.empty)
|
||||
} yield Party(partyId, context.system.settings.config, players, bis, loot)
|
||||
}
|
||||
|
||||
object Database {
|
||||
trait DatabaseRequest {
|
||||
def partyId: String
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Evgeniy Alekseev.
|
||||
*
|
||||
* This file is part of ffxivbis
|
||||
* (see https://github.com/arcan1s/ffxivbis).
|
||||
*
|
||||
* License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
package me.arcanis.ffxivbis.service
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Props}
|
||||
import akka.pattern.{ask, pipe}
|
||||
import akka.util.Timeout
|
||||
import com.typesafe.scalalogging.StrictLogging
|
||||
import me.arcanis.ffxivbis.models.Party
|
||||
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
|
||||
class PartyService(storage: ActorRef) extends Actor with StrictLogging {
|
||||
import PartyService._
|
||||
import me.arcanis.ffxivbis.utils.Implicits._
|
||||
|
||||
private val cacheTimeout: FiniteDuration =
|
||||
context.system.settings.config.getDuration("me.arcanis.ffxivbis.settings.cache-timeout")
|
||||
implicit private val executionContext: ExecutionContext = context.dispatcher
|
||||
implicit private val timeout: Timeout =
|
||||
context.system.settings.config.getDuration("me.arcanis.ffxivbis.settings.request-timeout")
|
||||
|
||||
override def receive: Receive = handle(Map.empty)
|
||||
|
||||
private def handle(cache: Map[String, Party]): Receive = {
|
||||
case ForgetParty(partyId) =>
|
||||
context become handle(cache - partyId)
|
||||
|
||||
case GetNewPartyId =>
|
||||
val client = sender()
|
||||
getPartyId.pipeTo(client)
|
||||
|
||||
case req @ impl.DatabasePartyHandler.GetParty(partyId) =>
|
||||
val client = sender()
|
||||
val party = cache.get(partyId) match {
|
||||
case Some(party) => Future.successful(party)
|
||||
case None =>
|
||||
(storage ? req).mapTo[Party].map { party =>
|
||||
context become handle(cache + (partyId -> party))
|
||||
context.system.scheduler.scheduleOnce(cacheTimeout, self, ForgetParty(partyId))
|
||||
party
|
||||
}
|
||||
}
|
||||
party.pipeTo(client)
|
||||
|
||||
case req: Database.DatabaseRequest =>
|
||||
self ! ForgetParty(req.partyId)
|
||||
storage.forward(req)
|
||||
}
|
||||
|
||||
private def getPartyId: Future[String] = {
|
||||
val partyId = Party.randomPartyId
|
||||
(storage ? impl.DatabaseUserHandler.Exists(partyId)).mapTo[Boolean].flatMap {
|
||||
case true => getPartyId
|
||||
case false => Future.successful(partyId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object PartyService {
|
||||
def props(storage: ActorRef): Props = Props(new PartyService(storage))
|
||||
|
||||
case class ForgetParty(partyId: String)
|
||||
case object GetNewPartyId
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
package me.arcanis.ffxivbis.service.impl
|
||||
|
||||
import akka.pattern.pipe
|
||||
import me.arcanis.ffxivbis.models.{BiS, Piece, PlayerId}
|
||||
import me.arcanis.ffxivbis.models.{Piece, PlayerId}
|
||||
import me.arcanis.ffxivbis.service.Database
|
||||
|
||||
trait DatabaseBiSHandler { this: Database =>
|
||||
@ -33,7 +33,11 @@ trait DatabaseBiSHandler { this: Database =>
|
||||
}
|
||||
|
||||
object DatabaseBiSHandler {
|
||||
case class AddPieceToBis(playerId: PlayerId, piece: Piece)
|
||||
case class GetBiS(partyId: String, playerId: Option[PlayerId])
|
||||
case class RemovePieceFromBiS(playerId: PlayerId, piece: Piece)
|
||||
case class AddPieceToBis(playerId: PlayerId, piece: Piece) extends Database.DatabaseRequest {
|
||||
override def partyId: String = playerId.partyId
|
||||
}
|
||||
case class GetBiS(partyId: String, playerId: Option[PlayerId]) extends Database.DatabaseRequest
|
||||
case class RemovePieceFromBiS(playerId: PlayerId, piece: Piece) extends Database.DatabaseRequest {
|
||||
override def partyId: String = playerId.partyId
|
||||
}
|
||||
}
|
||||
|
@ -37,8 +37,12 @@ trait DatabaseLootHandler { this: Database =>
|
||||
}
|
||||
|
||||
object DatabaseLootHandler {
|
||||
case class AddPieceTo(playerId: PlayerId, piece: Piece)
|
||||
case class GetLoot(partyId: String, playerId: Option[PlayerId])
|
||||
case class RemovePieceFrom(playerId: PlayerId, piece: Piece)
|
||||
case class SuggestLoot(partyId: String, piece: Piece)
|
||||
case class AddPieceTo(playerId: PlayerId, piece: Piece) extends Database.DatabaseRequest {
|
||||
override def partyId: String = playerId.partyId
|
||||
}
|
||||
case class GetLoot(partyId: String, playerId: Option[PlayerId]) extends Database.DatabaseRequest
|
||||
case class RemovePieceFrom(playerId: PlayerId, piece: Piece) extends Database.DatabaseRequest {
|
||||
override def partyId: String = playerId.partyId
|
||||
}
|
||||
case class SuggestLoot(partyId: String, piece: Piece) extends Database.DatabaseRequest
|
||||
}
|
||||
|
@ -47,8 +47,14 @@ trait DatabasePartyHandler { this: Database =>
|
||||
}
|
||||
|
||||
object DatabasePartyHandler {
|
||||
case class AddPlayer(player: Player)
|
||||
case class GetParty(partyId: String)
|
||||
case class GetPlayer(playerId: PlayerId)
|
||||
case class RemovePlayer(playerId: PlayerId)
|
||||
case class AddPlayer(player: Player) extends Database.DatabaseRequest {
|
||||
override def partyId: String = player.partyId
|
||||
}
|
||||
case class GetParty(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
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,10 @@ trait DatabaseUserHandler { this: Database =>
|
||||
val client = sender()
|
||||
profile.deleteUser(partyId, username).pipeTo(client)
|
||||
|
||||
case Exists(partyId) =>
|
||||
val client = sender()
|
||||
profile.exists(partyId).pipeTo(client)
|
||||
|
||||
case GetUser(partyId, username) =>
|
||||
val client = sender()
|
||||
profile.getUser(partyId, username).pipeTo(client)
|
||||
@ -36,8 +40,11 @@ trait DatabaseUserHandler { this: Database =>
|
||||
}
|
||||
|
||||
object DatabaseUserHandler {
|
||||
case class AddUser(user: User, isHashedPassword: Boolean)
|
||||
case class DeleteUser(partyId: String, username: String)
|
||||
case class GetUser(partyId: String, username: String)
|
||||
case class GetUsers(partyId: String)
|
||||
case class AddUser(user: User, isHashedPassword: Boolean) extends Database.DatabaseRequest {
|
||||
override def partyId: String = user.partyId
|
||||
}
|
||||
case class DeleteUser(partyId: String, username: String) extends Database.DatabaseRequest
|
||||
case class Exists(partyId: String) extends Database.DatabaseRequest
|
||||
case class GetUser(partyId: String, username: String) extends Database.DatabaseRequest
|
||||
case class GetUsers(partyId: String) extends Database.DatabaseRequest
|
||||
}
|
||||
|
@ -42,6 +42,8 @@ trait UsersProfile { this: DatabaseProfile =>
|
||||
|
||||
def deleteUser(partyId: String, username: String): Future[Int] =
|
||||
db.run(user(partyId, Some(username)).delete)
|
||||
def exists(partyId: String): Future[Boolean] =
|
||||
db.run(user(partyId, None).exists.result)
|
||||
def getUser(partyId: String, username: String): Future[Option[User]] =
|
||||
db.run(user(partyId, Some(username)).result.headOption).map(_.map(_.toUser))
|
||||
def getUsers(partyId: String): Future[Seq[User]] =
|
||||
|
@ -17,11 +17,14 @@ import scala.concurrent.duration.FiniteDuration
|
||||
import scala.language.implicitConversions
|
||||
|
||||
object Implicits {
|
||||
implicit def getBooleanFromOptionString(maybeYes: Option[String]): Boolean = maybeYes match {
|
||||
implicit def getBooleanFromOptionString(maybeYes: Option[String]): Boolean = maybeYes.map(_.toLowerCase) match {
|
||||
case Some("yes" | "on") => true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
implicit def getFiniteDuration(duration: Duration): Timeout =
|
||||
implicit def getFiniteDuration(duration: Duration): FiniteDuration =
|
||||
FiniteDuration(duration.toNanos, TimeUnit.NANOSECONDS)
|
||||
|
||||
implicit def getTimeout(duration: Duration): Timeout =
|
||||
FiniteDuration(duration.toNanos, TimeUnit.NANOSECONDS)
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import com.typesafe.config.Config
|
||||
import me.arcanis.ffxivbis.{Fixtures, Settings}
|
||||
import me.arcanis.ffxivbis.http.api.v1.json._
|
||||
import me.arcanis.ffxivbis.models.BiS
|
||||
import me.arcanis.ffxivbis.service.{Ariyala, impl}
|
||||
import me.arcanis.ffxivbis.service.{Ariyala, PartyService, impl}
|
||||
import me.arcanis.ffxivbis.storage.Migration
|
||||
import org.scalatest.{Matchers, WordSpec}
|
||||
|
||||
@ -31,7 +31,8 @@ class BiSEndpointTest extends WordSpec
|
||||
|
||||
private val storage: ActorRef = system.actorOf(impl.DatabaseImpl.props)
|
||||
private val ariyala: ActorRef = system.actorOf(Ariyala.props)
|
||||
private val route: Route = new BiSEndpoint(storage, ariyala)(timeout).route
|
||||
private val party: ActorRef = system.actorOf(PartyService.props(storage))
|
||||
private val route: Route = new BiSEndpoint(party, ariyala)(timeout).route
|
||||
|
||||
override def testConfig: Config = Settings.withRandomDatabase
|
||||
|
||||
|
@ -10,7 +10,7 @@ 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.impl
|
||||
import me.arcanis.ffxivbis.service.{PartyService, impl}
|
||||
import me.arcanis.ffxivbis.storage.Migration
|
||||
import org.scalatest.{Matchers, WordSpec}
|
||||
|
||||
@ -29,7 +29,8 @@ class LootEndpointTest extends WordSpec
|
||||
implicit private val routeTimeout: RouteTestTimeout = RouteTestTimeout(timeout)
|
||||
|
||||
private val storage: ActorRef = system.actorOf(impl.DatabaseImpl.props)
|
||||
private val route: Route = new LootEndpoint(storage)(timeout).route
|
||||
private val party: ActorRef = system.actorOf(PartyService.props(storage))
|
||||
private val route: Route = new LootEndpoint(party)(timeout).route
|
||||
|
||||
override def testConfig: Config = Settings.withRandomDatabase
|
||||
|
||||
|
@ -10,7 +10,7 @@ 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, impl}
|
||||
import me.arcanis.ffxivbis.service.{Ariyala, PartyService, impl}
|
||||
import me.arcanis.ffxivbis.storage.Migration
|
||||
import org.scalatest.{Matchers, WordSpec}
|
||||
|
||||
@ -30,7 +30,8 @@ class PartyEndpointTest extends WordSpec
|
||||
|
||||
private val storage: ActorRef = system.actorOf(impl.DatabaseImpl.props)
|
||||
private val ariyala: ActorRef = system.actorOf(Ariyala.props)
|
||||
private val route: Route = new PlayerEndpoint(storage, ariyala)(timeout).route
|
||||
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
|
||||
|
||||
|
@ -9,7 +9,7 @@ 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.impl
|
||||
import me.arcanis.ffxivbis.service.{PartyService, impl}
|
||||
import me.arcanis.ffxivbis.storage.Migration
|
||||
import org.scalatest.{Matchers, WordSpec}
|
||||
|
||||
@ -22,12 +22,14 @@ class UserEndpointTest extends WordSpec
|
||||
|
||||
private val auth: Authorization =
|
||||
Authorization(BasicHttpCredentials(Fixtures.userAdmin.username, Fixtures.userPassword))
|
||||
private val endpoint: Uri = Uri(s"/party/${Fixtures.partyId}/users")
|
||||
private def endpoint: Uri = Uri(s"/party/$partyId/users")
|
||||
private val timeout: FiniteDuration = 60 seconds
|
||||
implicit private val routeTimeout: RouteTestTimeout = RouteTestTimeout(timeout)
|
||||
|
||||
private var partyId: String = Fixtures.partyId
|
||||
private val storage: ActorRef = system.actorOf(impl.DatabaseImpl.props)
|
||||
private val route: Route = new UserEndpoint(storage)(timeout).route
|
||||
private val party: ActorRef = system.actorOf(PartyService.props(storage))
|
||||
private val route: Route = new UserEndpoint(party)(timeout).route
|
||||
|
||||
override def testConfig: Config = Settings.withRandomDatabase
|
||||
|
||||
@ -43,18 +45,17 @@ class UserEndpointTest extends WordSpec
|
||||
"api v1 users endpoint" must {
|
||||
|
||||
"create a party" in {
|
||||
val uri = Uri(s"/party/${Fixtures.partyId}/create")
|
||||
val uri = Uri(s"/party")
|
||||
val entity = UserResponse.fromUser(Fixtures.userAdmin).copy(password = Fixtures.userPassword)
|
||||
println(entity)
|
||||
|
||||
Put(uri, entity) ~> route ~> check {
|
||||
status shouldEqual StatusCodes.Created
|
||||
responseAs[String] shouldEqual ""
|
||||
status shouldEqual StatusCodes.OK
|
||||
partyId = responseAs[PartyIdResponse].partyId
|
||||
}
|
||||
}
|
||||
|
||||
"add user" in {
|
||||
val entity = UserResponse.fromUser(Fixtures.userGet).copy(password = Fixtures.userPassword2)
|
||||
val entity = UserResponse.fromUser(Fixtures.userGet).copy(partyId = partyId, password = Fixtures.userPassword2)
|
||||
|
||||
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
|
||||
status shouldEqual StatusCodes.Accepted
|
||||
@ -70,13 +71,28 @@ class UserEndpointTest extends WordSpec
|
||||
status shouldEqual StatusCodes.OK
|
||||
|
||||
val users = responseAs[Seq[UserResponse]]
|
||||
users.map(_.partyId).distinct shouldEqual Seq(Fixtures.partyId)
|
||||
users.map(_.partyId).distinct shouldEqual Seq(partyId)
|
||||
users.map(user => user.username -> user.permission).toMap shouldEqual party
|
||||
}
|
||||
}
|
||||
|
||||
"remove user" in {
|
||||
val entity = UserResponse.fromUser(Fixtures.userGet).copy(partyId = partyId)
|
||||
|
||||
Delete(endpoint.toString + s"/${entity.username}").withHeaders(auth) ~> route ~> check {
|
||||
status shouldEqual StatusCodes.Accepted
|
||||
}
|
||||
|
||||
val party = Seq(Fixtures.userAdmin)
|
||||
.map(user => user.username -> Some(user.permission)).toMap
|
||||
|
||||
Get(endpoint).withHeaders(auth) ~> route ~> check {
|
||||
status shouldEqual StatusCodes.OK
|
||||
|
||||
val users = responseAs[Seq[UserResponse]]
|
||||
users.map(_.partyId).distinct shouldEqual Seq(partyId)
|
||||
users.map(user => user.username -> user.permission).toMap shouldEqual party
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user