70 lines
2 KiB
Ruby
70 lines
2 KiB
Ruby
|
|
# frozen_string_literal: true
|
||
|
|
|
||
|
|
require "net/http"
|
||
|
|
require "oj"
|
||
|
|
|
||
|
|
require "provider/adapters/adapter"
|
||
|
|
|
||
|
|
class Provider
|
||
|
|
module Adapters
|
||
|
|
# Bank Al-Maghrib. Publishes daily mid-market rates for ~30 currencies against MAD.
|
||
|
|
# Older data (pre-2016) lacks a mid-rate field; the adapter averages buy/sell.
|
||
|
|
class BAM < Adapter
|
||
|
|
URL = "https://api.centralbankofmorocco.ma/cours/Version1/api/CoursVirement"
|
||
|
|
class << self
|
||
|
|
def api_key = ENV["BAM_API_KEY"] || raise(Adapter::Unavailable, "no API key")
|
||
|
|
|
||
|
|
def backfill_range = 7
|
||
|
|
end
|
||
|
|
|
||
|
|
def fetch(after: nil, upto: nil)
|
||
|
|
end_date = upto || Date.today
|
||
|
|
|
||
|
|
(after..end_date).each_with_object([]) do |date, dataset|
|
||
|
|
next if date.saturday? || date.sunday?
|
||
|
|
|
||
|
|
dataset.concat(fetch_date(date))
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
def parse(json)
|
||
|
|
data = json.is_a?(String) ? Oj.load(json, mode: :strict) : json
|
||
|
|
return [] unless data.is_a?(Array)
|
||
|
|
|
||
|
|
data.filter_map do |record|
|
||
|
|
code = record["libDevise"]
|
||
|
|
next unless code&.match?(/\A[A-Z]{3}\z/)
|
||
|
|
|
||
|
|
date = Date.parse(record["date"])
|
||
|
|
mid = record["moyen"]&.to_f || ((record["achat"].to_f + record["vente"].to_f) / 2).round(4)
|
||
|
|
unite = record["uniteDevise"].to_f
|
||
|
|
next if mid.zero? || unite.zero?
|
||
|
|
|
||
|
|
{ date:, base: code, quote: "MAD", rate: mid / unite }
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
private
|
||
|
|
|
||
|
|
def fetch_date(date)
|
||
|
|
uri = URI(URL)
|
||
|
|
uri.query = URI.encode_www_form(date: "#{date.strftime("%Y-%m-%d")}T12:30:00")
|
||
|
|
request = Net::HTTP::Get.new(uri)
|
||
|
|
request["Ocp-Apim-Subscription-Key"] = self.class.api_key
|
||
|
|
|
||
|
|
loop do
|
||
|
|
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }
|
||
|
|
|
||
|
|
if response.is_a?(Net::HTTPTooManyRequests)
|
||
|
|
sleep(response["retry-after"].to_i)
|
||
|
|
else
|
||
|
|
sleep(1)
|
||
|
|
response.value
|
||
|
|
return parse(response.body)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|