Add exchange rate service with caching and fallback API
ExchangeRateService fetches rates from fawazahmed0/exchange-api with Frankfurter API as fallback. Rates are cached in SQLite for 24 hours. Includes response parsers, 30 supported currencies, and 8 unit tests covering parsing, caching, and same-currency identity.
This commit is contained in:
@@ -387,6 +387,39 @@ impl Database {
|
||||
rows.collect()
|
||||
}
|
||||
|
||||
// -- Exchange Rates --
|
||||
|
||||
pub fn get_cached_rate(&self, base: &str, target: &str) -> SqlResult<Option<ExchangeRate>> {
|
||||
match self.conn.query_row(
|
||||
"SELECT base, target, rate, fetched_at FROM exchange_rates WHERE base = ?1 AND target = ?2",
|
||||
params![base.to_lowercase(), target.to_lowercase()],
|
||||
|row| {
|
||||
Ok(ExchangeRate {
|
||||
base: row.get(0)?,
|
||||
target: row.get(1)?,
|
||||
rate: row.get(2)?,
|
||||
fetched_at: row.get(3)?,
|
||||
})
|
||||
},
|
||||
) {
|
||||
Ok(rate) => Ok(Some(rate)),
|
||||
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cache_rates(&self, base: &str, rates: &std::collections::HashMap<String, f64>) -> SqlResult<()> {
|
||||
let now = chrono::Utc::now().to_rfc3339();
|
||||
let base_lower = base.to_lowercase();
|
||||
let mut stmt = self.conn.prepare(
|
||||
"INSERT OR REPLACE INTO exchange_rates (base, target, rate, fetched_at) VALUES (?1, ?2, ?3, ?4)"
|
||||
)?;
|
||||
for (target, rate) in rates {
|
||||
stmt.execute(params![base_lower, target.to_lowercase(), rate, now])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// -- Settings --
|
||||
|
||||
pub fn get_setting(&self, key: &str) -> SqlResult<Option<String>> {
|
||||
|
||||
Reference in New Issue
Block a user