diff --git a/src/main/resources/logback-application.xml b/src/main/resources/logback-application.xml new file mode 100644 index 0000000..dab4978 --- /dev/null +++ b/src/main/resources/logback-application.xml @@ -0,0 +1,27 @@ + + + + + [%-5level %d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] [%logger{50}]: %msg%n + + + application.log + + + 1 + 20 + application.log.%i.gz + + + + 100MB + + + + + + 50000 + true + + + \ No newline at end of file diff --git a/src/main/resources/logback-http.xml b/src/main/resources/logback-http.xml new file mode 100644 index 0000000..d8d39dd --- /dev/null +++ b/src/main/resources/logback-http.xml @@ -0,0 +1,27 @@ + + + + + [%d{yyyy-MM-dd HH:mm:ss.SSS}] %marker %msg%n + + + http.log + + + 1 + 20 + http.log.%i.gz + + + + 100MB + + + + + + 50000 + true + + + \ No newline at end of file diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 20a0ca5..722a79e 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,5 +1,13 @@ + + + + + + + + diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpoint.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpoint.scala index 5565b23..13dbc12 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpoint.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpoint.scala @@ -29,7 +29,7 @@ import scala.util.{Failure, Success} @Path("api/v1") class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout) - extends BiSHelper(storage, ariyala) with Authorization with JsonSupport with HttpExceptionsHandler { + extends BiSHelper(storage, ariyala) with Authorization with JsonSupport with HttpHandler { def route: Route = createBiS ~ getBiS ~ modifyBiS @@ -55,14 +55,16 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti def createBiS: Route = path("party" / Segment / "bis") { partyId => handleExceptions(exceptionHandler) { - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => - put { - entity(as[PlayerBiSLinkResponse]) { bisLink => - val playerId = bisLink.playerId.withPartyId(partyId) - onComplete(putBiS(playerId, bisLink.link)) { - case Success(_) => complete(StatusCodes.Created, HttpEntity.Empty) - case Failure(exception) => throw exception + handleRejections(rejectionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => + put { + entity(as[PlayerBiSLinkResponse]) { bisLink => + val playerId = bisLink.playerId.withPartyId(partyId) + onComplete(putBiS(playerId, bisLink.link)) { + case Success(_) => complete(StatusCodes.Created, HttpEntity.Empty) + case Failure(exception) => throw exception + } } } } @@ -95,17 +97,20 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti def getBiS: Route = path("party" / Segment / "bis") { partyId => handleExceptions(exceptionHandler) { - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => - get { - parameters("nick".as[String].?, "job".as[String].?) { (maybeNick, maybeJob) => - val playerId = PlayerId(partyId, maybeNick, maybeJob) - onComplete(bis(partyId, playerId)) { - case Success(response) => complete(response.map(PlayerResponse.fromPlayer)) - case Failure(exception) => throw exception + handleRejections(rejectionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => + get { + parameters("nick".as[String].?, "job".as[String].?) { (maybeNick, maybeJob) => + val playerId = PlayerId(partyId, maybeNick, maybeJob) + onComplete(bis(partyId, playerId)) { + case Success(response) => complete(response.map(PlayerResponse.fromPlayer)) + case Failure(exception) => throw exception + } } } } + } } } @@ -133,14 +138,16 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti def modifyBiS: Route = path("party" / Segment / "bis") { partyId => handleExceptions(exceptionHandler) { - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => - post { - entity(as[PieceActionResponse]) { action => - val playerId = action.playerIdResponse.withPartyId(partyId) - onComplete(doModifyBiS(action.action, playerId, action.piece.toPiece)) { - case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty) - case Failure(exception) => throw exception + handleRejections(rejectionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => + post { + entity(as[PieceActionResponse]) { action => + val playerId = action.playerIdResponse.withPartyId(partyId) + onComplete(doModifyBiS(action.action, playerId, action.piece.toPiece)) { + case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty) + case Failure(exception) => throw exception + } } } } diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/HttpExceptionsHandler.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/HttpExceptionsHandler.scala deleted file mode 100644 index 75fdd56..0000000 --- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/HttpExceptionsHandler.scala +++ /dev/null @@ -1,16 +0,0 @@ -package me.arcanis.ffxivbis.http.api.v1 - -import akka.http.scaladsl.model.StatusCodes -import akka.http.scaladsl.server.Directives._ -import akka.http.scaladsl.server._ -import com.typesafe.scalalogging.StrictLogging -import me.arcanis.ffxivbis.http.api.v1.json._ - -trait HttpExceptionsHandler extends StrictLogging { this: JsonSupport => - - def exceptionHandler: ExceptionHandler = ExceptionHandler { - case other: Exception => - logger.error("exception during request completion", other) - complete(StatusCodes.InternalServerError, ErrorResponse("unknown server error")) - } -} diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/HttpHandler.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/HttpHandler.scala new file mode 100644 index 0000000..47a51df --- /dev/null +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/HttpHandler.scala @@ -0,0 +1,26 @@ +package me.arcanis.ffxivbis.http.api.v1 + +import akka.http.scaladsl.model._ +import akka.http.scaladsl.server.Directives._ +import akka.http.scaladsl.server._ +import com.typesafe.scalalogging.StrictLogging +import me.arcanis.ffxivbis.http.api.v1.json._ +import spray.json._ + +trait HttpHandler extends StrictLogging { this: JsonSupport => + + implicit def exceptionHandler: ExceptionHandler = ExceptionHandler { + case other: Exception => + logger.error("exception during request completion", other) + complete(StatusCodes.InternalServerError, ErrorResponse("unknown server error")) + } + + implicit def rejectionHandler: RejectionHandler = + RejectionHandler.default + .mapRejectionResponse { + case response @ HttpResponse(_, _, entity: HttpEntity.Strict, _) => + val message = ErrorResponse(entity.data.utf8String).toJson + response.copy(entity = HttpEntity(ContentTypes.`application/json`, message.compactPrint)) + case other => other + } +} diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpoint.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpoint.scala index 02651d8..35a39b2 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpoint.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpoint.scala @@ -28,7 +28,7 @@ import scala.util.{Failure, Success} @Path("api/v1") class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) - extends LootHelper(storage) with Authorization with JsonSupport with HttpExceptionsHandler { + extends LootHelper(storage) with Authorization with JsonSupport with HttpHandler { def route: Route = getLoot ~ modifyLoot @@ -56,14 +56,16 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) def getLoot: Route = path("party" / Segment / "loot") { partyId => handleExceptions(exceptionHandler) { - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => - get { - parameters("nick".as[String].?, "job".as[String].?) { (maybeNick, maybeJob) => - val playerId = PlayerId(partyId, maybeNick, maybeJob) - onComplete(loot(partyId, playerId)) { - case Success(response) => complete(response.map(PlayerResponse.fromPlayer)) - case Failure(exception) => throw exception + handleRejections(rejectionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => + get { + parameters("nick".as[String].?, "job".as[String].?) { (maybeNick, maybeJob) => + val playerId = PlayerId(partyId, maybeNick, maybeJob) + onComplete(loot(partyId, playerId)) { + case Success(response) => complete(response.map(PlayerResponse.fromPlayer)) + case Failure(exception) => throw exception + } } } } @@ -94,14 +96,16 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) def modifyLoot: Route = path("party" / Segment / "loot") { partyId => handleExceptions(exceptionHandler) { - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => - post { - entity(as[PieceActionResponse]) { action => - val playerId = action.playerIdResponse.withPartyId(partyId) - onComplete(doModifyLoot(action.action, playerId, action.piece.toPiece)) { - case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty) - case Failure(exception) => throw exception + handleRejections(rejectionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => + post { + entity(as[PieceActionResponse]) { action => + val playerId = action.playerIdResponse.withPartyId(partyId) + onComplete(doModifyLoot(action.action, playerId, action.piece.toPiece)) { + case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty) + case Failure(exception) => throw exception + } } } } @@ -136,13 +140,15 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) def suggestLoot: Route = path("party" / Segment / "loot") { partyId => handleExceptions(exceptionHandler) { - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => - put { - entity(as[PieceResponse]) { piece => - onComplete(suggestPiece(partyId, piece.toPiece)) { - case Success(response) => complete(response.map(PlayerIdWithCountersResponse.fromPlayerId)) - case Failure(exception) => throw exception + handleRejections(rejectionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => + put { + entity(as[PieceResponse]) { piece => + onComplete(suggestPiece(partyId, piece.toPiece)) { + case Success(response) => complete(response.map(PlayerIdWithCountersResponse.fromPlayerId)) + case Failure(exception) => throw exception + } } } } diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpoint.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpoint.scala index 8506027..fb6e7c3 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpoint.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpoint.scala @@ -28,7 +28,7 @@ import scala.util.{Failure, Success} @Path("api/v1") class PlayerEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout) - extends PlayerHelper(storage, ariyala) with Authorization with JsonSupport with HttpExceptionsHandler { + extends PlayerHelper(storage, ariyala) with Authorization with JsonSupport with HttpHandler { def route: Route = getParty ~ modifyParty @@ -56,14 +56,16 @@ class PlayerEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit def getParty: Route = path("party" / Segment) { partyId => handleExceptions(exceptionHandler) { - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => - get { - parameters("nick".as[String].?, "job".as[String].?) { (maybeNick, maybeJob) => - val playerId = PlayerId(partyId, maybeNick, maybeJob) - onComplete(getPlayers(partyId, playerId)) { - case Success(response) => complete(response.map(PlayerResponse.fromPlayer)) - case Failure(exception) => throw exception + handleRejections(rejectionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => + get { + parameters("nick".as[String].?, "job".as[String].?) { (maybeNick, maybeJob) => + val playerId = PlayerId(partyId, maybeNick, maybeJob) + onComplete(getPlayers(partyId, playerId)) { + case Success(response) => complete(response.map(PlayerResponse.fromPlayer)) + case Failure(exception) => throw exception + } } } } @@ -94,13 +96,15 @@ class PlayerEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit def modifyParty: Route = path("party" / Segment) { partyId => handleExceptions(exceptionHandler) { - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => - entity(as[PlayerActionResponse]) { action => - val player = action.playerIdResponse.toPlayer.copy(partyId = partyId) - onComplete(doModifyPlayer(action.action, player)) { - case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty) - case Failure(exception) => throw exception + handleRejections(rejectionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => + entity(as[PlayerActionResponse]) { action => + val player = action.playerIdResponse.toPlayer.copy(partyId = partyId) + onComplete(doModifyPlayer(action.action, player)) { + case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty) + case Failure(exception) => throw exception + } } } } diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/UserEndpoint.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/UserEndpoint.scala index b9e33fc..f7d51d3 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/UserEndpoint.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/UserEndpoint.scala @@ -28,7 +28,7 @@ import scala.util.{Failure, Success} @Path("api/v1") class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) - extends UserHelper(storage) with Authorization with JsonSupport with HttpExceptionsHandler { + extends UserHelper(storage) with Authorization with JsonSupport with HttpHandler { def route: Route = createParty ~ createUser ~ deleteUser ~ getUsers @@ -52,13 +52,15 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) def createParty: Route = path("party" / Segment / "create") { partyId => handleExceptions(exceptionHandler) { - extractExecutionContext { implicit executionContext => - put { - entity(as[UserResponse]) { user => - val admin = user.toUser.copy(partyId = partyId, permission = Permission.admin) - onComplete(addUser(admin, isHashedPassword = false)) { - case Success(_) => complete(StatusCodes.Created, HttpEntity.Empty) - case Failure(exception) => throw exception + handleRejections(rejectionHandler) { + extractExecutionContext { implicit executionContext => + put { + entity(as[UserResponse]) { user => + val admin = user.toUser.copy(partyId = partyId, permission = Permission.admin) + onComplete(addUser(admin, isHashedPassword = false)) { + case Success(_) => complete(StatusCodes.Created, HttpEntity.Empty) + case Failure(exception) => throw exception + } } } } @@ -88,14 +90,16 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) def createUser: Route = path("party" / Segment / "users") { partyId => handleExceptions(exceptionHandler) { - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ => - post { - entity(as[UserResponse]) { user => - val withPartyId = user.toUser.copy(partyId = partyId) - onComplete(addUser(withPartyId, isHashedPassword = false)) { - case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty) - case Failure(exception) => throw exception + handleRejections(rejectionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ => + post { + entity(as[UserResponse]) { user => + val withPartyId = user.toUser.copy(partyId = partyId) + onComplete(addUser(withPartyId, isHashedPassword = false)) { + case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty) + case Failure(exception) => throw exception + } } } } @@ -123,12 +127,14 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) def deleteUser: Route = path("party" / Segment / "users" / Segment) { (partyId, username) => handleExceptions(exceptionHandler) { - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ => - delete { - onComplete(removeUser(partyId, username)) { - case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty) - case Failure(exception) => throw exception + handleRejections(rejectionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ => + delete { + onComplete(removeUser(partyId, username)) { + case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty) + case Failure(exception) => throw exception + } } } } @@ -158,12 +164,14 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) def getUsers: Route = path("party" / Segment / "users") { partyId => handleExceptions(exceptionHandler) { - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ => - get { - onComplete(users(partyId)) { - case Success(response) => complete(response.map(UserResponse.fromUser)) - case Failure(exception) => throw exception + handleRejections(rejectionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ => + get { + onComplete(users(partyId)) { + case Success(response) => complete(response.map(UserResponse.fromUser)) + case Failure(exception) => throw exception + } } } }