diff --git a/.env.example b/.env.example index 039ddf74..af024b8c 100644 --- a/.env.example +++ b/.env.example @@ -6,8 +6,8 @@ HOSTED=false APP_DOMAIN= # Exchange Rate API -# This is used to convert between different currencies in the app. We're currently using Open Exchange Rates (openexchangerates.org) to sync exchange rate data and, at the moment, that requies a $12/mo subscription. This is NOT required to run Maybe with one currency, but it is required to sync exchange rate data if you need multiple currencies. In the future we'll be adding support for other exchange rate APIs that are cheaper/free. -OPEN_EXCHANGE_APP_ID= +# This is used to convert between different currencies in the app. We use Synth, which is a Maybe product. You can sign up for a free account at synthfinance.com. +SYNTH_API_KEY= # Currency Configuration # A list of currencies that you want to support. This is used to generate the list of currencies that users can select from when creating a new account. diff --git a/app/jobs/daily_exchange_rate_job.rb b/app/jobs/daily_exchange_rate_job.rb index 46592530..8f71afa2 100644 --- a/app/jobs/daily_exchange_rate_job.rb +++ b/app/jobs/daily_exchange_rate_job.rb @@ -2,8 +2,6 @@ class DailyExchangeRateJob < ApplicationJob queue_as :default def perform - app_id = ENV["OPEN_EXCHANGE_APP_ID"] - # Get the last date for which exchange rates were fetched for each currency last_fetched_dates = ExchangeRate.group(:base_currency).maximum(:date) @@ -11,10 +9,12 @@ class DailyExchangeRateJob < ApplicationJob Currency.all.each do |currency| last_fetched_date = last_fetched_dates[currency.iso_code] || Date.yesterday next_day = last_fetched_date + 1.day - response = Faraday.get("https://openexchangerates.org/api/historical/#{next_day}.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(",") + + response = Faraday.get("https://api.synthfinance.com/rates/historical") do |req| + req.headers["Authorization"] = "Bearer #{ENV["SYNTH_API_KEY"]}" + req.params["date"] = next_day.to_s + req.params["from"] = currency.iso_code + req.params["to"] = Currency.where.not(iso_code: currency.iso_code).pluck(:iso_code).join(",") end if response.success? diff --git a/lib/tasks/currencies.rake b/lib/tasks/currencies.rake index d9bbe12c..73a250b8 100644 --- a/lib/tasks/currencies.rake +++ b/lib/tasks/currencies.rake @@ -3,18 +3,18 @@ namespace :currencies do 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" + if currencies.count > 1 && ENV["SYNTH_API_KEY"].present? + url = "https://api.synthfinance.com/currencies" response = Faraday.get(url) do |req| - req.params["app_id"] = ENV["OPEN_EXCHANGE_APP_ID"] + req.headers["Authorization"] = "Bearer #{ENV["SYNTH_API_KEY"]}" end - oxr_currencies = JSON.parse(response.body) + synth_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] + c.name = synth_currencies["data"].find { |currency| currency["iso_code"] == iso_code.downcase }["name"] end end diff --git a/lib/tasks/exchange_rates.rake b/lib/tasks/exchange_rates.rake index ce202ce3..df6a9a41 100644 --- a/lib/tasks/exchange_rates.rake +++ b/lib/tasks/exchange_rates.rake @@ -1,81 +1,26 @@ namespace :exchange_rates do - desc "Fetch exchange rates from openexchangerates.org" + desc "Fetch exchange rates from Synth API" 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 + Currency.all.each do |currency| + (Date.today - 30.days).upto(Date.today) do |date| + response = Faraday.get("https://api.synthfinance.com/rates/historical") do |req| + req.headers["Authorization"] = "Bearer #{ENV["SYNTH_API_KEY"]}" + req.params["date"] = date.to_s + req.params["from"] = currency.iso_code + req.params["to"] = Currency.where.not(iso_code: currency.iso_code).pluck(:iso_code).join(",") 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) + if response.success? + rates = JSON.parse(response.body)["data"]["rates"] - 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 + 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 - else - puts "Failed to fetch exchange rates for period #{start_period} to #{current_end_date}: #{response.status}" + puts "#{currency.iso_code} to #{currency_iso_code} on #{date}: #{value}" end - - start_period = current_end_date + 1.day + else + puts "Failed to fetch exchange rates for #{currency.iso_code} on #{date}: #{response.status}" end end end