From f55d4e32c0e84d47731877fe0e52d1221ba36890 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 28 Jun 2018 07:32:58 +0200 Subject: [PATCH] Implement currency exchange rate API. --- app/Api/V1/Controllers/BudgetController.php | 4 +- .../V1/Controllers/BudgetLimitController.php | 6 +- app/Api/V1/Controllers/CategoryController.php | 10 +- .../Controllers/ConfigurationController.php | 1 + .../CurrencyExchangeRateController.php | 115 ++++++++++++++++++ .../Controllers/Json/ExchangeController.php | 2 +- app/Models/CurrencyExchangeRate.php | 9 ++ app/Models/TransactionCurrency.php | 1 + .../Currency/CurrencyRepository.php | 10 +- .../Currency/CurrencyRepositoryInterface.php | 6 +- .../CurrencyExchangeRateTransformer.php | 99 +++++++++++++++ routes/api.php | 9 ++ 12 files changed, 255 insertions(+), 17 deletions(-) create mode 100644 app/Api/V1/Controllers/CurrencyExchangeRateController.php create mode 100644 app/Transformers/CurrencyExchangeRateTransformer.php diff --git a/app/Api/V1/Controllers/BudgetController.php b/app/Api/V1/Controllers/BudgetController.php index 5b153f5027..d2a2ba2041 100644 --- a/app/Api/V1/Controllers/BudgetController.php +++ b/app/Api/V1/Controllers/BudgetController.php @@ -162,12 +162,12 @@ class BudgetController extends Controller public function update(BudgetRequest $request, Budget $budget): JsonResponse { $data = $request->getAll(); - $bill = $this->repository->update($budget, $data); + $budget = $this->repository->update($budget, $data); $manager = new Manager(); $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); - $resource = new Item($bill, new BudgetTransformer($this->parameters), 'budgets'); + $resource = new Item($budget, new BudgetTransformer($this->parameters), 'budgets'); return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); diff --git a/app/Api/V1/Controllers/BudgetLimitController.php b/app/Api/V1/Controllers/BudgetLimitController.php index 70c452157d..891ae9f098 100644 --- a/app/Api/V1/Controllers/BudgetLimitController.php +++ b/app/Api/V1/Controllers/BudgetLimitController.php @@ -191,9 +191,9 @@ class BudgetLimitController extends Controller throw new FireflyException('Unknown budget.'); } $data['budget'] = $budget; - $budgetLimit = $this->repository->storeBudgetLimit($data); - $manager = new Manager; - $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $budgetLimit = $this->repository->storeBudgetLimit($data); + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); $resource = new Item($budgetLimit, new BudgetLimitTransformer($this->parameters), 'budget_limits'); diff --git a/app/Api/V1/Controllers/CategoryController.php b/app/Api/V1/Controllers/CategoryController.php index 672b044ea0..fc33a6fe16 100644 --- a/app/Api/V1/Controllers/CategoryController.php +++ b/app/Api/V1/Controllers/CategoryController.php @@ -161,13 +161,13 @@ class CategoryController extends Controller */ public function update(CategoryRequest $request, Category $category): JsonResponse { - $data = $request->getAll(); - $bill = $this->repository->update($category, $data); - $manager = new Manager(); - $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $data = $request->getAll(); + $category = $this->repository->update($category, $data); + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); - $resource = new Item($bill, new CategoryTransformer($this->parameters), 'categories'); + $resource = new Item($category, new CategoryTransformer($this->parameters), 'categories'); return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); diff --git a/app/Api/V1/Controllers/ConfigurationController.php b/app/Api/V1/Controllers/ConfigurationController.php index 40c019f943..9bedd0a459 100644 --- a/app/Api/V1/Controllers/ConfigurationController.php +++ b/app/Api/V1/Controllers/ConfigurationController.php @@ -50,6 +50,7 @@ class ConfigurationController extends Controller /** * @param Request $request * + * @return JsonResponse * @throws FireflyException */ public function update(Request $request): JsonResponse diff --git a/app/Api/V1/Controllers/CurrencyExchangeRateController.php b/app/Api/V1/Controllers/CurrencyExchangeRateController.php new file mode 100644 index 0000000000..54d8672b0e --- /dev/null +++ b/app/Api/V1/Controllers/CurrencyExchangeRateController.php @@ -0,0 +1,115 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Services\Currency\ExchangeRateInterface; +use FireflyIII\Transformers\CurrencyExchangeRateTransformer; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use InvalidArgumentException; +use League\Fractal\Manager; +use League\Fractal\Resource\Item; +use Log; + +/** + * + * Class CurrencyExchangeRateController + */ +class CurrencyExchangeRateController extends Controller +{ + /** @var CurrencyRepositoryInterface */ + private $repository; + + /** + * CurrencyExchangeRateController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + $this->repository = app(CurrencyRepositoryInterface::class); + $this->repository->setUser(auth()->user()); + + return $next($request); + } + ); + + } + + /** + * @param Request $request + * + * @return JsonResponse + * @throws FireflyException + */ + public function index(Request $request): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // currencies + $fromCurrency = $this->repository->findByCodeNull($request->get('from') ?? 'EUR'); + $toCurrency = $this->repository->findByCodeNull($request->get('to') ?? 'USD'); + + if (null === $fromCurrency) { + throw new FireflyException('Unknown source currency.'); + } + if (null === $toCurrency) { + throw new FireflyException('Unknown destination currency.'); + } + + $dateObj = new Carbon; + try { + $dateObj = Carbon::createFromFormat('Y-m-d', $request->get('date') ?? date('Y-m-d')); + } catch (InvalidArgumentException $e) { + Log::debug($e->getMessage()); + } + + + $this->parameters->set('from', $fromCurrency->code); + $this->parameters->set('to', $toCurrency->code); + $this->parameters->set('date', $dateObj->format('Y-m-d')); + + // get the exchange rate. + $rate = $this->repository->getExchangeRate($fromCurrency, $toCurrency, $dateObj); + if (null === $rate) { + // create service: + /** @var ExchangeRateInterface $service */ + $service = app(ExchangeRateInterface::class); + $service->setUser(auth()->user()); + + // get rate: + $rate = $service->getRate($fromCurrency, $toCurrency, $dateObj); + } + + $resource = new Item($rate, new CurrencyExchangeRateTransformer($this->parameters), 'currency_exchange_rates'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Json/ExchangeController.php b/app/Http/Controllers/Json/ExchangeController.php index e4846f43f7..34b8eeddd6 100644 --- a/app/Http/Controllers/Json/ExchangeController.php +++ b/app/Http/Controllers/Json/ExchangeController.php @@ -50,7 +50,7 @@ class ExchangeController extends Controller $rate = $repository->getExchangeRate($fromCurrency, $toCurrency, $date); - if (null === $rate->id) { + if (null === $rate) { Log::debug(sprintf('No cached exchange rate in database for %s to %s on %s', $fromCurrency->code, $toCurrency->code, $date->format('Y-m-d'))); // create service: diff --git a/app/Models/CurrencyExchangeRate.php b/app/Models/CurrencyExchangeRate.php index 7701cb9fff..6f06cc8862 100644 --- a/app/Models/CurrencyExchangeRate.php +++ b/app/Models/CurrencyExchangeRate.php @@ -22,12 +22,21 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * Class CurrencyExchange. + * + * @property int $id + * @property Carbon $created_at + * @property Carbon $updated_at + * @property TransactionCurrency $fromCurrency + * @property TransactionCurrency $toCurrency + * @property float $rate + * */ class CurrencyExchangeRate extends Model { diff --git a/app/Models/TransactionCurrency.php b/app/Models/TransactionCurrency.php index e54c7513a6..5948f38811 100644 --- a/app/Models/TransactionCurrency.php +++ b/app/Models/TransactionCurrency.php @@ -32,6 +32,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property string $code * @property string $symbol * @property int $decimal_places + * @property int $id * */ class TransactionCurrency extends Model diff --git a/app/Repositories/Currency/CurrencyRepository.php b/app/Repositories/Currency/CurrencyRepository.php index 4258432124..f754c1bf0a 100644 --- a/app/Repositories/Currency/CurrencyRepository.php +++ b/app/Repositories/Currency/CurrencyRepository.php @@ -265,13 +265,15 @@ class CurrencyRepository implements CurrencyRepositoryInterface } /** + * Get currency exchange rate. + * * @param TransactionCurrency $fromCurrency * @param TransactionCurrency $toCurrency * @param Carbon $date * - * @return CurrencyExchangeRate + * @return CurrencyExchangeRate|null */ - public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate + public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): ?CurrencyExchangeRate { if ($fromCurrency->id === $toCurrency->id) { $rate = new CurrencyExchangeRate; @@ -280,7 +282,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface return $rate; } - + /** @var CurrencyExchangeRate $rate */ $rate = $this->user->currencyExchangeRates() ->where('from_currency_id', $fromCurrency->id) ->where('to_currency_id', $toCurrency->id) @@ -291,7 +293,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface return $rate; } - return new CurrencyExchangeRate; + return null; } /** diff --git a/app/Repositories/Currency/CurrencyRepositoryInterface.php b/app/Repositories/Currency/CurrencyRepositoryInterface.php index 6f1be847d7..72f12a0188 100644 --- a/app/Repositories/Currency/CurrencyRepositoryInterface.php +++ b/app/Repositories/Currency/CurrencyRepositoryInterface.php @@ -154,13 +154,15 @@ interface CurrencyRepositoryInterface public function getCurrencyByPreference(Preference $preference): TransactionCurrency; /** + * Get currency exchange rate. + * * @param TransactionCurrency $fromCurrency * @param TransactionCurrency $toCurrency * @param Carbon $date * - * @return CurrencyExchangeRate + * @return CurrencyExchangeRate|null */ - public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate; + public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): ?CurrencyExchangeRate; /** * @param User $user diff --git a/app/Transformers/CurrencyExchangeRateTransformer.php b/app/Transformers/CurrencyExchangeRateTransformer.php new file mode 100644 index 0000000000..228ef4dbbb --- /dev/null +++ b/app/Transformers/CurrencyExchangeRateTransformer.php @@ -0,0 +1,99 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use FireflyIII\Models\CurrencyExchangeRate; +use League\Fractal\Resource\Item; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +class CurrencyExchangeRateTransformer extends TransformerAbstract +{ + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = ['from_currency', 'to_currency']; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = ['from_currency', 'to_currency']; + + /** @var ParameterBag */ + protected $parameters; + + /** + * PiggyBankEventTransformer constructor. + * + * @codeCoverageIgnore + * + * @param ParameterBag $parameters + */ + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + /** + * @param CurrencyExchangeRate $rate + * + * @return Item + */ + public function includeFromCurrency(CurrencyExchangeRate $rate): Item + { + return $this->item($rate->fromCurrency, new CurrencyTransformer($this->parameters), 'transaction_currencies'); + } + + /** + * @param CurrencyExchangeRate $rate + * + * @return \League\Fractal\Resource\Item + */ + public function includeToCurrency(CurrencyExchangeRate $rate): Item + { + return $this->item($rate->toCurrency, new CurrencyTransformer($this->parameters), 'transaction_currencies'); + } + + public function transform(CurrencyExchangeRate $rate): array + { + $data = [ + 'id' => (int)$rate->id, + 'updated_at' => $rate->updated_at->toAtomString(), + 'created_at' => $rate->created_at->toAtomString(), + 'rate' => (float)$rate->rate, + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/currency_exchange_rates/' . $rate->id, + ], + ], + ]; + + return $data; + } +} \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 24a9fab273..fb49221135 100644 --- a/routes/api.php +++ b/routes/api.php @@ -134,6 +134,15 @@ Route::group( } ); +Route::group( + ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'cer', 'as' => 'api.v1.cer.'], + function () { + + // Currency Exchange Rate API routes: + Route::get('', ['uses' => 'CurrencyExchangeRateController@index', 'as' => 'index']); + } +); + Route::group( ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'currencies', 'as' => 'api.v1.currencies.'], function () {