additional methods to types endpoint

This commit is contained in:
Evgenii Alekseev 2019-11-17 16:23:43 +03:00
parent ad144534a9
commit 65b9e53b66
7 changed files with 98 additions and 11 deletions

View File

@ -54,5 +54,11 @@ me.arcanis.ffxivbis {
host = "127.0.0.1"
# port to bind, int, required
port = 8000
# rate limits
limits {
intetval = 1m
max-count = 60
}
}
}

View File

@ -27,7 +27,7 @@ class RootEndpoint(system: ActorSystem, storage: ActorRef, ariyala: ActorRef)
implicit val timeout: Timeout =
config.getDuration("me.arcanis.ffxivbis.settings.request-timeout")
private val rootApiV1Endpoint: RootApiV1Endpoint = new RootApiV1Endpoint(storage, ariyala)
private val rootApiV1Endpoint: RootApiV1Endpoint = new RootApiV1Endpoint(storage, ariyala, config)
private val rootView: RootView = new RootView(storage, ariyala)
private val httpLogger = Logger("http")

View File

@ -12,16 +12,17 @@ import akka.actor.ActorRef
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.util.Timeout
import com.typesafe.config.Config
import me.arcanis.ffxivbis.http.api.v1.json.JsonSupport
class RootApiV1Endpoint(storage: ActorRef, ariyala: ActorRef)
class RootApiV1Endpoint(storage: ActorRef, ariyala: ActorRef, config: Config)
(implicit timeout: Timeout)
extends JsonSupport with HttpHandler {
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 typesEndpoint = new TypesEndpoint(config)
private val userEndpoint = new UserEndpoint(storage)
def route: Route =

View File

@ -10,17 +10,18 @@ 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, Permission, Piece}
import me.arcanis.ffxivbis.models.{Job, Party, Permission, Piece}
@Path("api/v1")
class TypesEndpoint extends JsonSupport {
class TypesEndpoint(config: Config) extends JsonSupport {
def route: Route = getJobs ~ getPermissions ~ getPieces
def route: Route = getJobs ~ getPermissions ~ getPieces ~ getPriority
@GET
@Path("types/jobs")
@ -84,4 +85,25 @@ class TypesEndpoint extends JsonSupport {
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))
}
}
}

View File

@ -36,9 +36,6 @@ case class Party(partyId: String, rules: Seq[String], players: Map[PlayerId, Pla
}
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 =
new Party(partyId.getOrElse(randomPartyId), getRules(config), Map.empty)
@ -55,5 +52,8 @@ object Party {
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
}

View File

@ -0,0 +1,47 @@
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)
}

View File

@ -3,8 +3,10 @@ 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, Permission, Piece}
import me.arcanis.ffxivbis.models.{Job, Party, Permission, Piece}
import org.scalatest.{Matchers, WordSpec}
import scala.language.postfixOps
@ -12,7 +14,9 @@ import scala.language.postfixOps
class TypesEndpointTest extends WordSpec
with Matchers with ScalatestRouteTest with JsonSupport {
private val route: Route = new TypesEndpoint().route
private val route: Route = new TypesEndpoint(testConfig).route
override def testConfig: Config = Settings.withRandomDatabase
"api v1 types endpoint" must {
@ -37,5 +41,12 @@ class TypesEndpointTest extends WordSpec
}
}
"return current priority" in {
Get("/types/priority") ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[Seq[String]] shouldEqual Party.getRules(testConfig)
}
}
}
}