diff --git a/docs/release-notes/eclair-vnext.md b/docs/release-notes/eclair-vnext.md index 6cdc1f3976..8362fbb6b2 100644 --- a/docs/release-notes/eclair-vnext.md +++ b/docs/release-notes/eclair-vnext.md @@ -116,6 +116,7 @@ upgrading to this release. - `channelbalances` and `usablebalances` return a `shortIds` object instead of a single `shortChannelId` (#2323) - `stop` stops eclair: please note that the recommended way of stopping eclair is simply to kill its process (#2233) - `rbfopen` lets the initiator of a dual-funded channel RBF the funding transaction (#2275) +- `listinvoices` now accepts `--count` and `--skip` parameters to limit the number of retrieved items (#2474) ### Miscellaneous improvements and bug fixes diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala index 544438920c..06aa17d69c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala @@ -137,7 +137,7 @@ trait Eclair { def pendingInvoices(from: TimestampSecond, to: TimestampSecond)(implicit timeout: Timeout): Future[Seq[Invoice]] - def allInvoices(from: TimestampSecond, to: TimestampSecond)(implicit timeout: Timeout): Future[Seq[Invoice]] + def allInvoices(from: TimestampSecond, to: TimestampSecond, paginated_opt: Option[Paginated])(implicit timeout: Timeout): Future[Seq[Invoice]] def deleteInvoice(paymentHash: ByteVector32): Future[String] @@ -435,8 +435,8 @@ class EclairImpl(appKit: Kit) extends Eclair with Logging { Future(appKit.nodeParams.db.audit.stats(from.toTimestampMilli, to.toTimestampMilli)) } - override def allInvoices(from: TimestampSecond, to: TimestampSecond)(implicit timeout: Timeout): Future[Seq[Invoice]] = Future { - appKit.nodeParams.db.payments.listIncomingPayments(from.toTimestampMilli, to.toTimestampMilli).map(_.invoice) + override def allInvoices(from: TimestampSecond, to: TimestampSecond, paginated_opt: Option[Paginated])(implicit timeout: Timeout): Future[Seq[Invoice]] = Future { + appKit.nodeParams.db.payments.listIncomingPayments(from.toTimestampMilli, to.toTimestampMilli, paginated_opt).map(_.invoice) } override def pendingInvoices(from: TimestampSecond, to: TimestampSecond)(implicit timeout: Timeout): Future[Seq[Invoice]] = Future { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Paginated.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Paginated.scala new file mode 100644 index 0000000000..166fc96634 --- /dev/null +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Paginated.scala @@ -0,0 +1,27 @@ +/* + * Copyright 2022 ACINQ SAS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package fr.acinq.eclair + +/** + * Created by rorp on 03/11/2022. + * + * Simple class for pagination database and API queries. + */ +case class Paginated(count: Int, skip: Int) { + require(count >= 0, "count must be a positive number") + require(skip >= 0, "skip must be a positive number") +} diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/DualDatabases.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/DualDatabases.scala index 38320d0787..d5c22e93a7 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/DualDatabases.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/DualDatabases.scala @@ -11,7 +11,7 @@ import fr.acinq.eclair.payment._ import fr.acinq.eclair.payment.relay.Relayer.RelayFees import fr.acinq.eclair.router.Router import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelUpdate, NodeAddress, NodeAnnouncement} -import fr.acinq.eclair.{CltvExpiry, MilliSatoshi, RealShortChannelId, ShortChannelId, TimestampMilli} +import fr.acinq.eclair.{CltvExpiry, MilliSatoshi, Paginated, RealShortChannelId, ShortChannelId, TimestampMilli} import grizzled.slf4j.Logging import scodec.bits.ByteVector @@ -306,9 +306,9 @@ case class DualPaymentsDb(primary: PaymentsDb, secondary: PaymentsDb) extends Pa primary.removeIncomingPayment(paymentHash) } - override def listIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = { - runAsync(secondary.listIncomingPayments(from, to)) - primary.listIncomingPayments(from, to) + override def listIncomingPayments(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[IncomingPayment] = { + runAsync(secondary.listIncomingPayments(from, to, paginated_opt)) + primary.listIncomingPayments(from, to, paginated_opt) } override def listPendingIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala index e4e7002f27..b8824d1274 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala @@ -20,7 +20,7 @@ import fr.acinq.bitcoin.scalacompat.ByteVector32 import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.eclair.payment._ import fr.acinq.eclair.router.Router.{ChannelHop, Hop, NodeHop} -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId, TimestampMilli} +import fr.acinq.eclair.{MilliSatoshi, Paginated, ShortChannelId, TimestampMilli} import scodec.bits.ByteVector import java.util.UUID @@ -52,7 +52,7 @@ trait IncomingPaymentsDb { def removeIncomingPayment(paymentHash: ByteVector32): Try[Unit] /** List all incoming payments (pending, expired and succeeded) in the given time range (milli-seconds). */ - def listIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] + def listIncomingPayments(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[IncomingPayment] /** List all pending (not paid, not expired) incoming payments in the given time range (milli-seconds). */ def listPendingIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/jdbc/JdbcUtils.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/jdbc/JdbcUtils.scala index b3da5d4595..6b8a500ef7 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/jdbc/JdbcUtils.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/jdbc/JdbcUtils.scala @@ -17,7 +17,7 @@ package fr.acinq.eclair.db.jdbc import fr.acinq.bitcoin.scalacompat.ByteVector32 -import fr.acinq.eclair.MilliSatoshi +import fr.acinq.eclair.{MilliSatoshi, Paginated} import grizzled.slf4j.Logger import org.sqlite.SQLiteConnection import scodec.Decoder @@ -214,6 +214,11 @@ trait JdbcUtils { implicit def conv(rs: ResultSet): ExtendedResultSet = ExtendedResultSet(rs) } + def limited(sql: String, paginated_opt: Option[Paginated]): String = paginated_opt match { + case Some(paginated) => s"$sql LIMIT ${paginated.count} OFFSET ${paginated.skip}" + case None => sql + } + } object JdbcUtils extends JdbcUtils diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgPaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgPaymentsDb.scala index ae19ab51d2..d6526f10c3 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgPaymentsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgPaymentsDb.scala @@ -24,7 +24,7 @@ import fr.acinq.eclair.db.PaymentsDb._ import fr.acinq.eclair.db._ import fr.acinq.eclair.db.pg.PgUtils.PgLock import fr.acinq.eclair.payment._ -import fr.acinq.eclair.{MilliSatoshi, TimestampMilli, TimestampMilliLong} +import fr.acinq.eclair.{MilliSatoshi, Paginated, TimestampMilli, TimestampMilliLong} import grizzled.slf4j.Logging import scodec.bits.{BitVector, ByteVector} @@ -326,9 +326,9 @@ class PgPaymentsDb(implicit ds: DataSource, lock: PgLock) extends PaymentsDb wit } } - override def listIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = withMetrics("payments/list-incoming", DbBackends.Postgres) { + override def listIncomingPayments(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[IncomingPayment] = withMetrics("payments/list-incoming", DbBackends.Postgres) { withLock { pg => - using(pg.prepareStatement("SELECT * FROM payments.received WHERE created_at > ? AND created_at < ? ORDER BY created_at")) { statement => + using(pg.prepareStatement(limited("SELECT * FROM payments.received WHERE created_at > ? AND created_at < ? ORDER BY created_at", paginated_opt))) { statement => statement.setTimestamp(1, from.toSqlTimestamp) statement.setTimestamp(2, to.toSqlTimestamp) statement.executeQuery().flatMap(parseIncomingPayment).toSeq diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala index bce1640c93..ca2297154a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala @@ -24,7 +24,7 @@ import fr.acinq.eclair.db.PaymentsDb._ import fr.acinq.eclair.db._ import fr.acinq.eclair.db.sqlite.SqliteUtils._ import fr.acinq.eclair.payment._ -import fr.acinq.eclair.{MilliSatoshi, TimestampMilli, TimestampMilliLong} +import fr.acinq.eclair.{MilliSatoshi, Paginated, TimestampMilli, TimestampMilliLong} import grizzled.slf4j.Logging import scodec.bits.{BitVector, ByteVector} @@ -328,8 +328,8 @@ class SqlitePaymentsDb(val sqlite: Connection) extends PaymentsDb with Logging { } } - override def listIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = withMetrics("payments/list-incoming", DbBackends.Sqlite) { - using(sqlite.prepareStatement("SELECT * FROM received_payments WHERE created_at > ? AND created_at < ? ORDER BY created_at")) { statement => + override def listIncomingPayments(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[IncomingPayment] = withMetrics("payments/list-incoming", DbBackends.Sqlite) { + using(sqlite.prepareStatement(limited("SELECT * FROM received_payments WHERE created_at > ? AND created_at < ? ORDER BY created_at", paginated_opt))) { statement => statement.setLong(1, from.toLong) statement.setLong(2, to.toLong) statement.executeQuery().flatMap(parseIncomingPayment).toSeq diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/PaymentsDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/PaymentsDbSpec.scala index 39ffa96e64..00f7ece10e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/PaymentsDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/PaymentsDbSpec.scala @@ -28,7 +28,7 @@ import fr.acinq.eclair.payment._ import fr.acinq.eclair.router.Router.{ChannelHop, ChannelRelayParams, NodeHop} import fr.acinq.eclair.wire.protocol.OfferTypes._ import fr.acinq.eclair.wire.protocol.{ChannelUpdate, TlvStream, UnknownNextPeer} -import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshiLong, ShortChannelId, TimestampMilli, TimestampMilliLong, TimestampSecond, TimestampSecondLong, randomBytes32, randomBytes64, randomKey} +import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshiLong, Paginated, ShortChannelId, TimestampMilli, TimestampMilliLong, TimestampSecond, TimestampSecondLong, randomBytes32, randomBytes64, randomKey} import org.scalatest.funsuite.AnyFunSuite import scodec.bits.HexStringSyntax @@ -88,7 +88,7 @@ class PaymentsDbSpec extends AnyFunSuite { db.addIncomingPayment(i1, preimage1) db.receiveIncomingPayment(i1.paymentHash, 550 msat, 1100 unixms) - assert(db.listIncomingPayments(1 unixms, 1500 unixms) == Seq(pr1)) + assert(db.listIncomingPayments(1 unixms, 1500 unixms, None) == Seq(pr1)) assert(db.listOutgoingPayments(1 unixms, 1500 unixms) == Seq(ps1)) } ) @@ -204,7 +204,7 @@ class PaymentsDbSpec extends AnyFunSuite { db.updateOutgoingPayment(PaymentFailed(ps6.id, ps6.paymentHash, Nil, 1300 unixms)) assert(db.listOutgoingPayments(1 unixms, 2000 unixms) == Seq(ps1, ps2, ps3, ps4, ps5, ps6)) - assert(db.listIncomingPayments(1 unixms, TimestampMilli.now()) == Seq(pr1, pr2, pr3)) + assert(db.listIncomingPayments(1 unixms, TimestampMilli.now(), None) == Seq(pr1, pr2, pr3)) assert(db.listExpiredIncomingPayments(1 unixms, 2000 unixms) == Seq(pr2)) }) } @@ -436,9 +436,9 @@ class PaymentsDbSpec extends AnyFunSuite { assert(db.getIncomingPayment(i1.paymentHash).contains(pr1)) assert(db.getIncomingPayment(i2.paymentHash).contains(pr2)) - assert(db.listIncomingPayments(TimestampMilli(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2100-12-31T23:59:59.00Z").toEpochMilli)) == Seq(pr2, pr1)) - assert(db.listIncomingPayments(TimestampMilli(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2020-12-31T23:59:59.00Z").toEpochMilli)) == Seq(pr2)) - assert(db.listIncomingPayments(TimestampMilli(Instant.parse("2010-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2011-12-31T23:59:59.00Z").toEpochMilli)) == Seq.empty) + assert(db.listIncomingPayments(TimestampMilli(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2100-12-31T23:59:59.00Z").toEpochMilli), None) == Seq(pr2, pr1)) + assert(db.listIncomingPayments(TimestampMilli(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2020-12-31T23:59:59.00Z").toEpochMilli), None) == Seq(pr2)) + assert(db.listIncomingPayments(TimestampMilli(Instant.parse("2010-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2011-12-31T23:59:59.00Z").toEpochMilli), None) == Seq.empty) assert(db.listExpiredIncomingPayments(TimestampMilli(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2100-12-31T23:59:59.00Z").toEpochMilli)) == Seq(pr2)) assert(db.listExpiredIncomingPayments(TimestampMilli(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2020-12-31T23:59:59.00Z").toEpochMilli)) == Seq(pr2)) assert(db.listExpiredIncomingPayments(TimestampMilli(Instant.parse("2010-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2011-12-31T23:59:59.00Z").toEpochMilli)) == Seq.empty) @@ -556,7 +556,7 @@ class PaymentsDbSpec extends AnyFunSuite { assert(db.getIncomingPayment(paidInvoice3.paymentHash).contains(payment3.copy(status = IncomingPaymentStatus.Pending))) val now = TimestampMilli.now() - assert(db.listIncomingPayments(0 unixms, now) == Seq(expiredPayment1, expiredPayment2, expiredPayment3, pendingPayment1, pendingPayment2, pendingPayment3, payment1.copy(status = IncomingPaymentStatus.Pending), payment2.copy(status = IncomingPaymentStatus.Pending), payment3.copy(status = IncomingPaymentStatus.Pending))) + assert(db.listIncomingPayments(0 unixms, now, None) == Seq(expiredPayment1, expiredPayment2, expiredPayment3, pendingPayment1, pendingPayment2, pendingPayment3, payment1.copy(status = IncomingPaymentStatus.Pending), payment2.copy(status = IncomingPaymentStatus.Pending), payment3.copy(status = IncomingPaymentStatus.Pending))) assert(db.listExpiredIncomingPayments(0 unixms, now) == Seq(expiredPayment1, expiredPayment2, expiredPayment3)) assert(db.listReceivedIncomingPayments(0 unixms, now) == Nil) assert(db.listPendingIncomingPayments(0 unixms, now) == Seq(pendingPayment1, pendingPayment2, pendingPayment3, payment1.copy(status = IncomingPaymentStatus.Pending), payment2.copy(status = IncomingPaymentStatus.Pending), payment3.copy(status = IncomingPaymentStatus.Pending))) @@ -570,11 +570,16 @@ class PaymentsDbSpec extends AnyFunSuite { assert(db.getIncomingPayment(paidInvoice1.paymentHash).contains(payment1)) assert(db.getIncomingPayment(paidInvoice3.paymentHash).contains(payment3)) - assert(db.listIncomingPayments(0 unixms, now) == Seq(expiredPayment1, expiredPayment2, expiredPayment3, pendingPayment1, pendingPayment2, pendingPayment3, payment1, payment2, payment3)) - assert(db.listIncomingPayments(now - 60.seconds, now) == Seq(pendingPayment1, pendingPayment2, pendingPayment3, payment1, payment2, payment3)) + assert(db.listIncomingPayments(0 unixms, now, None) == Seq(expiredPayment1, expiredPayment2, expiredPayment3, pendingPayment1, pendingPayment2, pendingPayment3, payment1, payment2, payment3)) + assert(db.listIncomingPayments(now - 60.seconds, now, None) == Seq(pendingPayment1, pendingPayment2, pendingPayment3, payment1, payment2, payment3)) + assert(db.listIncomingPayments(0 unixms, now, Some(Paginated(0,0))) == Seq()) + assert(db.listIncomingPayments(0 unixms, now, Some(Paginated(0,3))) == Seq()) + assert(db.listIncomingPayments(0 unixms, now, Some(Paginated(3,0))) == Seq(expiredPayment1, expiredPayment2, expiredPayment3)) + assert(db.listIncomingPayments(0 unixms, now, Some(Paginated(3,3))) == Seq(pendingPayment1, pendingPayment2, pendingPayment3)) assert(db.listPendingIncomingPayments(0 unixms, now) == Seq(pendingPayment1, pendingPayment2, pendingPayment3)) assert(db.listReceivedIncomingPayments(0 unixms, now) == Seq(payment1, payment2, payment3)) + assert(db.removeIncomingPayment(paidInvoice1.paymentHash).isFailure) db.removeIncomingPayment(paidInvoice1.paymentHash).failed.foreach(e => assert(e.getMessage == "Cannot remove a received incoming payment")) assert(db.removeIncomingPayment(paidInvoice3.paymentHash).isFailure) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/receive/InvoicePurgerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/receive/InvoicePurgerSpec.scala index 9e8e7543ab..77278740b5 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/receive/InvoicePurgerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/receive/InvoicePurgerSpec.scala @@ -60,8 +60,8 @@ class InvoicePurgerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("ap }) val now = TimestampMilli.now() - assert(db.listIncomingPayments(0 unixms, now) == expiredPayments ++ pendingPayments ++ paidPayments) - assert(db.listIncomingPayments(now - 100.days, now) == pendingPayments ++ paidPayments) + assert(db.listIncomingPayments(0 unixms, now, None) == expiredPayments ++ pendingPayments ++ paidPayments) + assert(db.listIncomingPayments(now - 100.days, now, None) == pendingPayments ++ paidPayments) assert(db.listPendingIncomingPayments(0 unixms, now) == pendingPayments) assert(db.listReceivedIncomingPayments(0 unixms, now) == paidPayments) assert(db.listExpiredIncomingPayments(0 unixms, now) == expiredPayments) @@ -75,7 +75,7 @@ class InvoicePurgerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("ap probe.expectMessage(5 seconds, PurgeCompleted) probe.expectNoMessage() assert(db.listExpiredIncomingPayments(0 unixms, now).isEmpty) - assert(db.listIncomingPayments(0 unixms, now) == pendingPayments ++ paidPayments) + assert(db.listIncomingPayments(0 unixms, now, None) == pendingPayments ++ paidPayments) testKit.stop(purger) } diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/directives/ExtraDirectives.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/directives/ExtraDirectives.scala index ff11b940e8..8627aabd7f 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/api/directives/ExtraDirectives.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/directives/ExtraDirectives.scala @@ -27,7 +27,7 @@ import fr.acinq.eclair.ApiTypes.ChannelIdentifier import fr.acinq.eclair.api.serde.FormParamExtractors._ import fr.acinq.eclair.api.serde.JsonSupport._ import fr.acinq.eclair.payment.Bolt11Invoice -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId, TimestampSecond} +import fr.acinq.eclair.{MilliSatoshi, Paginated, ShortChannelId, TimestampSecond} import scala.concurrent.Future import scala.concurrent.duration.DurationInt @@ -52,6 +52,8 @@ trait ExtraDirectives extends Directives { val ignoreNodeIdsFormParam: NameUnmarshallerReceptacle[List[PublicKey]] = "ignoreNodeIds".as[List[PublicKey]](pubkeyListUnmarshaller) val ignoreShortChannelIdsFormParam: NameUnmarshallerReceptacle[List[ShortChannelId]] = "ignoreShortChannelIds".as[List[ShortChannelId]](shortChannelIdsUnmarshaller) val maxFeeMsatFormParam: NameReceptacle[MilliSatoshi] = "maxFeeMsat".as[MilliSatoshi] + val countFormParam: NameReceptacle[Int] = "count".as[Int] + val skipFormParam: NameReceptacle[Int] = "skip".as[Int] // custom directive to fail with HTTP 404 (and JSON response) if the element was not found def completeOrNotFound[T](fut: Future[Option[T]])(implicit marshaller: ToResponseMarshaller[T]): Route = onComplete(fut) { @@ -61,6 +63,13 @@ trait ExtraDirectives extends Directives { case Failure(_) => reject } + def withPaginated: Directive1[Option[Paginated]] = formFields(countFormParam.?, skipFormParam.?).tflatMap { + case (Some(count), Some(skip)) => provide(Some(Paginated(count = count, skip = skip))) + case (Some(count), None) => provide(Some(Paginated(count = count, skip = 0))) + case (None, Some(_)) => reject(MalformedFormFieldRejection("skip", "Must specify count")) + case (None, None) => provide(None) + } + def withChannelIdentifier: Directive1[ChannelIdentifier] = formFields(channelIdFormParam.?, shortChannelIdFormParam.?).tflatMap { case (Some(channelId), None) => provide(Left(channelId)) case (None, Some(shortChannelId)) => provide(Right(shortChannelId)) diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Invoice.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Invoice.scala index 884a3fa3ef..2d78e261d6 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Invoice.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Invoice.scala @@ -42,8 +42,10 @@ trait Invoice { } val listInvoices: Route = postRequest("listinvoices") { implicit t => - formFields(fromFormParam, toFormParam) { (from, to) => - complete(eclairApi.allInvoices(from, to)) + withPaginated { paginated_opt => + formFields(fromFormParam, toFormParam) { (from, to) => + complete(eclairApi.allInvoices(from, to, paginated_opt)) + } } } diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/OnChain.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/OnChain.scala index 7074b49bfd..286d0fd583 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/OnChain.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/OnChain.scala @@ -43,8 +43,12 @@ trait OnChain { } val onChainTransactions: Route = postRequest("onchaintransactions") { implicit t => - formFields("count".as[Int].?, "skip".as[Int].?) { (count_opt, skip_opt) => - complete(eclairApi.onChainTransactions(count_opt.getOrElse(10), skip_opt.getOrElse(0))) + withPaginated { paginated_opt => + formFields(countFormParam, skipFormParam) { (count_opt, skip_opt) => + val count = paginated_opt.map(_.count).getOrElse(10) + val skip = paginated_opt.map(_.skip).getOrElse(0) + complete(eclairApi.onChainTransactions(count, skip)) + } } }