Skip to content

Commit

Permalink
Fix Jackson serialization, MonoApis definitions, other scope issues
Browse files Browse the repository at this point in the history
  • Loading branch information
smaugfm committed Nov 27, 2023
1 parent 69c0558 commit b9d5c14
Show file tree
Hide file tree
Showing 11 changed files with 105 additions and 56 deletions.
2 changes: 1 addition & 1 deletion src/main/kotlin/io/github/smaugfm/monobudget/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ private fun runtimeModule(
single { settings.retry }
single { apiRetry() }
settings.accounts.settings.filterIsInstance<MonoAccountSettings>()
.forEach { s -> single { MonoApi(s.token, s.accountId) } }
.forEach { s -> single(StringQualifier(s.alias)) { MonoApi(s.token, s.accountId, s.alias) } }
settings.transfer.forEach { s ->
single(qualifier = StringQualifier(s.descriptionRegex.pattern)) { s }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ abstract class StatementItemProcessor<TTransaction, TNewTransaction>(
transferDetector.checkTransfer()

val transaction = transactionFactory.create(maybeTransfer)
val message = messageFormatter.format(transaction)
val message = messageFormatter.format(ctx.item, transaction)

telegramMessageSender.send(ctx.item.accountId, message)
}
Expand All @@ -42,7 +42,7 @@ abstract class StatementItemProcessor<TTransaction, TNewTransaction>(
this.pp()
} else {
"\tAmount: ${amount}\n" +
"\tDescription: $description" +
"\tDescription: $description\n" +
"\tMemo: $comment"
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
package io.github.smaugfm.monobudget.common.retry

import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.module.SimpleModule
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 kotlinx.datetime.Instant
import java.nio.file.Path
import java.nio.file.StandardOpenOption
import java.util.UUID
Expand All @@ -17,11 +25,17 @@ import kotlin.time.Duration
class JacksonFileStatementRetryRepository(
private val path: Path,
) : StatementRetryRepository {
val objectMapper =
internal val objectMapper =
jsonMapper {
enable(SerializationFeature.INDENT_OUTPUT)
addModule(kotlinModule())
addModule(JavaTimeModule())
addModule(
SimpleModule().also {
it.addSerializer(KotlinxTimeInstantJacksonSerializer())
it.addDeserializer(Instant::class.java, KotlinxTimeInstantJacksonDeserializer())
},
)
}

override suspend fun addRetryRequest(
Expand Down Expand Up @@ -50,7 +64,25 @@ class JacksonFileStatementRetryRepository(
path.writeText(
objectMapper.writeValueAsString(list),
Charsets.UTF_8,
StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE
StandardOpenOption.TRUNCATE_EXISTING,
StandardOpenOption.CREATE,
)
}

private class KotlinxTimeInstantJacksonSerializer : StdSerializer<Instant>(Instant::class.java) {
override fun serialize(
value: Instant?,
gen: JsonGenerator?,
provider: SerializerProvider?,
) {
gen?.writeString(value?.toString())
}
}

private class KotlinxTimeInstantJacksonDeserializer : StdDeserializer<Instant>(Instant::class.java) {
override fun deserialize(
p: JsonParser?,
ctxt: DeserializationContext?,
): Instant = Instant.parse(p?.valueAsString!!)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.smaugfm.monobudget.common.retry

import com.fasterxml.jackson.annotation.JsonIgnore
import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
Expand All @@ -16,5 +17,7 @@ data class StatementRetryRequest(
Clock.System.now() + retryIn,
)

val retryIn get() = (retryAt - Clock.System.now()).coerceAtLeast(Duration.ZERO)
val retryIn
@JsonIgnore
get() = (retryAt - Clock.System.now()).coerceAtLeast(Duration.ZERO)
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class TelegramApi(
allowSendingWithoutReply = null,
replyMarkup = replyMarkup,
).also {
log.debug { "Sending message. \n\tTo: $chatId\n\ttext: $text\n\tkeyboard: ${replyMarkup?.pp()}" }
log.debug { "Sent message. \n\tTo: $chatId\n\ttext: $text\n\tkeyboard: ${replyMarkup?.pp()}" }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@ import kotlin.math.roundToLong

private val log = KotlinLogging.logger {}

abstract class TransactionMessageFormatter<TTransaction>(
protected val statementItem: StatementItem,
) : KoinComponent {
abstract class TransactionMessageFormatter<TTransaction> : KoinComponent {
private val bankAccounts: BankAccountService by inject()

suspend fun format(transaction: TTransaction): MessageWithReplyKeyboard {
suspend fun format(
statementItem: StatementItem,
transaction: TTransaction,
): MessageWithReplyKeyboard {
val msg =
formatHTMLStatementMessage(
statementItem,
bankAccounts.getAccountCurrency(statementItem.accountId)!!,
transaction,
)
Expand Down Expand Up @@ -53,6 +55,7 @@ abstract class TransactionMessageFormatter<TTransaction>(
protected abstract fun getReplyKeyboard(pressed: PressedButtons): InlineKeyboardMarkup

protected abstract suspend fun formatHTMLStatementMessage(
statementItem: StatementItem,
accountCurrency: Currency,
transaction: TTransaction,
): String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,24 @@ 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.lifecycle.StatementProcessingContext
import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingScopeComponent
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.formatW
import io.github.smaugfm.monobudget.common.util.replaceNewLines
import io.github.smaugfm.monobudget.common.util.toLocalDateTime
import kotlinx.datetime.Clock
import kotlinx.datetime.LocalDate
import org.koin.core.annotation.Scope
import org.koin.core.annotation.Scoped
import org.koin.core.annotation.Single
import java.util.Currency

@Scoped
@Scope(StatementProcessingScopeComponent::class)
@Single
class LunchmoneyTransactionMessageFormatter(
private val categoryService: CategoryService,
private val ctx: StatementProcessingContext,
) : TransactionMessageFormatter<LunchmoneyTransaction>(ctx.item) {
) : TransactionMessageFormatter<LunchmoneyTransaction>() {
private val shouldNotifyStatuses =
setOf(
LunchmoneyTransactionStatus.UNCLEARED,
Expand All @@ -34,6 +30,7 @@ class LunchmoneyTransactionMessageFormatter(
)

override suspend fun formatHTMLStatementMessage(
statementItem: StatementItem,
accountCurrency: Currency,
transaction: LunchmoneyTransaction,
): String {
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/io/github/smaugfm/monobudget/mono/MonoApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import java.net.URI

private val log = KotlinLogging.logger {}

class MonoApi(token: String, val accountId: BankAccountId) {
class MonoApi(token: String, val accountId: BankAccountId, private val alias: String) {
init {
require(token.isNotBlank())
}
Expand All @@ -37,7 +37,7 @@ class MonoApi(token: String, val accountId: BankAccountId) {
get(url.path) {
call.response.status(HttpStatusCode.OK)
call.respondText("OK\n", ContentType.Text.Plain)
log.info { "Webhook setup successful: $url" }
log.info { "Webhook setup for $alias successful: $url" }
waitForWebhook.complete(Unit)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ 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.lifecycle.StatementProcessingScopeComponent
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
Expand All @@ -11,17 +10,15 @@ import io.github.smaugfm.monobudget.common.transaction.TransactionMessageFormatt
import io.github.smaugfm.monobudget.common.util.replaceNewLines
import io.github.smaugfm.monobudget.ynab.model.YnabCleared
import io.github.smaugfm.monobudget.ynab.model.YnabTransactionDetail
import org.koin.core.annotation.Scope
import org.koin.core.annotation.Scoped
import org.koin.core.annotation.Single
import java.util.Currency

@Scoped
@Scope(StatementProcessingScopeComponent::class)
@Single
class YnabTransactionMessageFormatter(
private val categoryService: CategoryService,
statementItem: StatementItem,
) : TransactionMessageFormatter<YnabTransactionDetail>(statementItem) {
) : TransactionMessageFormatter<YnabTransactionDetail>() {
override suspend fun formatHTMLStatementMessage(
statementItem: StatementItem,
accountCurrency: Currency,
transaction: YnabTransactionDetail,
): String {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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.integration.RetriesTest
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.koin.core.KoinApplication
import org.koin.dsl.bind
import org.koin.dsl.module
import java.nio.file.Files
import java.nio.file.Paths
import kotlin.time.Duration

class JacksonFileStatementRetryRepositoryTest : RetriesTest() {
private val repo = JacksonFileStatementRetryRepository(Paths.get("retries.json"))

override fun testKoinApplication(app: KoinApplication) {
super.testKoinApplication(app)
app.modules(
module {
single { repo } bind StatementRetryRepository::class
},
)
}

@Test
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)
repo.removeRetryRequest(req1.id)
repo.removeRetryRequest(req2.id)
assertThat(repo.getAllRequests()).isEmpty()
}
}

@BeforeEach
fun deleteFile() {
Files.deleteIfExists(Paths.get("retries.json"))
}
}

This file was deleted.

0 comments on commit b9d5c14

Please sign in to comment.