api docs review

This commit is contained in:
Evgenii Alekseev 2022-01-21 02:30:34 +03:00
parent feea01a47e
commit 963e84f792
29 changed files with 87 additions and 52 deletions

View File

@ -12,6 +12,7 @@ libraryDependencies += "com.typesafe.akka" %% "akka-actor-typed" % AkkaVersion
libraryDependencies += "com.typesafe.akka" %% "akka-stream" % AkkaVersion
libraryDependencies += "com.github.swagger-akka-http" %% "swagger-akka-http" % "2.6.0"
libraryDependencies += "jakarta.platform" % "jakarta.jakartaee-web-api" % "9.1.0"
libraryDependencies += "ch.megard" %% "akka-http-cors" % "1.1.2"
libraryDependencies += "io.spray" %% "spray-json" % "1.3.6"

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>FFXIV loot helper API</title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Embed elements Elements via Web Component -->
<script src="https://unpkg.com/@stoplight/elements/web-components.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@stoplight/elements/styles.min.css" type="text/css">
<link rel="shortcut icon" href="/static/favicon.ico">
</head>
<body>
<elements-api
apiDescriptionUrl="/api-docs/swagger.json"
router="hash"
layout="sidebar"
/>
</body>
</html>

View File

@ -139,6 +139,7 @@
<footer class="d-flex flex-wrap justify-content-between align-items-center border-top">
<ul class="nav">
<li><a class="nav-link" href="/" title="home">home</a></li>
<li><a class="nav-link" href="/api-docs" title="api">api</a></li>
</ul>
<ul class="nav">

View File

@ -71,7 +71,9 @@
<div class="container">
<footer class="d-flex flex-wrap justify-content-between align-items-center border-top">
<ul class="nav"></ul>
<ul class="nav">
<li><a class="nav-link" href="/api-docs" title="api">api</a></li>
</ul>
<ul class="nav">
<li><a class="nav-link" href="https://github.com/arcan1s/ffxivbis" title="sources">ffxivbis</a></li>

View File

@ -156,6 +156,7 @@
<footer class="d-flex flex-wrap justify-content-between align-items-center border-top">
<ul class="nav">
<li><a class="nav-link" href="/" title="home">home</a></li>
<li><a class="nav-link" href="/api-docs" title="api">api</a></li>
</ul>
<ul class="nav">

View File

@ -129,6 +129,7 @@
<footer class="d-flex flex-wrap justify-content-between align-items-center border-top">
<ul class="nav">
<li><a class="nav-link" href="/" title="home">home</a></li>
<li><a class="nav-link" href="/api-docs" title="api">api</a></li>
</ul>
<ul class="nav">

View File

@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>FFXIV loot helper API</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" type="text/css">
<link rel="shortcut icon" href="/static/favicon.ico">
</head>
<body>
<redoc spec-url="/api-docs/swagger.json"></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
</body>
</html>

View File

@ -123,6 +123,7 @@
<footer class="d-flex flex-wrap justify-content-between align-items-center border-top">
<ul class="nav">
<li><a class="nav-link" href="/" title="home">home</a></li>
<li><a class="nav-link" href="/api-docs" title="api">api</a></li>
</ul>
<ul class="nav">

View File

@ -50,7 +50,7 @@ class Application(context: ActorContext[Nothing]) extends AbstractBehavior[Nothi
val party = context.spawn(PartyService(storage), "party")
val http = new RootEndpoint(context.system, party, bisProvider)
val flow = Route.toFlow(http.route)(context.system)
val flow = Route.toFlow(http.routes)(context.system)
Http(context.system).newServerAt(host, port).bindFlow(flow)
case Success(result) =>

View File

@ -12,6 +12,7 @@ import akka.actor.typed.{ActorRef, ActorSystem, Scheduler}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server._
import akka.util.Timeout
import ch.megard.akka.http.cors.scaladsl.CorsDirectives.cors
import com.typesafe.scalalogging.StrictLogging
import me.arcanis.ffxivbis.http.api.v1.RootApiV1Endpoint
import me.arcanis.ffxivbis.http.view.RootView
@ -33,28 +34,30 @@ class RootEndpoint(system: ActorSystem[Nothing], storage: ActorRef[Message], pro
private val rootView = new RootView(auth)
private val swagger = new Swagger(config)
def route: Route =
def routes: Route =
withHttpLog {
ignoreTrailingSlash {
apiRoute ~ htmlRoute ~ swagger.routes ~ swaggerUIRoute
cors() {
apiRoutes ~ htmlRoutes ~ swagger.routes ~ swaggerUIRoutes
}
}
}
private def apiRoute: Route =
private def apiRoutes: Route =
pathPrefix("api") {
pathPrefix(Segment) {
case "v1" => rootApiV1Endpoint.route
case "v1" => rootApiV1Endpoint.routes
case _ => reject
}
}
private def htmlRoute: Route =
private def htmlRoutes: Route =
pathPrefix("static") {
getFromResourceDirectory("static")
} ~ rootView.route
} ~ rootView.routes
private def swaggerUIRoute: Route =
private def swaggerUIRoutes: Route =
path("api-docs") {
getFromResource("html/redoc.html")
getFromResource("html/api.html")
}
}

View File

@ -42,8 +42,8 @@ class Swagger(config: Config) extends SwaggerHttpService {
.description("basic http auth")
.`type`(SecurityScheme.Type.HTTP)
.in(SecurityScheme.In.HEADER)
.scheme("bearer")
override val securitySchemes: Map[String, SecurityScheme] = Map("basic auth" -> basicAuth)
.scheme("basic")
override val securitySchemes: Map[String, SecurityScheme] = Map("auth" -> basicAuth)
override val unwantedDefinitions: Seq[String] =
Seq("Function1", "Function1RequestContextFutureRouteResult")

View File

@ -40,7 +40,7 @@ class BiSEndpoint(
with Authorization
with JsonSupport {
def route: Route = createBiS ~ getBiS ~ modifyBiS
def routes: Route = createBiS ~ getBiS ~ modifyBiS
@PUT
@Path("party/{partyId}/bis")

View File

@ -11,6 +11,7 @@ package me.arcanis.ffxivbis.http.api.v1
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server._
import ch.megard.akka.http.cors.scaladsl.CorsDirectives.corsRejectionHandler
import com.typesafe.scalalogging.StrictLogging
import me.arcanis.ffxivbis.http.api.v1.json._
import spray.json._
@ -27,9 +28,26 @@ trait HttpHandler extends StrictLogging { this: JsonSupport =>
}
def rejectionHandler: RejectionHandler =
RejectionHandler.default
RejectionHandler
.newBuilder()
.handleAll[MethodRejection] { rejections =>
val (methods, names) = rejections.map(r => r.supported -> r.supported.name).unzip
respondWithHeader(headers.Allow(methods)) {
options {
complete(StatusCodes.OK, HttpEntity.Empty)
} ~
complete(
StatusCodes.MethodNotAllowed,
s"HTTP method not allowed, supported methods: ${names.mkString(", ")}"
)
}
}
.result()
.withFallback(corsRejectionHandler)
.seal
.mapRejectionResponse {
case response @ HttpResponse(_, _, entity: HttpEntity.Strict, _) =>
case response @ HttpResponse(_, _, entity: HttpEntity.Strict, _) if entity.data.nonEmpty =>
val message = ErrorModel(entity.data.utf8String).toJson
response.withEntity(HttpEntity(ContentTypes.`application/json`, message.compactPrint))
case other => other

View File

@ -37,7 +37,7 @@ class LootEndpoint(override val storage: ActorRef[Message], override val auth: A
with JsonSupport
with HttpHandler {
def route: Route = getLoot ~ modifyLoot ~ suggestLoot
def routes: Route = getLoot ~ modifyLoot ~ suggestLoot
@GET
@Path("party/{partyId}/loot")

View File

@ -40,7 +40,7 @@ class PartyEndpoint(
with JsonSupport
with HttpHandler {
def route: Route = getPartyDescription ~ modifyPartyDescription
def routes: Route = getPartyDescription ~ modifyPartyDescription
@GET
@Path("party/{partyId}/description")

View File

@ -41,7 +41,7 @@ class PlayerEndpoint(
with JsonSupport
with HttpHandler {
def route: Route = getParty ~ getPartyStats ~ modifyParty
def routes: Route = getParty ~ getPartyStats ~ modifyParty
@GET
@Path("party/{partyId}")

View File

@ -36,11 +36,11 @@ class RootApiV1Endpoint(
private val typesEndpoint = new TypesEndpoint(config)
private val userEndpoint = new UserEndpoint(storage, auth)
def route: Route =
def routes: Route =
handleExceptions(exceptionHandler) {
handleRejections(rejectionHandler) {
biSEndpoint.route ~ lootEndpoint.route ~ partyEndpoint.route ~ playerEndpoint.route ~
statusEndpoint.route ~ typesEndpoint.route ~ userEndpoint.route
biSEndpoint.routes ~ lootEndpoint.routes ~ partyEndpoint.routes ~ playerEndpoint.routes ~
statusEndpoint.routes ~ typesEndpoint.routes ~ userEndpoint.routes
}
}
}

View File

@ -19,7 +19,7 @@ import me.arcanis.ffxivbis.http.api.v1.json._
@Path("/api/v1")
class StatusEndpoint extends JsonSupport {
def route: Route = getServerStatus
def routes: Route = getServerStatus
@GET
@Path("status")

View File

@ -21,7 +21,7 @@ import me.arcanis.ffxivbis.models._
@Path("/api/v1")
class TypesEndpoint(config: Config) extends JsonSupport {
def route: Route = getAllJobs ~ getJobs ~ getPermissions ~ getPieces ~ getPieceTypes ~ getPriority
def routes: Route = getAllJobs ~ getJobs ~ getPermissions ~ getPieces ~ getPieceTypes ~ getPriority
@GET
@Path("types/jobs/all")

View File

@ -36,7 +36,7 @@ class UserEndpoint(override val storage: ActorRef[Message], override val auth: A
with Authorization
with JsonSupport {
def route: Route = createParty ~ createUser ~ deleteUser ~ getUsers ~ getUsersCurrent
def routes: Route = createParty ~ createUser ~ deleteUser ~ getUsers ~ getUsersCurrent
@PUT
@Path("party")

View File

@ -19,7 +19,10 @@ case class PlayerModel(
@Schema(description = "looted pieces") loot: Option[Seq[LootModel]],
@Schema(description = "link to best in slot", example = "https://ffxiv.ariyala.com/19V5R") link: Option[String],
@Schema(description = "player loot priority", `type` = "number") priority: Option[Int],
@Schema(description = "count of looted pieces which are parts of best in slot") lootCountBiS: Option[Int],
@Schema(
description = "count of looted pieces which are parts of best in slot",
`type` = "number"
) lootCountBiS: Option[Int],
@Schema(description = "total count of looted pieces", `type` = "number") lootCountTotal: Option[Int],
) {

View File

@ -15,7 +15,7 @@ import me.arcanis.ffxivbis.http.{Authorization, AuthorizationProvider}
class RootView(override val auth: AuthorizationProvider) extends Authorization {
def route: Route = getBiS ~ getIndex ~ getLoot ~ getParty ~ getUsers
def routes: Route = getBiS ~ getIndex ~ getLoot ~ getParty ~ getUsers
def getBiS: Route =
path("party" / Segment / "bis") { partyId: String =>

View File

@ -37,7 +37,7 @@ class BiSEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRouteT
private val storage = testKit.spawn(Database())
private val provider = testKit.spawn(BisProvider())
private val party = testKit.spawn(PartyService(storage))
private val route = new BiSEndpoint(party, provider, Fixtures.authProvider)(askTimeout, testKit.scheduler).route
private val route = new BiSEndpoint(party, provider, Fixtures.authProvider)(askTimeout, testKit.scheduler).routes
override def beforeAll(): Unit = {
super.beforeAll()

View File

@ -34,7 +34,7 @@ class LootEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRoute
private val storage = testKit.spawn(Database())
private val party = testKit.spawn(PartyService(storage))
private val route = new LootEndpoint(party, Fixtures.authProvider)(askTimeout, testKit.scheduler).route
private val route = new LootEndpoint(party, Fixtures.authProvider)(askTimeout, testKit.scheduler).routes
override def beforeAll(): Unit = {
super.beforeAll()

View File

@ -35,7 +35,7 @@ class PartyEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRout
private val storage = testKit.spawn(Database())
private val provider = testKit.spawn(BisProvider())
private val party = testKit.spawn(PartyService(storage))
private val route = new PartyEndpoint(party, provider, Fixtures.authProvider)(askTimeout, testKit.scheduler).route
private val route = new PartyEndpoint(party, provider, Fixtures.authProvider)(askTimeout, testKit.scheduler).routes
override def beforeAll(): Unit = {
super.beforeAll()

View File

@ -34,7 +34,7 @@ class PlayerEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRou
private val storage = testKit.spawn(Database())
private val provider = testKit.spawn(BisProvider())
private val party = testKit.spawn(PartyService(storage))
private val route = new PlayerEndpoint(party, provider, Fixtures.authProvider)(askTimeout, testKit.scheduler).route
private val route = new PlayerEndpoint(party, provider, Fixtures.authProvider)(askTimeout, testKit.scheduler).routes
override def beforeAll(): Unit = {
super.beforeAll()

View File

@ -15,7 +15,7 @@ class StatusEndpointTest extends AnyWordSpecLike
override val testConfig: Config = Settings.withRandomDatabase
private val route = new StatusEndpoint().route
private val route = new StatusEndpoint().routes
"api v1 status endpoint" must {

View File

@ -16,7 +16,7 @@ class TypesEndpointTest extends AnyWordSpecLike
override val testConfig: Config = Settings.withRandomDatabase
private val route = new TypesEndpoint(testConfig).route
private val route = new TypesEndpoint(testConfig).routes
"api v1 types endpoint" must {

View File

@ -30,7 +30,7 @@ class UserEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRoute
private var partyId = Fixtures.partyId
private val storage = testKit.spawn(Database())
private val party = testKit.spawn(PartyService(storage))
private val route = new UserEndpoint(party, Fixtures.authProvider)(askTimeout, testKit.scheduler).route
private val route = new UserEndpoint(party, Fixtures.authProvider)(askTimeout, testKit.scheduler).routes
override def beforeAll(): Unit = {
super.beforeAll()