[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.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [libeufin] branch master updated: GET /accounts DB logic, fix POST /transfer idempotence.,
gnunet <=