-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
19 changed files
with
338 additions
and
144 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,5 +7,6 @@ out/ | |
|
||
settings*.json | ||
settings*.yml | ||
import-config*.yml | ||
retries.json | ||
docker-compose.yml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
54 changes: 8 additions & 46 deletions
54
src/main/kotlin/io/github/smaugfm/monobudget/Application.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,71 +1,33 @@ | ||
package io.github.smaugfm.monobudget | ||
|
||
import io.github.oshai.kotlinlogging.KotlinLogging | ||
import io.github.smaugfm.monobudget.common.exception.BudgetBackendException | ||
import io.github.smaugfm.monobudget.common.BaseApplication | ||
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 kotlinx.coroutines.flow.asFlow | ||
import kotlinx.coroutines.flow.collect | ||
import kotlinx.coroutines.flow.filter | ||
import kotlinx.coroutines.flow.flatMapMerge | ||
import kotlinx.coroutines.flow.map | ||
import kotlinx.coroutines.flow.onEach | ||
import org.koin.core.component.KoinComponent | ||
import org.koin.core.component.inject | ||
import kotlin.system.exitProcess | ||
|
||
private val log = KotlinLogging.logger {} | ||
|
||
class Application<TTransaction, TNewTransaction> : | ||
KoinComponent { | ||
class Application<TTransaction, TNewTransaction>(statementSources: List<StatementSource>) : | ||
BaseApplication<TTransaction, TNewTransaction>(statementSources) { | ||
private val telegramApi by inject<TelegramApi>() | ||
private val statementSources by injectAll<StatementSource>() | ||
private val startupVerifiers by injectAll<ApplicationStartupVerifier>() | ||
private val telegramCallbackHandler by inject<TelegramCallbackHandler<TTransaction>>() | ||
private val statementEvents by inject<StatementEvents>() | ||
|
||
suspend fun run() { | ||
runStartupChecks() | ||
|
||
statementSources.forEach { it.prepare() } | ||
|
||
telegramApi.start(telegramCallbackHandler::handle) | ||
log.info { "Started application" } | ||
|
||
statementSources.asFlow() | ||
.flatMapMerge { it.statements() } | ||
.filter(statementEvents::onNewStatement) | ||
.map(::StatementProcessingScopeComponent) | ||
.onEach { | ||
with(it) { | ||
try { | ||
scope.get<StatementItemProcessor<TTransaction, TNewTransaction>>() | ||
.process() | ||
statementEvents.onStatementEnd(ctx) | ||
} catch (e: BudgetBackendException) { | ||
statementEvents.onStatementRetry(ctx, e) | ||
} catch (e: Throwable) { | ||
statementEvents.onStatementError(ctx, e) | ||
} finally { | ||
scope.close() | ||
} | ||
} | ||
} | ||
.collect() | ||
} | ||
|
||
private suspend fun runStartupChecks() { | ||
override suspend fun beforeStart() { | ||
try { | ||
startupVerifiers.forEach { it.verify() } | ||
} catch (e: Throwable) { | ||
log.error(e) { "Failed to start application. Exiting..." } | ||
exitProcess(1) | ||
} | ||
} | ||
|
||
override suspend fun afterSourcesPrepare() { | ||
telegramApi.start(telegramCallbackHandler::handle) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
src/main/kotlin/io/github/smaugfm/monobudget/common/BaseApplication.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package io.github.smaugfm.monobudget.common | ||
|
||
import io.github.oshai.kotlinlogging.KotlinLogging | ||
import io.github.smaugfm.monobudget.common.exception.BudgetBackendException | ||
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 kotlinx.coroutines.flow.asFlow | ||
import kotlinx.coroutines.flow.collect | ||
import kotlinx.coroutines.flow.filter | ||
import kotlinx.coroutines.flow.flatMapMerge | ||
import kotlinx.coroutines.flow.map | ||
import kotlinx.coroutines.flow.onEach | ||
import org.koin.core.component.KoinComponent | ||
import org.koin.core.component.inject | ||
|
||
private val log = KotlinLogging.logger {} | ||
|
||
open class BaseApplication<TTransaction, TNewTransaction>( | ||
private val statementSources: List<StatementSource> | ||
) : KoinComponent { | ||
|
||
private val statementEvents by inject<StatementEvents>() | ||
|
||
suspend fun run() { | ||
beforeStart() | ||
|
||
statementSources.forEach { it.prepare() } | ||
|
||
afterSourcesPrepare() | ||
|
||
log.info { "Started application" } | ||
|
||
statementSources.asFlow() | ||
.flatMapMerge { it.statements() } | ||
.filter(statementEvents::onNewStatement) | ||
.map(::StatementProcessingScopeComponent) | ||
.onEach { | ||
with(it) { | ||
try { | ||
scope.get<StatementItemProcessor<TTransaction, TNewTransaction>>() | ||
.process() | ||
statementEvents.onStatementEnd(ctx) | ||
} catch (e: BudgetBackendException) { | ||
statementEvents.onStatementRetry(ctx, e) | ||
} catch (e: Throwable) { | ||
statementEvents.onStatementError(ctx, e) | ||
} finally { | ||
scope.close() | ||
} | ||
} | ||
} | ||
.collect() | ||
} | ||
|
||
protected open suspend fun beforeStart() { | ||
} | ||
|
||
protected open suspend fun afterSourcesPrepare() { | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 26 additions & 0 deletions
26
src/main/kotlin/io/github/smaugfm/monobudget/importer/CsvMonoItem.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package io.github.smaugfm.monobudget.importer | ||
|
||
import io.github.smaugfm.monobudget.common.model.financial.BankAccountId | ||
import io.github.smaugfm.monobudget.common.model.serializer.CurrencyAsStringSerializer | ||
import kotlinx.datetime.Instant | ||
import kotlinx.serialization.Serializable | ||
import java.util.Currency | ||
|
||
@Serializable | ||
internal data class CsvMonoItem( | ||
@Serializable(MonobankInstantSerializer::class) | ||
val date: Instant, | ||
val description: String, | ||
val mcc: Int, | ||
val cardCurrencyAmount: Double, | ||
val transactionCurrencyAmount: Double, | ||
@Serializable(CurrencyAsStringSerializer::class) | ||
val currency: Currency, | ||
val exchangeRate: Double?, | ||
val cardCurrencyCommissionAmount: Double?, | ||
val cardCurrencyCashbackAmount: Double?, | ||
val balance: Double?, | ||
) { | ||
fun toStatementItem(accountId: BankAccountId, accountCurrency: Currency) = | ||
ImportStatementItem(this, accountId, accountCurrency) | ||
} |
8 changes: 8 additions & 0 deletions
8
src/main/kotlin/io/github/smaugfm/monobudget/importer/ImportAccountConfig.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package io.github.smaugfm.monobudget.importer | ||
|
||
import io.github.smaugfm.monobudget.common.model.financial.BankAccountId | ||
|
||
data class ImportAccountConfig( | ||
val accountAlias: String, | ||
val transactionsFileContent: String | ||
) |
53 changes: 53 additions & 0 deletions
53
src/main/kotlin/io/github/smaugfm/monobudget/importer/ImportApplication.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package io.github.smaugfm.monobudget.importer | ||
|
||
import io.github.oshai.kotlinlogging.KotlinLogging | ||
import io.github.smaugfm.lunchmoney.model.LunchmoneyInsertTransaction | ||
import io.github.smaugfm.lunchmoney.model.LunchmoneyTransaction | ||
import io.github.smaugfm.monobudget.common.BaseApplication | ||
import io.github.smaugfm.monobudget.common.model.settings.Settings | ||
import io.github.smaugfm.monobudget.common.retry.InMemoryStatementRetryRepository | ||
import io.github.smaugfm.monobudget.lunchmoney.LunchmoneyNewTransactionFactory | ||
import io.github.smaugfm.monobudget.lunchmoney.LunchmoneyTransferCache | ||
import io.github.smaugfm.monobudget.mono.MonoWebhookSettings | ||
import io.github.smaugfm.monobudget.setupKoinModules | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.datetime.Clock | ||
import org.koin.core.context.startKoin | ||
import org.koin.dsl.module | ||
import java.net.URI | ||
import java.nio.file.Paths | ||
import kotlin.time.Duration.Companion.seconds | ||
|
||
private val log = KotlinLogging.logger {} | ||
|
||
class ImportApplication(source: ImportStatementSource) : | ||
BaseApplication<LunchmoneyTransaction, LunchmoneyInsertTransaction>(listOf(source)) { | ||
|
||
companion object { | ||
suspend fun main(coroutineScope: CoroutineScope) { | ||
val settings = Settings.load( | ||
Paths.get(System.getenv()["SETTINGS_FILE"] ?: "settings.yml") | ||
) | ||
val importConfig = ImportConfig.load( | ||
Paths.get(System.getenv()["IMPORT_CONFIG_FILE"] ?: "import-config.yml") | ||
) | ||
val noteSuffix = " monobudget-import-${Clock.System.now()}" | ||
log.info { "Inserting with note suffix: \"$noteSuffix\"" } | ||
|
||
startKoin { | ||
setupKoinModules( | ||
coroutineScope, | ||
InMemoryStatementRetryRepository(), | ||
settings, | ||
MonoWebhookSettings(false, URI.create("none://none"), 0) | ||
) | ||
modules(module { | ||
single { LunchmoneyNewTransactionFactory(noteSuffix) } | ||
single { LunchmoneyTransferCache(Long.MAX_VALUE.seconds) } | ||
}) | ||
}.koin | ||
|
||
ImportApplication(ImportStatementSource(importConfig.getImports())).run() | ||
} | ||
} | ||
} |
30 changes: 30 additions & 0 deletions
30
src/main/kotlin/io/github/smaugfm/monobudget/importer/ImportConfig.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package io.github.smaugfm.monobudget.importer | ||
|
||
import com.charleskorn.kaml.Yaml | ||
import com.charleskorn.kaml.YamlConfiguration | ||
import io.github.oshai.kotlinlogging.KotlinLogging | ||
import kotlinx.serialization.Serializable | ||
import kotlinx.serialization.decodeFromString | ||
import java.io.File | ||
import java.nio.file.Path | ||
|
||
private val log = KotlinLogging.logger {} | ||
|
||
@Serializable | ||
data class ImportConfig( | ||
private val imports: Map<String, String> | ||
) { | ||
fun getImports() = | ||
imports.entries.map { ImportAccountConfig(it.key, it.value) } | ||
|
||
companion object { | ||
fun load(path: Path): ImportConfig = load(File(path.toString()).readText()) | ||
|
||
private fun load(content: String): ImportConfig = | ||
Yaml(configuration = YamlConfiguration(strictMode = false)) | ||
.decodeFromString<ImportConfig>(content) | ||
.also { | ||
log.debug { "Loaded import-config: $it" } | ||
} | ||
} | ||
} |
28 changes: 28 additions & 0 deletions
28
src/main/kotlin/io/github/smaugfm/monobudget/importer/ImportStatementItem.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package io.github.smaugfm.monobudget.importer | ||
|
||
import io.github.smaugfm.monobudget.common.model.financial.Amount | ||
import io.github.smaugfm.monobudget.common.model.financial.BankAccountId | ||
import io.github.smaugfm.monobudget.common.model.financial.StatementItem | ||
import java.util.Currency | ||
import java.util.UUID | ||
|
||
internal data class ImportStatementItem( | ||
val csv: CsvMonoItem, | ||
override val accountId: BankAccountId, | ||
val accountCurrency: Currency | ||
) : StatementItem { | ||
override val id = UUID.randomUUID().toString() | ||
override val time = csv.date | ||
override val description = csv.description | ||
override val comment = null | ||
override val mcc = csv.mcc | ||
override val amount = Amount.fromLunchmoneyAmount( | ||
csv.cardCurrencyAmount, | ||
accountCurrency | ||
) | ||
override val operationAmount = Amount.fromLunchmoneyAmount( | ||
csv.transactionCurrencyAmount, | ||
csv.currency | ||
) | ||
override val currency = csv.currency | ||
} |
Oops, something went wrong.