Browse Source

server: Update "GET /addresses/:address" (#18)

Now it retrieves the address balance from the database
instead of the xsn server, this reduces the data because
now we get the balance only and the transactions are retrieved
with another endpoint.
prometheus-integration
Alexis Hernandez 7 years ago
parent
commit
6ed2b5d31e
  1. 19
      server/app/com/xsn/explorer/models/AddressDetails.scala
  2. 15
      server/app/com/xsn/explorer/services/AddressService.scala
  3. 4
      server/app/controllers/AddressesController.scala
  4. 2
      server/conf/routes
  5. 18
      server/test/com/xsn/explorer/helpers/BalanceDummyDataHandler.scala
  6. 8
      server/test/com/xsn/explorer/helpers/DataHelper.scala
  7. 18
      server/test/com/xsn/explorer/helpers/TransactionDummyDataHandler.scala
  8. 64
      server/test/controllers/AddressesControllerSpec.scala

19
server/app/com/xsn/explorer/models/AddressDetails.scala

@ -1,19 +0,0 @@
package com.xsn.explorer.models
import com.xsn.explorer.models.rpc.AddressBalance
import play.api.libs.json._
case class AddressDetails(balance: AddressBalance, transactions: List[TransactionId])
object AddressDetails {
implicit val writes: Writes[AddressDetails] = Writes { obj =>
val transactions = obj.transactions.map { txid => Json.toJson(txid) }
val values = Map(
"balance" -> JsNumber(obj.balance.balance),
"received" -> JsNumber(obj.balance.received),
"transactions" -> JsArray(transactions))
JsObject.apply(values)
}
}

15
server/app/com/xsn/explorer/services/AddressService.scala

@ -4,21 +4,24 @@ import javax.inject.Inject
import com.alexitc.playsonify.core.FutureOr.Implicits.{FutureOps, OrOps}
import com.alexitc.playsonify.core.{ApplicationResult, FutureApplicationResult}
import com.xsn.explorer.data.async.BalanceFutureDataHandler
import com.xsn.explorer.errors.AddressFormatError
import com.xsn.explorer.models.{Address, AddressDetails}
import com.xsn.explorer.models.{Address, Balance}
import org.scalactic.{One, Or}
import play.api.libs.json.JsValue
import scala.concurrent.ExecutionContext
class AddressService @Inject() (xsnService: XSNService)(implicit ec: ExecutionContext) {
class AddressService @Inject() (
xsnService: XSNService,
balanceFutureDataHandler: BalanceFutureDataHandler)(
implicit ec: ExecutionContext) {
def getDetails(addressString: String): FutureApplicationResult[AddressDetails] = {
def getBy(addressString: String): FutureApplicationResult[Balance] = {
val result = for {
address <- getAddress(addressString).toFutureOr
balance <- xsnService.getAddressBalance(address).toFutureOr
transactions <- xsnService.getTransactions(address).toFutureOr
} yield AddressDetails(balance, transactions)
balance <- balanceFutureDataHandler.getBy(address).toFutureOr
} yield balance
result.toFuture
}

4
server/app/controllers/AddressesController.scala

@ -12,8 +12,8 @@ class AddressesController @Inject() (
cc: MyJsonControllerComponents)
extends MyJsonController(cc) {
def getDetails(address: String) = publicNoInput { _ =>
addressService.getDetails(address)
def getBy(address: String) = publicNoInput { _ =>
addressService.getBy(address)
}
def getTransactions(address: String, offset: Int, limit: Int, ordering: String) = publicNoInput { _ =>

2
server/conf/routes

@ -9,7 +9,7 @@ GET /transactions/:txid controllers.TransactionsController.getTransacti
GET /transactions/:txid/raw controllers.TransactionsController.getRawTransaction(txid: String)
POST /transactions controllers.TransactionsController.sendRawTransaction()
GET /addresses/:address controllers.AddressesController.getDetails(address: String)
GET /addresses/:address controllers.AddressesController.getBy(address: String)
GET /addresses/:address/transactions controllers.AddressesController.getTransactions(address: String, offset: Int ?= 0, limit: Int ?= 10, orderBy: String ?= "")
GET /addresses/:address/utxos controllers.AddressesController.getUnspentOutputs(address: String)

18
server/test/com/xsn/explorer/helpers/BalanceDummyDataHandler.scala

@ -0,0 +1,18 @@
package com.xsn.explorer.helpers
import com.alexitc.playsonify.core.ApplicationResult
import com.alexitc.playsonify.models.{FieldOrdering, PaginatedQuery, PaginatedResult}
import com.xsn.explorer.data.BalanceBlockingDataHandler
import com.xsn.explorer.models.fields.BalanceField
import com.xsn.explorer.models.{Address, Balance}
class BalanceDummyDataHandler extends BalanceBlockingDataHandler {
override def upsert(balance: Balance): ApplicationResult[Balance] = ???
override def get(query: PaginatedQuery, ordering: FieldOrdering[BalanceField]): ApplicationResult[PaginatedResult[Balance]] = ???
override def getBy(address: Address): ApplicationResult[Balance] = ???
override def getNonZeroBalances(query: PaginatedQuery, ordering: FieldOrdering[BalanceField]): ApplicationResult[PaginatedResult[Balance]] = ???
}

8
server/test/com/xsn/explorer/helpers/DataHelper.scala

@ -1,7 +1,7 @@
package com.xsn.explorer.helpers
import com.xsn.explorer.models.rpc.{AddressBalance, ScriptPubKey, TransactionVOUT}
import com.xsn.explorer.models.{Address, AddressDetails, Blockhash, TransactionId}
import com.xsn.explorer.models.rpc.{ScriptPubKey, TransactionVOUT}
import com.xsn.explorer.models.{Address, Blockhash, TransactionId}
object DataHelper {
@ -25,8 +25,4 @@ object DataHelper {
def createScriptPubKey(scriptType: String, asm: String, address: Option[Address] = None) = {
ScriptPubKey(scriptType, asm, address.toList)
}
def createAddressDetails(balance: Int, received: Int, transactions: List[TransactionId]) = {
AddressDetails(AddressBalance(BigDecimal(balance), BigDecimal(received)), transactions)
}
}

18
server/test/com/xsn/explorer/helpers/TransactionDummyDataHandler.scala

@ -0,0 +1,18 @@
package com.xsn.explorer.helpers
import com.alexitc.playsonify.core.ApplicationResult
import com.alexitc.playsonify.models.{FieldOrdering, PaginatedQuery, PaginatedResult}
import com.xsn.explorer.data.TransactionBlockingDataHandler
import com.xsn.explorer.models._
import com.xsn.explorer.models.fields.TransactionField
class TransactionDummyDataHandler extends TransactionBlockingDataHandler {
override def upsert(transaction: Transaction): ApplicationResult[Transaction] = ???
override def delete(transactionId: TransactionId): ApplicationResult[Transaction] = ???
override def deleteBy(blockhash: Blockhash): ApplicationResult[List[Transaction]] = ???
override def getBy(address: Address, paginatedQuery: PaginatedQuery, ordering: FieldOrdering[TransactionField]): ApplicationResult[PaginatedResult[TransactionWithValues]] = ???
}

64
server/test/controllers/AddressesControllerSpec.scala

@ -3,15 +3,13 @@ package controllers
import com.alexitc.playsonify.PublicErrorRenderer
import com.alexitc.playsonify.core.{ApplicationResult, FutureApplicationResult}
import com.alexitc.playsonify.models.{Count, FieldOrdering, PaginatedQuery, PaginatedResult}
import com.xsn.explorer.data.TransactionBlockingDataHandler
import com.xsn.explorer.errors.AddressFormatError
import com.xsn.explorer.helpers.{DataHelper, DummyXSNService}
import com.xsn.explorer.data.{BalanceBlockingDataHandler, TransactionBlockingDataHandler}
import com.xsn.explorer.helpers.{BalanceDummyDataHandler, DataHelper, DummyXSNService, TransactionDummyDataHandler}
import com.xsn.explorer.models._
import com.xsn.explorer.models.fields.TransactionField
import com.xsn.explorer.models.rpc.AddressBalance
import com.xsn.explorer.services.XSNService
import controllers.common.MyAPISpec
import org.scalactic.{Good, One, Or}
import org.scalactic.Good
import play.api.inject.bind
import play.api.libs.json.{JsValue, Json}
import play.api.test.Helpers._
@ -22,15 +20,8 @@ class AddressesControllerSpec extends MyAPISpec {
import DataHelper._
val addressEmpty = createAddressDetails(0, 0, List())
val addressFilled = createAddressDetails(
100,
200,
List(
createTransactionId("0834641a7d30d8a2d2b451617599670445ee94ed7736e146c13be260c576c641"),
createTransactionId("024aba1d535cfe5dd3ea465d46a828a57b00e1df012d7a2d158e0f7484173f7c")
)
)
val addressWithBalance = createAddress("XeNEPsgeWqNbrEGEN5vqv4wYcC3qQrqNyp")
val addressBalance = Balance(addressWithBalance, spent = 100, received = 200)
val addressForUtxos = DataHelper.createAddress("XeNEPsgeWqNbrEGEN5vqv4wYcC3qQrqNyp")
val utxosResponse =
@ -66,22 +57,6 @@ class AddressesControllerSpec extends MyAPISpec {
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 getTransactions(address: Address): FutureApplicationResult[List[TransactionId]] = {
val maybe = map.get(address.string).map(_.transactions)
val result = Or.from(maybe, One(AddressFormatError))
Future.successful(result)
}
override def getUnspentOutputs(address: Address): FutureApplicationResult[JsValue] = {
if (address == addressForUtxos) {
@ -93,13 +68,7 @@ class AddressesControllerSpec extends MyAPISpec {
}
}
private val customTransactionDataHandler = new TransactionBlockingDataHandler {
override def upsert(transaction: Transaction): ApplicationResult[Transaction] = ???
override def delete(transactionId: TransactionId): ApplicationResult[Transaction] = ???
override def deleteBy(blockhash: Blockhash): ApplicationResult[List[Transaction]] = ???
private val customTransactionDataHandler = new TransactionDummyDataHandler {
override def getBy(address: Address, paginatedQuery: PaginatedQuery, ordering: FieldOrdering[TransactionField]): ApplicationResult[PaginatedResult[TransactionWithValues]] = {
if (address == addressForTransactions) {
@ -110,27 +79,36 @@ class AddressesControllerSpec extends MyAPISpec {
}
}
private val customBalanceDataHandler = new BalanceDummyDataHandler {
override def getBy(address: Address): ApplicationResult[Balance] = {
if (address == addressWithBalance) {
Good(addressBalance)
} else {
Good(Balance(address))
}
}
}
override val application = guiceApplicationBuilder
.overrides(bind[XSNService].to(customXSNService))
.overrides(bind[TransactionBlockingDataHandler].to(customTransactionDataHandler))
.overrides(bind[BalanceBlockingDataHandler].to(customBalanceDataHandler))
.build()
"GET /addresses/:address" should {
def url(address: String) = s"/addresses/$address"
"retrieve address information" in {
val address = addressFilled
val response = GET(url("XnH3bC9NruJ4wnu4Dgi8F3wemmJtcxpKp6"))
val address = addressWithBalance
val response = GET(url(address.string))
status(response) mustEqual OK
val json = contentAsJson(response)
(json \ "balance").as[BigDecimal] mustEqual address.balance.balance
(json \ "received").as[BigDecimal] mustEqual address.balance.received
(json \ "transactions").as[List[String]].size mustEqual address.transactions.size
(json \ "spent").as[BigDecimal] mustEqual addressBalance.spent
(json \ "received").as[BigDecimal] mustEqual addressBalance.received
}
"fail on bad address format" in {
val address = "XnH3bC9NruJ4wnu4Dgi8F3wemmJtcxpKp"
val response = GET(url(address))

Loading…
Cancel
Save