Browse Source

server: Add "GET /blocks/:blockhash/transactions" (#19)

prometheus-integration
Alexis Hernandez 7 years ago
parent
commit
b4a4577186
  1. 16
      server/app/com/xsn/explorer/services/TransactionService.scala
  2. 10
      server/app/controllers/BlocksController.scala
  3. 1
      server/conf/routes
  4. 54
      server/test/controllers/BlocksControllerSpec.scala

16
server/app/com/xsn/explorer/services/TransactionService.scala

@ -7,7 +7,7 @@ import com.alexitc.playsonify.core.{FutureApplicationResult, FuturePaginatedResu
import com.alexitc.playsonify.models.{OrderingQuery, PaginatedQuery}
import com.alexitc.playsonify.validators.PaginatedQueryValidator
import com.xsn.explorer.data.async.TransactionFutureDataHandler
import com.xsn.explorer.errors.{AddressFormatError, InvalidRawTransactionError, TransactionFormatError, TransactionNotFoundError}
import com.xsn.explorer.errors._
import com.xsn.explorer.models._
import com.xsn.explorer.models.rpc.TransactionVIN
import com.xsn.explorer.parsers.TransactionOrderingParser
@ -23,6 +23,8 @@ class TransactionService @Inject() (
transactionFutureDataHandler: TransactionFutureDataHandler)(
implicit ec: ExecutionContext) {
private val maxTransactionsPerQuery = 100
def getRawTransaction(txidString: String): FutureApplicationResult[JsValue] = {
val result = for {
txid <- {
@ -85,7 +87,7 @@ class TransactionService @Inject() (
Or.from(maybe, One(AddressFormatError)).toFutureOr
}
paginatedQuery <- paginatedQueryValidator.validate(paginatedQuery, 100).toFutureOr
paginatedQuery <- paginatedQueryValidator.validate(paginatedQuery, maxTransactionsPerQuery).toFutureOr
ordering <- transactionOrderingParser.from(orderingQuery).toFutureOr
transactions <- transactionFutureDataHandler.getBy(address, paginatedQuery, ordering).toFutureOr
} yield transactions
@ -102,6 +104,16 @@ class TransactionService @Inject() (
result.toFuture
}
def getByBlockhash(blockhashString: String, paginatedQuery: PaginatedQuery, orderingQuery: OrderingQuery): FuturePaginatedResult[TransactionWithValues] = {
val result = for {
blockhash <- Or.from(Blockhash.from(blockhashString), One(BlockhashFormatError)).toFutureOr
validatedQuery <- paginatedQueryValidator.validate(paginatedQuery, maxTransactionsPerQuery).toFutureOr
order <- transactionOrderingParser.from(orderingQuery).toFutureOr
r <- transactionFutureDataHandler.getByBlockhash(blockhash, validatedQuery, order).toFutureOr
} yield r
result.toFuture
}
private def getTransactionValue(vin: TransactionVIN): FutureApplicationResult[TransactionValue] = {
val valueMaybe = for {
value <- vin.value

10
server/app/controllers/BlocksController.scala

@ -2,14 +2,16 @@ package controllers
import javax.inject.Inject
import com.alexitc.playsonify.models.{Limit, Offset, OrderingQuery, PaginatedQuery}
import com.xsn.explorer.models.Height
import com.xsn.explorer.services.BlockService
import com.xsn.explorer.services.{BlockService, TransactionService}
import controllers.common.{MyJsonController, MyJsonControllerComponents}
import scala.util.Try
class BlocksController @Inject() (
blockService: BlockService,
transactionService: TransactionService,
cc: MyJsonControllerComponents)
extends MyJsonController(cc) {
@ -35,4 +37,10 @@ class BlocksController @Inject() (
.map(blockService.getRawBlock)
.getOrElse(blockService.getRawBlock(query))
}
def getTransactions(blockhash: String, offset: Int, limit: Int, orderBy: String) = publicNoInput { _ =>
val query = PaginatedQuery(Offset(offset), Limit(limit))
val ordering = OrderingQuery(orderBy)
transactionService.getByBlockhash(blockhash, query, ordering)
}
}

1
server/conf/routes

@ -16,6 +16,7 @@ GET /addresses/:address/utxos controllers.AddressesController.getUns
GET /blocks controllers.BlocksController.getLatestBlocks()
GET /blocks/:query controllers.BlocksController.getDetails(query: String)
GET /blocks/:query/raw controllers.BlocksController.getRawBlock(query: String)
GET /blocks/:blockhash/transactions controllers.BlocksController.getTransactions(blockhash: String, offset: Int ?= 0, limit: Int ?= 10, orderBy: String ?= "")
GET /stats controllers.StatisticsController.getStatus()

54
server/test/controllers/BlocksControllerSpec.scala

@ -1,10 +1,13 @@
package controllers
import com.alexitc.playsonify.PublicErrorRenderer
import com.alexitc.playsonify.core.FutureApplicationResult
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.{BlockNotFoundError, TransactionNotFoundError}
import com.xsn.explorer.helpers.{BlockLoader, DummyXSNService, TransactionLoader}
import com.xsn.explorer.helpers.{BlockLoader, DummyXSNService, TransactionDummyDataHandler, TransactionLoader}
import com.xsn.explorer.models._
import com.xsn.explorer.models.fields.TransactionField
import com.xsn.explorer.models.rpc.{Block, Transaction}
import com.xsn.explorer.services.XSNService
import controllers.common.MyAPISpec
@ -106,8 +109,38 @@ class BlocksControllerSpec extends MyAPISpec {
}
}
val transactionDataHandler = new TransactionDummyDataHandler {
// TODO: Handle ordering
override def getByBlockhash(blockhash: Blockhash, paginatedQuery: PaginatedQuery, ordering: FieldOrdering[TransactionField]): ApplicationResult[PaginatedResult[TransactionWithValues]] = {
val transactions = BlockLoader
.get(blockhash.string)
.transactions
.map(_.string)
.map(TransactionLoader.get)
.map { tx =>
TransactionWithValues(
id = tx.id,
blockhash = blockhash,
time = tx.time,
size = tx.size,
sent = tx.vin.flatMap(_.value).sum,
received = tx.vout.map(_.value).sum
)
}
val page = PaginatedResult(
paginatedQuery.offset,
paginatedQuery.limit,
Count(transactions.size),
transactions.drop(paginatedQuery.offset.int).take(paginatedQuery.limit.int))
Good(page)
}
}
override val application = guiceApplicationBuilder
.overrides(bind[XSNService].to(customXSNService))
.overrides(bind[TransactionBlockingDataHandler].to(transactionDataHandler))
.build()
"GET /blocks/:query" should {
@ -394,4 +427,21 @@ class BlocksControllerSpec extends MyAPISpec {
json mustEqual BlockLoader.json(block.hash.string)
}
}
"GET /blocks/:blockhash/transactions" should {
"return the transactions for the given block" in {
val blockhash = "000003fb382f6892ae96594b81aa916a8923c70701de4e7054aac556c7271ef7"
val response = GET(s"/blocks/$blockhash/transactions?offset=0&limit=5&orderBy=time:desc")
status(response) mustEqual OK
val json = contentAsJson(response)
(json \ "total").as[Int] mustEqual 1
(json \ "offset").as[Int] mustEqual 0
(json \ "limit").as[Int] mustEqual 5
val data = (json \ "data").as[List[JsValue]]
data.size mustEqual 1
}
}
}

Loading…
Cancel
Save