diff --git a/server/app/com/xsn/explorer/data/BlockDataHandler.scala b/server/app/com/xsn/explorer/data/BlockDataHandler.scala index 95a9954..86619b1 100644 --- a/server/app/com/xsn/explorer/data/BlockDataHandler.scala +++ b/server/app/com/xsn/explorer/data/BlockDataHandler.scala @@ -1,7 +1,7 @@ package com.xsn.explorer.data import com.alexitc.playsonify.core.ApplicationResult -import com.alexitc.playsonify.models.ordering.FieldOrdering +import com.alexitc.playsonify.models.ordering.{FieldOrdering, OrderingCondition} import com.alexitc.playsonify.models.pagination.{Limit, PaginatedQuery, PaginatedResult} import com.xsn.explorer.models.fields.BlockField import com.xsn.explorer.models.persisted.{Block, BlockHeader} @@ -25,7 +25,7 @@ trait BlockDataHandler[F[_]] { def getFirstBlock(): F[Block] - def getHeaders(limit: Limit, lastSeenHash: Option[Blockhash]): F[List[BlockHeader]] + def getHeaders(limit: Limit, orderingCondition: OrderingCondition, lastSeenHash: Option[Blockhash]): F[List[BlockHeader]] } trait BlockBlockingDataHandler extends BlockDataHandler[ApplicationResult] diff --git a/server/app/com/xsn/explorer/data/anorm/BlockPostgresDataHandler.scala b/server/app/com/xsn/explorer/data/anorm/BlockPostgresDataHandler.scala index 7e2eaf6..75c8a30 100644 --- a/server/app/com/xsn/explorer/data/anorm/BlockPostgresDataHandler.scala +++ b/server/app/com/xsn/explorer/data/anorm/BlockPostgresDataHandler.scala @@ -1,7 +1,7 @@ package com.xsn.explorer.data.anorm import com.alexitc.playsonify.core.ApplicationResult -import com.alexitc.playsonify.models.ordering.FieldOrdering +import com.alexitc.playsonify.models.ordering.{FieldOrdering, OrderingCondition} import com.alexitc.playsonify.models.pagination import com.alexitc.playsonify.models.pagination.{PaginatedQuery, PaginatedResult} import com.xsn.explorer.data.BlockBlockingDataHandler @@ -58,13 +58,14 @@ class BlockPostgresDataHandler @Inject() ( override def getHeaders( limit: pagination.Limit, + orderingCondition: OrderingCondition, lastSeenHash: Option[Blockhash]): ApplicationResult[List[BlockHeader]] = withConnection { implicit conn => val result = lastSeenHash .map { hash => - blockPostgresDAO.getHeaders(hash, limit) + blockPostgresDAO.getHeaders(hash, limit, orderingCondition) } - .getOrElse { blockPostgresDAO.getHeaders(limit) } + .getOrElse { blockPostgresDAO.getHeaders(limit, orderingCondition) } Good(result) } diff --git a/server/app/com/xsn/explorer/data/anorm/dao/BlockPostgresDAO.scala b/server/app/com/xsn/explorer/data/anorm/dao/BlockPostgresDAO.scala index 8d100be..47fc553 100644 --- a/server/app/com/xsn/explorer/data/anorm/dao/BlockPostgresDAO.scala +++ b/server/app/com/xsn/explorer/data/anorm/dao/BlockPostgresDAO.scala @@ -151,12 +151,14 @@ class BlockPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderingSQLI getBy(query, ordering).headOption } - def getHeaders(limit: Limit)(implicit conn: Connection): List[BlockHeader] = { + def getHeaders(limit: Limit, orderingCondition: OrderingCondition)(implicit conn: Connection): List[BlockHeader] = { + val order = toSQL(orderingCondition) + SQL( - """ + s""" |SELECT blockhash, previous_blockhash, merkle_root, height, time |FROM blocks - |ORDER BY height + |ORDER BY height $order |LIMIT {limit} """.stripMargin ).on( @@ -164,9 +166,15 @@ class BlockPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderingSQLI ).as(parseHeader.*) } - def getHeaders(lastSeenHash: Blockhash, limit: Limit)(implicit conn: Connection): List[BlockHeader] = { + def getHeaders(lastSeenHash: Blockhash, limit: Limit, orderingCondition: OrderingCondition)(implicit conn: Connection): List[BlockHeader] = { + val order = toSQL(orderingCondition) + val comparator = orderingCondition match { + case OrderingCondition.DescendingOrder => "<" + case OrderingCondition.AscendingOrder => ">" + } + SQL( - """ + s""" |WITH CTE AS ( | SELECT height as lastSeenHeight | FROM blocks @@ -174,8 +182,8 @@ class BlockPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderingSQLI |) |SELECT b.blockhash, b.previous_blockhash, b.merkle_root, b.height, b.time |FROM CTE CROSS JOIN blocks b - |WHERE b.height > lastSeenHeight - |ORDER BY height + |WHERE b.height $comparator lastSeenHeight + |ORDER BY height $order |LIMIT {limit} """.stripMargin ).on( @@ -183,4 +191,9 @@ class BlockPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderingSQLI 'limit -> limit.int ).as(parseHeader.*) } + + private def toSQL(condition: OrderingCondition): String = condition match { + case OrderingCondition.AscendingOrder => "ASC" + case OrderingCondition.DescendingOrder => "DESC" + } } diff --git a/server/app/com/xsn/explorer/data/async/BlockFutureDataHandler.scala b/server/app/com/xsn/explorer/data/async/BlockFutureDataHandler.scala index 71fec53..1384946 100644 --- a/server/app/com/xsn/explorer/data/async/BlockFutureDataHandler.scala +++ b/server/app/com/xsn/explorer/data/async/BlockFutureDataHandler.scala @@ -1,7 +1,7 @@ package com.xsn.explorer.data.async import com.alexitc.playsonify.core.{FutureApplicationResult, FuturePaginatedResult} -import com.alexitc.playsonify.models.ordering.FieldOrdering +import com.alexitc.playsonify.models.ordering.{FieldOrdering, OrderingCondition} import com.alexitc.playsonify.models.pagination import com.alexitc.playsonify.models.pagination.PaginatedQuery import com.xsn.explorer.data.{BlockBlockingDataHandler, BlockDataHandler} @@ -42,7 +42,11 @@ class BlockFutureDataHandler @Inject() ( blockBlockingDataHandler.getFirstBlock() } - override def getHeaders(limit: pagination.Limit, lastSeenHash: Option[Blockhash]): FutureApplicationResult[List[BlockHeader]] = Future { - blockBlockingDataHandler.getHeaders(limit, lastSeenHash) + override def getHeaders( + limit: pagination.Limit, + orderingCondition: OrderingCondition, + lastSeenHash: Option[Blockhash]): FutureApplicationResult[List[BlockHeader]] = Future { + + blockBlockingDataHandler.getHeaders(limit, orderingCondition, lastSeenHash) } } diff --git a/server/app/com/xsn/explorer/services/BlockService.scala b/server/app/com/xsn/explorer/services/BlockService.scala index 4a521f4..ef59bcc 100644 --- a/server/app/com/xsn/explorer/services/BlockService.scala +++ b/server/app/com/xsn/explorer/services/BlockService.scala @@ -9,6 +9,7 @@ import com.xsn.explorer.errors.{BlockRewardsNotFoundError, BlockhashFormatError} import com.xsn.explorer.models._ import com.xsn.explorer.models.rpc.{Block, TransactionVIN} import com.xsn.explorer.models.values.{Blockhash, Height} +import com.xsn.explorer.parsers.OrderingConditionParser import com.xsn.explorer.services.logic.{BlockLogic, TransactionLogic} import com.xsn.explorer.util.Extensions.FutureOrExt import javax.inject.Inject @@ -22,12 +23,17 @@ class BlockService @Inject() ( blockDataHandler: BlockFutureDataHandler, paginatedQueryValidator: PaginatedQueryValidator, blockLogic: BlockLogic, - transactionLogic: TransactionLogic)( + transactionLogic: TransactionLogic, + orderingConditionParser: OrderingConditionParser)( implicit ec: ExecutionContext) { private val maxHeadersPerQuery = 1000 - def getBlockHeaders(limit: Limit, lastSeenHashString: Option[String]): FutureApplicationResult[WrappedResult[List[persisted.BlockHeader]]] = { + def getBlockHeaders( + limit: Limit, + lastSeenHashString: Option[String], + orderingConditionString: String): FutureApplicationResult[WrappedResult[List[persisted.BlockHeader]]] = { + val result = for { lastSeenHash <- { lastSeenHashString @@ -38,8 +44,9 @@ class BlockService @Inject() ( } _ <- paginatedQueryValidator.validate(PaginatedQuery(Offset(0), limit), maxHeadersPerQuery).toFutureOr + orderingCondition <- orderingConditionParser.parseReuslt(orderingConditionString).toFutureOr - headers <- blockDataHandler.getHeaders(limit, lastSeenHash).toFutureOr + headers <- blockDataHandler.getHeaders(limit, orderingCondition, lastSeenHash).toFutureOr } yield WrappedResult(headers) result.toFuture diff --git a/server/app/controllers/BlocksController.scala b/server/app/controllers/BlocksController.scala index 69d92aa..3dffc80 100644 --- a/server/app/controllers/BlocksController.scala +++ b/server/app/controllers/BlocksController.scala @@ -24,8 +24,8 @@ class BlocksController @Inject() ( blockService.getLatestBlocks() } - def getBlockHeaders(lastSeenHash: Option[String], limit: Int) = public { _ => - blockService.getBlockHeaders(Limit(limit), lastSeenHash) + def getBlockHeaders(lastSeenHash: Option[String], limit: Int, orderingCondition: String) = public { _ => + blockService.getBlockHeaders(Limit(limit), lastSeenHash, orderingCondition) } /** diff --git a/server/conf/routes b/server/conf/routes index 1e162ac..150a893 100644 --- a/server/conf/routes +++ b/server/conf/routes @@ -16,7 +16,7 @@ GET /v2/addresses/:address/transactions controllers.AddressesContro GET /addresses/:address/utxos controllers.AddressesController.getUnspentOutputs(address: String) GET /blocks controllers.BlocksController.getLatestBlocks() -GET /blocks/headers controllers.BlocksController.getBlockHeaders(lastSeenHash: Option[String], limit: Int ?= 10) +GET /blocks/headers controllers.BlocksController.getBlockHeaders(lastSeenHash: Option[String], limit: Int ?= 10, order: String ?= "asc") GET /blocks/:query controllers.BlocksController.getDetails(query: String) GET /blocks/:query/raw controllers.BlocksController.getRawBlock(query: String) diff --git a/server/test/com/xsn/explorer/services/LedgerSynchronizerServiceSpec.scala b/server/test/com/xsn/explorer/services/LedgerSynchronizerServiceSpec.scala index c2fe99c..60e6610 100644 --- a/server/test/com/xsn/explorer/services/LedgerSynchronizerServiceSpec.scala +++ b/server/test/com/xsn/explorer/services/LedgerSynchronizerServiceSpec.scala @@ -221,7 +221,8 @@ class LedgerSynchronizerServiceSpec extends PostgresDataHandlerSpec with BeforeA new BlockFutureDataHandler(blockDataHandler)(Executors.databaseEC), new PaginatedQueryValidator, new BlockLogic, - new TransactionLogic) + new TransactionLogic, + new OrderingConditionParser) val transactionRPCService = new TransactionRPCService(xsnService) new LedgerSynchronizerService( xsnService,