aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine A <>2024-04-04 16:19:20 +0200
committerAntoine A <>2024-04-04 16:19:20 +0200
commita4e1b86575e75960fd5d0b158994adef0096a8bf (patch)
tree8c8dd4bfdf7e8981130f128d59490be1ccdb1ce3
parent42eb74f888d156a16414ec44746addd512a0e9e0 (diff)
downloadlibeufin-a4e1b86575e75960fd5d0b158994adef0096a8bf.tar.gz
libeufin-a4e1b86575e75960fd5d0b158994adef0096a8bf.tar.bz2
libeufin-a4e1b86575e75960fd5d0b158994adef0096a8bf.zip
Improve monitor API
-rw-r--r--API_CHANGES.md4
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/Constants.kt2
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt11
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/params.kt34
-rw-r--r--bank/src/test/kotlin/StatsTest.kt106
-rw-r--r--database-versioning/libeufin-bank-procedures.sql18
6 files changed, 97 insertions, 78 deletions
diff --git a/API_CHANGES.md b/API_CHANGES.md
index ff80790f..5feb4353 100644
--- a/API_CHANGES.md
+++ b/API_CHANGES.md
@@ -46,9 +46,11 @@ This files contains all the API changes for the current release:
- GET /accounts: add row_id field
- GET /public-accounts: add row_id field
- GET /config: new bank_name field for the bank name
-- POST /accounts/USERNAME/transactions: new request_uid field for idempotency and new idempotency error
+- POST /accounts/USERNAME/transactions: new request_uid field for idempotency
+ and new idempotency error
- GET /accounts: new status field
- GET /accounts/USERNAME: new status field
+- GET /monitor: new date_s params
## bank cli
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt b/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt
index 15fe5be1..504f068f 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt
@@ -40,7 +40,7 @@ const val IBAN_ALLOCATION_RETRY_COUNTER: Int = 5
const val MAX_BODY_LENGTH: Long = 4 * 1024 // 4kB
// API version
-const val COREBANK_API_VERSION: String = "4:5:0"
+const val COREBANK_API_VERSION: String = "4:6:0"
const val CONVERSION_API_VERSION: String = "0:0:0"
const val INTEGRATION_API_VERSION: String = "2:0:2"
const val WIRE_GATEWAY_API_VERSION: String = "0:2:0"
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt
index c843d355..b021ff6c 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt
@@ -32,6 +32,7 @@ import tech.libeufin.common.db.*
import java.sql.PreparedStatement
import java.sql.ResultSet
import java.sql.Types
+import java.time.LocalDateTime
import kotlin.math.abs
private val logger: Logger = LoggerFactory.getLogger("libeufin-bank-db")
@@ -70,14 +71,10 @@ class Database(dbConfig: String, internal val bankCurrency: String, internal val
,taler_out_count
,(taler_out_volume).val as taler_out_volume_val
,(taler_out_volume).frac as taler_out_volume_frac
- FROM stats_get_frame(NULL, ?::stat_timeframe_enum, ?)
+ FROM stats_get_frame(?::timestamp, ?::stat_timeframe_enum)
""")
- stmt.setString(1, params.timeframe.name)
- if (params.which != null) {
- stmt.setInt(2, params.which)
- } else {
- stmt.setNull(2, Types.INTEGER)
- }
+ stmt.setObject(1, params.date)
+ stmt.setString(2, params.timeframe.name)
stmt.oneOrNull {
fiatCurrency?.run {
MonitorWithConversion(
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/params.kt b/bank/src/main/kotlin/tech/libeufin/bank/params.kt
index 26cb2eba..4972ef99 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/params.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/params.kt
@@ -22,7 +22,8 @@ package tech.libeufin.bank
import io.ktor.http.*
import tech.libeufin.common.TalerAmount
import tech.libeufin.common.TalerErrorCode
-import java.time.OffsetDateTime
+import java.time.Instant
+import java.time.LocalDateTime
import java.time.ZoneOffset
import java.time.temporal.TemporalAdjusters
import java.util.UUID
@@ -59,20 +60,39 @@ fun Parameters.amount(name: String): TalerAmount?
data class MonitorParams(
val timeframe: Timeframe,
- val which: Int?
+ val date: LocalDateTime
) {
+ constructor(timeframe: Timeframe, now: LocalDateTime, which: Int) : this(
+ timeframe,
+ when (timeframe) {
+ Timeframe.hour -> now.withHour(which)
+ Timeframe.day -> now.withDayOfMonth(which)
+ Timeframe.month -> now.withMonth(which)
+ Timeframe.year -> now.withYear(which)
+ }
+ )
+ constructor(timeframe: Timeframe, secs: Long) : this(
+ timeframe,
+ LocalDateTime.ofInstant(Instant.ofEpochSecond(secs), ZoneOffset.UTC)
+ )
companion object {
val names = Timeframe.entries.map { it.name }
val names_fmt = names.joinToString()
+
fun extract(params: Parameters): MonitorParams {
val raw = params.get("timeframe") ?: "hour"
if (!names.contains(raw)) {
throw badRequest("Param 'timeframe' must be one of $names_fmt", TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
}
val timeframe = Timeframe.valueOf(raw)
+ val now = LocalDateTime.now(ZoneOffset.UTC)
val which = params.int("which")
- if (which != null) {
- val lastDayOfMonth = OffsetDateTime.now(ZoneOffset.UTC).with(TemporalAdjusters.lastDayOfMonth()).dayOfMonth
+ val dateS = params.long("date_s")
+ if (which != null && dateS != null) {
+ throw badRequest("Cannot use both 'date_s' and deprecated 'which'", TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
+ }
+ return if (which != null) {
+ val lastDayOfMonth = now.with(TemporalAdjusters.lastDayOfMonth()).dayOfMonth
when {
timeframe == Timeframe.hour && (0 > which || which > 23) ->
throw badRequest("For hour timestamp param 'which' must be between 00 to 23", TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
@@ -84,8 +104,12 @@ data class MonitorParams(
throw badRequest("For year timestamp param 'which' must be between 0001 to 9999", TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
else -> {}
}
+ MonitorParams(timeframe, now, which)
+ } else if (dateS != null) {
+ MonitorParams(timeframe, dateS)
+ } else {
+ MonitorParams(timeframe, now)
}
- return MonitorParams(timeframe, which)
}
}
}
diff --git a/bank/src/test/kotlin/StatsTest.kt b/bank/src/test/kotlin/StatsTest.kt
index 71f35d0a..4dd52e3d 100644
--- a/bank/src/test/kotlin/StatsTest.kt
+++ b/bank/src/test/kotlin/StatsTest.kt
@@ -19,13 +19,13 @@
import io.ktor.client.request.*
import org.junit.Test
-import tech.libeufin.bank.MonitorResponse
-import tech.libeufin.bank.MonitorWithConversion
-import tech.libeufin.bank.Timeframe
+import tech.libeufin.bank.*
import tech.libeufin.common.*
import tech.libeufin.common.db.*
import java.time.Instant
+import java.time.ZoneOffset
import java.time.LocalDateTime
+import java.time.OffsetDateTime
import kotlin.test.assertEquals
class StatsTest {
@@ -56,9 +56,10 @@ class StatsTest {
fiatVolume: ((MonitorWithConversion) -> TalerAmount)? = null,
fiatAmount: String? = null
) {
- Timeframe.entries.forEach { timestamp ->
- client.get("/monitor?timestamp=${timestamp.name}") { pwAuth("admin") }.assertOkJson<MonitorResponse> {
+ Timeframe.entries.forEach { timeframe ->
+ client.get("/monitor?timestamp=${timeframe.name}") { pwAuth("admin") }.assertOkJson<MonitorResponse> {
val resp = it as MonitorWithConversion
+ println("$resp")
assertEquals(count, dbCount(resp))
assertEquals(TalerAmount(regionalAmount), regionalVolume(resp))
fiatVolume?.run { assertEquals(TalerAmount(fiatAmount!!), this(resp)) }
@@ -131,39 +132,36 @@ class StatsTest {
}
suspend fun check(
- now: LocalDateTime,
- timeframe: Timeframe,
- which: Int?,
+ params: MonitorParams,
count: Long,
amount: TalerAmount
) {
- val stmt = conn.prepareStatement("""
- SELECT
- taler_out_count
- ,(taler_out_volume).val as taler_out_volume_val
- ,(taler_out_volume).frac as taler_out_volume_frac
- FROM stats_get_frame(?::timestamp, ?::stat_timeframe_enum, ?)
- """)
- stmt.setObject(1, now)
- stmt.setString(2, timeframe.name)
- if (which != null) {
- stmt.setInt(3, which)
- } else {
- stmt.setNull(3, java.sql.Types.INTEGER)
- }
- stmt.oneOrNull {
- val talerOutCount = it.getLong("taler_out_count")
- val talerOutVolume = TalerAmount(
- value = it.getLong("taler_out_volume_val"),
- frac = it.getInt("taler_out_volume_frac"),
- currency = "KUDOS"
- )
- assertEquals(count, talerOutCount, "taler count")
- assertEquals(amount, talerOutVolume, "taler volume")
- }!!
+ val res = db.monitor(params)
+ assertEquals(count, res.talerOutCount, "taler count")
+ assertEquals(amount, res.talerOutVolume, "taler volume")
}
- val now = LocalDateTime.now()
+ suspend fun checkSimple(
+ now: LocalDateTime,
+ timeframe: Timeframe,
+ count: Long,
+ amount: TalerAmount
+ ) = check(MonitorParams(timeframe, now), count, amount)
+ suspend fun checkWhich(
+ now: LocalDateTime,
+ timeframe: Timeframe,
+ which: Int,
+ count: Long,
+ amount: TalerAmount
+ ) = check(MonitorParams(timeframe, now, which), count, amount)
+ suspend fun checkDate(
+ secs: Long,
+ timeframe: Timeframe,
+ count: Long,
+ amount: TalerAmount
+ ) = check(MonitorParams(timeframe, secs), count, amount)
+
+ val now = LocalDateTime.now(ZoneOffset.UTC)
val otherHour = now.withHour((now.hour + 1) % 24)
val otherDay = now.withDayOfMonth((now.dayOfMonth) % 28 + 1)
val otherMonth = now.withMonth((now.monthValue) % 12 + 1)
@@ -176,26 +174,36 @@ class StatsTest {
register(otherYear, TalerAmount("KUDOS:50.0"))
// Check with timestamp and truncating
- check(now, Timeframe.hour, null, 1, TalerAmount("KUDOS:10.0"))
- check(otherHour, Timeframe.hour, null, 1, TalerAmount("KUDOS:20.0"))
- check(otherDay, Timeframe.day, null, 1, TalerAmount("KUDOS:35.0"))
- check(otherMonth, Timeframe.month, null, 1, TalerAmount("KUDOS:40.0"))
- check(otherYear, Timeframe.year, null, 1, TalerAmount("KUDOS:50.0"))
+ checkSimple(now, Timeframe.hour, 1, TalerAmount("KUDOS:10.0"))
+ checkSimple(otherHour, Timeframe.hour, 1, TalerAmount("KUDOS:20.0"))
+ checkSimple(otherDay, Timeframe.day, 1, TalerAmount("KUDOS:35.0"))
+ checkSimple(otherMonth, Timeframe.month, 1, TalerAmount("KUDOS:40.0"))
+ checkSimple(otherYear, Timeframe.year, 1, TalerAmount("KUDOS:50.0"))
// Check with timestamp and intervals
- check(now, Timeframe.hour, now.hour, 1, TalerAmount("KUDOS:10.0"))
- check(now, Timeframe.hour, otherHour.hour, 1, TalerAmount("KUDOS:20.0"))
- check(now, Timeframe.day, otherDay.dayOfMonth, 1, TalerAmount("KUDOS:35.0"))
- check(now, Timeframe.month, otherMonth.monthValue, 1, TalerAmount("KUDOS:40.0"))
- check(now, Timeframe.year, otherYear.year, 1, TalerAmount("KUDOS:50.0"))
+ checkWhich(now, Timeframe.hour, now.hour, 1, TalerAmount("KUDOS:10.0"))
+ checkWhich(now, Timeframe.hour, otherHour.hour, 1, TalerAmount("KUDOS:20.0"))
+ checkWhich(now, Timeframe.day, otherDay.dayOfMonth, 1, TalerAmount("KUDOS:35.0"))
+ checkWhich(now, Timeframe.month, otherMonth.monthValue, 1, TalerAmount("KUDOS:40.0"))
+ checkWhich(now, Timeframe.year, otherYear.year, 1, TalerAmount("KUDOS:50.0"))
+
+ // Check with date seconds
+ checkDate(now.toEpochSecond(ZoneOffset.UTC), Timeframe.hour, 1, TalerAmount("KUDOS:10.0"))
+ checkDate(otherHour.toEpochSecond(ZoneOffset.UTC), Timeframe.hour, 1, TalerAmount("KUDOS:20.0"))
+ checkDate(otherDay.toEpochSecond(ZoneOffset.UTC), Timeframe.day, 1, TalerAmount("KUDOS:35.0"))
+ checkDate(otherMonth.toEpochSecond(ZoneOffset.UTC), Timeframe.month, 1, TalerAmount("KUDOS:40.0"))
+ checkDate(otherYear.toEpochSecond(ZoneOffset.UTC), Timeframe.year, 1, TalerAmount("KUDOS:50.0"))
// Check timestamp aggregation
- check(now, Timeframe.day, now.dayOfMonth, 2, TalerAmount("KUDOS:30.0"))
- check(now, Timeframe.month, now.monthValue, 3, TalerAmount("KUDOS:65.0"))
- check(now, Timeframe.year, now.year, 4, TalerAmount("KUDOS:105.0"))
- check(now, Timeframe.day, null, 2, TalerAmount("KUDOS:30.0"))
- check(now, Timeframe.month, null, 3, TalerAmount("KUDOS:65.0"))
- check(now, Timeframe.year, null, 4, TalerAmount("KUDOS:105.0"))
+ checkSimple(now, Timeframe.day, 2, TalerAmount("KUDOS:30.0"))
+ checkSimple(now, Timeframe.month, 3, TalerAmount("KUDOS:65.0"))
+ checkSimple(now, Timeframe.year, 4, TalerAmount("KUDOS:105.0"))
+ checkWhich(now, Timeframe.day, now.dayOfMonth, 2, TalerAmount("KUDOS:30.0"))
+ checkWhich(now, Timeframe.month, now.monthValue, 3, TalerAmount("KUDOS:65.0"))
+ checkWhich(now, Timeframe.year, now.year, 4, TalerAmount("KUDOS:105.0"))
+ checkDate(now.toEpochSecond(ZoneOffset.UTC), Timeframe.day, 2, TalerAmount("KUDOS:30.0"))
+ checkDate(now.toEpochSecond(ZoneOffset.UTC), Timeframe.month, 3, TalerAmount("KUDOS:65.0"))
+ checkDate(now.toEpochSecond(ZoneOffset.UTC), Timeframe.year, 4, TalerAmount("KUDOS:105.0"))
}
}
}
diff --git a/database-versioning/libeufin-bank-procedures.sql b/database-versioning/libeufin-bank-procedures.sql
index 7ef7c3f1..0dceea3c 100644
--- a/database-versioning/libeufin-bank-procedures.sql
+++ b/database-versioning/libeufin-bank-procedures.sql
@@ -1281,9 +1281,8 @@ END $$;
COMMENT ON FUNCTION tan_challenge_try IS 'Try to confirm a challenge, return true if the challenge have been confirmed';
CREATE FUNCTION stats_get_frame(
- IN now TIMESTAMP,
+ IN date TIMESTAMP,
IN in_timeframe stat_timeframe_enum,
- IN which INTEGER,
OUT cashin_count INT8,
OUT cashin_regional_volume taler_amount,
OUT cashin_fiat_volume taler_amount,
@@ -1296,19 +1295,8 @@ CREATE FUNCTION stats_get_frame(
OUT taler_out_volume taler_amount
)
LANGUAGE plpgsql AS $$
-DECLARE
- local_start_time TIMESTAMP;
BEGIN
- IF now IS NULL THEN
- now = timezone('utc', now())::TIMESTAMP;
- END IF;
- local_start_time = CASE
- WHEN which IS NULL THEN date_trunc(in_timeframe::text, now)
- WHEN in_timeframe = 'hour' THEN date_trunc('day' , now) + make_interval(hours => which)
- WHEN in_timeframe = 'day' THEN date_trunc('month', now) + make_interval(days => which-1)
- WHEN in_timeframe = 'month' THEN date_trunc('year' , now) + make_interval(months => which-1)
- WHEN in_timeframe = 'year' THEN make_date(which, 1, 1)::TIMESTAMP
- END;
+ date = date_trunc(in_timeframe::text, date);
SELECT
s.cashin_count
,(s.cashin_regional_volume).val
@@ -1345,7 +1333,7 @@ BEGIN
,taler_out_volume.frac
FROM bank_stats AS s
WHERE s.timeframe = in_timeframe
- AND s.start_time = local_start_time;
+ AND s.start_time = date;
END $$;
CREATE PROCEDURE stats_register_payment(