From f1bf820b758605d69c2850f9c4a34e622411ab8e Mon Sep 17 00:00:00 2001 From: Alexis Hernandez Date: Sun, 17 Jun 2018 11:53:39 -0500 Subject: [PATCH] server: Add endpoint "GET /addresses/:address/transactions" This is a piece for #18, it allow us to retrieve the transactions for the given address. NOTE: This commit doesn't include tests in order to work in the frontend concurrently, tests will be included before the release. --- .../services/TransactionService.scala | 32 +++++++- .../app/controllers/AddressesController.scala | 10 ++- server/conf/routes | 3 +- .../processors/BlockEventsProcessorSpec.scala | 81 ++++++++----------- 4 files changed, 74 insertions(+), 52 deletions(-) diff --git a/server/app/com/xsn/explorer/services/TransactionService.scala b/server/app/com/xsn/explorer/services/TransactionService.scala index 6d91cb6..e12752a 100644 --- a/server/app/com/xsn/explorer/services/TransactionService.scala +++ b/server/app/com/xsn/explorer/services/TransactionService.scala @@ -2,17 +2,24 @@ package com.xsn.explorer.services import javax.inject.Inject -import com.alexitc.playsonify.core.FutureApplicationResult import com.alexitc.playsonify.core.FutureOr.Implicits.{FutureListOps, FutureOps, OrOps} -import com.xsn.explorer.errors.{TransactionFormatError, TransactionNotFoundError} +import com.alexitc.playsonify.core.{FutureApplicationResult, FuturePaginatedResult} +import com.alexitc.playsonify.models.PaginatedQuery +import com.alexitc.playsonify.validators.PaginatedQueryValidator +import com.xsn.explorer.data.async.TransactionFutureDataHandler +import com.xsn.explorer.errors.{AddressFormatError, TransactionFormatError, TransactionNotFoundError} +import com.xsn.explorer.models._ import com.xsn.explorer.models.rpc.TransactionVIN -import com.xsn.explorer.models.{Transaction, TransactionDetails, TransactionId, TransactionValue} import org.scalactic.{Bad, Good, One, Or} import play.api.libs.json.JsValue import scala.concurrent.{ExecutionContext, Future} -class TransactionService @Inject() (xsnService: XSNService)(implicit ec: ExecutionContext) { +class TransactionService @Inject() ( + paginatedQueryValidator: PaginatedQueryValidator, + xsnService: XSNService, + transactionFutureDataHandler: TransactionFutureDataHandler)( + implicit ec: ExecutionContext) { def getRawTransaction(txidString: String): FutureApplicationResult[JsValue] = { val result = for { @@ -65,6 +72,23 @@ class TransactionService @Inject() (xsnService: XSNService)(implicit ec: Executi result.toFuture } + def getTransactions( + addressString: String, + paginatedQuery: PaginatedQuery): FuturePaginatedResult[TransactionWithValues] = { + + val result = for { + address <- { + val maybe = Address.from(addressString) + Or.from(maybe, One(AddressFormatError)).toFutureOr + } + + paginatedQuery <- paginatedQueryValidator.validate(paginatedQuery, 100).toFutureOr + transactions <- transactionFutureDataHandler.getBy(address, paginatedQuery).toFutureOr + } yield transactions + + result.toFuture + } + private def getTransactionValue(vin: TransactionVIN): FutureApplicationResult[TransactionValue] = { val valueMaybe = for { value <- vin.value diff --git a/server/app/controllers/AddressesController.scala b/server/app/controllers/AddressesController.scala index a65287b..6064339 100644 --- a/server/app/controllers/AddressesController.scala +++ b/server/app/controllers/AddressesController.scala @@ -2,15 +2,23 @@ package controllers import javax.inject.Inject -import com.xsn.explorer.services.AddressService +import com.alexitc.playsonify.models.{Limit, Offset, PaginatedQuery} +import com.xsn.explorer.services.{AddressService, TransactionService} import controllers.common.{MyJsonController, MyJsonControllerComponents} class AddressesController @Inject() ( addressService: AddressService, + transactionService: TransactionService, cc: MyJsonControllerComponents) extends MyJsonController(cc) { def getDetails(address: String) = publicNoInput { _ => addressService.getDetails(address) } + + def getTransactions(address: String, offset: Int, limit: Int) = publicNoInput { _ => + val paginatedQuery = PaginatedQuery(Offset(offset), Limit(limit)) + + transactionService.getTransactions(address, paginatedQuery) + } } diff --git a/server/conf/routes b/server/conf/routes index a5e9a16..f135dd1 100644 --- a/server/conf/routes +++ b/server/conf/routes @@ -8,7 +8,8 @@ GET /health controllers.HealthController.check() GET /transactions/:txid controllers.TransactionsController.getTransaction(txid: String) GET /transactions/:txid/raw controllers.TransactionsController.getRawTransaction(txid: String) -GET /addresses/:address controllers.AddressesController.getDetails(address: String) +GET /addresses/:address controllers.AddressesController.getDetails(address: String) +GET /addresses/:address/transactions controllers.AddressesController.getTransactions(address: String, offset: Int ?= 0, limit: Int ?= 10) GET /blocks controllers.BlocksController.getLatestBlocks() GET /blocks/:query controllers.BlocksController.getDetails(query: String) diff --git a/server/test/com/xsn/explorer/processors/BlockEventsProcessorSpec.scala b/server/test/com/xsn/explorer/processors/BlockEventsProcessorSpec.scala index 65f82e4..8514629 100644 --- a/server/test/com/xsn/explorer/processors/BlockEventsProcessorSpec.scala +++ b/server/test/com/xsn/explorer/processors/BlockEventsProcessorSpec.scala @@ -2,10 +2,11 @@ package com.xsn.explorer.processors import com.alexitc.playsonify.core.FutureApplicationResult import com.alexitc.playsonify.models._ +import com.alexitc.playsonify.validators.PaginatedQueryValidator +import com.xsn.explorer.data.anorm._ import com.xsn.explorer.data.anorm.dao.{BalancePostgresDAO, BlockPostgresDAO, StatisticsPostgresDAO, TransactionPostgresDAO} import com.xsn.explorer.data.anorm.interpreters.FieldOrderingSQLInterpreter -import com.xsn.explorer.data.anorm.{BalancePostgresDataHandler, BlockPostgresDataHandler, DatabasePostgresSeeder, StatisticsPostgresDataHandler} -import com.xsn.explorer.data.async.{BlockFutureDataHandler, DatabaseFutureSeeder} +import com.xsn.explorer.data.async.{BlockFutureDataHandler, DatabaseFutureSeeder, TransactionFutureDataHandler} import com.xsn.explorer.data.common.PostgresDataHandlerSpec import com.xsn.explorer.errors.{BlockNotFoundError, TransactionNotFoundError} import com.xsn.explorer.helpers.{BlockLoader, Executors, FileBasedXSNService} @@ -13,7 +14,7 @@ import com.xsn.explorer.models.fields.BalanceField import com.xsn.explorer.models.rpc.{Block, Transaction} import com.xsn.explorer.models.{Blockhash, TransactionId} import com.xsn.explorer.processors.BlockEventsProcessor._ -import com.xsn.explorer.services.TransactionService +import com.xsn.explorer.services.{TransactionService, XSNService} import org.scalactic.{Bad, Good} import org.scalatest.BeforeAndAfter import org.scalatest.concurrent.ScalaFutures @@ -23,24 +24,10 @@ import scala.concurrent.Future class BlockEventsProcessorSpec extends PostgresDataHandlerSpec with ScalaFutures with BeforeAndAfter { lazy val dataHandler = new BlockPostgresDataHandler(database, new BlockPostgresDAO) - lazy val dataSeeder = new DatabasePostgresSeeder( - database, - new BlockPostgresDAO, - new TransactionPostgresDAO, - new BalancePostgresDAO(new FieldOrderingSQLInterpreter)) + lazy val transactionDataHandler = new TransactionPostgresDataHandler(database, new TransactionPostgresDAO) lazy val xsnService = new FileBasedXSNService - - lazy val blockOps = new BlockOps( - new DatabaseFutureSeeder(dataSeeder)(Executors.databaseEC), - new BlockFutureDataHandler(dataHandler)(Executors.databaseEC)) - - lazy val processor = new BlockEventsProcessor( - xsnService, - new TransactionService(xsnService)(Executors.globalEC), - new DatabaseFutureSeeder(dataSeeder)(Executors.databaseEC), - new BlockFutureDataHandler(dataHandler)(Executors.databaseEC), - blockOps) + lazy val processor = blockEventsProcessor() before { clearDatabase() @@ -196,14 +183,8 @@ class BlockEventsProcessorSpec extends PostgresDataHandlerSpec with ScalaFutures } } - val processor = new BlockEventsProcessor( - xsnService, - new TransactionService(xsnService)(Executors.globalEC), - new DatabaseFutureSeeder(dataSeeder)(Executors.databaseEC), - new BlockFutureDataHandler(dataHandler)(Executors.databaseEC), - blockOps) - - + val processor = blockEventsProcessor(xsn = xsnService) + List(block1, block2) .map(_.hash) .map(processor.processBlock) @@ -236,13 +217,7 @@ class BlockEventsProcessorSpec extends PostgresDataHandlerSpec with ScalaFutures } } - val processor = new BlockEventsProcessor( - xsnService, - new TransactionService(xsnService)(Executors.globalEC), - new DatabaseFutureSeeder(dataSeeder)(Executors.databaseEC), - new BlockFutureDataHandler(dataHandler)(Executors.databaseEC), - blockOps) - + val processor = blockEventsProcessor(xsn = xsnService) List(block1, block2) .map(_.hash) @@ -278,12 +253,7 @@ class BlockEventsProcessorSpec extends PostgresDataHandlerSpec with ScalaFutures } } - val processor = new BlockEventsProcessor( - xsnService, - new TransactionService(xsnService)(Executors.globalEC), - new DatabaseFutureSeeder(dataSeeder)(Executors.databaseEC), - new BlockFutureDataHandler(dataHandler)(Executors.databaseEC), - blockOps) + val processor = blockEventsProcessor(xsn = xsnService) List(block1, block2) .map(_.hash) @@ -317,12 +287,7 @@ class BlockEventsProcessorSpec extends PostgresDataHandlerSpec with ScalaFutures } } - val processor = new BlockEventsProcessor( - xsnService, - new TransactionService(xsnService)(Executors.globalEC), - new DatabaseFutureSeeder(dataSeeder)(Executors.databaseEC), - new BlockFutureDataHandler(dataHandler)(Executors.databaseEC), - blockOps) + val processor = blockEventsProcessor(xsn = xsnService) List(block1, rareBlock3) .map(_.hash) @@ -351,4 +316,28 @@ class BlockEventsProcessorSpec extends PostgresDataHandlerSpec with ScalaFutures _root_.anorm.SQL("""SELECT COUNT(*) FROM blocks""").as(_root_.anorm.SqlParser.scalar[Int].single) } } + + private def blockEventsProcessor(xsn: XSNService = xsnService) = { + val dataSeeder = new DatabasePostgresSeeder( + database, + new BlockPostgresDAO, + new TransactionPostgresDAO, + new BalancePostgresDAO(new FieldOrderingSQLInterpreter)) + + val blockOps = new BlockOps( + new DatabaseFutureSeeder(dataSeeder)(Executors.databaseEC), + new BlockFutureDataHandler(dataHandler)(Executors.databaseEC)) + + val transactionService = new TransactionService( + new PaginatedQueryValidator, + xsn, + new TransactionFutureDataHandler(transactionDataHandler)(Executors.databaseEC))(Executors.globalEC) + + new BlockEventsProcessor( + xsn, + transactionService, + new DatabaseFutureSeeder(dataSeeder)(Executors.databaseEC), + new BlockFutureDataHandler(dataHandler)(Executors.databaseEC), + blockOps) + } }