Skip to content

Commit

Permalink
migrate from ExchangeRate.Host to Frankfurter
Browse files Browse the repository at this point in the history
  • Loading branch information
mtotschnig committed Sep 27, 2023
1 parent 58295f4 commit 1824c8f
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,17 @@ class ExchangeRateDaoTest {
@Throws(InterruptedException::class)
fun getProductsWhenNoProductInserted() {
runBlocking {
assertNull(exchangeRateDao.getRate("EUR", "USD", localDate, "EXCHANGE_RATE_HOST"))
assertNull(exchangeRateDao.getRate("EUR", "USD", localDate, "FRANKFURTER"))
}
}

@Test
@Throws(InterruptedException::class)
fun getProductsAfterInserted() {
val exchangeRate = ExchangeRate("EUR", "USD", localDate, 1.444, "EXCHANGE_RATE_HOST")
val exchangeRate = ExchangeRate("EUR", "USD", localDate, 1.444, "FRANKFURTER")
runBlocking {
exchangeRateDao.insert(exchangeRate)
assertEquals(1.444, exchangeRateDao.getRate("EUR", "USD", localDate, "EXCHANGE_RATE_HOST"))
assertEquals(1.444, exchangeRateDao.getRate("EUR", "USD", localDate, "FRANKFURTER"))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import org.totschnig.myexpenses.MyApplication
import org.totschnig.myexpenses.preference.PrefHandler
import org.totschnig.myexpenses.provider.ExchangeRateRepository
import org.totschnig.myexpenses.retrofit.CoinApi
import org.totschnig.myexpenses.retrofit.ExchangeRateHost
import org.totschnig.myexpenses.retrofit.ExchangeRateService
import org.totschnig.myexpenses.retrofit.Frankfurter
import org.totschnig.myexpenses.retrofit.OpenExchangeRates
import org.totschnig.myexpenses.retrofit.RoadmapService
import org.totschnig.myexpenses.room.ExchangeRateDatabase.Companion.getDatabase
Expand Down Expand Up @@ -112,16 +112,16 @@ open class NetworkModule {
@JvmStatic
@Provides
@Singleton
fun provideExchangeRateHost(
fun provideFrankfurter(
builder: OkHttpClient.Builder,
converterFactory: GsonConverterFactory
): ExchangeRateHost {
): Frankfurter {
val retrofit = Retrofit.Builder()
.baseUrl("https://api.exchangerate.host/")
.baseUrl("https://api.frankfurter.app/")
.addConverterFactory(converterFactory)
.client(builder.build())
.build()
return retrofit.create(ExchangeRateHost::class.java)
return retrofit.create(Frankfurter::class.java)
}

@JvmStatic
Expand Down Expand Up @@ -161,7 +161,7 @@ open class NetworkModule {
@Provides
@Singleton
fun provideExchangeRateService(
api1: ExchangeRateHost,
api1: Frankfurter,
api2: OpenExchangeRates,
api3: CoinApi
) = ExchangeRateService(api1, api2, api3)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import org.totschnig.myexpenses.preference.PrefKey
import retrofit2.HttpException
import retrofit2.await
import java.io.IOException
import java.lang.UnsupportedOperationException
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneId
Expand All @@ -23,30 +24,67 @@ sealed class ExchangeRateSource(val id: String, val host: String) {

companion object {

val values = arrayOf(ExchangeRateHost, OpenExchangeRates, CoinApi)
val values = arrayOf(Frankfurter, OpenExchangeRates, CoinApi)

fun preferredSource(prefHandler: PrefHandler) =
preferredSource(prefHandler.getString(PrefKey.EXCHANGE_RATE_PROVIDER, null))

fun preferredSource(preferenceValue: String?) =
values.firstOrNull { it.id == preferenceValue } ?: ExchangeRateHost
values.firstOrNull { it.id == preferenceValue } ?: Frankfurter
}

object ExchangeRateHost : ExchangeRateSource("EXCHANGE_RATE_HOST", "exchangerate.host") {
override fun extractError(body: ResponseBody) = body.string().takeIf { it.isNotEmpty() }
data object Frankfurter : ExchangeRateSource("FRANKFURTER", "api.frankfurter.app") {

val SUPPORTED_CURRENCIES = arrayOf(
"AUD",
"BGN",
"BRL",
"CAD",
"CHF",
"CNY",
"CZK",
"DKK",
"EUR",
"GBP",
"HKD",
"HUF",
"IDR",
"ILS",
"INR",
"ISK",
"JPY",
"KRW",
"MXN",
"MYR",
"NOK",
"NZD",
"PHP",
"PLN",
"RON",
"SEK",
"SGD",
"THB",
"TRY",
"USD",
"ZAR"
)

override fun extractError(body: ResponseBody): String =
JSONObject(body.string()).getString("message")

}

sealed class SourceWithApiKey(
val prefKey: PrefKey,
host: String,
id: String
): ExchangeRateSource(id, host) {
) : ExchangeRateSource(id, host) {
fun requireApiKey(prefHandler: PrefHandler): String =
prefHandler.getString(prefKey)
?: throw MissingApiKeyException(this)
}

object OpenExchangeRates : SourceWithApiKey(
data object OpenExchangeRates : SourceWithApiKey(
prefKey = PrefKey.OPEN_EXCHANGE_RATES_APP_ID,
host = "openexchangerates.com",
id = "OPENEXCHANGERATES"
Expand All @@ -55,7 +93,7 @@ sealed class ExchangeRateSource(val id: String, val host: String) {
JSONObject(body.string()).getString("description")
}

object CoinApi : SourceWithApiKey(
data object CoinApi : SourceWithApiKey(
prefKey = PrefKey.COIN_API_API_KEY,
host = "coinapi.io",
id = "COIN_API"
Expand All @@ -69,7 +107,7 @@ class MissingApiKeyException(val source: ExchangeRateSource.SourceWithApiKey) :
java.lang.IllegalStateException("${source.prefKey.name} not configured")

class ExchangeRateService(
private val exchangeRateHost: @NotNull ExchangeRateHost,
private val frankfurter: @NotNull Frankfurter,
private val openExchangeRates: @NotNull OpenExchangeRates,
private val coinApi: @NotNull CoinApi
) {
Expand All @@ -81,18 +119,19 @@ class ExchangeRateService(
base: String
): Pair<LocalDate, Double> = try {
when (source) {
ExchangeRateSource.ExchangeRateHost -> {
val today = LocalDate.now()
if (date < today) {
val result: ExchangeRateHost.TimeSeriesResult = exchangeRateHost.getTimeSeries(date, date, symbol, base).await()
result.rates[date]?.get(symbol)?.let {
date to it
} ?: throw IOException("Unable to retrieve data")
} else {
val result = exchangeRateHost.getLatest(symbol, base).await()
ExchangeRateSource.Frankfurter -> {
if (symbol in ExchangeRateSource.Frankfurter.SUPPORTED_CURRENCIES && base in ExchangeRateSource.Frankfurter.SUPPORTED_CURRENCIES) {
val today = LocalDate.now()
val (dateOfResult, result) = if (date < today) {
date to frankfurter.getHistorical(date, symbol, base).await()
} else {
today to frankfurter.getLatest(symbol, base).await()
}
result.rates[symbol]?.let {
today to it
dateOfResult to it
} ?: throw IOException("Unable to retrieve data")
} else {
throw UnsupportedOperationException()
}
}

Expand All @@ -118,7 +157,12 @@ class ExchangeRateService(
if (date < today) {
val call = coinApi.getHistory(base, symbol, date, date.plusDays(1), apiKey)
val result = call.await().first()
date to arrayOf(result.rate_close, result.rate_high, result.rate_low, result.rate_close).average()
date to arrayOf(
result.rate_close,
result.rate_high,
result.rate_low,
result.rate_close
).average()
} else {
val call = coinApi.getExchangeRate(base, symbol, apiKey)
val result = call.await()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.totschnig.myexpenses.retrofit

import androidx.annotation.Keep
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Query
import java.time.LocalDate

interface Frankfurter {
@GET("latest")
fun getLatest(
@Query("to") symbol: String,
@Query("from") base: String
): Call<Result>

@GET("{date}")
fun getHistorical(
@Path("date") date: LocalDate,
@Query("to") symbol: String,
@Query("from") base: String
): Call<Result>

@Keep
data class Result(val rates: Map<String, Double>)
}
2 changes: 1 addition & 1 deletion myExpenses/src/main/res/xml/preferences_exchange_rate.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
android:title="@string/pref_category_exchange_rates">
<org.totschnig.myexpenses.preference.HeaderPreference />
<ListPreference
android:defaultValue="EXCHANGE_RATE_HOST"
android:defaultValue="FRANKFURTER"
android:key="@string/pref_exchange_rate_provider_key"
android:summary="@string/pref_exchange_rate_provider_summary"
android:title="@string/pref_exchange_rate_provider_title" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ class ExchangeRateServiceTest {
}

@Test
fun exchangeRateHostIsAlive() {
fun frankfurterIsAlive() {
runBlocking {
val rate = service.getRate(ExchangeRateSource.ExchangeRateHost, null, date, "USD", "EUR")
val rate = service.getRate(ExchangeRateSource.Frankfurter, null, date, "USD", "EUR")
Truth.assertThat(rate.first).isEqualTo(date)
println(rate.second.toString())
}
Expand Down

0 comments on commit 1824c8f

Please sign in to comment.