mirror of
https://github.com/arcan1s/ffxivbis.git
synced 2025-07-07 19:05:52 +00:00
Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
abe3818ead |
12
.travis.yml
12
.travis.yml
@ -1,9 +1,9 @@
|
|||||||
language: scala
|
sudo: required
|
||||||
scala:
|
language: generic
|
||||||
- 2.13.1
|
|
||||||
|
|
||||||
sbt_args: -no-colors
|
services:
|
||||||
|
- docker
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- sbt compile
|
- docker run -it --rm -v "$(pwd):/opt/build" -w /opt/build mozilla/sbt sbt compile
|
||||||
- sbt test
|
- docker run -it --rm -v "$(pwd):/opt/build" -w /opt/build mozilla/sbt sbt test
|
||||||
|
16
README.md
16
README.md
@ -1,31 +1,23 @@
|
|||||||
# FFXIV BiS
|
# FFXIV BiS
|
||||||
|
|
||||||
[](https://travis-ci.org/arcan1s/ffxivbis) 
|
|
||||||
|
|
||||||
Service which allows to manage savage loot distribution easy.
|
Service which allows to manage savage loot distribution easy.
|
||||||
|
|
||||||
## Installation and usage
|
## Installation and usage
|
||||||
|
|
||||||
In general compilation process looks like:
|
In general installation process looks like:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sbt dist
|
sbt assembly
|
||||||
```
|
```
|
||||||
|
|
||||||
Or alternatively you can download latest distribution zip from the releases page. Service can be run by using command:
|
Service can be run by using command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bin/ffxivbis
|
java -cp ./target/scala-2.13/ffxivbis-scala-assembly-0.1.jar me.arcanis.ffxivbis.ffxivbis
|
||||||
```
|
```
|
||||||
|
|
||||||
from the extracted archive root.
|
|
||||||
|
|
||||||
## Web service
|
## Web service
|
||||||
|
|
||||||
REST API documentation is available at `http://0.0.0.0:8000/swagger`. HTML representation is available at `http://0.0.0.0:8000`.
|
REST API documentation is available at `http://0.0.0.0:8000/swagger`. HTML representation is available at `http://0.0.0.0:8000`.
|
||||||
|
|
||||||
*Note*: host and port depend on configuration settings.
|
*Note*: host and port depend on configuration settings.
|
||||||
|
|
||||||
## Public service
|
|
||||||
|
|
||||||
There is also public service which is available at https://ffxivbis.arcanis.me.
|
|
@ -4,8 +4,6 @@ scalaVersion := "2.13.1"
|
|||||||
|
|
||||||
scalacOptions ++= Seq("-deprecation", "-feature")
|
scalacOptions ++= Seq("-deprecation", "-feature")
|
||||||
|
|
||||||
enablePlugins(JavaAppPackaging)
|
|
||||||
|
|
||||||
assemblyMergeStrategy in assembly := {
|
assemblyMergeStrategy in assembly := {
|
||||||
case PathList("META-INF", xs @ _*) => MergeStrategy.discard
|
case PathList("META-INF", xs @ _*) => MergeStrategy.discard
|
||||||
case "application.conf" => MergeStrategy.concat
|
case "application.conf" => MergeStrategy.concat
|
||||||
|
1
project/assembly.sbt
Normal file
1
project/assembly.sbt
Normal file
@ -0,0 +1 @@
|
|||||||
|
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6")
|
1
project/dependency.sbt
Normal file
1
project/dependency.sbt
Normal file
@ -0,0 +1 @@
|
|||||||
|
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.10.0-RC1")
|
@ -1,3 +0,0 @@
|
|||||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6")
|
|
||||||
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.4")
|
|
||||||
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.10.0-RC1")
|
|
@ -1,6 +1,6 @@
|
|||||||
create table players (
|
create table players (
|
||||||
party_id text not null,
|
party_id text not null,
|
||||||
player_id bigserial unique,
|
player_id bigserial,
|
||||||
created bigint not null,
|
created bigint not null,
|
||||||
nick text not null,
|
nick text not null,
|
||||||
job text not null,
|
job text not null,
|
||||||
@ -9,7 +9,7 @@ create table players (
|
|||||||
create unique index players_nick_job_idx on players(party_id, nick, job);
|
create unique index players_nick_job_idx on players(party_id, nick, job);
|
||||||
|
|
||||||
create table loot (
|
create table loot (
|
||||||
loot_id bigserial unique,
|
loot_id bigserial,
|
||||||
player_id bigint not null,
|
player_id bigint not null,
|
||||||
created bigint not null,
|
created bigint not null,
|
||||||
piece text not null,
|
piece text not null,
|
||||||
@ -29,7 +29,7 @@ create unique index bis_piece_player_id_idx on bis(player_id, piece);
|
|||||||
|
|
||||||
create table users (
|
create table users (
|
||||||
party_id text not null,
|
party_id text not null,
|
||||||
user_id bigserial unique,
|
user_id bigserial,
|
||||||
username text not null,
|
username text not null,
|
||||||
password text not null,
|
password text not null,
|
||||||
permission text not null);
|
permission text not null);
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
update loot set piece = 'left ring' where piece = 'leftRing';
|
|
||||||
update loot set piece = 'right ring' where piece = 'rightRing';
|
|
||||||
|
|
||||||
update bis set piece = 'left ring' where piece = 'leftRing';
|
|
||||||
update bis set piece = 'right ring' where piece = 'rightRing';
|
|
@ -1,5 +0,0 @@
|
|||||||
update loot set piece = 'left ring' where piece = 'leftRing';
|
|
||||||
update loot set piece = 'right ring' where piece = 'rightRing';
|
|
||||||
|
|
||||||
update bis set piece = 'left ring' where piece = 'leftRing';
|
|
||||||
update bis set piece = 'right ring' where piece = 'rightRing';
|
|
@ -27,11 +27,8 @@ me.arcanis.ffxivbis {
|
|||||||
profile = "slick.jdbc.PostgresProfile$"
|
profile = "slick.jdbc.PostgresProfile$"
|
||||||
db {
|
db {
|
||||||
url = "jdbc:postgresql://localhost/ffxivbis"
|
url = "jdbc:postgresql://localhost/ffxivbis"
|
||||||
user = "ffxivbis"
|
user = "user"
|
||||||
password = "ffxivbis"
|
password = "password"
|
||||||
|
|
||||||
connectionPool = disabled
|
|
||||||
keepAliveConnection = yes
|
|
||||||
}
|
}
|
||||||
numThreads = 10
|
numThreads = 10
|
||||||
}
|
}
|
||||||
@ -51,23 +48,8 @@ me.arcanis.ffxivbis {
|
|||||||
|
|
||||||
web {
|
web {
|
||||||
# address to bind, string, required
|
# address to bind, string, required
|
||||||
host = "127.0.0.1"
|
host = "0.0.0.0"
|
||||||
# port to bind, int, required
|
# port to bind, int, required
|
||||||
port = 8000
|
port = 8000
|
||||||
|
|
||||||
# rate limits
|
|
||||||
limits {
|
|
||||||
intetval = 1m
|
|
||||||
max-count = 60
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default-dispatcher {
|
|
||||||
type = Dispatcher
|
|
||||||
executor = "thread-pool-executor"
|
|
||||||
thread-pool-executor {
|
|
||||||
fixed-pool-size = 16
|
|
||||||
}
|
|
||||||
throughput = 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
REST json API description to interact with FFXIVBiS service.
|
|
||||||
|
|
||||||
# Basic workflow
|
|
||||||
|
|
||||||
* Create party using `PUT /api/v1/party` endpoint. It consumes username and password of administrator (which can't be restored). As the result it returns unique id of created party.
|
|
||||||
* Create new users which have access to this party. Note that user belongs to specific party id and in scope of the specified party it must be unique.
|
|
||||||
* Add players with their best in slot sets (probably by using ariyala links).
|
|
||||||
* Add loot items if any.
|
|
||||||
* By using `PUT /api/v1/party/{partyId}/loot` API find players which are better for the specified loot.
|
|
||||||
* Add new loot item to the selected player.
|
|
||||||
|
|
||||||
# Limitations
|
|
||||||
|
|
||||||
# Authentication
|
|
||||||
|
|
||||||
For the most party utils service requires user to be authenticated. User permission can be one of `get`, `post` or `admin`.
|
|
||||||
|
|
||||||
* `admin` permission means that the user is allowed to do anything, especially this permission is required to be able to add or modify users.
|
|
||||||
* `post` permission is required to deal with the most POST API endpoints, but to be precise only endpoints which modifies party content require this permission.
|
|
||||||
* `get` permission is required to have access to party.
|
|
||||||
|
|
||||||
`admin` permission includes any other permissions, `post` allows to perform get requests.
|
|
||||||
|
|
||||||
<security-definitions />
|
|
@ -13,12 +13,12 @@ import akka.http.scaladsl.Http
|
|||||||
import akka.stream.ActorMaterializer
|
import akka.stream.ActorMaterializer
|
||||||
import com.typesafe.scalalogging.StrictLogging
|
import com.typesafe.scalalogging.StrictLogging
|
||||||
import me.arcanis.ffxivbis.http.RootEndpoint
|
import me.arcanis.ffxivbis.http.RootEndpoint
|
||||||
import me.arcanis.ffxivbis.service.impl.DatabaseImpl
|
|
||||||
import me.arcanis.ffxivbis.service.{Ariyala, PartyService}
|
import me.arcanis.ffxivbis.service.{Ariyala, PartyService}
|
||||||
|
import me.arcanis.ffxivbis.service.impl.DatabaseImpl
|
||||||
import me.arcanis.ffxivbis.storage.Migration
|
import me.arcanis.ffxivbis.storage.Migration
|
||||||
|
|
||||||
import scala.concurrent.duration.Duration
|
|
||||||
import scala.concurrent.{Await, ExecutionContext}
|
import scala.concurrent.{Await, ExecutionContext}
|
||||||
|
import scala.concurrent.duration.Duration
|
||||||
import scala.util.{Failure, Success}
|
import scala.util.{Failure, Success}
|
||||||
|
|
||||||
class Application extends Actor with StrictLogging {
|
class Application extends Actor with StrictLogging {
|
||||||
|
@ -11,7 +11,7 @@ package me.arcanis.ffxivbis.http
|
|||||||
import akka.actor.ActorRef
|
import akka.actor.ActorRef
|
||||||
import akka.pattern.ask
|
import akka.pattern.ask
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
import me.arcanis.ffxivbis.models.{BiS, Job}
|
import me.arcanis.ffxivbis.models.{BiS, Job, Piece}
|
||||||
import me.arcanis.ffxivbis.service.Ariyala
|
import me.arcanis.ffxivbis.service.Ariyala
|
||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
|
@ -11,8 +11,8 @@ package me.arcanis.ffxivbis.http
|
|||||||
import akka.actor.ActorRef
|
import akka.actor.ActorRef
|
||||||
import akka.http.scaladsl.model.headers._
|
import akka.http.scaladsl.model.headers._
|
||||||
import akka.http.scaladsl.server.AuthenticationFailedRejection._
|
import akka.http.scaladsl.server.AuthenticationFailedRejection._
|
||||||
import akka.http.scaladsl.server.Directives._
|
|
||||||
import akka.http.scaladsl.server._
|
import akka.http.scaladsl.server._
|
||||||
|
import akka.http.scaladsl.server.Directives._
|
||||||
import akka.pattern.ask
|
import akka.pattern.ask
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
import me.arcanis.ffxivbis.models.{Permission, User}
|
import me.arcanis.ffxivbis.models.{Permission, User}
|
||||||
|
@ -27,7 +27,7 @@ class RootEndpoint(system: ActorSystem, storage: ActorRef, ariyala: ActorRef)
|
|||||||
implicit val timeout: Timeout =
|
implicit val timeout: Timeout =
|
||||||
config.getDuration("me.arcanis.ffxivbis.settings.request-timeout")
|
config.getDuration("me.arcanis.ffxivbis.settings.request-timeout")
|
||||||
|
|
||||||
private val rootApiV1Endpoint: RootApiV1Endpoint = new RootApiV1Endpoint(storage, ariyala, config)
|
private val rootApiV1Endpoint: RootApiV1Endpoint = new RootApiV1Endpoint(storage, ariyala)
|
||||||
private val rootView: RootView = new RootView(storage, ariyala)
|
private val rootView: RootView = new RootView(storage, ariyala)
|
||||||
private val httpLogger = Logger("http")
|
private val httpLogger = Logger("http")
|
||||||
|
|
||||||
|
@ -9,24 +9,16 @@
|
|||||||
package me.arcanis.ffxivbis.http
|
package me.arcanis.ffxivbis.http
|
||||||
|
|
||||||
import com.github.swagger.akka.SwaggerHttpService
|
import com.github.swagger.akka.SwaggerHttpService
|
||||||
import com.github.swagger.akka.model.{Info, License}
|
import com.github.swagger.akka.model.Info
|
||||||
import io.swagger.v3.oas.models.security.SecurityScheme
|
import io.swagger.v3.oas.models.security.SecurityScheme
|
||||||
|
|
||||||
import scala.io.Source
|
|
||||||
|
|
||||||
object Swagger extends SwaggerHttpService {
|
object Swagger extends SwaggerHttpService {
|
||||||
override val apiClasses: Set[Class[_]] = Set(
|
override val apiClasses: Set[Class[_]] = Set(
|
||||||
classOf[api.v1.BiSEndpoint], classOf[api.v1.LootEndpoint],
|
classOf[api.v1.BiSEndpoint], classOf[api.v1.LootEndpoint],
|
||||||
classOf[api.v1.PlayerEndpoint], classOf[api.v1.TypesEndpoint],
|
classOf[api.v1.PlayerEndpoint], classOf[api.v1.UserEndpoint]
|
||||||
classOf[api.v1.UserEndpoint]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
override val info: Info = Info(
|
override val info: Info = Info()
|
||||||
description = Source.fromResource("swagger-info/description.md").mkString,
|
|
||||||
version = getClass.getPackage.getImplementationVersion,
|
|
||||||
title = "FFXIV static loot tracker",
|
|
||||||
license = Some(License("BSD", "https://raw.githubusercontent.com/arcan1s/ffxivbis/master/LICENSE"))
|
|
||||||
)
|
|
||||||
|
|
||||||
private val basicAuth = new SecurityScheme()
|
private val basicAuth = new SecurityScheme()
|
||||||
.description("basic http auth")
|
.description("basic http auth")
|
||||||
|
@ -13,15 +13,16 @@ import akka.http.scaladsl.model.{HttpEntity, StatusCodes}
|
|||||||
import akka.http.scaladsl.server.Directives._
|
import akka.http.scaladsl.server.Directives._
|
||||||
import akka.http.scaladsl.server._
|
import akka.http.scaladsl.server._
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
|
import com.typesafe.scalalogging.StrictLogging
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn
|
import io.swagger.v3.oas.annotations.enums.ParameterIn
|
||||||
import io.swagger.v3.oas.annotations.media.{ArraySchema, Content, Schema}
|
import io.swagger.v3.oas.annotations.media.{ArraySchema, Content, Schema}
|
||||||
import io.swagger.v3.oas.annotations.parameters.RequestBody
|
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
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 io.swagger.v3.oas.annotations.{Operation, Parameter}
|
||||||
import javax.ws.rs._
|
import io.swagger.v3.oas.annotations.parameters.RequestBody
|
||||||
import me.arcanis.ffxivbis.http.api.v1.json._
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement
|
||||||
|
import javax.ws.rs.{Consumes, GET, POST, PUT, Path, Produces}
|
||||||
import me.arcanis.ffxivbis.http.{Authorization, BiSHelper}
|
import me.arcanis.ffxivbis.http.{Authorization, BiSHelper}
|
||||||
|
import me.arcanis.ffxivbis.http.api.v1.json._
|
||||||
import me.arcanis.ffxivbis.models.PlayerId
|
import me.arcanis.ffxivbis.models.PlayerId
|
||||||
|
|
||||||
import scala.util.{Failure, Success}
|
import scala.util.{Failure, Success}
|
||||||
@ -43,14 +44,10 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti
|
|||||||
content = Array(new Content(schema = new Schema(implementation = classOf[PlayerBiSLinkResponse])))),
|
content = Array(new Content(schema = new Schema(implementation = classOf[PlayerBiSLinkResponse])))),
|
||||||
responses = Array(
|
responses = Array(
|
||||||
new ApiResponse(responseCode = "201", description = "Best in slot set has been created"),
|
new ApiResponse(responseCode = "201", description = "Best in slot set has been created"),
|
||||||
new ApiResponse(responseCode = "400", description = "Invalid parameters were supplied",
|
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"),
|
||||||
new ApiResponse(responseCode = "401", description = "Supplied authorization is invalid",
|
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"),
|
||||||
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"))),
|
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("post"))),
|
||||||
tags = Array("best in slot"),
|
tags = Array("best in slot"),
|
||||||
@ -86,12 +83,9 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti
|
|||||||
content = Array(new Content(
|
content = Array(new Content(
|
||||||
array = new ArraySchema(schema = new Schema(implementation = classOf[PlayerResponse]))
|
array = new ArraySchema(schema = new Schema(implementation = classOf[PlayerResponse]))
|
||||||
))),
|
))),
|
||||||
new ApiResponse(responseCode = "401", description = "Supplied authorization is invalid",
|
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"),
|
||||||
new ApiResponse(responseCode = "403", description = "Access is forbidden",
|
new ApiResponse(responseCode = "500", description = "Internal server error"),
|
||||||
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"))),
|
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("get"))),
|
||||||
tags = Array("best in slot"),
|
tags = Array("best in slot"),
|
||||||
@ -125,14 +119,10 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti
|
|||||||
content = Array(new Content(schema = new Schema(implementation = classOf[PieceActionResponse])))),
|
content = Array(new Content(schema = new Schema(implementation = classOf[PieceActionResponse])))),
|
||||||
responses = Array(
|
responses = Array(
|
||||||
new ApiResponse(responseCode = "202", description = "Best in slot set has been modified"),
|
new ApiResponse(responseCode = "202", description = "Best in slot set has been modified"),
|
||||||
new ApiResponse(responseCode = "400", description = "Invalid parameters were supplied",
|
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"),
|
||||||
new ApiResponse(responseCode = "401", description = "Supplied authorization is invalid",
|
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"),
|
||||||
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"))),
|
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("post"))),
|
||||||
tags = Array("best in slot"),
|
tags = Array("best in slot"),
|
||||||
@ -143,7 +133,7 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti
|
|||||||
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
|
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
|
||||||
post {
|
post {
|
||||||
entity(as[PieceActionResponse]) { action =>
|
entity(as[PieceActionResponse]) { action =>
|
||||||
val playerId = action.playerId.withPartyId(partyId)
|
val playerId = action.playerIdResponse.withPartyId(partyId)
|
||||||
onComplete(doModifyBiS(action.action, playerId, action.piece.toPiece)) {
|
onComplete(doModifyBiS(action.action, playerId, action.piece.toPiece)) {
|
||||||
case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
|
case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
|
||||||
case Failure(exception) => throw exception
|
case Failure(exception) => throw exception
|
||||||
|
@ -18,9 +18,6 @@ import spray.json._
|
|||||||
trait HttpHandler extends StrictLogging { this: JsonSupport =>
|
trait HttpHandler extends StrictLogging { this: JsonSupport =>
|
||||||
|
|
||||||
implicit def exceptionHandler: ExceptionHandler = ExceptionHandler {
|
implicit def exceptionHandler: ExceptionHandler = ExceptionHandler {
|
||||||
case ex: IllegalArgumentException =>
|
|
||||||
complete(StatusCodes.BadRequest, ErrorResponse(ex.getMessage))
|
|
||||||
|
|
||||||
case other: Exception =>
|
case other: Exception =>
|
||||||
logger.error("exception during request completion", other)
|
logger.error("exception during request completion", other)
|
||||||
complete(StatusCodes.InternalServerError, ErrorResponse("unknown server error"))
|
complete(StatusCodes.InternalServerError, ErrorResponse("unknown server error"))
|
||||||
|
@ -15,13 +15,13 @@ import akka.http.scaladsl.server._
|
|||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn
|
import io.swagger.v3.oas.annotations.enums.ParameterIn
|
||||||
import io.swagger.v3.oas.annotations.media.{ArraySchema, Content, Schema}
|
import io.swagger.v3.oas.annotations.media.{ArraySchema, Content, Schema}
|
||||||
import io.swagger.v3.oas.annotations.parameters.RequestBody
|
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
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 io.swagger.v3.oas.annotations.{Operation, Parameter}
|
||||||
import javax.ws.rs._
|
import io.swagger.v3.oas.annotations.parameters.RequestBody
|
||||||
import me.arcanis.ffxivbis.http.api.v1.json._
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement
|
||||||
|
import javax.ws.rs.{Consumes, GET, POST, PUT, Path, Produces}
|
||||||
import me.arcanis.ffxivbis.http.{Authorization, LootHelper}
|
import me.arcanis.ffxivbis.http.{Authorization, LootHelper}
|
||||||
|
import me.arcanis.ffxivbis.http.api.v1.json._
|
||||||
import me.arcanis.ffxivbis.models.PlayerId
|
import me.arcanis.ffxivbis.models.PlayerId
|
||||||
|
|
||||||
import scala.util.{Failure, Success}
|
import scala.util.{Failure, Success}
|
||||||
@ -46,12 +46,9 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
content = Array(new Content(
|
content = Array(new Content(
|
||||||
array = new ArraySchema(schema = new Schema(implementation = classOf[PlayerResponse]))
|
array = new ArraySchema(schema = new Schema(implementation = classOf[PlayerResponse]))
|
||||||
))),
|
))),
|
||||||
new ApiResponse(responseCode = "401", description = "Supplied authorization is invalid",
|
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"),
|
||||||
new ApiResponse(responseCode = "403", description = "Access is forbidden",
|
new ApiResponse(responseCode = "500", description = "Internal server error"),
|
||||||
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"))),
|
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("get"))),
|
||||||
tags = Array("loot"),
|
tags = Array("loot"),
|
||||||
@ -84,14 +81,10 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
content = Array(new Content(schema = new Schema(implementation = classOf[PieceActionResponse])))),
|
content = Array(new Content(schema = new Schema(implementation = classOf[PieceActionResponse])))),
|
||||||
responses = Array(
|
responses = Array(
|
||||||
new ApiResponse(responseCode = "202", description = "Loot list has been modified"),
|
new ApiResponse(responseCode = "202", description = "Loot list has been modified"),
|
||||||
new ApiResponse(responseCode = "400", description = "Invalid parameters were supplied",
|
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"),
|
||||||
new ApiResponse(responseCode = "401", description = "Supplied authorization is invalid",
|
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"),
|
||||||
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"))),
|
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("post"))),
|
||||||
tags = Array("loot"),
|
tags = Array("loot"),
|
||||||
@ -102,7 +95,7 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
|
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
|
||||||
post {
|
post {
|
||||||
entity(as[PieceActionResponse]) { action =>
|
entity(as[PieceActionResponse]) { action =>
|
||||||
val playerId = action.playerId.withPartyId(partyId)
|
val playerId = action.playerIdResponse.withPartyId(partyId)
|
||||||
onComplete(doModifyLoot(action.action, playerId, action.piece.toPiece)) {
|
onComplete(doModifyLoot(action.action, playerId, action.piece.toPiece)) {
|
||||||
case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
|
case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
|
||||||
case Failure(exception) => throw exception
|
case Failure(exception) => throw exception
|
||||||
@ -128,14 +121,10 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
content = Array(new Content(
|
content = Array(new Content(
|
||||||
array = new ArraySchema(schema = new Schema(implementation = classOf[PlayerIdWithCountersResponse])),
|
array = new ArraySchema(schema = new Schema(implementation = classOf[PlayerIdWithCountersResponse])),
|
||||||
))),
|
))),
|
||||||
new ApiResponse(responseCode = "400", description = "Invalid parameters were supplied",
|
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"),
|
||||||
new ApiResponse(responseCode = "401", description = "Supplied authorization is invalid",
|
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"),
|
||||||
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"))),
|
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("get"))),
|
||||||
tags = Array("loot"),
|
tags = Array("loot"),
|
||||||
|
@ -15,13 +15,13 @@ import akka.http.scaladsl.server._
|
|||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn
|
import io.swagger.v3.oas.annotations.enums.ParameterIn
|
||||||
import io.swagger.v3.oas.annotations.media.{ArraySchema, Content, Schema}
|
import io.swagger.v3.oas.annotations.media.{ArraySchema, Content, Schema}
|
||||||
import io.swagger.v3.oas.annotations.parameters.RequestBody
|
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
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 io.swagger.v3.oas.annotations.{Operation, Parameter}
|
||||||
import javax.ws.rs._
|
import io.swagger.v3.oas.annotations.parameters.RequestBody
|
||||||
import me.arcanis.ffxivbis.http.api.v1.json._
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement
|
||||||
|
import javax.ws.rs.{Consumes, GET, POST, Path, Produces}
|
||||||
import me.arcanis.ffxivbis.http.{Authorization, PlayerHelper}
|
import me.arcanis.ffxivbis.http.{Authorization, PlayerHelper}
|
||||||
|
import me.arcanis.ffxivbis.http.api.v1.json._
|
||||||
import me.arcanis.ffxivbis.models.PlayerId
|
import me.arcanis.ffxivbis.models.PlayerId
|
||||||
|
|
||||||
import scala.util.{Failure, Success}
|
import scala.util.{Failure, Success}
|
||||||
@ -46,12 +46,9 @@ class PlayerEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit
|
|||||||
content = Array(new Content(
|
content = Array(new Content(
|
||||||
array = new ArraySchema(schema = new Schema(implementation = classOf[PlayerResponse])),
|
array = new ArraySchema(schema = new Schema(implementation = classOf[PlayerResponse])),
|
||||||
))),
|
))),
|
||||||
new ApiResponse(responseCode = "401", description = "Supplied authorization is invalid",
|
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"),
|
||||||
new ApiResponse(responseCode = "403", description = "Access is forbidden",
|
new ApiResponse(responseCode = "500", description = "Internal server error"),
|
||||||
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"))),
|
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("get"))),
|
||||||
tags = Array("party"),
|
tags = Array("party"),
|
||||||
@ -84,14 +81,10 @@ class PlayerEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit
|
|||||||
content = Array(new Content(schema = new Schema(implementation = classOf[PlayerActionResponse])))),
|
content = Array(new Content(schema = new Schema(implementation = classOf[PlayerActionResponse])))),
|
||||||
responses = Array(
|
responses = Array(
|
||||||
new ApiResponse(responseCode = "202", description = "Party has been modified"),
|
new ApiResponse(responseCode = "202", description = "Party has been modified"),
|
||||||
new ApiResponse(responseCode = "400", description = "Invalid parameters were supplied",
|
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"),
|
||||||
new ApiResponse(responseCode = "401", description = "Supplied authorization is invalid",
|
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"),
|
||||||
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"))),
|
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("post"))),
|
||||||
tags = Array("party"),
|
tags = Array("party"),
|
||||||
@ -101,7 +94,7 @@ class PlayerEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit
|
|||||||
extractExecutionContext { implicit executionContext =>
|
extractExecutionContext { implicit executionContext =>
|
||||||
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
|
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
|
||||||
entity(as[PlayerActionResponse]) { action =>
|
entity(as[PlayerActionResponse]) { action =>
|
||||||
val player = action.playerId.toPlayer.copy(partyId = partyId)
|
val player = action.playerIdResponse.toPlayer.copy(partyId = partyId)
|
||||||
onComplete(doModifyPlayer(action.action, player)) {
|
onComplete(doModifyPlayer(action.action, player)) {
|
||||||
case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
|
case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty)
|
||||||
case Failure(exception) => throw exception
|
case Failure(exception) => throw exception
|
||||||
|
@ -12,24 +12,21 @@ import akka.actor.ActorRef
|
|||||||
import akka.http.scaladsl.server.Directives._
|
import akka.http.scaladsl.server.Directives._
|
||||||
import akka.http.scaladsl.server.Route
|
import akka.http.scaladsl.server.Route
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
import com.typesafe.config.Config
|
|
||||||
import me.arcanis.ffxivbis.http.api.v1.json.JsonSupport
|
import me.arcanis.ffxivbis.http.api.v1.json.JsonSupport
|
||||||
|
|
||||||
class RootApiV1Endpoint(storage: ActorRef, ariyala: ActorRef, config: Config)
|
class RootApiV1Endpoint(storage: ActorRef, ariyala: ActorRef)
|
||||||
(implicit timeout: Timeout)
|
(implicit timeout: Timeout)
|
||||||
extends JsonSupport with HttpHandler {
|
extends JsonSupport with HttpHandler {
|
||||||
|
|
||||||
private val biSEndpoint = new BiSEndpoint(storage, ariyala)
|
private val biSEndpoint = new BiSEndpoint(storage, ariyala)
|
||||||
private val lootEndpoint = new LootEndpoint(storage)
|
private val lootEndpoint = new LootEndpoint(storage)
|
||||||
private val playerEndpoint = new PlayerEndpoint(storage, ariyala)
|
private val playerEndpoint = new PlayerEndpoint(storage, ariyala)
|
||||||
private val typesEndpoint = new TypesEndpoint(config)
|
|
||||||
private val userEndpoint = new UserEndpoint(storage)
|
private val userEndpoint = new UserEndpoint(storage)
|
||||||
|
|
||||||
def route: Route =
|
def route: Route =
|
||||||
handleExceptions(exceptionHandler) {
|
handleExceptions(exceptionHandler) {
|
||||||
handleRejections(rejectionHandler) {
|
handleRejections(rejectionHandler) {
|
||||||
biSEndpoint.route ~ lootEndpoint.route ~ playerEndpoint.route ~
|
biSEndpoint.route ~ lootEndpoint.route ~ playerEndpoint.route ~ userEndpoint.route
|
||||||
typesEndpoint.route ~ userEndpoint.route
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,109 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019 Evgeniy Alekseev.
|
|
||||||
*
|
|
||||||
* This file is part of ffxivbis
|
|
||||||
* (see https://github.com/arcan1s/ffxivbis).
|
|
||||||
*
|
|
||||||
* License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
|
|
||||||
*/
|
|
||||||
package me.arcanis.ffxivbis.http.api.v1
|
|
||||||
|
|
||||||
import akka.http.scaladsl.server.Directives._
|
|
||||||
import akka.http.scaladsl.server._
|
|
||||||
import com.typesafe.config.Config
|
|
||||||
import io.swagger.v3.oas.annotations.media.{ArraySchema, Content, Schema}
|
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
|
||||||
import io.swagger.v3.oas.annotations.Operation
|
|
||||||
import javax.ws.rs._
|
|
||||||
import me.arcanis.ffxivbis.http.api.v1.json._
|
|
||||||
import me.arcanis.ffxivbis.models.{Job, Party, Permission, Piece}
|
|
||||||
|
|
||||||
@Path("api/v1")
|
|
||||||
class TypesEndpoint(config: Config) extends JsonSupport {
|
|
||||||
|
|
||||||
def route: Route = getJobs ~ getPermissions ~ getPieces ~ getPriority
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("types/jobs")
|
|
||||||
@Produces(value = Array("application/json"))
|
|
||||||
@Operation(summary = "jobs list", description = "Returns the available jobs",
|
|
||||||
responses = Array(
|
|
||||||
new ApiResponse(responseCode = "200", description = "List of available jobs",
|
|
||||||
content = Array(new Content(
|
|
||||||
array = new ArraySchema(schema = new Schema(implementation = classOf[String]))
|
|
||||||
))),
|
|
||||||
new ApiResponse(responseCode = "500", description = "Internal server error",
|
|
||||||
content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))),
|
|
||||||
),
|
|
||||||
tags = Array("types"),
|
|
||||||
)
|
|
||||||
def getJobs: Route =
|
|
||||||
path("types" / "jobs") {
|
|
||||||
get {
|
|
||||||
complete(Job.availableWithAnyJob.map(_.toString))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("types/permissions")
|
|
||||||
@Produces(value = Array("application/json"))
|
|
||||||
@Operation(summary = "permissions list", description = "Returns the available permissions",
|
|
||||||
responses = Array(
|
|
||||||
new ApiResponse(responseCode = "200", description = "List of available permissions",
|
|
||||||
content = Array(new Content(
|
|
||||||
array = new ArraySchema(schema = new Schema(implementation = classOf[String]))
|
|
||||||
))),
|
|
||||||
new ApiResponse(responseCode = "500", description = "Internal server error",
|
|
||||||
content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))),
|
|
||||||
),
|
|
||||||
tags = Array("types"),
|
|
||||||
)
|
|
||||||
def getPermissions: Route =
|
|
||||||
path("types" / "permissions") {
|
|
||||||
get {
|
|
||||||
complete(Permission.values.toSeq.sorted.map(_.toString))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("types/pieces")
|
|
||||||
@Produces(value = Array("application/json"))
|
|
||||||
@Operation(summary = "pieces list", description = "Returns the available pieces",
|
|
||||||
responses = Array(
|
|
||||||
new ApiResponse(responseCode = "200", description = "List of available pieces",
|
|
||||||
content = Array(new Content(
|
|
||||||
array = new ArraySchema(schema = new Schema(implementation = classOf[String]))
|
|
||||||
))),
|
|
||||||
new ApiResponse(responseCode = "500", description = "Internal server error",
|
|
||||||
content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))),
|
|
||||||
),
|
|
||||||
tags = Array("types"),
|
|
||||||
)
|
|
||||||
def getPieces: Route =
|
|
||||||
path("types" / "pieces") {
|
|
||||||
get {
|
|
||||||
complete(Piece.available)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("types/priority")
|
|
||||||
@Produces(value = Array("application/json"))
|
|
||||||
@Operation(summary = "priority list", description = "Returns the current priority list",
|
|
||||||
responses = Array(
|
|
||||||
new ApiResponse(responseCode = "200", description = "Priority order",
|
|
||||||
content = Array(new Content(
|
|
||||||
array = new ArraySchema(schema = new Schema(implementation = classOf[String]))
|
|
||||||
))),
|
|
||||||
new ApiResponse(responseCode = "500", description = "Internal server error",
|
|
||||||
content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))),
|
|
||||||
),
|
|
||||||
tags = Array("types"),
|
|
||||||
)
|
|
||||||
def getPriority: Route =
|
|
||||||
path("types" / "priority") {
|
|
||||||
get {
|
|
||||||
complete(Party.getRules(config))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,13 +15,13 @@ import akka.http.scaladsl.server._
|
|||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn
|
import io.swagger.v3.oas.annotations.enums.ParameterIn
|
||||||
import io.swagger.v3.oas.annotations.media.{ArraySchema, Content, Schema}
|
import io.swagger.v3.oas.annotations.media.{ArraySchema, Content, Schema}
|
||||||
import io.swagger.v3.oas.annotations.parameters.RequestBody
|
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
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 io.swagger.v3.oas.annotations.{Operation, Parameter}
|
||||||
import javax.ws.rs._
|
import io.swagger.v3.oas.annotations.parameters.RequestBody
|
||||||
import me.arcanis.ffxivbis.http.api.v1.json._
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement
|
||||||
|
import javax.ws.rs.{Consumes, DELETE, GET, POST, PUT, Path, Produces}
|
||||||
import me.arcanis.ffxivbis.http.{Authorization, UserHelper}
|
import me.arcanis.ffxivbis.http.{Authorization, UserHelper}
|
||||||
|
import me.arcanis.ffxivbis.http.api.v1.json._
|
||||||
import me.arcanis.ffxivbis.models.Permission
|
import me.arcanis.ffxivbis.models.Permission
|
||||||
|
|
||||||
import scala.util.{Failure, Success}
|
import scala.util.{Failure, Success}
|
||||||
@ -40,12 +40,9 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
content = Array(new Content(schema = new Schema(implementation = classOf[UserResponse])))),
|
content = Array(new Content(schema = new Schema(implementation = classOf[UserResponse])))),
|
||||||
responses = Array(
|
responses = Array(
|
||||||
new ApiResponse(responseCode = "200", 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 = "400", description = "Invalid parameters were supplied"),
|
||||||
content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))),
|
new ApiResponse(responseCode = "406", description = "Party with the specified ID already exists"),
|
||||||
new ApiResponse(responseCode = "406", description = "Party with the specified ID already exists",
|
new ApiResponse(responseCode = "500", description = "Internal server error"),
|
||||||
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])))),
|
|
||||||
),
|
),
|
||||||
tags = Array("party"),
|
tags = Array("party"),
|
||||||
)
|
)
|
||||||
@ -79,14 +76,10 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
content = Array(new Content(schema = new Schema(implementation = classOf[UserResponse])))),
|
content = Array(new Content(schema = new Schema(implementation = classOf[UserResponse])))),
|
||||||
responses = Array(
|
responses = Array(
|
||||||
new ApiResponse(responseCode = "201", description = "User has been created"),
|
new ApiResponse(responseCode = "201", description = "User has been created"),
|
||||||
new ApiResponse(responseCode = "400", description = "Invalid parameters were supplied",
|
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"),
|
||||||
new ApiResponse(responseCode = "401", description = "Supplied authorization is invalid",
|
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"),
|
||||||
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("admin"))),
|
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("admin"))),
|
||||||
tags = Array("users"),
|
tags = Array("users"),
|
||||||
@ -117,12 +110,9 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
),
|
),
|
||||||
responses = Array(
|
responses = Array(
|
||||||
new ApiResponse(responseCode = "202", description = "User has been removed"),
|
new ApiResponse(responseCode = "202", description = "User has been removed"),
|
||||||
new ApiResponse(responseCode = "401", description = "Supplied authorization is invalid",
|
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"),
|
||||||
new ApiResponse(responseCode = "403", description = "Access is forbidden",
|
new ApiResponse(responseCode = "500", description = "Internal server error"),
|
||||||
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("admin"))),
|
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("admin"))),
|
||||||
tags = Array("users"),
|
tags = Array("users"),
|
||||||
@ -153,12 +143,9 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
content = Array(new Content(
|
content = Array(new Content(
|
||||||
array = new ArraySchema(schema = new Schema(implementation = classOf[UserResponse])),
|
array = new ArraySchema(schema = new Schema(implementation = classOf[UserResponse])),
|
||||||
))),
|
))),
|
||||||
new ApiResponse(responseCode = "401", description = "Supplied authorization is invalid",
|
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"),
|
||||||
new ApiResponse(responseCode = "403", description = "Access is forbidden",
|
new ApiResponse(responseCode = "500", description = "Internal server error"),
|
||||||
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("admin"))),
|
security = Array(new SecurityRequirement(name = "basic auth", scopes = Array("admin"))),
|
||||||
tags = Array("users"),
|
tags = Array("users"),
|
||||||
|
@ -13,4 +13,4 @@ import io.swagger.v3.oas.annotations.media.Schema
|
|||||||
case class PieceActionResponse(
|
case class PieceActionResponse(
|
||||||
@Schema(description = "action to perform", required = true, `type` = "string", allowableValues = Array("add", "remove")) action: ApiAction.Value,
|
@Schema(description = "action to perform", required = true, `type` = "string", allowableValues = Array("add", "remove")) action: ApiAction.Value,
|
||||||
@Schema(description = "piece description", required = true) piece: PieceResponse,
|
@Schema(description = "piece description", required = true) piece: PieceResponse,
|
||||||
@Schema(description = "player description", required = true) playerId: PlayerIdResponse)
|
@Schema(description = "player description", required = true) playerIdResponse: PlayerIdResponse)
|
||||||
|
@ -12,4 +12,4 @@ import io.swagger.v3.oas.annotations.media.Schema
|
|||||||
|
|
||||||
case class PlayerActionResponse(
|
case class PlayerActionResponse(
|
||||||
@Schema(description = "action to perform", required = true, `type` = "string", allowableValues = Array("add", "remove"), example = "add") action: ApiAction.Value,
|
@Schema(description = "action to perform", required = true, `type` = "string", allowableValues = Array("add", "remove"), example = "add") action: ApiAction.Value,
|
||||||
@Schema(description = "player description", required = true) playerId: PlayerResponse)
|
@Schema(description = "player description", required = true) playerIdResponse: PlayerResponse)
|
||||||
|
@ -37,7 +37,6 @@ class BasePartyView(override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
object BasePartyView {
|
object BasePartyView {
|
||||||
import scalatags.Text
|
import scalatags.Text
|
||||||
import scalatags.Text.all._
|
import scalatags.Text.all._
|
||||||
import scalatags.Text.tags2.{title => titleTag}
|
|
||||||
|
|
||||||
def root(partyId: String): Text.TypedTag[String] =
|
def root(partyId: String): Text.TypedTag[String] =
|
||||||
a(href:=s"/party/$partyId", title:="root")("root")
|
a(href:=s"/party/$partyId", title:="root")("root")
|
||||||
@ -46,7 +45,7 @@ object BasePartyView {
|
|||||||
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
|
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
|
||||||
html(lang:="en",
|
html(lang:="en",
|
||||||
head(
|
head(
|
||||||
titleTag(s"Party $partyId"),
|
title:=s"Party $partyId",
|
||||||
link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css")
|
link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css")
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -86,13 +86,12 @@ class BiSView(override val storage: ActorRef, ariyala: ActorRef)(implicit timeou
|
|||||||
|
|
||||||
object BiSView {
|
object BiSView {
|
||||||
import scalatags.Text.all._
|
import scalatags.Text.all._
|
||||||
import scalatags.Text.tags2.{title => titleTag}
|
|
||||||
|
|
||||||
def template(partyId: String, party: Seq[Player], error: Option[String]): String =
|
def template(partyId: String, party: Seq[Player], error: Option[String]): String =
|
||||||
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
|
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
|
||||||
html(lang:="en",
|
html(lang:="en",
|
||||||
head(
|
head(
|
||||||
titleTag("Best in slot"),
|
title:="Best in slot",
|
||||||
link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css")
|
link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css")
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ import akka.http.scaladsl.server.Directives._
|
|||||||
import akka.http.scaladsl.server._
|
import akka.http.scaladsl.server._
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
import me.arcanis.ffxivbis.http.UserHelper
|
import me.arcanis.ffxivbis.http.UserHelper
|
||||||
import me.arcanis.ffxivbis.models.{Permission, User}
|
import me.arcanis.ffxivbis.models.{Party, Permission, User}
|
||||||
|
|
||||||
import scala.util.{Failure, Success}
|
import scala.util.{Failure, Success}
|
||||||
|
|
||||||
@ -54,13 +54,12 @@ class IndexView(storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
|
|
||||||
object IndexView {
|
object IndexView {
|
||||||
import scalatags.Text.all._
|
import scalatags.Text.all._
|
||||||
import scalatags.Text.tags2.{title => titleTag}
|
|
||||||
|
|
||||||
def template: String =
|
def template: String =
|
||||||
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
|
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
|
||||||
html(
|
html(
|
||||||
head(
|
head(
|
||||||
titleTag("FFXIV loot helper"),
|
title:="FFXIV loot helper",
|
||||||
link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css")
|
link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css")
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ import akka.http.scaladsl.server.Directives._
|
|||||||
import akka.http.scaladsl.server.Route
|
import akka.http.scaladsl.server.Route
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
import me.arcanis.ffxivbis.http.{Authorization, LootHelper}
|
import me.arcanis.ffxivbis.http.{Authorization, LootHelper}
|
||||||
import me.arcanis.ffxivbis.models.{Job, Piece, PlayerIdWithCounters}
|
import me.arcanis.ffxivbis.models.{Piece, PlayerIdWithCounters}
|
||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
import scala.util.{Failure, Success, Try}
|
import scala.util.{Failure, Success, Try}
|
||||||
@ -43,9 +43,9 @@ class LootSuggestView(override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
extractExecutionContext { implicit executionContext =>
|
extractExecutionContext { implicit executionContext =>
|
||||||
authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
|
authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
|
||||||
post {
|
post {
|
||||||
formFields("piece".as[String], "job".as[String], "is_tome".as[String].?) { (piece, job, maybeTome) =>
|
formFields("piece".as[String], "is_tome".as[String].?) { (piece, maybeTome) =>
|
||||||
import me.arcanis.ffxivbis.utils.Implicits._
|
import me.arcanis.ffxivbis.utils.Implicits._
|
||||||
val maybePiece = Try(Piece(piece, maybeTome, Job.withName(job))).toOption
|
val maybePiece = Try(Piece(piece, maybeTome)).toOption
|
||||||
|
|
||||||
onComplete(suggestLootCall(partyId, maybePiece)) {
|
onComplete(suggestLootCall(partyId, maybePiece)) {
|
||||||
case Success(players) =>
|
case Success(players) =>
|
||||||
@ -71,13 +71,12 @@ class LootSuggestView(override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
|
|
||||||
object LootSuggestView {
|
object LootSuggestView {
|
||||||
import scalatags.Text.all._
|
import scalatags.Text.all._
|
||||||
import scalatags.Text.tags2.{title => titleTag}
|
|
||||||
|
|
||||||
def template(partyId: String, party: Seq[PlayerIdWithCounters], piece: Option[Piece], error: Option[String]): String =
|
def template(partyId: String, party: Seq[PlayerIdWithCounters], piece: Option[Piece], error: Option[String]): String =
|
||||||
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
|
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
|
||||||
html(lang:="en",
|
html(lang:="en",
|
||||||
head(
|
head(
|
||||||
titleTag("Suggest loot"),
|
title:="Suggest loot",
|
||||||
link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css")
|
link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css")
|
||||||
),
|
),
|
||||||
|
|
||||||
@ -90,8 +89,6 @@ object LootSuggestView {
|
|||||||
form(action:=s"/party/$partyId/suggest", method:="post")(
|
form(action:=s"/party/$partyId/suggest", method:="post")(
|
||||||
select(name:="piece", id:="piece", title:="piece")
|
select(name:="piece", id:="piece", title:="piece")
|
||||||
(for (piece <- Piece.available) yield option(piece)),
|
(for (piece <- Piece.available) yield option(piece)),
|
||||||
select(name:="job", id:="job", title:="job")
|
|
||||||
(for (job <- Job.availableWithAnyJob) yield option(job.toString)),
|
|
||||||
input(name:="is_tome", id:="is_tome", title:="is tome", `type`:="checkbox"),
|
input(name:="is_tome", id:="is_tome", title:="is tome", `type`:="checkbox"),
|
||||||
label(`for`:="is_tome")("is tome gear"),
|
label(`for`:="is_tome")("is tome gear"),
|
||||||
input(name:="suggest", id:="suggest", `type`:="submit", value:="suggest")
|
input(name:="suggest", id:="suggest", `type`:="submit", value:="suggest")
|
||||||
|
@ -79,13 +79,12 @@ class LootView (override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
|
|
||||||
object LootView {
|
object LootView {
|
||||||
import scalatags.Text.all._
|
import scalatags.Text.all._
|
||||||
import scalatags.Text.tags2.{title => titleTag}
|
|
||||||
|
|
||||||
def template(partyId: String, party: Seq[Player], error: Option[String]): String =
|
def template(partyId: String, party: Seq[Player], error: Option[String]): String =
|
||||||
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
|
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
|
||||||
html(lang:="en",
|
html(lang:="en",
|
||||||
head(
|
head(
|
||||||
titleTag("Loot"),
|
title:="Loot",
|
||||||
link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css")
|
link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css")
|
||||||
),
|
),
|
||||||
|
|
||||||
@ -113,7 +112,7 @@ object LootView {
|
|||||||
th("is tome"),
|
th("is tome"),
|
||||||
th("")
|
th("")
|
||||||
),
|
),
|
||||||
for (player <- party; piece <- player.loot) yield tr(
|
for (player <- party; piece <- player.bis.pieces) yield tr(
|
||||||
td(`class`:="include_search")(player.playerId.toString),
|
td(`class`:="include_search")(player.playerId.toString),
|
||||||
td(`class`:="include_search")(piece.piece),
|
td(`class`:="include_search")(piece.piece),
|
||||||
td(piece.isTomeToString),
|
td(piece.isTomeToString),
|
||||||
|
@ -14,7 +14,7 @@ import akka.http.scaladsl.server.Directives._
|
|||||||
import akka.http.scaladsl.server.Route
|
import akka.http.scaladsl.server.Route
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
import me.arcanis.ffxivbis.http.{Authorization, PlayerHelper}
|
import me.arcanis.ffxivbis.http.{Authorization, PlayerHelper}
|
||||||
import me.arcanis.ffxivbis.models._
|
import me.arcanis.ffxivbis.models.{BiS, Job, Player, PlayerId, PlayerIdWithCounters}
|
||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
|
|
||||||
@ -74,13 +74,12 @@ class PlayerView(override val storage: ActorRef, ariyala: ActorRef)(implicit tim
|
|||||||
|
|
||||||
object PlayerView {
|
object PlayerView {
|
||||||
import scalatags.Text.all._
|
import scalatags.Text.all._
|
||||||
import scalatags.Text.tags2.{title => titleTag}
|
|
||||||
|
|
||||||
def template(partyId: String, party: Seq[PlayerIdWithCounters], error: Option[String]): String =
|
def template(partyId: String, party: Seq[PlayerIdWithCounters], error: Option[String]): String =
|
||||||
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
|
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
|
||||||
html(lang:="en",
|
html(lang:="en",
|
||||||
head(
|
head(
|
||||||
titleTag("Party"),
|
title:="Party",
|
||||||
link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css")
|
link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css")
|
||||||
),
|
),
|
||||||
|
|
||||||
@ -93,7 +92,7 @@ object PlayerView {
|
|||||||
form(action:=s"/party/$partyId/players", method:="post")(
|
form(action:=s"/party/$partyId/players", method:="post")(
|
||||||
input(name:="nick", id:="nick", placeholder:="nick", title:="nick", `type`:="nick"),
|
input(name:="nick", id:="nick", placeholder:="nick", title:="nick", `type`:="nick"),
|
||||||
select(name:="job", id:="job", title:="job")
|
select(name:="job", id:="job", title:="job")
|
||||||
(for (job <- Job.available) yield option(job.toString)),
|
(for (job <- Job.jobs) yield option(job.toString)),
|
||||||
input(name:="link", id:="link", placeholder:="player bis link", title:="link", `type`:="text"),
|
input(name:="link", id:="link", placeholder:="player bis link", title:="link", `type`:="text"),
|
||||||
input(name:="prioiry", id:="priority", placeholder:="priority", title:="priority", `type`:="number", value:="0"),
|
input(name:="prioiry", id:="priority", placeholder:="priority", title:="priority", `type`:="number", value:="0"),
|
||||||
input(name:="action", id:="action", `type`:="hidden", value:="add"),
|
input(name:="action", id:="action", `type`:="hidden", value:="add"),
|
||||||
|
@ -77,13 +77,12 @@ class UserView(override val storage: ActorRef)(implicit timeout: Timeout)
|
|||||||
|
|
||||||
object UserView {
|
object UserView {
|
||||||
import scalatags.Text.all._
|
import scalatags.Text.all._
|
||||||
import scalatags.Text.tags2.{title => titleTag}
|
|
||||||
|
|
||||||
def template(partyId: String, users: Seq[User], error: Option[String]) =
|
def template(partyId: String, users: Seq[User], error: Option[String]) =
|
||||||
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
|
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
|
||||||
html(lang:="en",
|
html(lang:="en",
|
||||||
head(
|
head(
|
||||||
titleTag("Users"),
|
title:="Users",
|
||||||
link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css")
|
link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css")
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -50,8 +50,8 @@ case class BiS(weapon: Option[Piece],
|
|||||||
"ears" -> ears,
|
"ears" -> ears,
|
||||||
"neck" -> neck,
|
"neck" -> neck,
|
||||||
"wrist" -> wrist,
|
"wrist" -> wrist,
|
||||||
"left ring" -> leftRing,
|
"leftRing" -> leftRing,
|
||||||
"right ring" -> rightRing
|
"rightRing" -> rightRing
|
||||||
) + (name -> piece)
|
) + (name -> piece)
|
||||||
BiS(params)
|
BiS(params)
|
||||||
}
|
}
|
||||||
@ -70,8 +70,8 @@ object BiS {
|
|||||||
data.get("ears").flatten,
|
data.get("ears").flatten,
|
||||||
data.get("neck").flatten,
|
data.get("neck").flatten,
|
||||||
data.get("wrist").flatten,
|
data.get("wrist").flatten,
|
||||||
data.get("left ring").flatten,
|
data.get("leftRing").flatten,
|
||||||
data.get("right ring").flatten)
|
data.get("rightRing").flatten)
|
||||||
|
|
||||||
def apply(): BiS = BiS(Seq.empty)
|
def apply(): BiS = BiS(Seq.empty)
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ object Job {
|
|||||||
def equality(objRepr: String): Boolean = objRepr match {
|
def equality(objRepr: String): Boolean = objRepr match {
|
||||||
case _ if objRepr == AnyJob.toString => true
|
case _ if objRepr == AnyJob.toString => true
|
||||||
case _ if this.toString == AnyJob.toString => true
|
case _ if this.toString == AnyJob.toString => true
|
||||||
case _ => this.toString == objRepr
|
case _ => this.toString == obj.toString
|
||||||
}
|
}
|
||||||
|
|
||||||
canEqual(obj) && equality(obj.toString)
|
canEqual(obj) && equality(obj.toString)
|
||||||
@ -96,14 +96,8 @@ object Job {
|
|||||||
case object SMN extends Casters
|
case object SMN extends Casters
|
||||||
case object RDM extends Casters
|
case object RDM extends Casters
|
||||||
|
|
||||||
lazy val available: Seq[Job] =
|
lazy val jobs: Seq[Job] =
|
||||||
Seq(PLD, WAR, DRK, GNB, WHM, SCH, AST, MNK, DRG, NIN, SAM, BRD, MCH, DNC, BLM, SMN, RDM)
|
Seq(PLD, WAR, DRK, GNB, WHM, SCH, AST, MNK, DRG, NIN, SAM, BRD, MCH, DNC, BLM, SMN, RDM)
|
||||||
lazy val availableWithAnyJob: Seq[Job] = available.prepended(AnyJob)
|
|
||||||
|
|
||||||
def withName(job: String): Job.Job =
|
def withName(job: String): Job.Job = jobs.find(_.toString == job.toUpperCase).getOrElse(AnyJob)
|
||||||
availableWithAnyJob.find(_.toString.equalsIgnoreCase(job.toUpperCase)) match {
|
|
||||||
case Some(value) => value
|
|
||||||
case None if job.isEmpty => AnyJob
|
|
||||||
case _ => throw new IllegalArgumentException("Invalid or unknown job")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,9 @@ case class Party(partyId: String, rules: Seq[String], players: Map[PlayerId, Pla
|
|||||||
}
|
}
|
||||||
|
|
||||||
object Party {
|
object Party {
|
||||||
|
private def getRules(config: Config): Seq[String] =
|
||||||
|
config.getStringList("me.arcanis.ffxivbis.settings.priority").asScala.toSeq
|
||||||
|
|
||||||
def apply(partyId: Option[String], config: Config): Party =
|
def apply(partyId: Option[String], config: Config): Party =
|
||||||
new Party(partyId.getOrElse(randomPartyId), getRules(config), Map.empty)
|
new Party(partyId.getOrElse(randomPartyId), getRules(config), Map.empty)
|
||||||
|
|
||||||
@ -52,8 +55,5 @@ object Party {
|
|||||||
Party(partyId, getRules(config), playersWithItems)
|
Party(partyId, getRules(config), playersWithItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
def getRules(config: Config): Seq[String] =
|
|
||||||
config.getStringList("me.arcanis.ffxivbis.settings.priority").asScala.toSeq
|
|
||||||
|
|
||||||
def randomPartyId: String = Random.alphanumeric.take(20).mkString
|
def randomPartyId: String = Random.alphanumeric.take(20).mkString
|
||||||
}
|
}
|
||||||
|
@ -108,7 +108,9 @@ object Piece {
|
|||||||
case "ears" => Ears(isTome, job)
|
case "ears" => Ears(isTome, job)
|
||||||
case "neck" => Neck(isTome, job)
|
case "neck" => Neck(isTome, job)
|
||||||
case "wrist" => Wrist(isTome, job)
|
case "wrist" => Wrist(isTome, job)
|
||||||
case ring @ ("ring" | "left ring" | "right ring") => Ring(isTome, job, ring)
|
case "ring" => Ring(isTome, job)
|
||||||
|
case "leftring" => Ring(isTome, job).copy(piece = "leftRing")
|
||||||
|
case "rightring" => Ring(isTome, job).copy(piece = "rightRing")
|
||||||
case "accessory upgrade" => AccessoryUpgrade
|
case "accessory upgrade" => AccessoryUpgrade
|
||||||
case "body upgrade" => BodyUpgrade
|
case "body upgrade" => BodyUpgrade
|
||||||
case "weapon upgrade" => WeaponUpgrade
|
case "weapon upgrade" => WeaponUpgrade
|
||||||
@ -117,6 +119,5 @@ object Piece {
|
|||||||
|
|
||||||
lazy val available: Seq[String] = Seq("weapon",
|
lazy val available: Seq[String] = Seq("weapon",
|
||||||
"head", "body", "hands", "waist", "legs", "feet",
|
"head", "body", "hands", "waist", "legs", "feet",
|
||||||
"ears", "neck", "wrist", "left ring", "right ring",
|
"ears", "neck", "wrist", "leftRing", "rightRing")
|
||||||
"accessory upgrade", "body upgrade", "weapon upgrade")
|
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,8 @@ package me.arcanis.ffxivbis.service
|
|||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
|
||||||
import akka.actor.{Actor, Props}
|
import akka.actor.{Actor, Props}
|
||||||
import akka.http.scaladsl.Http
|
|
||||||
import akka.http.scaladsl.model._
|
import akka.http.scaladsl.model._
|
||||||
|
import akka.http.scaladsl.Http
|
||||||
import akka.pattern.pipe
|
import akka.pattern.pipe
|
||||||
import akka.stream.ActorMaterializer
|
import akka.stream.ActorMaterializer
|
||||||
import akka.stream.scaladsl.{Keep, Sink}
|
import akka.stream.scaladsl.{Keep, Sink}
|
||||||
@ -34,8 +34,7 @@ class Ariyala extends Actor with StrictLogging {
|
|||||||
|
|
||||||
private val http = Http()(context.system)
|
private val http = Http()(context.system)
|
||||||
implicit private val materializer: ActorMaterializer = ActorMaterializer()
|
implicit private val materializer: ActorMaterializer = ActorMaterializer()
|
||||||
implicit private val executionContext: ExecutionContext =
|
implicit private val executionContext: ExecutionContext = context.dispatcher
|
||||||
context.system.dispatchers.lookup("me.arcanis.ffxivbis.default-dispatcher")
|
|
||||||
|
|
||||||
override def receive: Receive = {
|
override def receive: Receive = {
|
||||||
case GetBiS(link, job) =>
|
case GetBiS(link, job) =>
|
||||||
@ -43,11 +42,6 @@ class Ariyala extends Actor with StrictLogging {
|
|||||||
get(link, job).map(BiS(_)).pipeTo(client)
|
get(link, job).map(BiS(_)).pipeTo(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def postStop(): Unit = {
|
|
||||||
http.shutdownAllConnectionPools()
|
|
||||||
super.postStop()
|
|
||||||
}
|
|
||||||
|
|
||||||
private def get(link: String, job: Job.Job): Future[Seq[Piece]] = {
|
private def get(link: String, job: Job.Job): Future[Seq[Piece]] = {
|
||||||
val id = Paths.get(link).normalize.getFileName.toString
|
val id = Paths.get(link).normalize.getFileName.toString
|
||||||
val uri = Uri(ariyalaUrl)
|
val uri = Uri(ariyalaUrl)
|
||||||
@ -134,8 +128,8 @@ object Ariyala {
|
|||||||
private def remapKey(key: String): Option[String] = key match {
|
private def remapKey(key: String): Option[String] = key match {
|
||||||
case "mainhand" => Some("weapon")
|
case "mainhand" => Some("weapon")
|
||||||
case "chest" => Some("body")
|
case "chest" => Some("body")
|
||||||
case "ringLeft" => Some("left ring")
|
case "ringLeft" => Some("leftRing")
|
||||||
case "ringRight" => Some("right ring")
|
case "ringRight" => Some("rightRing")
|
||||||
case "head" | "hands" | "waist" | "legs" | "feet" | "ears" | "neck" | "wrist" => Some(key)
|
case "head" | "hands" | "waist" | "legs" | "feet" | "ears" | "neck" | "wrist" => Some(key)
|
||||||
case _ => None
|
case _ => None
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,7 @@ class PartyService(storage: ActorRef) extends Actor with StrictLogging {
|
|||||||
|
|
||||||
private val cacheTimeout: FiniteDuration =
|
private val cacheTimeout: FiniteDuration =
|
||||||
context.system.settings.config.getDuration("me.arcanis.ffxivbis.settings.cache-timeout")
|
context.system.settings.config.getDuration("me.arcanis.ffxivbis.settings.cache-timeout")
|
||||||
implicit private val executionContext: ExecutionContext =
|
implicit private val executionContext: ExecutionContext = context.dispatcher
|
||||||
context.system.dispatchers.lookup("me.arcanis.ffxivbis.default-dispatcher")
|
|
||||||
implicit private val timeout: Timeout =
|
implicit private val timeout: Timeout =
|
||||||
context.system.settings.config.getDuration("me.arcanis.ffxivbis.settings.request-timeout")
|
context.system.settings.config.getDuration("me.arcanis.ffxivbis.settings.request-timeout")
|
||||||
|
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
package me.arcanis.ffxivbis.service
|
|
||||||
|
|
||||||
import java.time.Instant
|
|
||||||
|
|
||||||
import akka.actor.Actor
|
|
||||||
|
|
||||||
import scala.concurrent.ExecutionContext
|
|
||||||
import scala.concurrent.duration.FiniteDuration
|
|
||||||
|
|
||||||
class RateLimiter extends Actor {
|
|
||||||
import RateLimiter._
|
|
||||||
import me.arcanis.ffxivbis.utils.Implicits._
|
|
||||||
|
|
||||||
implicit private val executionContext: ExecutionContext = context.system.dispatcher
|
|
||||||
|
|
||||||
private val maxRequestCount: Int = context.system.settings.config.getInt("me.arcanis.ffxivbis.web.limits.max-count")
|
|
||||||
private val requestInterval: FiniteDuration = context.system.settings.config.getDuration("me.arcanis.ffxivbis.web.limits.interval")
|
|
||||||
|
|
||||||
override def receive: Receive = handle(Map.empty)
|
|
||||||
|
|
||||||
private def handle(cache: Map[String, Usage]): Receive = {
|
|
||||||
case username: String =>
|
|
||||||
val client = sender()
|
|
||||||
val usage = if (cache.contains(username)) {
|
|
||||||
cache(username)
|
|
||||||
} else {
|
|
||||||
context.system.scheduler.scheduleOnce(requestInterval, self, Reset(username))
|
|
||||||
Usage()
|
|
||||||
}
|
|
||||||
context become handle(cache + (username -> usage.increment))
|
|
||||||
|
|
||||||
val response = if (usage.count > maxRequestCount) Some(usage.left) else None
|
|
||||||
client ! response
|
|
||||||
|
|
||||||
case Reset(username) =>
|
|
||||||
context become handle(cache - username)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object RateLimiter {
|
|
||||||
private case class Usage(count: Int = 0, since: Instant = Instant.now) {
|
|
||||||
def increment: Usage = copy(count = count + 1)
|
|
||||||
def left: Long = (Instant.now.toEpochMilli - since.toEpochMilli) / 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
case class Reset(username: String)
|
|
||||||
}
|
|
@ -18,8 +18,7 @@ class DatabaseImpl extends Database
|
|||||||
with DatabaseBiSHandler with DatabaseLootHandler
|
with DatabaseBiSHandler with DatabaseLootHandler
|
||||||
with DatabasePartyHandler with DatabaseUserHandler {
|
with DatabasePartyHandler with DatabaseUserHandler {
|
||||||
|
|
||||||
implicit val executionContext: ExecutionContext =
|
implicit val executionContext: ExecutionContext = context.dispatcher
|
||||||
context.system.dispatchers.lookup("me.arcanis.ffxivbis.default-dispatcher")
|
|
||||||
val profile = new DatabaseProfile(executionContext, context.system.settings.config)
|
val profile = new DatabaseProfile(executionContext, context.system.settings.config)
|
||||||
|
|
||||||
override def receive: Receive =
|
override def receive: Receive =
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
package me.arcanis.ffxivbis.storage
|
package me.arcanis.ffxivbis.storage
|
||||||
|
|
||||||
import me.arcanis.ffxivbis.models.{Job, Loot, Piece}
|
import me.arcanis.ffxivbis.models.{Job, Loot, Piece}
|
||||||
import slick.lifted.ForeignKeyQuery
|
import slick.lifted.{ForeignKeyQuery, Index, PrimaryKey}
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
package me.arcanis.ffxivbis.storage
|
package me.arcanis.ffxivbis.storage
|
||||||
|
|
||||||
import me.arcanis.ffxivbis.models.{BiS, Job, Player, PlayerId}
|
import me.arcanis.ffxivbis.models.{BiS, Job, Player, PlayerId}
|
||||||
|
import slick.lifted.{Index, PrimaryKey}
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
|
|
||||||
|
@ -15,8 +15,8 @@ object Fixtures {
|
|||||||
Ears(isTome = false, Job.DNC),
|
Ears(isTome = false, Job.DNC),
|
||||||
Neck(isTome = true, Job.DNC),
|
Neck(isTome = true, Job.DNC),
|
||||||
Wrist(isTome = false, Job.DNC),
|
Wrist(isTome = false, Job.DNC),
|
||||||
Ring(isTome = true, Job.DNC, "left ring"),
|
Ring(isTome = true, Job.DNC, "leftRing"),
|
||||||
Ring(isTome = true, Job.DNC, "right ring")
|
Ring(isTome = true, Job.DNC, "rightRing")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -30,8 +30,8 @@ object Fixtures {
|
|||||||
lazy val lootLegs: Piece = Legs(isTome = false, Job.AnyJob)
|
lazy val lootLegs: Piece = Legs(isTome = false, Job.AnyJob)
|
||||||
lazy val lootEars: Piece = Ears(isTome = false, Job.AnyJob)
|
lazy val lootEars: Piece = Ears(isTome = false, Job.AnyJob)
|
||||||
lazy val lootRing: Piece = Ring(isTome = true, Job.AnyJob)
|
lazy val lootRing: Piece = Ring(isTome = true, Job.AnyJob)
|
||||||
lazy val lootLeftRing: Piece = Ring(isTome = true, Job.AnyJob, "left ring")
|
lazy val lootLeftRing: Piece = Ring(isTome = true, Job.AnyJob, "leftRing")
|
||||||
lazy val lootRightRing: Piece = Ring(isTome = true, Job.AnyJob, "right ring")
|
lazy val lootRightRing: Piece = Ring(isTome = true, Job.AnyJob, "rightRing")
|
||||||
lazy val lootUpgrade: Piece = BodyUpgrade
|
lazy val lootUpgrade: Piece = BodyUpgrade
|
||||||
lazy val loot: Seq[Piece] = Seq(lootBody, lootHands, lootLegs, lootUpgrade)
|
lazy val loot: Seq[Piece] = Seq(lootBody, lootHands, lootLegs, lootUpgrade)
|
||||||
|
|
||||||
|
@ -16,13 +16,13 @@ object Settings {
|
|||||||
replace(default, values.toList)
|
replace(default, values.toList)
|
||||||
}
|
}
|
||||||
|
|
||||||
def clearDatabase(config: Config): Unit =
|
def clearDatabase(config: Config): Unit = {
|
||||||
config.getString("me.arcanis.ffxivbis.database.sqlite.db.url").split(":")
|
val databasePath =
|
||||||
.lastOption.foreach { databasePath =>
|
config.getString("me.arcanis.ffxivbis.database.sqlite.db.url").split(":").last
|
||||||
val databaseFile = new File(databasePath)
|
val databaseFile = new File(databasePath)
|
||||||
if (databaseFile.exists)
|
if (databaseFile.exists)
|
||||||
databaseFile.delete()
|
databaseFile.delete()
|
||||||
}
|
}
|
||||||
def randomDatabasePath: String = File.createTempFile("ffxivdb-",".db").toPath.toString
|
def randomDatabasePath: String = File.createTempFile("ffxivdb-",".db").toPath.toString
|
||||||
def withRandomDatabase: Config =
|
def withRandomDatabase: Config =
|
||||||
config(Map("me.arcanis.ffxivbis.database.sqlite.db.url" -> s"jdbc:sqlite:$randomDatabasePath"))
|
config(Map("me.arcanis.ffxivbis.database.sqlite.db.url" -> s"jdbc:sqlite:$randomDatabasePath"))
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
package me.arcanis.ffxivbis.http.api.v1
|
|
||||||
|
|
||||||
import akka.http.scaladsl.model.StatusCodes
|
|
||||||
import akka.http.scaladsl.testkit.ScalatestRouteTest
|
|
||||||
import akka.http.scaladsl.server._
|
|
||||||
import com.typesafe.config.Config
|
|
||||||
import me.arcanis.ffxivbis.Settings
|
|
||||||
import me.arcanis.ffxivbis.http.api.v1.json._
|
|
||||||
import me.arcanis.ffxivbis.models.{Job, Party, Permission, Piece}
|
|
||||||
import org.scalatest.{Matchers, WordSpec}
|
|
||||||
|
|
||||||
import scala.language.postfixOps
|
|
||||||
|
|
||||||
class TypesEndpointTest extends WordSpec
|
|
||||||
with Matchers with ScalatestRouteTest with JsonSupport {
|
|
||||||
|
|
||||||
private val route: Route = new TypesEndpoint(testConfig).route
|
|
||||||
|
|
||||||
override def testConfig: Config = Settings.withRandomDatabase
|
|
||||||
|
|
||||||
"api v1 types endpoint" must {
|
|
||||||
|
|
||||||
"return all available jobs" in {
|
|
||||||
Get("/types/jobs") ~> route ~> check {
|
|
||||||
status shouldEqual StatusCodes.OK
|
|
||||||
responseAs[Seq[String]] shouldEqual Job.availableWithAnyJob.map(_.toString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"return all available permissions" in {
|
|
||||||
Get("/types/permissions") ~> route ~> check {
|
|
||||||
status shouldEqual StatusCodes.OK
|
|
||||||
responseAs[Seq[String]] shouldEqual Permission.values.toSeq.sorted.map(_.toString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"return all available pieces" in {
|
|
||||||
Get("/types/pieces") ~> route ~> check {
|
|
||||||
status shouldEqual StatusCodes.OK
|
|
||||||
responseAs[Seq[String]] shouldEqual Piece.available
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"return current priority" in {
|
|
||||||
Get("/types/priority") ~> route ~> check {
|
|
||||||
status shouldEqual StatusCodes.OK
|
|
||||||
responseAs[Seq[String]] shouldEqual Party.getRules(testConfig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,21 +7,17 @@ class JobTest extends WordSpecLike with Matchers with BeforeAndAfterAll {
|
|||||||
"job model" must {
|
"job model" must {
|
||||||
|
|
||||||
"create job from string" in {
|
"create job from string" in {
|
||||||
Job.available.foreach { job =>
|
Job.jobs.foreach { job =>
|
||||||
Job.withName(job.toString) shouldEqual job
|
Job.withName(job.toString) shouldEqual job
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"return AnyJob" in {
|
"return AnyJob on unknown job" in {
|
||||||
Job.withName("anyjob") shouldEqual Job.AnyJob
|
Job.withName("random string") shouldEqual Job.AnyJob
|
||||||
}
|
|
||||||
|
|
||||||
"fail on unknown job" in {
|
|
||||||
an [IllegalArgumentException] should be thrownBy Job.withName("random string")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"equal AnyJob to others" in {
|
"equal AnyJob to others" in {
|
||||||
Job.available.foreach { job =>
|
Job.jobs.foreach { job =>
|
||||||
Job.AnyJob shouldBe job
|
Job.AnyJob shouldBe job
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
version := "0.9.5"
|
version := "0.9.0"
|
||||||
|
Reference in New Issue
Block a user