diff --git a/README.md b/README.md index 7c2b7d7..e4195ec 100644 --- a/README.md +++ b/README.md @@ -102,4 +102,4 @@ Previously this app supported only YNAB as this was my preferred financial manag Last commit where YNAB support was working reliably (and I was personally using it) is [3a7da7af](https://github.com/smaugfm/monobudget/commit/3a7da7afd85bffa310f54a322c46d626d24f488c) (May 2022) -PRs are feature requests are welcome! +PRs and feature requests are welcome! diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/Application.kt b/src/main/kotlin/io/github/smaugfm/monobudget/Application.kt index 8fd0425..f93ce1c 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/Application.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/Application.kt @@ -1,15 +1,15 @@ package io.github.smaugfm.monobudget import io.github.oshai.kotlinlogging.KotlinLogging -import io.github.smaugfm.monobudget.common.exception.BudgetBackendError -import io.github.smaugfm.monobudget.common.lifecycle.StatementItemProcessor -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingEventDelivery -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingScopeComponent +import io.github.smaugfm.monobudget.common.exception.BudgetBackendException +import io.github.smaugfm.monobudget.common.startup.ApplicationStartupVerifier import io.github.smaugfm.monobudget.common.statement.StatementSource +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementEvents +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementItemProcessor +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingScopeComponent import io.github.smaugfm.monobudget.common.telegram.TelegramApi import io.github.smaugfm.monobudget.common.telegram.TelegramCallbackHandler import io.github.smaugfm.monobudget.common.util.injectAll -import io.github.smaugfm.monobudget.common.verify.ApplicationStartupVerifier import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.filter @@ -28,7 +28,7 @@ class Application : private val statementSources by injectAll() private val startupVerifiers by injectAll() private val telegramCallbackHandler by inject>() - private val statementEvents by inject() + private val statementEvents by inject() suspend fun run() { runStartupChecks() @@ -48,7 +48,7 @@ class Application : scope.get>() .process() statementEvents.onStatementEnd(ctx) - } catch (e: BudgetBackendError) { + } catch (e: BudgetBackendException) { statementEvents.onStatementRetry(ctx, e) } catch (e: Throwable) { statementEvents.onStatementError(ctx, e) diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/Main.kt b/src/main/kotlin/io/github/smaugfm/monobudget/Main.kt index f1cd8e7..4b2e896 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/Main.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/Main.kt @@ -1,10 +1,7 @@ package io.github.smaugfm.monobudget import io.github.oshai.kotlinlogging.KotlinLogging -import io.github.resilience4j.core.IntervalFunction -import io.github.resilience4j.kotlin.retry.RetryConfig import io.github.resilience4j.reactor.retry.RetryOperator -import io.github.resilience4j.retry.Retry import io.github.smaugfm.lunchmoney.api.LunchmoneyApi import io.github.smaugfm.lunchmoney.model.LunchmoneyInsertTransaction import io.github.smaugfm.lunchmoney.model.LunchmoneyTransaction @@ -34,7 +31,6 @@ import org.koin.dsl.module import org.koin.ksp.generated.* import java.net.URI import java.nio.file.Paths -import java.time.Duration private val log = KotlinLogging.logger {} @@ -104,7 +100,6 @@ private fun runtimeModule( single { settings.bot } single { settings.accounts } single { settings.retry } - single { apiRetry() } settings.accounts.settings.filterIsInstance() .forEach { s -> single(StringQualifier(s.alias)) { MonoApi(s.token, s.accountId, s.alias) } } settings.transfer.forEach { s -> @@ -113,22 +108,6 @@ private fun runtimeModule( single { coroutineScope } } -@Suppress("MagicNumber") -private fun apiRetry(): Retry = - Retry.of( - "apiRetry", - RetryConfig { - maxAttempts(3) - failAfterMaxAttempts(true) - intervalFunction( - IntervalFunction.ofExponentialBackoff( - Duration.ofSeconds(1), - 2.0, - ), - ) - }, - ) - private fun lunchmoneyModule(budgetBackend: Lunchmoney) = module { single { diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/account/MaybeTransferStatement.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/account/MaybeTransfer.kt similarity index 86% rename from src/main/kotlin/io/github/smaugfm/monobudget/common/account/MaybeTransferStatement.kt rename to src/main/kotlin/io/github/smaugfm/monobudget/common/account/MaybeTransfer.kt index 38e1032..b484945 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/account/MaybeTransferStatement.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/account/MaybeTransfer.kt @@ -3,13 +3,13 @@ package io.github.smaugfm.monobudget.common.account import io.github.smaugfm.monobudget.common.model.financial.StatementItem import kotlinx.coroutines.CompletableDeferred -sealed class MaybeTransferStatement { +sealed class MaybeTransfer { abstract val statement: StatementItem data class Transfer( override val statement: StatementItem, private val processed: TTransaction, - ) : MaybeTransferStatement() { + ) : MaybeTransfer() { @Suppress("UNCHECKED_CAST") fun processed(): T { return processed as T @@ -19,7 +19,7 @@ sealed class MaybeTransferStatement { class NotTransfer( override val statement: StatementItem, private val processedDeferred: CompletableDeferred, - ) : MaybeTransferStatement() { + ) : MaybeTransfer() { @Volatile private var ran = false diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/account/TransferCache.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/account/TransferCache.kt index 453f4fc..9c8cc17 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/account/TransferCache.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/account/TransferCache.kt @@ -1,7 +1,7 @@ package io.github.smaugfm.monobudget.common.account -import io.github.smaugfm.monobudget.common.misc.ConcurrentExpiringMap import io.github.smaugfm.monobudget.common.model.financial.StatementItem +import io.github.smaugfm.monobudget.common.util.misc.ConcurrentExpiringMap import kotlinx.coroutines.Deferred import kotlin.time.Duration.Companion.minutes diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/account/TransferDetector.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/account/TransferDetector.kt index 04aafbd..abdbecb 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/account/TransferDetector.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/account/TransferDetector.kt @@ -1,9 +1,9 @@ package io.github.smaugfm.monobudget.common.account import io.github.oshai.kotlinlogging.KotlinLogging -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext -import io.github.smaugfm.monobudget.common.misc.ConcurrentExpiringMap import io.github.smaugfm.monobudget.common.model.financial.StatementItem +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext +import io.github.smaugfm.monobudget.common.util.misc.ConcurrentExpiringMap import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Deferred @@ -14,7 +14,7 @@ abstract class TransferDetector( private val ctx: StatementProcessingContext, private val cache: ConcurrentExpiringMap>, ) { - suspend fun checkTransfer(): MaybeTransferStatement = + suspend fun checkForTransfer(): MaybeTransfer = ctx.getOrPut("transfer") { val existingTransfer = cache.entries.firstOrNull { (recentStatementItem) -> @@ -27,12 +27,12 @@ abstract class TransferDetector( "Current: ${ctx.item}\n" + "Recent transfer: $existingTransfer" } - MaybeTransferStatement.Transfer(ctx.item, existingTransfer) + MaybeTransfer.Transfer(ctx.item, existingTransfer) } else { val deferred = CompletableDeferred() cache.add(ctx.item, deferred) - MaybeTransferStatement.NotTransfer(ctx.item, deferred) + MaybeTransfer.NotTransfer(ctx.item, deferred) } } diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/category/CategoryService.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/category/CategoryService.kt index 5745fed..6249490 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/category/CategoryService.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/category/CategoryService.kt @@ -1,9 +1,9 @@ package io.github.smaugfm.monobudget.common.category import io.github.oshai.kotlinlogging.KotlinLogging -import io.github.smaugfm.monobudget.common.misc.MCC import io.github.smaugfm.monobudget.common.model.financial.Amount import io.github.smaugfm.monobudget.common.model.settings.MccOverrideSettings +import io.github.smaugfm.monobudget.common.util.MCCRegistry import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -33,7 +33,7 @@ abstract class CategoryService : KoinComponent { mccOverride.mccToCategoryName[mcc] ?: categoryNameByMccGroup(mcc) private fun categoryNameByMccGroup(mcc: Int): String? { - val mccObj = MCC.map[mcc] + val mccObj = MCCRegistry.map[mcc] if (mccObj == null) { log.warn { "Unknown MCC code $mcc" } } else { diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/exception/BudgetBackendError.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/exception/BudgetBackendException.kt similarity index 80% rename from src/main/kotlin/io/github/smaugfm/monobudget/common/exception/BudgetBackendError.kt rename to src/main/kotlin/io/github/smaugfm/monobudget/common/exception/BudgetBackendException.kt index 9b3c99a..b01ed82 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/exception/BudgetBackendError.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/exception/BudgetBackendException.kt @@ -1,6 +1,6 @@ package io.github.smaugfm.monobudget.common.exception -class BudgetBackendError( +class BudgetBackendException( cause: Throwable, val userMessage: String, ) : Exception(cause) diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/retry/InMemoryStatementRetryRepository.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/retry/InMemoryStatementRetryRepository.kt index 8d3a655..cc98184 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/retry/InMemoryStatementRetryRepository.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/retry/InMemoryStatementRetryRepository.kt @@ -1,6 +1,6 @@ package io.github.smaugfm.monobudget.common.retry -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext import java.util.UUID import kotlin.time.Duration diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/retry/JacksonFileStatementRetryRepository.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/retry/JacksonFileStatementRetryRepository.kt index 4100417..431c240 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/retry/JacksonFileStatementRetryRepository.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/retry/JacksonFileStatementRetryRepository.kt @@ -12,7 +12,7 @@ import com.fasterxml.jackson.databind.ser.std.StdSerializer import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import com.fasterxml.jackson.module.kotlin.jsonMapper import com.fasterxml.jackson.module.kotlin.kotlinModule -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext import kotlinx.datetime.Instant import java.nio.file.Path import java.nio.file.StandardOpenOption diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/retry/RetryStatementSource.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/retry/RetryStatementSource.kt index 17fcf22..f559f55 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/retry/RetryStatementSource.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/retry/RetryStatementSource.kt @@ -1,11 +1,11 @@ package io.github.smaugfm.monobudget.common.retry import io.github.oshai.kotlinlogging.KotlinLogging -import io.github.smaugfm.monobudget.common.exception.BudgetBackendError -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingEventListener +import io.github.smaugfm.monobudget.common.exception.BudgetBackendException import io.github.smaugfm.monobudget.common.model.settings.RetrySettings import io.github.smaugfm.monobudget.common.statement.StatementSource +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingEventListener import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow @@ -31,7 +31,7 @@ class RetryStatementSource( override suspend fun handleRetry( ctx: StatementProcessingContext, - e: BudgetBackendError, + e: BudgetBackendException, ) { val request = repository.addRetryRequest( diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/retry/StatementRetryRepository.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/retry/StatementRetryRepository.kt index 885b896..489db3c 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/retry/StatementRetryRepository.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/retry/StatementRetryRepository.kt @@ -1,6 +1,6 @@ package io.github.smaugfm.monobudget.common.retry -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext import kotlin.time.Duration interface StatementRetryRepository { diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/retry/StatementRetryRequest.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/retry/StatementRetryRequest.kt index c918476..4df3804 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/retry/StatementRetryRequest.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/retry/StatementRetryRequest.kt @@ -1,7 +1,7 @@ package io.github.smaugfm.monobudget.common.retry import com.fasterxml.jackson.annotation.JsonIgnore -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlin.time.Duration diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/verify/ApplicationStartupVerifier.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/startup/ApplicationStartupVerifier.kt similarity index 56% rename from src/main/kotlin/io/github/smaugfm/monobudget/common/verify/ApplicationStartupVerifier.kt rename to src/main/kotlin/io/github/smaugfm/monobudget/common/startup/ApplicationStartupVerifier.kt index 5d08973..032cfcf 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/verify/ApplicationStartupVerifier.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/startup/ApplicationStartupVerifier.kt @@ -1,4 +1,4 @@ -package io.github.smaugfm.monobudget.common.verify +package io.github.smaugfm.monobudget.common.startup interface ApplicationStartupVerifier { suspend fun verify() diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/verify/BudgetSettingsVerifier.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/startup/BudgetSettingsVerifier.kt similarity index 97% rename from src/main/kotlin/io/github/smaugfm/monobudget/common/verify/BudgetSettingsVerifier.kt rename to src/main/kotlin/io/github/smaugfm/monobudget/common/startup/BudgetSettingsVerifier.kt index 7473076..d4b7082 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/verify/BudgetSettingsVerifier.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/startup/BudgetSettingsVerifier.kt @@ -1,4 +1,4 @@ -package io.github.smaugfm.monobudget.common.verify +package io.github.smaugfm.monobudget.common.startup import io.github.oshai.kotlinlogging.KotlinLogging import io.github.smaugfm.lunchmoney.api.LunchmoneyApi diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/OtherBankTransferStatementFactory.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/OtherBankTransferStatementFactory.kt index f13e4ed..6c1799b 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/OtherBankTransferStatementFactory.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/OtherBankTransferStatementFactory.kt @@ -1,10 +1,10 @@ package io.github.smaugfm.monobudget.common.statement -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingEventListener import io.github.smaugfm.monobudget.common.model.financial.OtherBankStatementItem import io.github.smaugfm.monobudget.common.model.financial.StatementItem import io.github.smaugfm.monobudget.common.model.settings.OtherBanksTransferSettings +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingEventListener import org.koin.core.annotation.Single @Single diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/OtherBankTransferStatementSource.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/OtherBankTransferStatementSource.kt index b9c1019..ea29a9c 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/OtherBankTransferStatementSource.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/OtherBankTransferStatementSource.kt @@ -1,7 +1,7 @@ package io.github.smaugfm.monobudget.common.statement -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext import io.github.smaugfm.monobudget.common.model.financial.OtherBankStatementItem +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext import kotlinx.coroutines.flow.MutableSharedFlow import org.koin.core.annotation.Single diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/StatementSource.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/StatementSource.kt index fc6f06b..f9b11a8 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/StatementSource.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/StatementSource.kt @@ -1,6 +1,6 @@ package io.github.smaugfm.monobudget.common.statement -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext import kotlinx.coroutines.flow.Flow interface StatementSource { diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/lifecycle/StatementProcessingEventDelivery.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/lifecycle/StatementEvents.kt similarity index 92% rename from src/main/kotlin/io/github/smaugfm/monobudget/common/lifecycle/StatementProcessingEventDelivery.kt rename to src/main/kotlin/io/github/smaugfm/monobudget/common/statement/lifecycle/StatementEvents.kt index 2fe5eee..0951777 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/lifecycle/StatementProcessingEventDelivery.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/lifecycle/StatementEvents.kt @@ -1,8 +1,8 @@ -package io.github.smaugfm.monobudget.common.lifecycle +package io.github.smaugfm.monobudget.common.statement.lifecycle import com.elbekd.bot.types.CallbackQuery import io.github.oshai.kotlinlogging.KotlinLogging -import io.github.smaugfm.monobudget.common.exception.BudgetBackendError +import io.github.smaugfm.monobudget.common.exception.BudgetBackendException import io.github.smaugfm.monobudget.common.model.callback.CallbackType import io.github.smaugfm.monobudget.common.util.injectAll import org.koin.core.annotation.Single @@ -11,7 +11,7 @@ import org.koin.core.component.KoinComponent private val log = KotlinLogging.logger {} @Single -class StatementProcessingEventDelivery : KoinComponent { +class StatementEvents : KoinComponent { private val newStatementListeners by injectAll() private val statementEndListeners @@ -57,7 +57,7 @@ class StatementProcessingEventDelivery : KoinComponent { suspend fun onStatementRetry( ctx: StatementProcessingContext, - e: BudgetBackendError, + e: BudgetBackendException, ) { retryScheduledEventListeners.forEach { l -> l.handleRetry(ctx, e) diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/lifecycle/StatementItemProcessor.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/lifecycle/StatementItemProcessor.kt similarity index 94% rename from src/main/kotlin/io/github/smaugfm/monobudget/common/lifecycle/StatementItemProcessor.kt rename to src/main/kotlin/io/github/smaugfm/monobudget/common/statement/lifecycle/StatementItemProcessor.kt index 3d08b37..25c1902 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/lifecycle/StatementItemProcessor.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/lifecycle/StatementItemProcessor.kt @@ -1,4 +1,4 @@ -package io.github.smaugfm.monobudget.common.lifecycle +package io.github.smaugfm.monobudget.common.statement.lifecycle import io.github.oshai.kotlinlogging.KotlinLogging import io.github.smaugfm.monobudget.common.account.BankAccountService @@ -25,7 +25,7 @@ abstract class StatementItemProcessor( private suspend fun processStatement() { val maybeTransfer = - transferDetector.checkTransfer() + transferDetector.checkForTransfer() val transaction = transactionFactory.create(maybeTransfer) val message = messageFormatter.format(ctx.item, transaction) diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/lifecycle/StatementProcessingContext.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/lifecycle/StatementProcessingContext.kt similarity index 93% rename from src/main/kotlin/io/github/smaugfm/monobudget/common/lifecycle/StatementProcessingContext.kt rename to src/main/kotlin/io/github/smaugfm/monobudget/common/statement/lifecycle/StatementProcessingContext.kt index 447d991..d5686c1 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/lifecycle/StatementProcessingContext.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/lifecycle/StatementProcessingContext.kt @@ -1,4 +1,4 @@ -package io.github.smaugfm.monobudget.common.lifecycle +package io.github.smaugfm.monobudget.common.statement.lifecycle import io.github.smaugfm.monobudget.common.model.financial.StatementItem diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/lifecycle/StatementProcessingEventListener.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/lifecycle/StatementProcessingEventListener.kt similarity index 90% rename from src/main/kotlin/io/github/smaugfm/monobudget/common/lifecycle/StatementProcessingEventListener.kt rename to src/main/kotlin/io/github/smaugfm/monobudget/common/statement/lifecycle/StatementProcessingEventListener.kt index e9f37d4..bedcd8d 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/lifecycle/StatementProcessingEventListener.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/lifecycle/StatementProcessingEventListener.kt @@ -1,7 +1,7 @@ -package io.github.smaugfm.monobudget.common.lifecycle +package io.github.smaugfm.monobudget.common.statement.lifecycle import com.elbekd.bot.types.CallbackQuery -import io.github.smaugfm.monobudget.common.exception.BudgetBackendError +import io.github.smaugfm.monobudget.common.exception.BudgetBackendException import io.github.smaugfm.monobudget.common.model.callback.CallbackType sealed interface StatementProcessingEventListener { @@ -23,7 +23,7 @@ sealed interface StatementProcessingEventListener { interface Retry : StatementProcessingEventListener { suspend fun handleRetry( ctx: StatementProcessingContext, - e: BudgetBackendError, + e: BudgetBackendException, ) } diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/lifecycle/StatementProcessingScopeComponent.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/lifecycle/StatementProcessingScopeComponent.kt similarity index 81% rename from src/main/kotlin/io/github/smaugfm/monobudget/common/lifecycle/StatementProcessingScopeComponent.kt rename to src/main/kotlin/io/github/smaugfm/monobudget/common/statement/lifecycle/StatementProcessingScopeComponent.kt index 81a180d..c1624da 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/lifecycle/StatementProcessingScopeComponent.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/statement/lifecycle/StatementProcessingScopeComponent.kt @@ -1,4 +1,4 @@ -package io.github.smaugfm.monobudget.common.lifecycle +package io.github.smaugfm.monobudget.common.statement.lifecycle import org.koin.core.component.KoinScopeComponent import org.koin.core.component.createScope diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/telegram/TelegramCallbackHandler.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/telegram/TelegramCallbackHandler.kt index 7458134..e577c46 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/telegram/TelegramCallbackHandler.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/telegram/TelegramCallbackHandler.kt @@ -8,7 +8,6 @@ import com.elbekd.bot.types.Message import com.elbekd.bot.types.ParseMode import io.github.oshai.kotlinlogging.KotlinLogging import io.github.smaugfm.monobudget.common.category.CategoryService -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingEventDelivery import io.github.smaugfm.monobudget.common.model.callback.ActionCallbackType import io.github.smaugfm.monobudget.common.model.callback.ActionCallbackType.ChooseCategory import io.github.smaugfm.monobudget.common.model.callback.CallbackType @@ -18,6 +17,7 @@ import io.github.smaugfm.monobudget.common.model.callback.TransactionUpdateType. import io.github.smaugfm.monobudget.common.model.callback.TransactionUpdateType.Uncategorize import io.github.smaugfm.monobudget.common.model.callback.TransactionUpdateType.UpdateCategory import io.github.smaugfm.monobudget.common.model.settings.MultipleAccountSettings +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementEvents import io.github.smaugfm.monobudget.common.transaction.TransactionMessageFormatter import io.github.smaugfm.monobudget.common.transaction.TransactionMessageFormatter.Companion.extractPayee import io.github.smaugfm.monobudget.common.transaction.TransactionMessageFormatter.Companion.extractTransactionId @@ -34,7 +34,7 @@ abstract class TelegramCallbackHandler : KoinComponent { private val formatter: TransactionMessageFormatter by inject() private val monoSettings: MultipleAccountSettings by inject() private val telegramChatIds = monoSettings.telegramChatIds - private val statementEvents by inject() + private val statementEvents by inject() suspend fun handle(callbackQuery: CallbackQuery) { var callbackType: CallbackType? = null diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/telegram/TelegramErrorHandlerEventListener.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/telegram/TelegramErrorHandlerEventListener.kt index 478ba01..1747373 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/telegram/TelegramErrorHandlerEventListener.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/telegram/TelegramErrorHandlerEventListener.kt @@ -3,11 +3,11 @@ package io.github.smaugfm.monobudget.common.telegram import com.elbekd.bot.model.ChatId import com.elbekd.bot.types.CallbackQuery import io.github.smaugfm.monobudget.common.account.BankAccountService -import io.github.smaugfm.monobudget.common.exception.BudgetBackendError -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingEventListener +import io.github.smaugfm.monobudget.common.exception.BudgetBackendException import io.github.smaugfm.monobudget.common.model.callback.CallbackType import io.github.smaugfm.monobudget.common.model.settings.MultipleAccountSettings +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingEventListener import org.koin.core.annotation.Single @Single @@ -23,7 +23,7 @@ class TelegramErrorHandlerEventListener( e: Throwable, ): Boolean { val chatIds = chatIdsFromContext(ctx) - if (e is BudgetBackendError) { + if (e is BudgetBackendException) { val header = "Виникла помилка при створенні транзакції. " + "Будь ласка створи цю транзакцію вручну. " @@ -44,7 +44,7 @@ class TelegramErrorHandlerEventListener( override suspend fun handleRetry( ctx: StatementProcessingContext, - e: BudgetBackendError, + e: BudgetBackendException, ) { // Send retry message only on first attempt if (ctx.attempt > 0) { @@ -76,13 +76,13 @@ class TelegramErrorHandlerEventListener( private suspend fun onBudgetBackendError( header: String, chatIds: List, - budgetBackendError: BudgetBackendError, + budgetBackendException: BudgetBackendException, ) { chatIds .forEach { chatId -> telegramApi.sendMessage( chatId = ChatId.IntegerId(chatId), - text = header + budgetBackendError.userMessage, + text = header + budgetBackendException.userMessage, ) } } diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/transaction/TransactionFactory.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/transaction/TransactionFactory.kt index aeb186a..2491119 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/transaction/TransactionFactory.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/transaction/TransactionFactory.kt @@ -1,11 +1,11 @@ package io.github.smaugfm.monobudget.common.transaction -import io.github.smaugfm.monobudget.common.account.MaybeTransferStatement +import io.github.smaugfm.monobudget.common.account.MaybeTransfer import org.koin.core.component.KoinComponent import org.koin.core.component.inject abstract class TransactionFactory : KoinComponent { protected val newTransactionFactory: NewTransactionFactory by inject() - abstract suspend fun create(maybeTransfer: MaybeTransferStatement): TTransaction + abstract suspend fun create(maybeTransfer: MaybeTransfer): TTransaction } diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/misc/MCC.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/util/MCCRegistry.kt similarity index 65% rename from src/main/kotlin/io/github/smaugfm/monobudget/common/misc/MCC.kt rename to src/main/kotlin/io/github/smaugfm/monobudget/common/util/MCCRegistry.kt index 53c92c7..38d7568 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/misc/MCC.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/util/MCCRegistry.kt @@ -1,10 +1,9 @@ -package io.github.smaugfm.monobudget.common.misc +package io.github.smaugfm.monobudget.common.util import io.github.smaugfm.monobudget.common.model.mcc.MccEntry -import io.github.smaugfm.monobudget.common.util.resourceAsString import kotlinx.serialization.json.Json -object MCC { +object MCCRegistry { val map: Map = Json.decodeFromString>(resourceAsString("mcc.json")).associateBy { it.mcc } } diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/util/ResourceUtils.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/util/ResourceUtils.kt index 450c819..7938f3c 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/util/ResourceUtils.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/util/ResourceUtils.kt @@ -1,8 +1,7 @@ package io.github.smaugfm.monobudget.common.util -import io.github.smaugfm.monobudget.common.misc.MCC import java.net.URL -fun resource(path: String): URL = MCC.javaClass.classLoader.getResource(path)!! +fun resource(path: String): URL = MCCRegistry.javaClass.classLoader.getResource(path)!! fun resourceAsString(path: String): String = resource(path).readText() diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/misc/ConcurrentExpiringMap.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/util/misc/ConcurrentExpiringMap.kt similarity index 93% rename from src/main/kotlin/io/github/smaugfm/monobudget/common/misc/ConcurrentExpiringMap.kt rename to src/main/kotlin/io/github/smaugfm/monobudget/common/util/misc/ConcurrentExpiringMap.kt index 1615890..94ed6c2 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/misc/ConcurrentExpiringMap.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/util/misc/ConcurrentExpiringMap.kt @@ -1,4 +1,4 @@ -package io.github.smaugfm.monobudget.common.misc +package io.github.smaugfm.monobudget.common.util.misc import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.asCoroutineDispatcher diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/misc/PeriodicFetcherFactory.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/util/misc/PeriodicFetcherFactory.kt similarity index 97% rename from src/main/kotlin/io/github/smaugfm/monobudget/common/misc/PeriodicFetcherFactory.kt rename to src/main/kotlin/io/github/smaugfm/monobudget/common/util/misc/PeriodicFetcherFactory.kt index 54149c8..66d5b2a 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/misc/PeriodicFetcherFactory.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/util/misc/PeriodicFetcherFactory.kt @@ -1,4 +1,4 @@ -package io.github.smaugfm.monobudget.common.misc +package io.github.smaugfm.monobudget.common.util.misc import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.CompletableDeferred diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/common/misc/StringSimilarityPayeeSuggestionService.kt b/src/main/kotlin/io/github/smaugfm/monobudget/common/util/misc/StringSimilarityPayeeSuggestionService.kt similarity index 87% rename from src/main/kotlin/io/github/smaugfm/monobudget/common/misc/StringSimilarityPayeeSuggestionService.kt rename to src/main/kotlin/io/github/smaugfm/monobudget/common/util/misc/StringSimilarityPayeeSuggestionService.kt index 4f275f8..752ffc5 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/common/misc/StringSimilarityPayeeSuggestionService.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/common/util/misc/StringSimilarityPayeeSuggestionService.kt @@ -1,4 +1,4 @@ -package io.github.smaugfm.monobudget.common.misc +package io.github.smaugfm.monobudget.common.util.misc import io.github.oshai.kotlinlogging.KotlinLogging import io.github.smaugfm.monobudget.common.util.jaroWinklerSimilarity @@ -13,12 +13,12 @@ class StringSimilarityPayeeSuggestionService { payees: List, ): List { log.debug { "Looking for best payee match for memo: $value" } - return twoPass(value, payees).map { it.first }.also { + return twoPassJaroWindlerSimilarity(value, payees).map { it.first }.also { log.debug { "Found best match: $it" } } } - internal fun twoPass( + internal fun twoPassJaroWindlerSimilarity( value: String, payees: List, ): List> { diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyCategoryService.kt b/src/main/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyCategoryService.kt index e1d8b97..5f9b59d 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyCategoryService.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyCategoryService.kt @@ -3,8 +3,8 @@ package io.github.smaugfm.monobudget.lunchmoney import io.github.smaugfm.lunchmoney.api.LunchmoneyApi import io.github.smaugfm.lunchmoney.model.LunchmoneyBudget import io.github.smaugfm.monobudget.common.category.CategoryService -import io.github.smaugfm.monobudget.common.misc.PeriodicFetcherFactory import io.github.smaugfm.monobudget.common.model.financial.Amount +import io.github.smaugfm.monobudget.common.util.misc.PeriodicFetcherFactory import kotlinx.coroutines.reactor.awaitSingle import org.koin.core.annotation.Single import java.time.LocalDate diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyStatementItemProcessor.kt b/src/main/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyStatementItemProcessor.kt index 55f5b8b..f76891f 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyStatementItemProcessor.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyStatementItemProcessor.kt @@ -4,9 +4,9 @@ import io.github.smaugfm.lunchmoney.model.LunchmoneyInsertTransaction import io.github.smaugfm.lunchmoney.model.LunchmoneyTransaction import io.github.smaugfm.monobudget.common.account.BankAccountService import io.github.smaugfm.monobudget.common.account.TransferDetector -import io.github.smaugfm.monobudget.common.lifecycle.StatementItemProcessor -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingScopeComponent +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementItemProcessor +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingScopeComponent import io.github.smaugfm.monobudget.common.telegram.TelegramMessageSender import io.github.smaugfm.monobudget.common.transaction.TransactionFactory import io.github.smaugfm.monobudget.common.transaction.TransactionMessageFormatter diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyTransactionCreator.kt b/src/main/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyTransactionCreator.kt index acd4def..ac891be 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyTransactionCreator.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyTransactionCreator.kt @@ -7,12 +7,12 @@ import io.github.smaugfm.lunchmoney.model.LunchmoneyInsertTransaction import io.github.smaugfm.lunchmoney.model.LunchmoneyTransaction import io.github.smaugfm.lunchmoney.model.LunchmoneyUpdateTransaction import io.github.smaugfm.lunchmoney.model.enumeration.LunchmoneyTransactionStatus -import io.github.smaugfm.monobudget.common.account.MaybeTransferStatement -import io.github.smaugfm.monobudget.common.exception.BudgetBackendError -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingScopeComponent +import io.github.smaugfm.monobudget.common.account.MaybeTransfer +import io.github.smaugfm.monobudget.common.exception.BudgetBackendException import io.github.smaugfm.monobudget.common.model.BudgetBackend import io.github.smaugfm.monobudget.common.model.financial.StatementItem +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingScopeComponent import io.github.smaugfm.monobudget.common.transaction.TransactionFactory import kotlinx.coroutines.reactor.awaitSingle import kotlinx.serialization.SerializationException @@ -30,24 +30,24 @@ class LunchmoneyTransactionCreator( ) : TransactionFactory() { private val transferCategoryId = budgetBackend.transferCategoryId.toLong() - override suspend fun create(maybeTransfer: MaybeTransferStatement) = + override suspend fun create(maybeTransfer: MaybeTransfer) = try { when (maybeTransfer) { - is MaybeTransferStatement.Transfer -> + is MaybeTransfer.Transfer -> processTransfer(maybeTransfer.statement, maybeTransfer.processed()) - is MaybeTransferStatement.NotTransfer -> + is MaybeTransfer.NotTransfer -> maybeTransfer.consume(::processSingle) } } catch (e: LunchmoneyApiResponseException) { val template = "Текст помилки: " if (e.cause is SerializationException) { - throw BudgetBackendError( + throw BudgetBackendException( e, template + (e.message?.substringBefore("JSON input:") + "HTTP Body:\n" + e.body), ) } else { - throw BudgetBackendError( + throw BudgetBackendException( e, template + e.message, ) diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyTransactionMessageFormatter.kt b/src/main/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyTransactionMessageFormatter.kt index e9e5b98..34686d9 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyTransactionMessageFormatter.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyTransactionMessageFormatter.kt @@ -4,12 +4,12 @@ import com.elbekd.bot.types.InlineKeyboardMarkup import io.github.smaugfm.lunchmoney.model.LunchmoneyTransaction import io.github.smaugfm.lunchmoney.model.enumeration.LunchmoneyTransactionStatus import io.github.smaugfm.monobudget.common.category.CategoryService -import io.github.smaugfm.monobudget.common.misc.MCC import io.github.smaugfm.monobudget.common.model.callback.ActionCallbackType import io.github.smaugfm.monobudget.common.model.callback.PressedButtons import io.github.smaugfm.monobudget.common.model.callback.TransactionUpdateType import io.github.smaugfm.monobudget.common.model.financial.StatementItem import io.github.smaugfm.monobudget.common.transaction.TransactionMessageFormatter +import io.github.smaugfm.monobudget.common.util.MCCRegistry import io.github.smaugfm.monobudget.common.util.formatW import io.github.smaugfm.monobudget.common.util.replaceNewLines import io.github.smaugfm.monobudget.common.util.toLocalDateTime @@ -40,7 +40,7 @@ class LunchmoneyTransactionMessageFormatter( return formatHTMLStatementMessage( "Lunchmoney", (description ?: "").replaceNewLines(), - (MCC.map[mcc]?.fullDescription ?: "Невідомий MCC") + " ($mcc)", + (MCCRegistry.map[mcc]?.fullDescription ?: "Невідомий MCC") + " ($mcc)", "$amount${(if (accountCurrency != currency) " ($operationAmount)" else "")}", category, transaction.payee, diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyTransferDetector.kt b/src/main/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyTransferDetector.kt index 0bc5593..5d91d9d 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyTransferDetector.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyTransferDetector.kt @@ -3,8 +3,8 @@ package io.github.smaugfm.monobudget.lunchmoney import io.github.smaugfm.lunchmoney.model.LunchmoneyTransaction import io.github.smaugfm.monobudget.common.account.BankAccountService import io.github.smaugfm.monobudget.common.account.TransferDetector -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingScopeComponent +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingScopeComponent import org.koin.core.annotation.Scope import org.koin.core.annotation.Scoped diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/mono/MonoSettingsVerifier.kt b/src/main/kotlin/io/github/smaugfm/monobudget/mono/MonoSettingsVerifier.kt index 125cdc1..5d1961f 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/mono/MonoSettingsVerifier.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/mono/MonoSettingsVerifier.kt @@ -1,7 +1,7 @@ package io.github.smaugfm.monobudget.mono import io.github.smaugfm.monobudget.common.model.settings.MultipleAccountSettings -import io.github.smaugfm.monobudget.common.verify.ApplicationStartupVerifier +import io.github.smaugfm.monobudget.common.startup.ApplicationStartupVerifier import org.koin.core.annotation.Single @Single diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/mono/MonoWebhookListener.kt b/src/main/kotlin/io/github/smaugfm/monobudget/mono/MonoWebhookListener.kt index c31ab5e..87c5427 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/mono/MonoWebhookListener.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/mono/MonoWebhookListener.kt @@ -2,8 +2,8 @@ package io.github.smaugfm.monobudget.mono import io.github.oshai.kotlinlogging.KotlinLogging import io.github.smaugfm.monobank.model.MonoWebhookResponse -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext import io.github.smaugfm.monobudget.common.statement.StatementSource +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext import io.github.smaugfm.monobudget.common.util.injectAll import io.github.smaugfm.monobudget.common.util.makeJson import io.ktor.http.ContentType diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabCategoryService.kt b/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabCategoryService.kt index 0dbe580..bf32d4d 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabCategoryService.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabCategoryService.kt @@ -1,9 +1,9 @@ package io.github.smaugfm.monobudget.ynab import io.github.smaugfm.monobudget.common.category.CategoryService -import io.github.smaugfm.monobudget.common.misc.PeriodicFetcherFactory import io.github.smaugfm.monobudget.common.model.BudgetBackend import io.github.smaugfm.monobudget.common.model.financial.Amount +import io.github.smaugfm.monobudget.common.util.misc.PeriodicFetcherFactory import org.koin.core.annotation.Single import java.util.Currency diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabCurrencyVerifier.kt b/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabCurrencyVerifier.kt index d1bf5a3..b182e06 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabCurrencyVerifier.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabCurrencyVerifier.kt @@ -2,7 +2,7 @@ package io.github.smaugfm.monobudget.ynab import io.github.smaugfm.monobudget.common.account.BankAccountService import io.github.smaugfm.monobudget.common.model.BudgetBackend -import io.github.smaugfm.monobudget.common.verify.ApplicationStartupVerifier +import io.github.smaugfm.monobudget.common.startup.ApplicationStartupVerifier import org.koin.core.annotation.Single import java.util.Currency diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabNewTransactionFactory.kt b/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabNewTransactionFactory.kt index f5f2d92..8729e80 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabNewTransactionFactory.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabNewTransactionFactory.kt @@ -1,10 +1,10 @@ package io.github.smaugfm.monobudget.ynab import io.github.oshai.kotlinlogging.KotlinLogging -import io.github.smaugfm.monobudget.common.misc.PeriodicFetcherFactory -import io.github.smaugfm.monobudget.common.misc.StringSimilarityPayeeSuggestionService import io.github.smaugfm.monobudget.common.model.financial.StatementItem import io.github.smaugfm.monobudget.common.transaction.NewTransactionFactory +import io.github.smaugfm.monobudget.common.util.misc.PeriodicFetcherFactory +import io.github.smaugfm.monobudget.common.util.misc.StringSimilarityPayeeSuggestionService import io.github.smaugfm.monobudget.common.util.toLocalDateTime import io.github.smaugfm.monobudget.ynab.model.YnabCleared import io.github.smaugfm.monobudget.ynab.model.YnabSaveTransaction diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabStatementItemProcessor.kt b/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabStatementItemProcessor.kt index 197af09..b0f619e 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabStatementItemProcessor.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabStatementItemProcessor.kt @@ -2,9 +2,9 @@ package io.github.smaugfm.monobudget.ynab import io.github.smaugfm.monobudget.common.account.BankAccountService import io.github.smaugfm.monobudget.common.account.TransferDetector -import io.github.smaugfm.monobudget.common.lifecycle.StatementItemProcessor -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingScopeComponent +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementItemProcessor +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingScopeComponent import io.github.smaugfm.monobudget.common.telegram.TelegramMessageSender import io.github.smaugfm.monobudget.common.transaction.TransactionFactory import io.github.smaugfm.monobudget.common.transaction.TransactionMessageFormatter diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabTransactionFactory.kt b/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabTransactionFactory.kt index 2444a3c..6e9586b 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabTransactionFactory.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabTransactionFactory.kt @@ -2,7 +2,7 @@ package io.github.smaugfm.monobudget.ynab import io.github.oshai.kotlinlogging.KotlinLogging import io.github.smaugfm.monobudget.common.account.BankAccountService -import io.github.smaugfm.monobudget.common.account.MaybeTransferStatement +import io.github.smaugfm.monobudget.common.account.MaybeTransfer import io.github.smaugfm.monobudget.common.model.financial.StatementItem import io.github.smaugfm.monobudget.common.transaction.TransactionFactory import io.github.smaugfm.monobudget.ynab.model.YnabCleared @@ -20,14 +20,15 @@ class YnabTransactionFactory( ) : TransactionFactory() { private val transferPayeeIdsCache = ConcurrentHashMap() - override suspend fun create(maybeTransfer: MaybeTransferStatement) = + override suspend fun create(maybeTransfer: MaybeTransfer) = when (maybeTransfer) { - is MaybeTransferStatement.Transfer -> + is MaybeTransfer.Transfer -> processTransfer( maybeTransfer.statement, maybeTransfer.processed(), ) - is MaybeTransferStatement.NotTransfer -> maybeTransfer.consume(::processSingle) + + is MaybeTransfer.NotTransfer -> maybeTransfer.consume(::processSingle) } private suspend fun processTransfer( diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabTransactionMessageFormatter.kt b/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabTransactionMessageFormatter.kt index 7819a16..51392ed 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabTransactionMessageFormatter.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabTransactionMessageFormatter.kt @@ -2,11 +2,11 @@ package io.github.smaugfm.monobudget.ynab import com.elbekd.bot.types.InlineKeyboardMarkup import io.github.smaugfm.monobudget.common.category.CategoryService -import io.github.smaugfm.monobudget.common.misc.MCC import io.github.smaugfm.monobudget.common.model.callback.PressedButtons import io.github.smaugfm.monobudget.common.model.callback.TransactionUpdateType import io.github.smaugfm.monobudget.common.model.financial.StatementItem import io.github.smaugfm.monobudget.common.transaction.TransactionMessageFormatter +import io.github.smaugfm.monobudget.common.util.MCCRegistry import io.github.smaugfm.monobudget.common.util.replaceNewLines import io.github.smaugfm.monobudget.ynab.model.YnabCleared import io.github.smaugfm.monobudget.ynab.model.YnabTransactionDetail @@ -28,7 +28,7 @@ class YnabTransactionMessageFormatter( return formatHTMLStatementMessage( "YNAB", (description ?: "").replaceNewLines(), - (MCC.map[mcc]?.fullDescription ?: "Невідомий MCC") + " ($mcc)", + (MCCRegistry.map[mcc]?.fullDescription ?: "Невідомий MCC") + " ($mcc)", "$amount${(if (accountCurrency != currency) " ($operationAmount)" else "")}", category, transaction.payeeName ?: "", diff --git a/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabTransferDetector.kt b/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabTransferDetector.kt index 02a7f79..55ed435 100644 --- a/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabTransferDetector.kt +++ b/src/main/kotlin/io/github/smaugfm/monobudget/ynab/YnabTransferDetector.kt @@ -2,8 +2,8 @@ package io.github.smaugfm.monobudget.ynab import io.github.smaugfm.monobudget.common.account.BankAccountService import io.github.smaugfm.monobudget.common.account.TransferDetector -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingScopeComponent +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingScopeComponent import io.github.smaugfm.monobudget.ynab.model.YnabTransactionDetail import org.koin.core.annotation.Scope import org.koin.core.annotation.Scoped diff --git a/src/test/kotlin/io/github/smaugfm/monobudget/TestBase.kt b/src/test/kotlin/io/github/smaugfm/monobudget/TestBase.kt index f7b72ca..f7d3a78 100644 --- a/src/test/kotlin/io/github/smaugfm/monobudget/TestBase.kt +++ b/src/test/kotlin/io/github/smaugfm/monobudget/TestBase.kt @@ -1,11 +1,7 @@ package io.github.smaugfm.monobudget -import io.github.smaugfm.monobank.model.MonoStatementItem -import io.github.smaugfm.monobank.model.MonoWebhookResponseData -import io.github.smaugfm.monobudget.mono.MonobankWebhookResponseStatementItem import io.mockk.junit5.MockKExtension import io.mockk.mockkClass -import kotlinx.datetime.Instant import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.RegisterExtension @@ -13,7 +9,6 @@ import org.koin.core.KoinApplication import org.koin.test.KoinTest import org.koin.test.junit5.KoinTestExtension import org.koin.test.mock.MockProvider -import java.util.Currency @ExtendWith(MockKExtension::class) @Suppress("MagicNumber") @@ -33,50 +28,4 @@ open class TestBase : KoinTest { fun declareMockProvider() { MockProvider.register { mockkClass(it) } } - - companion object { - fun statementItem1() = - MonobankWebhookResponseStatementItem( - MonoWebhookResponseData( - "acc1", - MonoStatementItem( - "aaa", - Instant.parse("2023-04-02T18:12:41Z"), - "З доларової картки", - 4829, - 4829, - true, - 3665, - 100, - Currency.getInstance("USD"), - 0, - 0, - 1234, - ), - ), - Currency.getInstance("UAH"), - ) - - fun statementItem2() = - MonobankWebhookResponseStatementItem( - MonoWebhookResponseData( - "acc2", - MonoStatementItem( - "bbb", - Instant.parse("2023-04-02T18:12:42Z"), - "Переказ на картку", - 4829, - 4829, - true, - -100, - -3665, - Currency.getInstance("UAH"), - 0, - 0, - 1234, - ), - ), - Currency.getInstance("UAH"), - ) - } } diff --git a/src/test/kotlin/io/github/smaugfm/monobudget/TestData.kt b/src/test/kotlin/io/github/smaugfm/monobudget/TestData.kt new file mode 100644 index 0000000..dcf8371 --- /dev/null +++ b/src/test/kotlin/io/github/smaugfm/monobudget/TestData.kt @@ -0,0 +1,114 @@ +package io.github.smaugfm.monobudget + +import io.github.smaugfm.lunchmoney.model.LunchmoneyCategoryMultiple +import io.github.smaugfm.monobank.model.MonoStatementItem +import io.github.smaugfm.monobank.model.MonoWebhookResponseData +import io.github.smaugfm.monobudget.mono.MonobankWebhookResponseStatementItem +import kotlinx.datetime.Clock +import java.time.Instant +import java.util.Currency +import java.util.UUID + +object TestData { + val UAH: Currency = Currency.getInstance("UAH") + + fun exampleStatement1(description: String) = + MonobankWebhookResponseStatementItem( + d = + MonoWebhookResponseData( + account = "MONO-EXAMPLE-UAH", + statementItem = + MonoStatementItem( + id = UUID.randomUUID().toString(), + time = Clock.System.now(), + description = description, + mcc = 4829, + originalMcc = 4829, + hold = true, + amount = 5600, + operationAmount = 5600, + currencyCode = UAH, + commissionRate = 0, + cashbackAmount = 0, + balance = 0, + ), + ), + accountCurrency = UAH, + ) + + fun exampleStatement2(description: String) = + MonobankWebhookResponseStatementItem( + d = + MonoWebhookResponseData( + account = "MONO-EXAMPLE-UAH2", + statementItem = + MonoStatementItem( + id = UUID.randomUUID().toString(), + time = Clock.System.now(), + description = description, + mcc = 4829, + originalMcc = 4829, + hold = true, + amount = -5600, + operationAmount = -5600, + currencyCode = UAH, + commissionRate = 0, + cashbackAmount = 0, + balance = 0, + ), + ), + accountCurrency = UAH, + ) + + val categories = + listOf( + LunchmoneyCategoryMultiple( + id = 444443, + name = "Авто", + description = null, + isIncome = false, + excludeFromBudget = false, + excludeFromTotals = false, + updatedAt = Instant.parse("2023-03-26T08:11:06.088Z"), + createdAt = Instant.parse("2023-03-26T08:11:06.088Z"), + isGroup = false, + groupId = null, + ), + LunchmoneyCategoryMultiple( + id = 444444, + name = "Перекази", + description = null, + isIncome = false, + excludeFromBudget = true, + excludeFromTotals = true, + updatedAt = Instant.parse("2023-03-26T08:47:50.132Z"), + createdAt = Instant.parse("2023-03-26T08:47:50.132Z"), + isGroup = false, + groupId = null, + ), + LunchmoneyCategoryMultiple( + id = 444445, + name = "Розваги", + description = null, + isIncome = false, + excludeFromBudget = false, + excludeFromTotals = false, + updatedAt = Instant.parse("2023-03-26T08:11:24.380Z"), + createdAt = Instant.parse("2023-03-26T08:11:24.380Z"), + isGroup = false, + groupId = null, + ), + LunchmoneyCategoryMultiple( + id = 444446, + name = "Транспорт", + description = null, + isIncome = false, + excludeFromBudget = false, + excludeFromTotals = false, + updatedAt = Instant.parse("2023-03-26T08:11:40.931Z"), + createdAt = Instant.parse("2023-03-26T08:11:40.931Z"), + isGroup = false, + groupId = null, + ), + ) +} diff --git a/src/test/kotlin/io/github/smaugfm/monobudget/common/account/TransferDetectorTest.kt b/src/test/kotlin/io/github/smaugfm/monobudget/common/account/TransferDetectorTest.kt index f50e11f..e4cdbaa 100644 --- a/src/test/kotlin/io/github/smaugfm/monobudget/common/account/TransferDetectorTest.kt +++ b/src/test/kotlin/io/github/smaugfm/monobudget/common/account/TransferDetectorTest.kt @@ -3,19 +3,23 @@ package io.github.smaugfm.monobudget.common.account import assertk.assertThat import assertk.assertions.isInstanceOf import io.github.oshai.kotlinlogging.KotlinLogging +import io.github.smaugfm.monobank.model.MonoStatementItem +import io.github.smaugfm.monobank.model.MonoWebhookResponseData import io.github.smaugfm.monobudget.TestBase -import io.github.smaugfm.monobudget.common.account.MaybeTransferStatement.NotTransfer -import io.github.smaugfm.monobudget.common.account.MaybeTransferStatement.Transfer -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingScopeComponent -import io.github.smaugfm.monobudget.common.misc.ConcurrentExpiringMap +import io.github.smaugfm.monobudget.common.account.MaybeTransfer.NotTransfer +import io.github.smaugfm.monobudget.common.account.MaybeTransfer.Transfer import io.github.smaugfm.monobudget.common.model.financial.StatementItem +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingScopeComponent +import io.github.smaugfm.monobudget.common.util.misc.ConcurrentExpiringMap +import io.github.smaugfm.monobudget.mono.MonobankWebhookResponseStatementItem import io.mockk.coEvery import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Deferred import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import kotlinx.datetime.Instant import org.junit.jupiter.api.Test import org.junit.jupiter.api.Timeout import org.koin.core.KoinApplication @@ -53,9 +57,9 @@ class TransferDetectorTest : TestBase() { @Timeout(2, unit = TimeUnit.SECONDS) @Test - fun `Detects transfer`() { - val ctx1 = StatementProcessingContext(statementItem1()) - val ctx2 = StatementProcessingContext(statementItem2()) + fun `Detects a transfer`() { + val ctx1 = StatementProcessingContext(transferStatement1) + val ctx2 = StatementProcessingContext(transferStatement2) declareMock { coEvery { getAccountCurrency(ctx1.item.accountId) } returns Currency.getInstance("UAH") @@ -67,8 +71,8 @@ class TransferDetectorTest : TestBase() { launch { val sc = StatementProcessingScopeComponent(ctx1) val detector = sc.scope.get() - assertThat(detector.checkTransfer()).isInstanceOf(NotTransfer::class) - val notTransfer = detector.checkTransfer() as NotTransfer + assertThat(detector.checkForTransfer()).isInstanceOf(NotTransfer::class) + val notTransfer = detector.checkForTransfer() as NotTransfer waitForNotTransfer.complete(Any()) notTransfer.consume { log.info { "Started consuming transaction 1..." } @@ -83,11 +87,55 @@ class TransferDetectorTest : TestBase() { launch { val sc = StatementProcessingScopeComponent(ctx2) val detector = sc.scope.get() - assertThat(detector.checkTransfer()).isInstanceOf(Transfer::class) + assertThat(detector.checkForTransfer()).isInstanceOf(Transfer::class) sc.scope.close() } job1.join() job2.join() } } + + private val transferStatement1 = + MonobankWebhookResponseStatementItem( + MonoWebhookResponseData( + "acc1", + MonoStatementItem( + "aaa", + Instant.parse("2023-04-02T18:12:41Z"), + "З доларової картки", + 4829, + 4829, + true, + 3665, + 100, + Currency.getInstance("USD"), + 0, + 0, + 1234, + ), + ), + Currency.getInstance("UAH"), + ) + + private val transferStatement2 = + MonobankWebhookResponseStatementItem( + MonoWebhookResponseData( + "acc2", + MonoStatementItem( + "bbb", + Instant.parse("2023-04-02T18:12:42Z"), + "Переказ на картку", + 4829, + 4829, + true, + -100, + -3665, + Currency.getInstance("UAH"), + 0, + 0, + 1234, + ), + ), + Currency.getInstance("UAH"), + ) } diff --git a/src/test/kotlin/io/github/smaugfm/monobudget/common/misc/ExpiryContainerTest.kt b/src/test/kotlin/io/github/smaugfm/monobudget/common/misc/ExpiryContainerTest.kt index 3bac34c..be1d32c 100644 --- a/src/test/kotlin/io/github/smaugfm/monobudget/common/misc/ExpiryContainerTest.kt +++ b/src/test/kotlin/io/github/smaugfm/monobudget/common/misc/ExpiryContainerTest.kt @@ -1,5 +1,6 @@ package io.github.smaugfm.monobudget.common.misc +import io.github.smaugfm.monobudget.common.util.misc.ConcurrentExpiringMap import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking diff --git a/src/test/kotlin/io/github/smaugfm/monobudget/common/misc/PayeeSuggestorTest.kt b/src/test/kotlin/io/github/smaugfm/monobudget/common/misc/PayeeSuggestorTest.kt index 169a0a8..738eab9 100644 --- a/src/test/kotlin/io/github/smaugfm/monobudget/common/misc/PayeeSuggestorTest.kt +++ b/src/test/kotlin/io/github/smaugfm/monobudget/common/misc/PayeeSuggestorTest.kt @@ -1,5 +1,6 @@ package io.github.smaugfm.monobudget.common.misc +import io.github.smaugfm.monobudget.common.util.misc.StringSimilarityPayeeSuggestionService import org.junit.jupiter.api.Test class PayeeSuggestorTest { @@ -7,7 +8,7 @@ class PayeeSuggestorTest { fun payeeSuggestorSimpleTest() { val suggestor = StringSimilarityPayeeSuggestionService() val result = - suggestor.twoPass( + suggestor.twoPassJaroWindlerSimilarity( "Intellij Idea Ultimate", listOf("intellij idea ultimate", "Intellij Idea", "idea", "ultimate"), ) diff --git a/src/test/kotlin/io/github/smaugfm/monobudget/common/misc/PeriodicFetcherFactoryTest.kt b/src/test/kotlin/io/github/smaugfm/monobudget/common/misc/PeriodicFetcherFactoryTest.kt index 43067e1..452ea4a 100644 --- a/src/test/kotlin/io/github/smaugfm/monobudget/common/misc/PeriodicFetcherFactoryTest.kt +++ b/src/test/kotlin/io/github/smaugfm/monobudget/common/misc/PeriodicFetcherFactoryTest.kt @@ -3,6 +3,7 @@ package io.github.smaugfm.monobudget.common.misc import assertk.assertThat import assertk.assertions.isEqualTo import assertk.assertions.isGreaterThan +import io.github.smaugfm.monobudget.common.util.misc.PeriodicFetcherFactory import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope diff --git a/src/test/kotlin/io/github/smaugfm/monobudget/common/misc/Playground.kt b/src/test/kotlin/io/github/smaugfm/monobudget/common/misc/Playground.kt index dcbf4d1..58921cb 100644 --- a/src/test/kotlin/io/github/smaugfm/monobudget/common/misc/Playground.kt +++ b/src/test/kotlin/io/github/smaugfm/monobudget/common/misc/Playground.kt @@ -3,6 +3,8 @@ package io.github.smaugfm.monobudget.common.misc import io.github.smaugfm.lunchmoney.api.LunchmoneyApi import io.github.smaugfm.monobudget.common.model.BudgetBackend import io.github.smaugfm.monobudget.common.model.settings.Settings +import io.github.smaugfm.monobudget.common.util.MCCRegistry +import io.github.smaugfm.monobudget.common.util.misc.PeriodicFetcherFactory import io.github.smaugfm.monobudget.lunchmoney.LunchmoneyCategoryService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi @@ -76,7 +78,7 @@ class Playground : KoinTest { } else { it.operationAmount + it.currency }, - "${it.mcc} " + MCC.map[it.mcc.toInt()]?.fullDescription, + "${it.mcc} " + MCCRegistry.map[it.mcc.toInt()]?.fullDescription, ) } diff --git a/src/test/kotlin/io/github/smaugfm/monobudget/common/retry/JacksonFileStatementRetryRepositoryTest.kt b/src/test/kotlin/io/github/smaugfm/monobudget/common/retry/JacksonFileStatementRetryRepositoryTest.kt index b383861..4c0f8cd 100644 --- a/src/test/kotlin/io/github/smaugfm/monobudget/common/retry/JacksonFileStatementRetryRepositoryTest.kt +++ b/src/test/kotlin/io/github/smaugfm/monobudget/common/retry/JacksonFileStatementRetryRepositoryTest.kt @@ -2,7 +2,8 @@ package io.github.smaugfm.monobudget.common.retry import assertk.assertThat import assertk.assertions.isEmpty -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext +import io.github.smaugfm.monobudget.TestData.exampleStatement1 +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext import io.github.smaugfm.monobudget.integration.RetriesTest import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.BeforeEach @@ -30,8 +31,16 @@ class JacksonFileStatementRetryRepositoryTest : RetriesTest() { fun `Mono statement item serializes & deserializes correctly`() { val repo = JacksonFileStatementRetryRepository(Paths.get("retries.json")) runBlocking { - val req1 = repo.addRetryRequest(StatementProcessingContext(statementItem1()), Duration.ZERO) - val req2 = repo.addRetryRequest(StatementProcessingContext(statementItem1()), Duration.ZERO) + val req1 = + repo.addRetryRequest( + StatementProcessingContext(exampleStatement1("test")), + Duration.ZERO, + ) + val req2 = + repo.addRetryRequest( + StatementProcessingContext(exampleStatement1("test")), + Duration.ZERO, + ) repo.removeRetryRequest(req1.id) repo.removeRetryRequest(req2.id) assertThat(repo.getAllRequests()).isEmpty() diff --git a/src/test/kotlin/io/github/smaugfm/monobudget/integration/RetriesTest.kt b/src/test/kotlin/io/github/smaugfm/monobudget/integration/RetriesTest.kt index 66d4076..584382d 100644 --- a/src/test/kotlin/io/github/smaugfm/monobudget/integration/RetriesTest.kt +++ b/src/test/kotlin/io/github/smaugfm/monobudget/integration/RetriesTest.kt @@ -3,8 +3,10 @@ package io.github.smaugfm.monobudget.integration import com.elbekd.bot.model.ChatId import io.github.smaugfm.monobank.model.MonoStatementItem import io.github.smaugfm.monobank.model.MonoWebhookResponseData -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext -import io.github.smaugfm.monobudget.integration.TestData.UAH +import io.github.smaugfm.monobudget.TestData.UAH +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext +import io.github.smaugfm.monobudget.integration.util.IntegrationFailConfig +import io.github.smaugfm.monobudget.integration.util.IntegrationTestBase import io.github.smaugfm.monobudget.mono.MonobankWebhookResponseStatementItem import io.mockk.coVerify import io.mockk.confirmVerified @@ -19,7 +21,7 @@ open class RetriesTest : IntegrationTestBase() { @Test open fun `When lunchmoney fails and then recovers transfer transaction is processed correctly`() { val (newTransactionId, newTransactionId2) = - setupTransferMocks( + setupTransferTransactionMocks( listOf( IntegrationFailConfig.CreateTransactionGroup(0..0), ), diff --git a/src/test/kotlin/io/github/smaugfm/monobudget/integration/TestData.kt b/src/test/kotlin/io/github/smaugfm/monobudget/integration/TestData.kt deleted file mode 100644 index 60125e6..0000000 --- a/src/test/kotlin/io/github/smaugfm/monobudget/integration/TestData.kt +++ /dev/null @@ -1,61 +0,0 @@ -package io.github.smaugfm.monobudget.integration - -import io.github.smaugfm.lunchmoney.model.LunchmoneyCategoryMultiple -import java.time.Instant -import java.util.Currency - -object TestData { - val UAH = Currency.getInstance("UAH") - - val categories = - listOf( - LunchmoneyCategoryMultiple( - id = 444443, - name = "Авто", - description = null, - isIncome = false, - excludeFromBudget = false, - excludeFromTotals = false, - updatedAt = Instant.parse("2023-03-26T08:11:06.088Z"), - createdAt = Instant.parse("2023-03-26T08:11:06.088Z"), - isGroup = false, - groupId = null, - ), - LunchmoneyCategoryMultiple( - id = 444444, - name = "Перекази", - description = null, - isIncome = false, - excludeFromBudget = true, - excludeFromTotals = true, - updatedAt = Instant.parse("2023-03-26T08:47:50.132Z"), - createdAt = Instant.parse("2023-03-26T08:47:50.132Z"), - isGroup = false, - groupId = null, - ), - LunchmoneyCategoryMultiple( - id = 444445, - name = "Розваги", - description = null, - isIncome = false, - excludeFromBudget = false, - excludeFromTotals = false, - updatedAt = Instant.parse("2023-03-26T08:11:24.380Z"), - createdAt = Instant.parse("2023-03-26T08:11:24.380Z"), - isGroup = false, - groupId = null, - ), - LunchmoneyCategoryMultiple( - id = 444446, - name = "Транспорт", - description = null, - isIncome = false, - excludeFromBudget = false, - excludeFromTotals = false, - updatedAt = Instant.parse("2023-03-26T08:11:40.931Z"), - createdAt = Instant.parse("2023-03-26T08:11:40.931Z"), - isGroup = false, - groupId = null, - ), - ) -} diff --git a/src/test/kotlin/io/github/smaugfm/monobudget/integration/TransactionsTest.kt b/src/test/kotlin/io/github/smaugfm/monobudget/integration/TransactionsTest.kt index f35b4f5..babf1ef 100644 --- a/src/test/kotlin/io/github/smaugfm/monobudget/integration/TransactionsTest.kt +++ b/src/test/kotlin/io/github/smaugfm/monobudget/integration/TransactionsTest.kt @@ -1,20 +1,17 @@ package io.github.smaugfm.monobudget.integration import com.elbekd.bot.model.ChatId -import io.github.smaugfm.monobank.model.MonoStatementItem -import io.github.smaugfm.monobank.model.MonoWebhookResponseData -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext -import io.github.smaugfm.monobudget.integration.TestData.UAH -import io.github.smaugfm.monobudget.mono.MonobankWebhookResponseStatementItem +import io.github.smaugfm.monobudget.TestData.exampleStatement1 +import io.github.smaugfm.monobudget.TestData.exampleStatement2 +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext +import io.github.smaugfm.monobudget.integration.util.IntegrationTestBase import io.mockk.coVerify import io.mockk.confirmVerified import io.mockk.verifySequence import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay -import kotlinx.datetime.Clock import org.junit.jupiter.api.Test import java.math.BigDecimal -import java.util.UUID @Suppress("LongMethod") class TransactionsTest : IntegrationTestBase(), CoroutineScope { @@ -27,33 +24,13 @@ class TransactionsTest : IntegrationTestBase(), CoroutineScope { @Test fun `Other account transaction triggers transfer`() { - val (newTransactionId, newTransactionId2) = setupTransferMocks { it.amount > BigDecimal.ZERO } + val (newTransactionId, newTransactionId2) = + setupTransferTransactionMocks { it.amount > BigDecimal.ZERO } runTestApplication { webhookStatementsFlow.emit( StatementProcessingContext( - MonobankWebhookResponseStatementItem( - d = - MonoWebhookResponseData( - account = "MONO-EXAMPLE-UAH", - statementItem = - MonoStatementItem( - id = UUID.randomUUID().toString(), - time = Clock.System.now(), - description = "Від: 777777****1234", - mcc = 4829, - originalMcc = 4829, - hold = true, - amount = 5600, - operationAmount = 5600, - currencyCode = UAH, - commissionRate = 0, - cashbackAmount = 0, - balance = 0, - ), - ), - accountCurrency = UAH, - ), + exampleStatement1("Від: 777777****1234"), ), ) @@ -89,60 +66,15 @@ class TransactionsTest : IntegrationTestBase(), CoroutineScope { @Test fun `Mono transfer triggers single and transfer transaction creation`() { - val (newTransactionId, newTransactionId2) = setupTransferMocks { it.amount < BigDecimal.ZERO } + val (newTransactionId, newTransactionId2) = + setupTransferTransactionMocks { it.amount < BigDecimal.ZERO } runTestApplication { webhookStatementsFlow.emit( - StatementProcessingContext( - MonobankWebhookResponseStatementItem( - d = - MonoWebhookResponseData( - account = "MONO-EXAMPLE-UAH2", - statementItem = - MonoStatementItem( - id = UUID.randomUUID().toString(), - time = Clock.System.now(), - description = "test send", - mcc = 4829, - originalMcc = 4829, - hold = true, - amount = -5600, - operationAmount = -5600, - currencyCode = UAH, - commissionRate = 0, - cashbackAmount = 0, - balance = 0, - ), - ), - accountCurrency = UAH, - ), - ), + StatementProcessingContext(exampleStatement2("test send")), ) webhookStatementsFlow.emit( - StatementProcessingContext( - MonobankWebhookResponseStatementItem( - d = - MonoWebhookResponseData( - account = "MONO-EXAMPLE-UAH", - statementItem = - MonoStatementItem( - id = UUID.randomUUID().toString(), - time = Clock.System.now(), - description = "test receive", - mcc = 4829, - originalMcc = 4829, - hold = true, - amount = 5600, - operationAmount = 5600, - currencyCode = UAH, - commissionRate = 0, - cashbackAmount = 0, - balance = 0, - ), - ), - accountCurrency = UAH, - ), - ), + StatementProcessingContext(exampleStatement1("test receive")), ) coVerify(timeout = 1000, exactly = 1) { tgMock.sendMessage( @@ -190,36 +122,11 @@ class TransactionsTest : IntegrationTestBase(), CoroutineScope { val newTransactionId = setupSingleTransactionMocks() runTestApplication { - webhookStatementsFlow.emit( - StatementProcessingContext( - MonobankWebhookResponseStatementItem( - d = - MonoWebhookResponseData( - account = "MONO-EXAMPLE-UAH", - statementItem = - MonoStatementItem( - id = UUID.randomUUID().toString(), - time = Clock.System.now(), - description = "test", - mcc = 4829, - originalMcc = 4829, - hold = true, - amount = -5600, - operationAmount = -5600, - currencyCode = UAH, - commissionRate = 0, - cashbackAmount = 0, - balance = 0, - ), - ), - accountCurrency = UAH, - ), - ), - ) + webhookStatementsFlow.emit(StatementProcessingContext(exampleStatement2("test"))) coVerify(timeout = 1000, exactly = 1) { tgMock.sendMessage( match { - it is ChatId.IntegerId && it.id == 55555555L + it is ChatId.IntegerId && it.id == 55555556L }, any(), any(), diff --git a/src/test/kotlin/io/github/smaugfm/monobudget/integration/FailTrackerTransformation.kt b/src/test/kotlin/io/github/smaugfm/monobudget/integration/util/FailTrackerTransformation.kt similarity index 54% rename from src/test/kotlin/io/github/smaugfm/monobudget/integration/FailTrackerTransformation.kt rename to src/test/kotlin/io/github/smaugfm/monobudget/integration/util/FailTrackerTransformation.kt index 53a4a89..4a76c66 100644 --- a/src/test/kotlin/io/github/smaugfm/monobudget/integration/FailTrackerTransformation.kt +++ b/src/test/kotlin/io/github/smaugfm/monobudget/integration/util/FailTrackerTransformation.kt @@ -1,4 +1,4 @@ -package io.github.smaugfm.monobudget.integration +package io.github.smaugfm.monobudget.integration.util import io.github.smaugfm.lunchmoney.exception.LunchmoneyApiResponseException import io.ktor.http.HttpStatusCode @@ -10,12 +10,6 @@ class FailTrackerTransformation(private val configs: List): Mono = - ( - if (configs.any { it.attemptFailRange.contains(attempt) }) { - Mono.error(LunchmoneyApiResponseException(HttpStatusCode.BadRequest.value)) - } else { - mono - } - ) - .also { attempt++ } + mono.takeIf { configs.all { !it.attemptFailRange.contains(attempt++) } } + ?: Mono.error(LunchmoneyApiResponseException(HttpStatusCode.BadRequest.value)) } diff --git a/src/test/kotlin/io/github/smaugfm/monobudget/integration/IntegrationFailConfig.kt b/src/test/kotlin/io/github/smaugfm/monobudget/integration/util/IntegrationFailConfig.kt similarity index 89% rename from src/test/kotlin/io/github/smaugfm/monobudget/integration/IntegrationFailConfig.kt rename to src/test/kotlin/io/github/smaugfm/monobudget/integration/util/IntegrationFailConfig.kt index b753f88..03407e8 100644 --- a/src/test/kotlin/io/github/smaugfm/monobudget/integration/IntegrationFailConfig.kt +++ b/src/test/kotlin/io/github/smaugfm/monobudget/integration/util/IntegrationFailConfig.kt @@ -1,4 +1,4 @@ -package io.github.smaugfm.monobudget.integration +package io.github.smaugfm.monobudget.integration.util sealed class IntegrationFailConfig(val attemptFailRange: IntRange) { class Update(attemptFailRange: IntRange) : diff --git a/src/test/kotlin/io/github/smaugfm/monobudget/integration/IntegrationTestBase.kt b/src/test/kotlin/io/github/smaugfm/monobudget/integration/util/IntegrationTestBase.kt similarity index 93% rename from src/test/kotlin/io/github/smaugfm/monobudget/integration/IntegrationTestBase.kt rename to src/test/kotlin/io/github/smaugfm/monobudget/integration/util/IntegrationTestBase.kt index dbb5400..b40696a 100644 --- a/src/test/kotlin/io/github/smaugfm/monobudget/integration/IntegrationTestBase.kt +++ b/src/test/kotlin/io/github/smaugfm/monobudget/integration/util/IntegrationTestBase.kt @@ -1,4 +1,4 @@ -package io.github.smaugfm.monobudget.integration +package io.github.smaugfm.monobudget.integration.util import io.github.oshai.kotlinlogging.KotlinLogging import io.github.smaugfm.lunchmoney.api.LunchmoneyApi @@ -8,15 +8,17 @@ import io.github.smaugfm.lunchmoney.model.LunchmoneyTransaction import io.github.smaugfm.lunchmoney.response.LunchmoneyUpdateTransactionResponse import io.github.smaugfm.monobudget.Application import io.github.smaugfm.monobudget.TestBase -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext -import io.github.smaugfm.monobudget.common.misc.PeriodicFetcherFactory +import io.github.smaugfm.monobudget.TestData import io.github.smaugfm.monobudget.common.model.settings.Settings import io.github.smaugfm.monobudget.common.retry.InMemoryStatementRetryRepository import io.github.smaugfm.monobudget.common.retry.StatementRetryRepository +import io.github.smaugfm.monobudget.common.startup.ApplicationStartupVerifier +import io.github.smaugfm.monobudget.common.startup.BudgetSettingsVerifier import io.github.smaugfm.monobudget.common.statement.StatementSource +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext import io.github.smaugfm.monobudget.common.telegram.TelegramApi -import io.github.smaugfm.monobudget.common.verify.ApplicationStartupVerifier -import io.github.smaugfm.monobudget.common.verify.BudgetSettingsVerifier +import io.github.smaugfm.monobudget.common.util.misc.PeriodicFetcherFactory +import io.github.smaugfm.monobudget.integration.TransactionsTest import io.github.smaugfm.monobudget.mono.MonoWebhookListener import io.github.smaugfm.monobudget.mono.MonoWebhookSettings import io.github.smaugfm.monobudget.setupKoinModules @@ -111,10 +113,11 @@ abstract class IntegrationTestBase : TestBase(), CoroutineScope { ) } - protected fun setupTransferMocks(isFirst: (LunchmoneyInsertTransaction) -> Boolean): Pair = - setupTransferMocks(emptyList(), isFirst) + protected fun setupTransferTransactionMocks( + isFirst: (LunchmoneyInsertTransaction) -> Boolean, + ): Pair = setupTransferTransactionMocks(emptyList(), isFirst) - protected fun setupTransferMocks( + protected fun setupTransferTransactionMocks( fails: List, isFirst: (LunchmoneyInsertTransaction) -> Boolean, ): Pair { diff --git a/src/test/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyTransactionCreatorTest.kt b/src/test/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyTransactionCreatorTest.kt index 1a61e47..daabe56 100644 --- a/src/test/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyTransactionCreatorTest.kt +++ b/src/test/kotlin/io/github/smaugfm/monobudget/lunchmoney/LunchmoneyTransactionCreatorTest.kt @@ -8,12 +8,13 @@ import io.github.smaugfm.lunchmoney.model.LunchmoneyTransaction import io.github.smaugfm.lunchmoney.model.enumeration.LunchmoneyTransactionStatus import io.github.smaugfm.lunchmoney.response.LunchmoneyUpdateTransactionResponse import io.github.smaugfm.monobudget.TestBase -import io.github.smaugfm.monobudget.common.account.MaybeTransferStatement -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext -import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingScopeComponent +import io.github.smaugfm.monobudget.TestData.exampleStatement1 +import io.github.smaugfm.monobudget.common.account.MaybeTransfer import io.github.smaugfm.monobudget.common.model.BudgetBackend import io.github.smaugfm.monobudget.common.retry.InMemoryStatementRetryRepository import io.github.smaugfm.monobudget.common.retry.StatementRetryRepository +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingContext +import io.github.smaugfm.monobudget.common.statement.lifecycle.StatementProcessingScopeComponent import io.github.smaugfm.monobudget.common.transaction.NewTransactionFactory import io.mockk.coEvery import io.mockk.every @@ -79,7 +80,7 @@ class LunchmoneyTransactionCreatorTest : TestBase() { declareMock {} runBlocking { val map = mutableMapOf() - val ctx = StatementProcessingContext(statementItem1(), map) + val ctx = StatementProcessingContext(exampleStatement1("test"), map) map["transactionUpdated"] = true map["transactionCreatedId"] = expectedTransactionCreatedId map["transaction"] = expectedTransaction @@ -88,7 +89,7 @@ class LunchmoneyTransactionCreatorTest : TestBase() { val creator = sc.get() assertThat( creator.create( - MaybeTransferStatement.Transfer( + MaybeTransfer.Transfer( sc.ctx.item, expectedTransaction, ), @@ -107,7 +108,7 @@ class LunchmoneyTransactionCreatorTest : TestBase() { } runBlocking { val map = mutableMapOf() - val ctx = StatementProcessingContext(statementItem1(), map) + val ctx = StatementProcessingContext(exampleStatement1("test"), map) map["transactionUpdated"] = true map["transactionCreatedId"] = expectedTransactionCreatedId map["transaction"] = expectedTransaction @@ -115,7 +116,7 @@ class LunchmoneyTransactionCreatorTest : TestBase() { val creator = sc.get() assertThat( creator.create( - MaybeTransferStatement.Transfer( + MaybeTransfer.Transfer( sc.ctx.item, expectedTransaction, ), @@ -146,11 +147,17 @@ class LunchmoneyTransactionCreatorTest : TestBase() { } runBlocking { val map = mutableMapOf() - val sc = StatementProcessingScopeComponent(StatementProcessingContext(statementItem1(), map)) + val sc = + StatementProcessingScopeComponent( + StatementProcessingContext( + exampleStatement1("test"), + map, + ), + ) val creator = sc.get() assertThat( creator.create( - MaybeTransferStatement.Transfer( + MaybeTransfer.Transfer( sc.ctx.item, expectedTransaction, ),