diff --git a/server/app/com/xsn/explorer/services/AddressService.scala b/server/app/com/xsn/explorer/services/AddressService.scala new file mode 100644 index 0000000..c3a314b --- /dev/null +++ b/server/app/com/xsn/explorer/services/AddressService.scala @@ -0,0 +1,28 @@ +package com.xsn.explorer.services + +import javax.inject.Inject + +import com.alexitc.playsonify.core.FutureApplicationResult +import com.alexitc.playsonify.core.FutureOr.Implicits.{FutureOps, OrOps} +import com.xsn.explorer.errors.AddressFormatError +import com.xsn.explorer.models.{Address, AddressDetails} +import org.scalactic.{One, Or} + +import scala.concurrent.ExecutionContext + +class AddressService @Inject() (xsnService: XSNService)(implicit ec: ExecutionContext) { + + def getDetails(addressString: String): FutureApplicationResult[AddressDetails] = { + val result = for { + address <- { + val maybe = Address.from(addressString) + Or.from(maybe, One(AddressFormatError)).toFutureOr + } + + balance <- xsnService.getAddressBalance(address).toFutureOr + transactionCount <- xsnService.getTransactionCount(address).toFutureOr + } yield AddressDetails(balance, transactionCount) + + result.toFuture + } +} diff --git a/server/app/controllers/AddressesController.scala b/server/app/controllers/AddressesController.scala new file mode 100644 index 0000000..a65287b --- /dev/null +++ b/server/app/controllers/AddressesController.scala @@ -0,0 +1,16 @@ +package controllers + +import javax.inject.Inject + +import com.xsn.explorer.services.AddressService +import controllers.common.{MyJsonController, MyJsonControllerComponents} + +class AddressesController @Inject() ( + addressService: AddressService, + cc: MyJsonControllerComponents) + extends MyJsonController(cc) { + + def getDetails(address: String) = publicNoInput { _ => + addressService.getDetails(address) + } +} diff --git a/server/conf/routes b/server/conf/routes index 39edd60..bba2354 100644 --- a/server/conf/routes +++ b/server/conf/routes @@ -4,3 +4,5 @@ # ~~~~ GET /transactions/:txid controllers.TransactionsController.getTransaction(txid: String) + +GET /addresses/:address controllers.AddressesController.getDetails(address: String) diff --git a/server/test/controllers/AddressesTransactionSpec.scala b/server/test/controllers/AddressesTransactionSpec.scala new file mode 100644 index 0000000..81557d5 --- /dev/null +++ b/server/test/controllers/AddressesTransactionSpec.scala @@ -0,0 +1,80 @@ +package controllers + +import com.alexitc.playsonify.PublicErrorRenderer +import com.alexitc.playsonify.core.FutureApplicationResult +import com.xsn.explorer.errors.AddressFormatError +import com.xsn.explorer.helpers.DummyXSNService +import com.xsn.explorer.models._ +import com.xsn.explorer.services.XSNService +import controllers.common.MyAPISpec +import org.scalactic.{One, Or} +import play.api.inject.bind +import play.api.libs.json.JsValue +import play.api.test.Helpers._ + +import scala.concurrent.Future + + +class AddressesTransactionSpec extends MyAPISpec { + + def addressDetails(balance: Int, received: Int, txCount: Int) = { + AddressDetails(AddressBalance(BigInt(balance), BigInt(received)), txCount) + } + + val addressEmpty = addressDetails(0, 0, 0) + val addressFilled = addressDetails(100, 200, 25) + + val customXSNService = new DummyXSNService { + val map = Map( + "Xi3sQfMQsy2CzMZTrnKW6HFGp1VqFThdLw" -> addressEmpty, + "XnH3bC9NruJ4wnu4Dgi8F3wemmJtcxpKp6" -> addressFilled + ) + + override def getAddressBalance(address: Address): FutureApplicationResult[AddressBalance] = { + val maybe = map.get(address.string).map(_.balance) + val result = Or.from(maybe, One(AddressFormatError)) + Future.successful(result) + } + + override def getTransactionCount(address: Address): FutureApplicationResult[Int] = { + val maybe = map.get(address.string).map(_.transactionCount) + val result = Or.from(maybe, One(AddressFormatError)) + Future.successful(result) + } + } + + override val application = guiceApplicationBuilder + .overrides(bind[XSNService].to(customXSNService)) + .build() + + "GET /addresses/:address" should { + def url(address: String) = s"/addresses/$address" + + "retrieve address information" in { + val address = addressFilled + val response = GET(url("XnH3bC9NruJ4wnu4Dgi8F3wemmJtcxpKp6")) + + status(response) mustEqual OK + val json = contentAsJson(response) + (json \ "balance").as[Int] mustEqual address.balance.balance.intValue() + (json \ "received").as[Int] mustEqual address.balance.received.intValue() + (json \ "transactionCount").as[Int] mustEqual address.transactionCount + } + + "fail on bad address format" in { + + val address = "XnH3bC9NruJ4wnu4Dgi8F3wemmJtcxpKp" + val response = GET(url(address)) + + status(response) mustEqual BAD_REQUEST + val json = contentAsJson(response) + val errorList = (json \ "errors").as[List[JsValue]] + + errorList.size mustEqual 1 + val error = errorList.head + + (error \ "type").as[String] mustEqual PublicErrorRenderer.FieldValidationErrorType + (error \ "field").as[String] mustEqual "address" + } + } +}