commit 4495f3d05129fcd5ab0a81b459e8303565365825
parent 3d9dffab37a4117a147748fdde402ee0a4622669
Author: Antoine A <>
Date: Thu, 12 Dec 2024 15:01:26 +0100
nexus: add manual management cmd
Diffstat:
5 files changed, 143 insertions(+), 74 deletions(-)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/LibeufinNexus.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/LibeufinNexus.kt
@@ -44,7 +44,7 @@ fun CliktCommand.transientOption() = option(
class LibeufinNexus : CliktCommand() {
init {
versionOption(VERSION)
- subcommands(DbInit(), EbicsSetup(), EbicsSubmit(), EbicsFetch(), Serve(), InitiatePayment(), CliConfigCmd(NEXUS_CONFIG_SOURCE), TestingCmd())
+ subcommands(DbInit(), EbicsSetup(), EbicsSubmit(), EbicsFetch(), Serve(), InitiatePayment(), ManualCmd(), CliConfigCmd(NEXUS_CONFIG_SOURCE), TestingCmd())
}
override fun run() = Unit
}
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/Manual.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/Manual.kt
@@ -0,0 +1,131 @@
+/*
+ * This file is part of LibEuFin.
+ * Copyright (C) 2024 Taler Systems S.A.
+
+ * LibEuFin is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3, or
+ * (at your option) any later version.
+
+ * LibEuFin is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
+ * Public License for more details.
+
+ * You should have received a copy of the GNU Affero General Public
+ * License along with LibEuFin; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>
+ */
+
+package tech.libeufin.nexus.cli
+
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.Context
+import com.github.ajalt.clikt.core.subcommands
+import com.github.ajalt.clikt.parameters.arguments.*
+import com.github.ajalt.clikt.parameters.groups.provideDelegate
+import com.github.ajalt.clikt.parameters.options.convert
+import com.github.ajalt.clikt.parameters.options.default
+import com.github.ajalt.clikt.parameters.options.flag
+import com.github.ajalt.clikt.parameters.options.option
+import com.github.ajalt.clikt.parameters.types.*
+import com.github.ajalt.mordant.terminal.*
+import tech.libeufin.common.*
+import tech.libeufin.nexus.*
+import tech.libeufin.nexus.db.*
+import tech.libeufin.nexus.ebics.*
+import tech.libeufin.nexus.iso20022.*
+import java.util.zip.*
+import java.time.Instant
+import java.io.*
+
+class ExportCmt: CliktCommand("export") {
+ override fun help(context: Context) = "Export pending batches as pain001 messages"
+
+ private val common by CommonOption()
+ private val out by argument().file()
+
+ override fun run() = cliCmd(logger, common.log) {
+ nexusConfig(common.config).withDb { db, cfg ->
+ // Create and get pending batches
+ db.initiated.batch(Instant.now(), randEbicsId())
+ val batches = db.initiated.submittable(cfg.currency)
+
+ var nbTx: Int = 0
+ ZipOutputStream(BufferedOutputStream(FileOutputStream(out))).use { zip ->
+ val ebicsCfg = cfg.ebics
+ for (batch in batches) {
+ nbTx = batch.payments.size
+ val entry = ZipEntry("${batch.creationDate.toDateTimeFilePath()}-${batch.messageId}.xml")
+ zip.putNextEntry(entry)
+ val msg = batchToPain001Msg(ebicsCfg.account, batch)
+
+ val xml = createPain001(
+ msg = msg,
+ dialect = ebicsCfg.dialect
+ )
+ zip.write(xml)
+ println("batch ${batch.messageId}:")
+ for (tx in batch.payments) {
+ println("tx ${tx.endToEndId} ${tx.amount} ${tx.creditor.iban} '${tx.creditor.receiverName}'")
+ }
+ println("")
+ }
+ }
+ println("Exported ${batches.size} pain.001 files in $out")
+ }
+ }
+}
+
+class ImportCmt: CliktCommand("import") {
+ override fun help(context: Context) = "Import EBICS camt files"
+
+ private val common by CommonOption()
+ private val sources by argument().file().multiple(required = true)
+
+ override fun run() = cliCmd(logger, common.log) {
+ nexusConfig(common.config).withDb { db, cfg ->
+ for (source in sources) {
+ var nbTx: Int = 0
+ source.inputStream().use { xml ->
+ nbTx += registerTxs(db, cfg, xml)
+ }
+ println("Imported $nbTx transactions from $source")
+ }
+ }
+ }
+}
+
+class StatusCmd: CliktCommand("status") {
+ override fun help(context: Context) = "Change batches or transactions status"
+
+ enum class Kind {
+ batch,
+ tx
+ }
+
+ private val common by CommonOption()
+ private val kind by argument().enum<Kind>()
+ private val id by argument()
+ private val status by argument().enum<StatusUpdate>()
+ private val msg by argument().optional()
+
+ override fun run() = cliCmd(logger, common.log) {
+ nexusConfig(common.config).withDb { db, cfg ->
+ when (kind) {
+ Kind.batch -> db.initiated.batchStatusUpdate(id, status, msg)
+ Kind.tx -> db.initiated.txStatusUpdate(id, null, status, msg)
+ }
+ }
+ }
+}
+
+class ManualCmd : CliktCommand("manual") {
+ init {
+ subcommands(ExportCmt(), ImportCmt(), StatusCmd())
+ }
+
+ override fun help(context: Context) = "Manual management commands"
+
+ override fun run() = Unit
+}
+\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/Testing.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/Testing.kt
@@ -72,7 +72,11 @@ class FakeIncoming: CliktCommand() {
private val common by CommonOption()
private val amount by option(
"--amount",
- help = "The amount to transfer, payto 'amount' parameter takes the precedence"
+ help = "The payment amount, payto 'amount' parameter takes the precedence"
+ ).convert { TalerAmount(it) }
+ private val creditFee by option(
+ "--credit-fee",
+ help = "The payment credit fee"
).convert { TalerAmount(it) }
private val subject by option(
"--subject",
@@ -96,6 +100,7 @@ class FakeIncoming: CliktCommand() {
amount = amount,
debtorPayto = payto,
subject = subject,
+ creditFee = creditFee,
executionTime = Instant.now(),
bankId = randEbicsId()
)
@@ -276,80 +281,12 @@ class ListCmd: CliktCommand("list") {
}
printTable(columnNames, rows)
}
- }
-}
-
-class ExportCmt: CliktCommand("export") {
- override fun help(context: Context) = "Export pending batches as pain001 messages"
-
- private val common by CommonOption()
- private val out by argument().file()
-
- override fun run() = cliCmd(logger, common.log) {
- nexusConfig(common.config).withDb { db, cfg ->
- // Create and get pending batches
- db.initiated.batch(Instant.now(), randEbicsId())
- val batches = db.initiated.submittable(cfg.currency)
-
- var nbTx: Int = 0
- ZipOutputStream(BufferedOutputStream(FileOutputStream(out))).use { zip ->
- val ebicsCfg = cfg.ebics
- for (batch in batches) {
- nbTx = batch.payments.size
- val entry = ZipEntry("${batch.creationDate.toDateTimeFilePath()}-${batch.messageId}.xml")
- zip.putNextEntry(entry)
- val msg = batchToPain001Msg(ebicsCfg.account, batch)
- val xml = createPain001(
- msg = msg,
- dialect = ebicsCfg.dialect
- )
- zip.write(xml)
- }
- }
- println("Exported $nbTx transactions in $out")
-
- // Update batches status
- val now = Instant.now()
- val t = Terminal()
- for (batch in batches) {
- val filename = "${batch.creationDate.toDateTimeFilePath()}-${batch.messageId}.xml"
- val success = YesNoPrompt("Was $filename upload successful?", t).ask()
- if (success == true) {
- db.initiated.batchSubmissionSuccess(batch.id, now, null)
- } else if (success == false) {
- val permanent = YesNoPrompt("Is the error permanent?", t).ask()
- val msg = StringPrompt("Error msg", t).ask()
- if (permanent != null) {
- db.initiated.batchSubmissionFailure(batch.id, now, msg, permanent)
- }
- }
- }
- }
- }
-}
-
-class ImportCmt: CliktCommand("import") {
- override fun help(context: Context) = "Import EBICS camt files"
-
- private val common by CommonOption()
- private val sources by argument().file().multiple(required = true)
-
- override fun run() = cliCmd(logger, common.log) {
- nexusConfig(common.config).withDb { db, cfg ->
- for (source in sources) {
- var nbTx: Int = 0
- source.inputStream().use { xml ->
- nbTx += registerTxs(db, cfg, xml)
- }
- println("Imported $nbTx transactions from $source")
- }
- }
- }
+ }
}
class TestingCmd : CliktCommand("testing") {
init {
- subcommands(FakeIncoming(), ListCmd(), EbicsDownload(), TxCheck(), Wss(), ExportCmt(), ImportCmt())
+ subcommands(FakeIncoming(), ListCmd(), EbicsDownload(), TxCheck(), Wss())
}
override fun help(context: Context) = "Testing helper commands"
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/InitiatedDAO.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/db/InitiatedDAO.kt
@@ -261,7 +261,7 @@ class InitiatedDAO(private val db: Database) {
}
/** Register payment status [state] with [msg] for transaction [endToEndId] in batch [msgId] */
- suspend fun txStatusUpdate(endToEndId: String, msgId: String?, state: StatusUpdate, msg: String) = db.serializable(
+ suspend fun txStatusUpdate(endToEndId: String, msgId: String?, state: StatusUpdate, msg: String?) = db.serializable(
"SELECT FROM tx_status_update(?,?,?::submission_state,?)"
) {
setString(1, endToEndId)
diff --git a/testbench/src/main/kotlin/Main.kt b/testbench/src/main/kotlin/Main.kt
@@ -176,7 +176,7 @@ class Cli : CliktCommand() {
put("list-initiated", "List initiated payments", "testing list $flags initiated")
put("wss", "Listen to notification over websocket", "testing wss $debugFlags")
put("submit", "Submit pending transactions", "ebics-submit $ebicsFlags")
- put("export", "Export pending batches as pain001 messages", "testing export $flags payments.zip")
+ put("export", "Export pending batches as pain001 messages", "manual export $flags payments.zip")
put("setup", "Setup", "ebics-setup $debugFlags")
put("reset-keys", suspend {
if (kind.test) {