Browse Source

server: Allow to retrieve transactions for an address on TransactionDataHandler

This is a piece for #18, we need to retrieve paginated transactions for a given
address.

NOTE: This commit doesn't include tests in order to work in the frontend
      concurrently, tests will be included before the release.
prometheus-integration
Alexis Hernandez 7 years ago
parent
commit
ef862b9272
  1. 5
      server/app/com/xsn/explorer/data/TransactionDataHandler.scala
  2. 14
      server/app/com/xsn/explorer/data/anorm/TransactionPostgresDataHandler.scala
  3. 57
      server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala
  4. 18
      server/app/com/xsn/explorer/data/anorm/parsers/TransactionParsers.scala
  5. 12
      server/app/com/xsn/explorer/data/async/TransactionFutureDataHandler.scala
  6. 16
      server/app/com/xsn/explorer/models/TransactionWithValues.scala

5
server/app/com/xsn/explorer/data/TransactionDataHandler.scala

@ -1,7 +1,8 @@
package com.xsn.explorer.data
import com.alexitc.playsonify.core.ApplicationResult
import com.xsn.explorer.models.{Blockhash, Transaction, TransactionId}
import com.alexitc.playsonify.models.{PaginatedQuery, PaginatedResult}
import com.xsn.explorer.models._
import scala.language.higherKinds
@ -12,6 +13,8 @@ trait TransactionDataHandler[F[_]] {
def delete(transactionId: TransactionId): F[Transaction]
def deleteBy(blockhash: Blockhash): F[List[Transaction]]
def getBy(address: Address, paginatedQuery: PaginatedQuery): F[PaginatedResult[TransactionWithValues]]
}
trait TransactionBlockingDataHandler extends TransactionDataHandler[ApplicationResult]

14
server/app/com/xsn/explorer/data/anorm/TransactionPostgresDataHandler.scala

@ -3,10 +3,11 @@ package com.xsn.explorer.data.anorm
import javax.inject.Inject
import com.alexitc.playsonify.core.ApplicationResult
import com.alexitc.playsonify.models.{PaginatedQuery, PaginatedResult}
import com.xsn.explorer.data.TransactionBlockingDataHandler
import com.xsn.explorer.data.anorm.dao.TransactionPostgresDAO
import com.xsn.explorer.errors.{TransactionNotFoundError, TransactionUnknownError}
import com.xsn.explorer.models.{Blockhash, Transaction, TransactionId}
import com.xsn.explorer.models._
import org.scalactic.{Good, One, Or}
import play.api.db.Database
@ -30,4 +31,15 @@ class TransactionPostgresDataHandler @Inject() (
val transactions = transactionPostgresDAO.deleteBy(blockhash)
Good(transactions)
}
override def getBy(
address: Address,
paginatedQuery: PaginatedQuery): ApplicationResult[PaginatedResult[TransactionWithValues]] = withConnection { implicit conn =>
val transactions = transactionPostgresDAO.getBy(address, paginatedQuery)
val total = transactionPostgresDAO.countBy(address)
val result = PaginatedResult(paginatedQuery.offset, paginatedQuery.limit, total, transactions)
Good(result)
}
}

57
server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala

@ -3,8 +3,9 @@ package com.xsn.explorer.data.anorm.dao
import java.sql.Connection
import anorm._
import com.alexitc.playsonify.models.{Count, PaginatedQuery}
import com.xsn.explorer.data.anorm.parsers.TransactionParsers._
import com.xsn.explorer.models.{Blockhash, Transaction, TransactionId}
import com.xsn.explorer.models._
class TransactionPostgresDAO {
@ -63,6 +64,60 @@ class TransactionPostgresDAO {
}
}
def getBy(address: Address, paginatedQuery: PaginatedQuery)(implicit conn: Connection): List[TransactionWithValues] = {
/**
* TODO: The query is very slow while aggregating the spent and received values,
* it might be worth creating an index-like table to get the accumulated
* values directly.
*/
SQL(
"""
|SELECT t.txid, blockhash, time, size,
| (SELECT COALESCE(SUM(value), 0) FROM transaction_inputs WHERE txid = t.txid) AS sent,
| (SELECT COALESCE(SUM(value), 0) FROM transaction_outputs WHERE txid = t.txid) AS received
|FROM transactions t
|WHERE t.txid IN (
| SELECT txid
| FROM transaction_inputs
| WHERE address = {address}
|) OR t.txid IN (
| SELECT txid
| FROM transaction_outputs
| WHERE address = {address}
|)
|ORDER BY time DESC
|OFFSET {offset}
|LIMIT {limit}
""".stripMargin
).on(
'address -> address.string,
'offset -> paginatedQuery.offset.int,
'limit -> paginatedQuery.limit.int
).as(parseTransactionWithValues.*).flatten
}
def countBy(address: Address)(implicit conn: Connection): Count = {
val result = SQL(
"""
|SELECT COUNT(*)
|FROM transactions
|WHERE txid IN (
| SELECT txid
| FROM transaction_inputs
| WHERE address = {address}
|) OR txid IN (
| SELECT txid
| FROM transaction_outputs
| WHERE address = {address}
|)
""".stripMargin
).on(
'address -> address.string
).as(SqlParser.scalar[Int].single)
Count(result)
}
private def upsertTransaction(transaction: Transaction)(implicit conn: Connection): Option[Transaction] = {
SQL(
"""

18
server/app/com/xsn/explorer/data/anorm/parsers/TransactionParsers.scala

@ -2,7 +2,7 @@ package com.xsn.explorer.data.anorm.parsers
import anorm.SqlParser.{get, str}
import anorm.~
import com.xsn.explorer.models.{Address, Transaction, TransactionId}
import com.xsn.explorer.models.{Address, Transaction, TransactionId, TransactionWithValues}
object TransactionParsers {
@ -11,6 +11,7 @@ object TransactionParsers {
val parseTransactionId = str("txid").map(TransactionId.from)
val parseReceived = get[BigDecimal]("received")
val parseSpent = get[BigDecimal]("spent")
val parseSent = get[BigDecimal]("sent")
val parseIndex = get[Int]("index")
val parseValue = get[BigDecimal]("value")
@ -26,6 +27,21 @@ object TransactionParsers {
} yield Transaction(txid, blockhash, time, size, List.empty, List.empty)
}
val parseTransactionWithValues = (
parseTransactionId ~
parseBlockhash ~
parseTime ~
parseSize ~
parseSent ~
parseReceived).map {
case txidMaybe ~ blockhashMaybe ~ time ~ size ~ sent ~ received =>
for {
txid <- txidMaybe
blockhash <- blockhashMaybe
} yield TransactionWithValues(txid, blockhash, time, size, sent, received)
}
val parseTransactionInput = (parseIndex ~ parseValue.? ~ parseAddress.?).map { case index ~ value ~ address =>
Transaction.Input(index, value, address.flatten)
}

12
server/app/com/xsn/explorer/data/async/TransactionFutureDataHandler.scala

@ -2,10 +2,11 @@ package com.xsn.explorer.data.async
import javax.inject.Inject
import com.alexitc.playsonify.core.FutureApplicationResult
import com.alexitc.playsonify.core.{FutureApplicationResult, FuturePaginatedResult}
import com.alexitc.playsonify.models.PaginatedQuery
import com.xsn.explorer.data.{TransactionBlockingDataHandler, TransactionDataHandler}
import com.xsn.explorer.executors.DatabaseExecutionContext
import com.xsn.explorer.models.{Blockhash, Transaction, TransactionId}
import com.xsn.explorer.models._
import scala.concurrent.Future
@ -25,4 +26,11 @@ class TransactionFutureDataHandler @Inject() (
override def deleteBy(blockhash: Blockhash): FutureApplicationResult[List[Transaction]] = Future {
blockingDataHandler.deleteBy(blockhash)
}
override def getBy(
address: Address,
paginatedQuery: PaginatedQuery): FuturePaginatedResult[TransactionWithValues] = Future {
blockingDataHandler.getBy(address, paginatedQuery)
}
}

16
server/app/com/xsn/explorer/models/TransactionWithValues.scala

@ -0,0 +1,16 @@
package com.xsn.explorer.models
import play.api.libs.json.{Json, Writes}
case class TransactionWithValues(
id: TransactionId,
blockhash: Blockhash,
time: Long,
size: Size,
sent: BigDecimal,
received: BigDecimal)
object TransactionWithValues {
implicit val writes: Writes[TransactionWithValues] = Json.writes[TransactionWithValues]
}
Loading…
Cancel
Save