diff --git a/README.md b/README.md index e52b5e2..e171c01 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,26 @@ Service which allows to manage savage loot distribution easy. ## Installation and usage -In general installation process looks like: +In general compliation process looks like: ```bash -sbt assembly +sbt dist ``` -Or alternatively you can download latest jar from releases page. Service can be run by using command: +Or alternatively you can download latest distribution zip from the releases page. Service can be run by using command: ```bash -java -cp ./target/scala-2.13/ffxivbis-scala-assembly-0.1.jar me.arcanis.ffxivbis.ffxivbis +bin/ffxivbis ``` +from the extracted archive root. + ## 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`. *Note*: host and port depend on configuration settings. + +## Public service + +There is also public service which is available at https://ffxivbis.arcanis.me. \ No newline at end of file diff --git a/build.sbt b/build.sbt index ebbc223..eabf29a 100644 --- a/build.sbt +++ b/build.sbt @@ -4,6 +4,8 @@ scalaVersion := "2.13.1" scalacOptions ++= Seq("-deprecation", "-feature") +enablePlugins(JavaAppPackaging) + assemblyMergeStrategy in assembly := { case PathList("META-INF", xs @ _*) => MergeStrategy.discard case "application.conf" => MergeStrategy.concat diff --git a/project/assembly.sbt b/project/assembly.sbt deleted file mode 100644 index 652a3b9..0000000 --- a/project/assembly.sbt +++ /dev/null @@ -1 +0,0 @@ -addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6") diff --git a/project/dependency.sbt b/project/dependency.sbt deleted file mode 100644 index 71e91e6..0000000 --- a/project/dependency.sbt +++ /dev/null @@ -1 +0,0 @@ -addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.10.0-RC1") \ No newline at end of file diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..cf376fc --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1,3 @@ +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") diff --git a/src/main/resources/swagger-info/description.md b/src/main/resources/swagger-info/description.md new file mode 100644 index 0000000..5309dd1 --- /dev/null +++ b/src/main/resources/swagger-info/description.md @@ -0,0 +1,24 @@ +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 is belong 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. + + \ No newline at end of file diff --git a/src/main/scala/me/arcanis/ffxivbis/http/Swagger.scala b/src/main/scala/me/arcanis/ffxivbis/http/Swagger.scala index 8fce27f..09d2dee 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/Swagger.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/Swagger.scala @@ -9,16 +9,24 @@ package me.arcanis.ffxivbis.http import com.github.swagger.akka.SwaggerHttpService -import com.github.swagger.akka.model.Info +import com.github.swagger.akka.model.{Info, License} import io.swagger.v3.oas.models.security.SecurityScheme +import scala.io.Source + object Swagger extends SwaggerHttpService { override val apiClasses: Set[Class[_]] = Set( classOf[api.v1.BiSEndpoint], classOf[api.v1.LootEndpoint], - classOf[api.v1.PlayerEndpoint], classOf[api.v1.UserEndpoint] + classOf[api.v1.PlayerEndpoint], classOf[api.v1.TypesEndpoint], + 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() .description("basic http auth") diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/RootApiV1Endpoint.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/RootApiV1Endpoint.scala index 8d3ebfd..24ad6e5 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/RootApiV1Endpoint.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/RootApiV1Endpoint.scala @@ -21,12 +21,14 @@ class RootApiV1Endpoint(storage: ActorRef, ariyala: ActorRef) private val biSEndpoint = new BiSEndpoint(storage, ariyala) private val lootEndpoint = new LootEndpoint(storage) private val playerEndpoint = new PlayerEndpoint(storage, ariyala) + private val typesEndpoint = new TypesEndpoint private val userEndpoint = new UserEndpoint(storage) def route: Route = handleExceptions(exceptionHandler) { handleRejections(rejectionHandler) { - biSEndpoint.route ~ lootEndpoint.route ~ playerEndpoint.route ~ userEndpoint.route + biSEndpoint.route ~ lootEndpoint.route ~ playerEndpoint.route ~ + typesEndpoint.route ~ userEndpoint.route } } } diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/TypesEndpoint.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/TypesEndpoint.scala new file mode 100644 index 0000000..88d6f86 --- /dev/null +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/TypesEndpoint.scala @@ -0,0 +1,87 @@ +/* + * 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 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, Permission, Piece} + +@Path("api/v1") +class TypesEndpoint extends JsonSupport { + + def route: Route = getJobs ~ getPermissions ~ getPieces + + @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.available.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) + } + } +} diff --git a/src/main/scala/me/arcanis/ffxivbis/http/view/PlayerView.scala b/src/main/scala/me/arcanis/ffxivbis/http/view/PlayerView.scala index 8e763d0..390be61 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/view/PlayerView.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/view/PlayerView.scala @@ -93,7 +93,7 @@ object PlayerView { form(action:=s"/party/$partyId/players", method:="post")( input(name:="nick", id:="nick", placeholder:="nick", title:="nick", `type`:="nick"), select(name:="job", id:="job", title:="job") - (for (job <- Job.jobs) yield option(job.toString)), + (for (job <- Job.available) yield option(job.toString)), 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:="action", id:="action", `type`:="hidden", value:="add"), diff --git a/src/main/scala/me/arcanis/ffxivbis/models/Job.scala b/src/main/scala/me/arcanis/ffxivbis/models/Job.scala index 33fd907..ce649c3 100644 --- a/src/main/scala/me/arcanis/ffxivbis/models/Job.scala +++ b/src/main/scala/me/arcanis/ffxivbis/models/Job.scala @@ -96,8 +96,8 @@ object Job { case object SMN extends Casters case object RDM extends Casters - lazy val jobs: Seq[Job] = + lazy val available: Seq[Job] = Seq(PLD, WAR, DRK, GNB, WHM, SCH, AST, MNK, DRG, NIN, SAM, BRD, MCH, DNC, BLM, SMN, RDM) - def withName(job: String): Job.Job = jobs.find(_.toString == job.toUpperCase).getOrElse(AnyJob) + def withName(job: String): Job.Job = available.find(_.toString == job.toUpperCase).getOrElse(AnyJob) } diff --git a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/TypesEndpointTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/TypesEndpointTest.scala new file mode 100644 index 0000000..3b46c96 --- /dev/null +++ b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/TypesEndpointTest.scala @@ -0,0 +1,41 @@ +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 me.arcanis.ffxivbis.http.api.v1.json._ +import me.arcanis.ffxivbis.models.{Job, 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().route + + "api v1 types endpoint" must { + + "return all available jobs" in { + Get("/types/jobs") ~> route ~> check { + status shouldEqual StatusCodes.OK + responseAs[Seq[String]] shouldEqual Job.available.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 + } + } + + } +} diff --git a/src/test/scala/me/arcanis/ffxivbis/models/JobTest.scala b/src/test/scala/me/arcanis/ffxivbis/models/JobTest.scala index 981629d..874c070 100644 --- a/src/test/scala/me/arcanis/ffxivbis/models/JobTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/models/JobTest.scala @@ -7,7 +7,7 @@ class JobTest extends WordSpecLike with Matchers with BeforeAndAfterAll { "job model" must { "create job from string" in { - Job.jobs.foreach { job => + Job.available.foreach { job => Job.withName(job.toString) shouldEqual job } } @@ -17,7 +17,7 @@ class JobTest extends WordSpecLike with Matchers with BeforeAndAfterAll { } "equal AnyJob to others" in { - Job.jobs.foreach { job => + Job.available.foreach { job => Job.AnyJob shouldBe job } } diff --git a/version.sbt b/version.sbt index 29e696e..0300ccd 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version := "0.9.3" +version := "0.9.4"