From 729483102e86110b65613a50cdd353d50ad981c7 Mon Sep 17 00:00:00 2001 From: Luca Bognolo Date: Tue, 1 Jan 2019 16:32:22 +0100 Subject: [PATCH] Implemented multiple Currency Exchange Rates sites engine Signed-off-by: Luca Bognolo --- .env.docker | 10 +- .env.example | 10 +- .env.heroku | 10 +- .env.sandstorm | 10 +- .env.testing | 10 +- app/Providers/FireflyServiceProvider.php | 6 +- app/Services/Currency/RatesApiIOv1.php | 125 +++++++++++++++++++++++ config/firefly.php | 7 ++ 8 files changed, 177 insertions(+), 11 deletions(-) create mode 100644 app/Services/Currency/RatesApiIOv1.php diff --git a/.env.docker b/.env.docker index 7267344004..414a0c2578 100644 --- a/.env.docker +++ b/.env.docker @@ -93,8 +93,14 @@ SEND_REPORT_JOURNALS=${SEND_REPORT_JOURNALS} # Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places. MAPBOX_API_KEY=${MAPBOX_API_KEY} -# Set a Fixer IO API key here (see https://fixer.io) to enable live currency exchange rates. -# Please note that this will only work for paid fixer.io accounts because they severly limited +# Firefly III currently supports two provider for live Currency Exchange Rates: +# "fixer" is the default (for backward compatibility), and "ratesapi" is the new one. +# RatesApi.IO (see https://ratesapi.io) is a FREE and OPEN SOURCE live currency exchange rates, +# built compatible with Fixer.IO, based on data published by European Central Bank, and don't require API key. +CER_PROVIDER=${CER_PROVIDER} +# If you have select "fixer" as default currency exchange rates, +# set a Fixer IO API key here (see https://fixer.io) to enable live currency exchange rates. +# Please note that this WILL ONLY WORK FOR PAID fixer.io accounts because they severely limited # the free API up to the point where you might as well offer nothing. FIXER_API_KEY=${FIXER_API_KEY} diff --git a/.env.example b/.env.example index a626fb6648..64d243c2e3 100644 --- a/.env.example +++ b/.env.example @@ -93,8 +93,14 @@ SEND_REPORT_JOURNALS=true # Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places. MAPBOX_API_KEY= -# Set a Fixer IO API key here (see https://fixer.io) to enable live currency exchange rates. -# Please note that this will only work for paid fixer.io accounts because they severly limited +# Firefly III currently supports two provider for live Currency Exchange Rates: +# "fixer" is the default (for backward compatibility), and "ratesapi" is the new one. +# RatesApi.IO (see https://ratesapi.io) is a FREE and OPEN SOURCE live currency exchange rates, +# built compatible with Fixer.IO, based on data published by European Central Bank, and don't require API key. +CER_PROVIDER=fixer +# If you have select "fixer" as default currency exchange rates, +# set a Fixer IO API key here (see https://fixer.io) to enable live currency exchange rates. +# Please note that this WILL ONLY WORK FOR PAID fixer.io accounts because they severely limited # the free API up to the point where you might as well offer nothing. FIXER_API_KEY= diff --git a/.env.heroku b/.env.heroku index 91265ca0b9..483635cfca 100644 --- a/.env.heroku +++ b/.env.heroku @@ -93,8 +93,14 @@ SEND_REPORT_JOURNALS=true # Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places. MAPBOX_API_KEY= -# Set a Fixer IO API key here (see https://fixer.io) to enable live currency exchange rates. -# Please note that this will only work for paid fixer.io accounts because they severly limited +# Firefly III currently supports two provider for live Currency Exchange Rates: +# "fixer" is the default (for backward compatibility), and "ratesapi" is the new one. +# RatesApi.IO (see https://ratesapi.io) is a FREE and OPEN SOURCE live currency exchange rates, +# built compatible with Fixer.IO, based on data published by European Central Bank, and don't require API key. +CER_PROVIDER=fixer +# If you have select "fixer" as default currency exchange rates, +# set a Fixer IO API key here (see https://fixer.io) to enable live currency exchange rates. +# Please note that this WILL ONLY WORK FOR PAID fixer.io accounts because they severely limited # the free API up to the point where you might as well offer nothing. FIXER_API_KEY= diff --git a/.env.sandstorm b/.env.sandstorm index e562d8fa06..92aa14ff50 100755 --- a/.env.sandstorm +++ b/.env.sandstorm @@ -93,8 +93,14 @@ SEND_REPORT_JOURNALS=true # Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places. MAPBOX_API_KEY= -# Set a Fixer IO API key here (see https://fixer.io) to enable live currency exchange rates. -# Please note that this will only work for paid fixer.io accounts because they severly limited +# Firefly III currently supports two provider for live Currency Exchange Rates: +# "fixer" is the default (for backward compatibility), and "ratesapi" is the new one. +# RatesApi.IO (see https://ratesapi.io) is a FREE and OPEN SOURCE live currency exchange rates, +# built compatible with Fixer.IO, based on data published by European Central Bank, and don't require API key. +CER_PROVIDER=fixer +# If you have select "fixer" as default currency exchange rates, +# set a Fixer IO API key here (see https://fixer.io) to enable live currency exchange rates. +# Please note that this WILL ONLY WORK FOR PAID fixer.io accounts because they severely limited # the free API up to the point where you might as well offer nothing. FIXER_API_KEY= diff --git a/.env.testing b/.env.testing index 0ac26d6891..353bdac8c3 100644 --- a/.env.testing +++ b/.env.testing @@ -93,8 +93,14 @@ SEND_REPORT_JOURNALS=true # Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places. MAPBOX_API_KEY= -# Set a Fixer IO API key here (see https://fixer.io) to enable live currency exchange rates. -# Please note that this will only work for paid fixer.io accounts because they severly limited +# Firefly III currently supports two provider for live Currency Exchange Rates: +# "fixer" is the default (for backward compatibility), and "ratesapi" is the new one. +# RatesApi.IO (see https://ratesapi.io) is a FREE and OPEN SOURCE live currency exchange rates, +# built compatible with Fixer.IO, based on data published by European Central Bank, and don't require API key. +CER_PROVIDER=fixer +# If you have select "fixer" as default currency exchange rates, +# set a Fixer IO API key here (see https://fixer.io) to enable live currency exchange rates. +# Please note that this WILL ONLY WORK FOR PAID fixer.io accounts because they severely limited # the free API up to the point where you might as well offer nothing. FIXER_API_KEY= diff --git a/app/Providers/FireflyServiceProvider.php b/app/Providers/FireflyServiceProvider.php index 396c2589ad..07b9dc9ced 100644 --- a/app/Providers/FireflyServiceProvider.php +++ b/app/Providers/FireflyServiceProvider.php @@ -185,7 +185,11 @@ class FireflyServiceProvider extends ServiceProvider $this->app->bind(FiscalHelperInterface::class, FiscalHelper::class); $this->app->bind(BalanceReportHelperInterface::class, BalanceReportHelper::class); $this->app->bind(BudgetReportHelperInterface::class, BudgetReportHelper::class); - $this->app->bind(ExchangeRateInterface::class, FixerIOv2::class); + $class = (string)config(sprintf('firefly.cer_providers.%s', (string)config('firefly.cer_provider'))); + if('' === $class) { + throw new FireflyException('Invalid currency exchange rate provider. Cannot continue.'); + } + $this->app->bind(ExchangeRateInterface::class, $class); // password verifier thing $this->app->bind(Verifier::class, PwndVerifierV2::class); diff --git a/app/Services/Currency/RatesApiIOv1.php b/app/Services/Currency/RatesApiIOv1.php new file mode 100644 index 0000000000..444dca7826 --- /dev/null +++ b/app/Services/Currency/RatesApiIOv1.php @@ -0,0 +1,125 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Services\Currency; + +use Carbon\Carbon; +use Exception; +use FireflyIII\Models\CurrencyExchangeRate; +use FireflyIII\Models\TransactionCurrency; +use FireflyIII\User; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; +use Log; + +/** + * Class RatesApiIOv1. + */ +class RatesApiIOv1 implements ExchangeRateInterface +{ + /** @var User */ + protected $user; + + /** + * Constructor. + */ + public function __construct() + { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + } + } + + /** + * @param TransactionCurrency $fromCurrency + * @param TransactionCurrency $toCurrency + * @param Carbon $date + * + * @return CurrencyExchangeRate + */ + public function getRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate + { + // create new exchange rate with default values. + $rate = 0; + $exchangeRate = new CurrencyExchangeRate; + $exchangeRate->user()->associate($this->user); + $exchangeRate->fromCurrency()->associate($fromCurrency); + $exchangeRate->toCurrency()->associate($toCurrency); + $exchangeRate->date = $date; + $exchangeRate->rate = $rate; + $exchangeRate->updated_at = new Carbon; + $exchangeRate->created_at = new Carbon; + + // build URI + $uri = sprintf( + 'https://ratesapi.io/api/%s?base=%s&symbols=%s', + $date->format('Y-m-d'), $fromCurrency->code, $toCurrency->code + ); + Log::debug(sprintf('Going to request exchange rate using URI %s', $uri)); + $client = new Client; + try { + $res = $client->request('GET', $uri); + $statusCode = $res->getStatusCode(); + $body = $res->getBody()->getContents(); + } catch (GuzzleException|Exception $e) { + // don't care about error + $body = sprintf('Guzzle exception: %s', $e->getMessage()); + $statusCode = 500; + } + Log::debug(sprintf('Result status code is %d', $statusCode)); + Log::debug(sprintf('Result body is: %s', $body)); + + $content = null; + if (200 !== $statusCode) { + Log::error(sprintf('Something went wrong. Received error code %d and body "%s" from RatesApiIO.', $statusCode, $body)); + } + $success = false; + // get rate from body: + if (200 === $statusCode) { + $content = json_decode($body, true); + $success = true; + } + if (null !== $content && true === $success) { + $code = $toCurrency->code; + $rate = (float)($content['rates'][$code] ?? 0); + Log::debug('Got the following rates from RatesApi: ', $content['rates'] ?? []); + } + + $exchangeRate->rate = $rate; + if (0.0 !== $rate) { + Log::debug('Rate is not zero, save it!'); + $exchangeRate->save(); + } + + return $exchangeRate; + } + + /** + * @param User $user + * + * @return mixed|void + */ + public function setUser(User $user) + { + $this->user = $user; + } +} diff --git a/config/firefly.php b/config/firefly.php index 5012d2be74..6e44f6d94f 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -24,6 +24,8 @@ declare(strict_types=1); use FireflyIII\Export\Exporter\CsvExporter; +use FireflyIII\Services\Currency\FixerIOv2; +use FireflyIII\Services\Currency\RatesApiIOv1; use FireflyIII\TransactionRules\Actions\AddTag; use FireflyIII\TransactionRules\Actions\AppendDescription; use FireflyIII\TransactionRules\Actions\AppendNotes; @@ -111,6 +113,7 @@ return [ 'analytics_id' => env('ANALYTICS_ID', ''), 'disable_frame_header' => env('DISABLE_FRAME_HEADER', false), 'login_provider' => envNonEmpty('LOGIN_PROVIDER', 'eloquent'), + 'cer_provider' => envNonEmpty('CER_PROVIDER', 'fixer'), 'allowedMimes' => [ /* plain files */ 'text/plain', @@ -479,4 +482,8 @@ return [ 'search_modifiers' => ['amount_is', 'amount', 'amount_max', 'amount_min', 'amount_less', 'amount_more', 'source', 'destination', 'category', 'budget', 'bill', 'type', 'date', 'date_before', 'date_after', 'on', 'before', 'after'], // tag notes has_attachments + 'cer_providers' => [ + 'fixer' => FixerIOv2::class, + 'ratesapi' => RatesApiIOv1::class, + ], ];