1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-07-25 08:09:38 +02:00

Multi-currency support (#425)

* Initial foundational pass at multi-currency

* Default format currency

* More work on currency and exchanging

* Re-build currencies on change

* Currency import/setup

* Background job overhaul + cheaper OXR plan support

* Lint fixes

* Test fixes

* Multi-currency setup instructions

* Allow decimals in the balance field

* Spacing fix for form

---------

Signed-off-by: Josh Pigford <josh@joshpigford.com>
This commit is contained in:
Josh Pigford 2024-02-10 16:18:56 -06:00 committed by GitHub
parent 94f7b4ea8f
commit aa351ae616
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 634 additions and 176 deletions

28
lib/tasks/currencies.rake Normal file
View file

@ -0,0 +1,28 @@
namespace :currencies do
desc "Seed Currencies"
task seed: :environment do
currencies = ENV["CURRENCIES"].split(",")
if currencies.count > 1 && ENV["OPEN_EXCHANGE_APP_ID"].present?
url = "https://openexchangerates.org/api/currencies.json"
response = Faraday.get(url) do |req|
req.params["app_id"] = ENV["OPEN_EXCHANGE_APP_ID"]
end
oxr_currencies = JSON.parse(response.body)
currencies.each do |iso_code|
Currency.find_or_create_by(iso_code: iso_code) do |c|
c.name = oxr_currencies[iso_code]
end
end
puts "Currencies created: #{Currency.count}"
elsif currencies.count == 1
Currency.find_or_create_by(iso_code: currencies.first)
else
puts "No currencies found in ENV['CURRENCIES']"
end
end
end

View file

@ -0,0 +1,83 @@
namespace :exchange_rates do
desc "Fetch exchange rates from openexchangerates.org"
task sync: :environment do
app_id = ENV["OPEN_EXCHANGE_APP_ID"]
MININUM_DATE_RANGE = 30.days
MAXIMUM_DATE_RANGE = 120.days
# First, check what plan the user is on at OXR
# If the user is on the Developer plan, we can only fetch exchange rates for the past 30 days and must use the historical endpoint
account_check = Faraday.get("https://openexchangerates.org/api/usage.json") do |req|
req.params["app_id"] = app_id
end
account_details = JSON.parse(account_check.body)
quota_limit = account_details["data"]["usage"]["requests_quota"]
if quota_limit <= 10000
# First loop through all Currency records and fetch exchange rates for each, but use the historical endpoint, which only allows fetching one day at a time. For each currency, set the base currency to the currency's iso_code and the symbols to all other currencies' iso_codes
Currency.all.each do |currency|
(Date.today - MININUM_DATE_RANGE).upto(Date.today) do |date|
response = Faraday.get("https://openexchangerates.org/api/historical/#{date}.json") do |req|
req.params["app_id"] = app_id
req.params["base"] = currency.iso_code
req.params["symbols"] = Currency.where.not(iso_code: currency.iso_code).pluck(:iso_code).join(",")
end
if response.success?
rates = JSON.parse(response.body)["rates"]
rates.each do |currency_iso_code, value|
ExchangeRate.find_or_create_by(date: date, base_currency: currency.iso_code, converted_currency: currency_iso_code) do |exchange_rate|
exchange_rate.rate = value
end
puts "#{currency.iso_code} to #{currency_iso_code} on #{date}: #{value}"
end
else
puts "Failed to fetch exchange rates for #{currency.iso_code} on #{date}: #{response.status}"
end
end
end
else
# Use Faraday to make a request openexchangerates.org time series endpoint
# Use the response to create or update exchange rates in the database
url = "https://openexchangerates.org/api/time-series.json"
start_date = (Date.today - MAXIMUM_DATE_RANGE).to_s
end_date = Date.today.to_s
# Loop through all Currency records and fetch exchange rates for each
Currency.all.each do |currency|
start_period = Date.parse(start_date)
end_period = Date.parse(end_date)
while start_period < end_period
current_end_date = [ start_period + 30.days, end_period ].min
response = Faraday.get(url) do |req|
req.params["app_id"] = app_id
req.params["start"] = start_period.to_s
req.params["end"] = current_end_date.to_s
req.params["base"] = currency.iso_code
req.params["symbols"] = Currency.where.not(iso_code: currency.iso_code).pluck(:iso_code).join(",")
end
if response.success?
rates = JSON.parse(response.body)["rates"]
rates.each do |date, rate|
rate.each do |currency_iso_code, value|
ExchangeRate.find_or_create_by(date: date, base_currency: currency.iso_code, converted_currency: currency_iso_code) do |exchange_rate|
exchange_rate.rate = value
end
puts "#{currency.iso_code} to #{currency_iso_code} on #{date}: #{value}"
end
end
else
puts "Failed to fetch exchange rates for period #{start_period} to #{current_end_date}: #{response.status}"
end
start_period = current_end_date + 1.day
end
end
end
end
end