* types api
* dist instead of assembly (assembly does not allow to use swagger easily)
* small swagger improvements
This commit is contained in:
Evgenii Alekseev 2019-11-15 01:27:15 +03:00
parent 4700768aed
commit ad144534a9
14 changed files with 187 additions and 16 deletions

View File

@ -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.

View File

@ -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

View File

@ -1 +0,0 @@
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6")

View File

@ -1 +0,0 @@
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.10.0-RC1")

3
project/plugins.sbt Normal file
View File

@ -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")

View File

@ -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.
<security-definitions />

View File

@ -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")

View File

@ -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
}
}
}

View File

@ -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)
}
}
}

View File

@ -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"),

View File

@ -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)
}

View File

@ -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
}
}
}
}

View File

@ -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
}
}

View File

@ -1 +1 @@
version := "0.9.3"
version := "0.9.4"