gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libeufin] branch master updated: GET /accounts DB logic, fix POST /tran


From: gnunet
Subject: [libeufin] branch master updated: GET /accounts DB logic, fix POST /transfer idempotence.
Date: Sat, 30 Sep 2023 22:26:34 +0200

This is an automated email from the git hooks/post-receive script.

ms pushed a commit to branch master
in repository libeufin.

The following commit(s) were added to refs/heads/master by this push:
     new 546f35fd GET /accounts DB logic, fix POST /transfer idempotence.
546f35fd is described below

commit 546f35fd8476f3c6adbb2b12702de32ec2de3476
Author: MS <ms@taler.net>
AuthorDate: Sat Sep 30 22:15:28 2023 +0200

    GET /accounts DB logic, fix POST /transfer idempotence.
    
    Along this change, the helper that strips IBAN payto URI
    got changed to return null on invalid input.  The reason
    is (1) a more convenient error handling by the caller: Elvis
    operator instead of a try-catch block and (2) the impossibility
    of 'util' to throw LibeufinBankException to drive Ktor to any
    wanted error response.
---
 .../main/kotlin/tech/libeufin/bank/BankMessages.kt | 11 +++++
 .../tech/libeufin/bank/CorebankApiHandlers.kt      | 16 +++----
 .../src/main/kotlin/tech/libeufin/bank/Database.kt | 56 +++++++++++++++++++++-
 .../tech/libeufin/bank/IntegrationApiHandlers.kt   |  2 +-
 bank/src/test/kotlin/DatabaseTest.kt               | 22 +++++++--
 bank/src/test/kotlin/TalerApiTest.kt               | 12 +++--
 util/src/main/kotlin/IbanPayto.kt                  | 14 +++---
 7 files changed, 106 insertions(+), 27 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
index d2a667fc..7142ba38 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
@@ -368,6 +368,17 @@ data class Balance(
     val credit_debit_indicator: CorebankCreditDebitInfo,
 )
 
+/**
+ * GET /accounts response.
+ */
+@Serializable
+data class AccountMinimalData(
+    val username: String,
+    val name: String,
+    val balance: Balance,
+    val debit_threshold: TalerAmount
+)
+
 /**
  * GET /accounts/$USERNAME response.
  */
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
index ccd10d09..a55f5328 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
@@ -118,9 +118,9 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: 
BankApplicationContext) {
             db.bankAccountGetFromOwnerId(this.expectRowId())
         }
         val internalPayto: String = if (req.internal_payto_uri != null) {
-            stripIbanPayto(req.internal_payto_uri)
+            stripIbanPayto(req.internal_payto_uri) ?: throw 
badRequest("internal_payto_uri is invalid")
         } else {
-            stripIbanPayto(genIbanPaytoUri())
+            stripIbanPayto(genIbanPaytoUri()) ?: throw 
internalServerError("Bank generated an invalid internal payto URI")
         }
         if (maybeCustomerExists != null && maybeHasBankAccount != null) {
             logger.debug("Registering username was found: 
${maybeCustomerExists.login}") // Checking _all_ the details are the same.
@@ -382,7 +382,6 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: 
BankApplicationContext) {
         if ((c.login != resourceName) && (call.getAuthToken() == null)) throw 
forbidden()
         val txData = call.receive<BankAccountTransactionCreate>()
         val payto = parsePayto(txData.payto_uri) ?: throw badRequest("Invalid 
creditor Payto")
-        val paytoWithoutParams = stripIbanPayto(txData.payto_uri)
         val subject = payto.message ?: throw badRequest("Wire transfer lacks 
subject")
         val debtorBankAccount = db.bankAccountGetFromOwnerId(c.expectRowId())
             ?: throw internalServerError("Debtor bank account not found")
@@ -397,11 +396,12 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: 
BankApplicationContext) {
             maxDebt = debtorBankAccount.maxDebt
         ))
             throw conflict(hint = "Insufficient balance.", talerEc = 
TalerErrorCode.TALER_EC_BANK_UNALLOWED_DEBIT)
-        logger.info("creditor payto: $paytoWithoutParams")
-        val creditorBankAccount = 
db.bankAccountGetFromInternalPayto(paytoWithoutParams) ?: throw notFound(
-            "Creditor account not found",
-            TalerErrorCode.TALER_EC_BANK_UNKNOWN_ACCOUNT
-        )
+        logger.info("creditor payto: ${txData.payto_uri}")
+        val creditorBankAccount = 
db.bankAccountGetFromInternalPayto("payto://iban/${payto.iban.lowercase()}")
+            ?: throw notFound(
+                "Creditor account not found",
+                TalerErrorCode.TALER_EC_BANK_UNKNOWN_ACCOUNT
+            )
         val dbInstructions = BankInternalTransaction(
             debtorAccountId = debtorBankAccount.expectRowId(),
             creditorAccountId = creditorBankAccount.expectRowId(),
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
index a402462c..fd467600 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
@@ -26,6 +26,7 @@ import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import tech.libeufin.util.getJdbcConnectionFromPg
 import tech.libeufin.util.microsToJavaInstant
+import tech.libeufin.util.stripIbanPayto
 import tech.libeufin.util.toDbMicros
 import java.io.File
 import java.sql.DriverManager
@@ -357,6 +358,58 @@ class Database(private val dbConfig: String, private val 
bankCurrency: String) {
         }
     }
 
+    // MIXED CUSTOMER AND BANK ACCOUNT DATA
+
+    /**
+     * Gets a minimal set of account data, as outlined in the GET /accounts
+     * endpoint.
+     */
+    fun accountsGetForAdmin(nameFilter: String = "%"): 
List<AccountMinimalData> {
+        reconnect()
+        val stmt = prepare("""
+            SELECT
+              login,
+              name,
+              (b.balance).val AS balance_val,
+              (b.balance).frac AS balance_frac,
+              (b).has_debt AS balance_has_debt,
+              (max_debt).val as max_debt_val,
+              (max_debt).frac as max_debt_frac
+              FROM customers JOIN bank_accounts AS b
+                ON customer_id = b.owning_customer_id
+              WHERE name LIKE ?;
+        """)
+        stmt.setString(1, nameFilter)
+        val res = stmt.executeQuery()
+        val ret = mutableListOf<AccountMinimalData>()
+        res.use {
+            if (!it.next()) return ret
+            do {
+                ret.add(AccountMinimalData(
+                    username = it.getString("login"),
+                    name = it.getString("name"),
+                    balance = Balance(
+                        amount = TalerAmount(
+                            value = it.getLong("balance_val"),
+                            frac = it.getInt("balance_frac"),
+                            currency = getCurrency()
+                        ),
+                        credit_debit_indicator = 
it.getBoolean("balance_has_debt").run {
+                            if (this) return@run CorebankCreditDebitInfo.debit
+                            return@run CorebankCreditDebitInfo.credit
+                        }
+                    ),
+                    debit_threshold = TalerAmount(
+                        value = it.getLong("max_debt_val"),
+                        frac = it.getInt("max_debt_frac"),
+                        currency = getCurrency()
+                    )
+                ))
+            } while (it.next())
+        }
+        return ret
+    }
+
     // BANK ACCOUNTS
 
     /**
@@ -1158,12 +1211,13 @@ class Database(private val dbConfig: String, private 
val bankCurrency: String) {
                   ?
                 );
         """)
+
         stmt.setString(1, req.request_uid)
         stmt.setString(2, req.wtid)
         stmt.setLong(3, req.amount.value)
         stmt.setInt(4, req.amount.frac)
         stmt.setString(5, req.exchange_base_url)
-        stmt.setString(6, req.credit_account)
+        stmt.setString(6, stripIbanPayto(req.credit_account) ?: throw 
badRequest("credit_account payto URI is invalid"))
         stmt.setLong(7, exchangeBankAccountId)
         stmt.setLong(8, timestamp.toDbMicros() ?: throw 
faultyTimestampByBank())
         stmt.setString(9, acctSvcrRef)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/IntegrationApiHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/IntegrationApiHandlers.kt
index e05d64fa..b117a8d8 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/IntegrationApiHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/IntegrationApiHandlers.kt
@@ -74,7 +74,7 @@ fun Routing.talerIntegrationHandlers(db: Database, ctx: 
BankApplicationContext)
             if (db.bankTransactionCheckExists(req.reserve_pub) != null) throw 
conflict(
                 "Reserve pub. already used", 
TalerErrorCode.TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT
             )
-            val exchangePayto = stripIbanPayto(req.selected_exchange)
+            val exchangePayto = stripIbanPayto(req.selected_exchange) ?: throw 
badRequest("selected_exchange payto is invalid")
             db.talerWithdrawalSetDetails(
                 op.withdrawalUuid, exchangePayto, req.reserve_pub
             )
diff --git a/bank/src/test/kotlin/DatabaseTest.kt 
b/bank/src/test/kotlin/DatabaseTest.kt
index 890ec41a..ee7d9dd6 100644
--- a/bank/src/test/kotlin/DatabaseTest.kt
+++ b/bank/src/test/kotlin/DatabaseTest.kt
@@ -61,14 +61,14 @@ class DatabaseTest {
         cashoutCurrency = "KUDOS"
     )
     private val bankAccountFoo = BankAccount(
-        internalPaytoUri = "FOO-IBAN-XYZ",
+        internalPaytoUri = "payto://iban/FOO-IBAN-XYZ".lowercase(),
         lastNexusFetchRowId = 1L,
         owningCustomerId = 1L,
         hasDebt = false,
         maxDebt = TalerAmount(10, 1, "KUDOS")
     )
     private val bankAccountBar = BankAccount(
-        internalPaytoUri = "BAR-IBAN-ABC",
+        internalPaytoUri = "payto://iban/BAR-IBAN-ABC".lowercase(),
         lastNexusFetchRowId = 1L,
         owningCustomerId = 2L,
         hasDebt = false,
@@ -108,7 +108,7 @@ class DatabaseTest {
     fun talerTransferTest() {
         val exchangeReq = TransferRequest(
             amount = TalerAmount(9, 0, "KUDOS"),
-            credit_account = "BAR-IBAN-ABC", // foo pays bar
+            credit_account = "payto://iban/BAR-IBAN-ABC".lowercase(), // foo 
pays bar
             exchange_base_url = "example.com/exchange",
             request_uid = "entropic 0",
             wtid = "entropic 1"
@@ -268,7 +268,7 @@ class DatabaseTest {
         // Setting the details.
         assert(db.talerWithdrawalSetDetails(
             opUuid = uuid,
-            exchangePayto = "BAR-IBAN-ABC",
+            exchangePayto = "payto://iban/BAR-IBAN-ABC".lowercase(),
             reservePub = "UNCHECKED-RESERVE-PUB"
         ))
         val opSelected = db.talerWithdrawalGet(uuid)
@@ -355,4 +355,18 @@ class DatabaseTest {
         assert(db.cashoutDelete(op.cashoutUuid) == 
Database.CashoutDeleteResult.CONFLICT_ALREADY_CONFIRMED)
         assert(db.cashoutGetFromUuid(op.cashoutUuid) != null) // previous 
didn't delete.
      }
+
+    // Tests the retrieval of many accounts, used along GET /accounts
+    @Test
+    fun accountsForAdmin() {
+        val db = initDb()
+        assert(db.accountsGetForAdmin().isEmpty()) // No data exists yet.
+        assert(db.customerCreate(customerFoo) != null)
+        assert(db.bankAccountCreate(bankAccountFoo) != null)
+        assert(db.customerCreate(customerBar) != null)
+        assert(db.bankAccountCreate(bankAccountBar) != null)
+        assert(db.accountsGetForAdmin().size == 2)
+        assert(db.accountsGetForAdmin("F%").size == 1) // gets Foo only
+        assert(db.accountsGetForAdmin("%ar").size == 1) // gets Bar only
+    }
 }
diff --git a/bank/src/test/kotlin/TalerApiTest.kt 
b/bank/src/test/kotlin/TalerApiTest.kt
index 37270b9d..1e391a9e 100644
--- a/bank/src/test/kotlin/TalerApiTest.kt
+++ b/bank/src/test/kotlin/TalerApiTest.kt
@@ -7,6 +7,7 @@ import kotlinx.serialization.json.Json
 import org.junit.Test
 import tech.libeufin.bank.*
 import tech.libeufin.util.CryptoUtil
+import tech.libeufin.util.stripIbanPayto
 import java.util.*
 
 class TalerApiTest {
@@ -20,14 +21,14 @@ class TalerApiTest {
         cashoutCurrency = "KUDOS"
     )
     private val bankAccountFoo = BankAccount(
-        internalPaytoUri = "FOO-IBAN-XYZ",
+        internalPaytoUri = "payto://iban/FOO-IBAN-XYZ".lowercase(),
         lastNexusFetchRowId = 1L,
         owningCustomerId = 1L,
         hasDebt = false,
         maxDebt = TalerAmount(10, 1, "KUDOS")
     )
     val bankAccountBar = BankAccount(
-        internalPaytoUri = "BAR-IBAN-ABC",
+        internalPaytoUri = stripIbanPayto("payto://iban/BAR-IBAN-ABC")!!,
         lastNexusFetchRowId = 1L,
         owningCustomerId = 2L,
         hasDebt = false,
@@ -64,7 +65,7 @@ class TalerApiTest {
                       "wtid": "entropic 1",
                       "exchange_base_url": "http://exchange.example.com/";,
                       "amount": "KUDOS:55",
-                      "credit_account": "BAR-IBAN-ABC"
+                      "credit_account": 
"${stripIbanPayto(bankAccountBar.internalPaytoUri)}"
                     }
                 """.trimIndent()
             // Checking exchange debt constraint.
@@ -74,6 +75,7 @@ class TalerApiTest {
                 expectSuccess = false
                 setBody(req)
             }
+            println(resp.bodyAsText())
             assert(resp.status == HttpStatusCode.Conflict)
             // Giving debt allowance and checking the OK case.
             assert(db.bankAccountSetMaxDebt(
@@ -189,7 +191,7 @@ class TalerApiTest {
                 setBody(deflater("""
                     {"amount": "KUDOS:44",
                      "reserve_pub": "RESERVE-PUB-TEST",
-                      "debit_account": "BAR-IBAN-ABC"
+                      "debit_account": 
"${"payto://iban/BAR-IBAN-ABC".lowercase()}"
                       }
                 """.trimIndent()))
             }
@@ -326,7 +328,7 @@ class TalerApiTest {
         // Specifying Bar as the exchange, via its Payto URI.
         assert(db.talerWithdrawalSetDetails(
             opUuid = uuid,
-            exchangePayto = "BAR-IBAN-ABC",
+            exchangePayto = "payto://iban/BAR-IBAN-ABC".lowercase(),
             reservePub = "UNCHECKED-RESERVE-PUB"
         ))
 
diff --git a/util/src/main/kotlin/IbanPayto.kt 
b/util/src/main/kotlin/IbanPayto.kt
index daa6872c..d869bb4d 100644
--- a/util/src/main/kotlin/IbanPayto.kt
+++ b/util/src/main/kotlin/IbanPayto.kt
@@ -98,14 +98,12 @@ fun buildIbanPaytoUri(
 }
 
 /**
- * Strip a payto://iban URI of everything
- * except the IBAN.
+ * Strip a payto://iban URI of everything except the IBAN.
+ * Return null on an invalid URI, letting the caller decide
+ * how to handle the problem.
  */
-fun stripIbanPayto(paytoUri: String): String {
-    val parsedPayto = parsePayto(paytoUri)
-    if (parsedPayto == null) {
-        throw Error("invalid payto://iban URI")
-    }
+fun stripIbanPayto(paytoUri: String): String? {
+    val parsedPayto = parsePayto(paytoUri) ?: return null
     val canonIban = parsedPayto.iban.lowercase()
     return "payto://iban/${canonIban}"
-}
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]