diff --git a/server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala b/server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala index 93f79c6..26f3cb2 100644 --- a/server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala +++ b/server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala @@ -21,6 +21,7 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi inputs <- upsertInputs(transaction.id, transaction.inputs) outputs <- upsertOutputs(transaction.id, transaction.outputs) _ <- spend(transaction.id, inputs) + _ = insertDetails(transaction) } yield partialTx.copy(inputs = inputs, outputs = outputs) } @@ -64,6 +65,7 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi val result = expectedTransactions.map { tx => val inputs = deleteInputs(tx.id) val outputs = deleteOutputs(tx.id) + val _ = deleteDetails(tx.id) tx.copy(inputs = inputs, outputs = outputs) } @@ -340,4 +342,62 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi result.flatten } + + private def insertDetails(transaction: Transaction)(implicit conn: Connection): List[AddressTransactionDetails] = { + val received = transaction + .outputs + .groupBy(_.address) + .mapValues { outputs => outputs.map(_.value).sum } + .map { case (address, value) => AddressTransactionDetails(address, transaction.id, time = transaction.time, received = value) } + + val sent = transaction + .inputs + .groupBy(_.address) + .mapValues { inputs => inputs.map(_.value).sum } + .map { case (address, value) => AddressTransactionDetails(address, transaction.id, time = transaction.time, sent = value) } + + val result = (received ++ sent) + .groupBy(_.address) + .mapValues { + case head :: list => list.foldLeft(head) { (acc, current) => + current.copy(received = current.received + acc.received, sent = current.sent + acc.sent) + } + } + .values + .map(d => insertDetails(d)) + + result.toList + } + + private def insertDetails(details: AddressTransactionDetails)(implicit conn: Connection): AddressTransactionDetails = { + SQL( + """ + |INSERT INTO address_transaction_details + | (address, txid, received, sent, time) + |VALUES + | ({address}, {txid}, {received}, {sent}, {time}) + |RETURNING address, txid, received, sent, time + """.stripMargin + ).on( + 'address -> details.address.string, + 'txid -> details.txid.string, + 'received -> details.received, + 'sent -> details.sent, + 'time -> details.time + ).as(parseAddressTransactionDetails.single) + } + + private def deleteDetails(txid: TransactionId)(implicit conn: Connection): List[AddressTransactionDetails] = { + val result = SQL( + """ + |DELETE FROM address_transaction_details + |WHERE txid = {txid} + |RETURNING address, txid, received, sent, time + """.stripMargin + ).on( + 'txid -> txid.string + ).as(parseAddressTransactionDetails.*) + + result + } } diff --git a/server/app/com/xsn/explorer/data/anorm/parsers/TransactionParsers.scala b/server/app/com/xsn/explorer/data/anorm/parsers/TransactionParsers.scala index adea489..df6d458 100644 --- a/server/app/com/xsn/explorer/data/anorm/parsers/TransactionParsers.scala +++ b/server/app/com/xsn/explorer/data/anorm/parsers/TransactionParsers.scala @@ -69,4 +69,13 @@ object TransactionParsers { script <- scriptMaybe } yield Transaction.Output(txid, index, value, address, script, tposOwnerAddress.flatten, tposMerchantAddress.flatten) } + + val parseAddressTransactionDetails = (parseAddress ~ parseTransactionId ~ parseSent ~ parseReceived ~ parseTime).map { + case address ~ txid ~ sent ~ received ~ time => AddressTransactionDetails( + address.getOrElse(throw new RuntimeException("failed to retrieve address")), + txid.getOrElse(throw new RuntimeException("failed to retrieve txid")), + time = time, + sent = sent, + received = received) + } } diff --git a/server/app/com/xsn/explorer/models/AddressTransactionDetails.scala b/server/app/com/xsn/explorer/models/AddressTransactionDetails.scala new file mode 100644 index 0000000..0355687 --- /dev/null +++ b/server/app/com/xsn/explorer/models/AddressTransactionDetails.scala @@ -0,0 +1,9 @@ +package com.xsn.explorer.models + +case class AddressTransactionDetails( + address: Address, + txid: TransactionId, + time: Long, + received: BigDecimal = 0, + sent: BigDecimal = 0) + diff --git a/server/test/com/xsn/explorer/data/common/PostgresDataHandlerSpec.scala b/server/test/com/xsn/explorer/data/common/PostgresDataHandlerSpec.scala index 2de27fe..b5cfe3d 100644 --- a/server/test/com/xsn/explorer/data/common/PostgresDataHandlerSpec.scala +++ b/server/test/com/xsn/explorer/data/common/PostgresDataHandlerSpec.scala @@ -62,6 +62,7 @@ trait PostgresDataHandlerSpec database.withConnection { implicit conn => _root_.anorm.SQL("""DELETE FROM transaction_inputs""").execute() _root_.anorm.SQL("""DELETE FROM transaction_outputs""").execute() + _root_.anorm.SQL("""DELETE FROM address_transaction_details""").execute() _root_.anorm.SQL("""DELETE FROM transactions""").execute() _root_.anorm.SQL("""DELETE FROM blocks""").execute() _root_.anorm.SQL("""DELETE FROM balances""").execute()