mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
Allow account endpoint to be filtered on various fields.
This commit is contained in:
parent
c8646e20cb
commit
b213148ae8
@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\JsonApi\V2\Accounts;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Rules\Account\IsValidAccountType;
|
||||
use FireflyIII\Rules\IsAllowedGroupAction;
|
||||
use FireflyIII\Rules\IsDateOrTime;
|
||||
use FireflyIII\Rules\IsValidDateRange;
|
||||
@ -20,6 +21,7 @@ class AccountCollectionQuery extends ResourceQuery
|
||||
public function rules(): array
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
$validFilters = config('api.valid_api_filters')[Account::class];
|
||||
|
||||
return [
|
||||
'fields' => [
|
||||
@ -47,7 +49,8 @@ class AccountCollectionQuery extends ResourceQuery
|
||||
'filter' => [
|
||||
'nullable',
|
||||
'array',
|
||||
JsonApiRule::filter(),
|
||||
JsonApiRule::filter($validFilters),
|
||||
new IsValidAccountType()
|
||||
],
|
||||
'include' => [
|
||||
'nullable',
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace FireflyIII\JsonApi\V2\Accounts;
|
||||
|
||||
use FireflyIII\Rules\BelongsUser;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Validation\Rule;
|
||||
use LaravelJsonApi\Laravel\Http\Requests\ResourceRequest;
|
||||
use LaravelJsonApi\Validation\Rule as JsonApiRule;
|
||||
@ -16,8 +18,17 @@ class AccountRequest extends ResourceRequest
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
die('am i used');
|
||||
return [
|
||||
// @TODO
|
||||
'type' => [
|
||||
new BelongsUser()
|
||||
],
|
||||
'name' => [
|
||||
'nullable',
|
||||
'string',
|
||||
'max:255',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -79,11 +79,13 @@ class AccountSchema extends Schema
|
||||
*/
|
||||
public function filters(): array
|
||||
{
|
||||
// Log::debug(__METHOD__);
|
||||
|
||||
return [
|
||||
Filter::make('id'),
|
||||
];
|
||||
Log::debug(__METHOD__);
|
||||
$array = [];
|
||||
$config = config('api.valid_api_filters')[Account::class];
|
||||
foreach ($config as $entry) {
|
||||
$array[] = Filter::make($entry);
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
public function repository(): AccountRepository
|
||||
|
@ -23,6 +23,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V2\Accounts\Capabilities;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Support\Http\Api\AccountFilter;
|
||||
use FireflyIII\Support\JsonApi\CollectsCustomParameters;
|
||||
use FireflyIII\Support\JsonApi\Concerns\UsergroupAware;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
|
||||
@ -44,6 +46,7 @@ class AccountQuery extends QueryAll implements HasPagination
|
||||
use UsergroupAware;
|
||||
use ValidateSortParameters;
|
||||
use CollectsCustomParameters;
|
||||
use AccountFilter;
|
||||
|
||||
#[\Override]
|
||||
/**
|
||||
@ -56,6 +59,7 @@ class AccountQuery extends QueryAll implements HasPagination
|
||||
Log::debug(__METHOD__);
|
||||
// collect filters
|
||||
$filters = $this->queryParameters->filter();
|
||||
|
||||
// collect sort options
|
||||
$sort = $this->queryParameters->sortFields();
|
||||
// collect pagination based on the page
|
||||
@ -77,7 +81,7 @@ class AccountQuery extends QueryAll implements HasPagination
|
||||
|
||||
// add sort and filter parameters to the query.
|
||||
$query = $this->addSortParams($query, $sort);
|
||||
$query = $this->addFilterParams('account', $query, $filters);
|
||||
$query = $this->addFilterParams(Account::class, $query, $filters);
|
||||
|
||||
|
||||
// collect the result.
|
||||
|
@ -25,6 +25,7 @@ namespace FireflyIII\Policies;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class AccountPolicy
|
||||
{
|
||||
@ -33,7 +34,7 @@ class AccountPolicy
|
||||
*/
|
||||
public function view(User $user, Account $account): bool
|
||||
{
|
||||
die('OK');
|
||||
die('OK1');
|
||||
return true;
|
||||
|
||||
return auth()->check() && $user->id === $account->user_id;
|
||||
@ -46,9 +47,7 @@ class AccountPolicy
|
||||
*/
|
||||
public function viewAny(): bool
|
||||
{
|
||||
die('OK');
|
||||
return true;
|
||||
|
||||
Log::debug(__METHOD__);
|
||||
return auth()->check();
|
||||
}
|
||||
|
||||
|
57
app/Rules/Account/IsValidAccountType.php
Normal file
57
app/Rules/Account/IsValidAccountType.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/*
|
||||
* IsValidAccountType.php
|
||||
* Copyright (c) 2024 james@firefly-iii.org.
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Rules\Account;
|
||||
|
||||
use Closure;
|
||||
use FireflyIII\Support\Http\Api\AccountFilter;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
|
||||
class IsValidAccountType implements ValidationRule
|
||||
{
|
||||
use AccountFilter;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
#[\Override] public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
|
||||
if (!is_array($value)) {
|
||||
$value = [$value];
|
||||
}
|
||||
// only check the type.
|
||||
if (array_key_exists('type', $value)) {
|
||||
$value = $value['type'];
|
||||
$filtered = [];
|
||||
$keys = array_keys($this->types);
|
||||
/** @var mixed $entry */
|
||||
foreach ($value as $entry) {
|
||||
$entry = (string) $entry;
|
||||
if (!in_array($entry, $keys)) {
|
||||
$fail('something');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -30,63 +30,62 @@ use FireflyIII\Models\AccountType;
|
||||
*/
|
||||
trait AccountFilter
|
||||
{
|
||||
protected array $types = [
|
||||
'all' => [
|
||||
AccountType::DEFAULT,
|
||||
AccountType::CASH,
|
||||
AccountType::ASSET,
|
||||
AccountType::EXPENSE,
|
||||
AccountType::REVENUE,
|
||||
AccountType::INITIAL_BALANCE,
|
||||
AccountType::BENEFICIARY,
|
||||
AccountType::IMPORT,
|
||||
AccountType::RECONCILIATION,
|
||||
AccountType::LOAN,
|
||||
AccountType::DEBT,
|
||||
AccountType::MORTGAGE,
|
||||
],
|
||||
'asset' => [AccountType::DEFAULT, AccountType::ASSET],
|
||||
'cash' => [AccountType::CASH],
|
||||
'expense' => [AccountType::EXPENSE, AccountType::BENEFICIARY],
|
||||
'revenue' => [AccountType::REVENUE],
|
||||
'special' => [AccountType::CASH, AccountType::INITIAL_BALANCE, AccountType::IMPORT, AccountType::RECONCILIATION],
|
||||
'hidden' => [AccountType::INITIAL_BALANCE, AccountType::IMPORT, AccountType::RECONCILIATION],
|
||||
'liability' => [AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD],
|
||||
'liabilities' => [AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD],
|
||||
AccountType::DEFAULT => [AccountType::DEFAULT],
|
||||
AccountType::CASH => [AccountType::CASH],
|
||||
AccountType::ASSET => [AccountType::ASSET],
|
||||
AccountType::EXPENSE => [AccountType::EXPENSE],
|
||||
AccountType::REVENUE => [AccountType::REVENUE],
|
||||
AccountType::INITIAL_BALANCE => [AccountType::INITIAL_BALANCE],
|
||||
AccountType::BENEFICIARY => [AccountType::BENEFICIARY],
|
||||
AccountType::IMPORT => [AccountType::IMPORT],
|
||||
AccountType::RECONCILIATION => [AccountType::RECONCILIATION],
|
||||
AccountType::LOAN => [AccountType::LOAN],
|
||||
AccountType::MORTGAGE => [AccountType::MORTGAGE],
|
||||
AccountType::DEBT => [AccountType::DEBT],
|
||||
AccountType::CREDITCARD => [AccountType::CREDITCARD],
|
||||
'default account' => [AccountType::DEFAULT],
|
||||
'cash account' => [AccountType::CASH],
|
||||
'asset account' => [AccountType::ASSET],
|
||||
'expense account' => [AccountType::EXPENSE],
|
||||
'revenue account' => [AccountType::REVENUE],
|
||||
'initial balance account' => [AccountType::INITIAL_BALANCE],
|
||||
'reconciliation' => [AccountType::RECONCILIATION],
|
||||
'loan' => [AccountType::LOAN],
|
||||
'mortgage' => [AccountType::MORTGAGE],
|
||||
'debt' => [AccountType::DEBT],
|
||||
'credit card' => [AccountType::CREDITCARD],
|
||||
'credit-card' => [AccountType::CREDITCARD],
|
||||
'creditcard' => [AccountType::CREDITCARD],
|
||||
'cc' => [AccountType::CREDITCARD],
|
||||
];
|
||||
/**
|
||||
* All the available types.
|
||||
*/
|
||||
protected function mapAccountTypes(string $type): array
|
||||
{
|
||||
$types = [
|
||||
'all' => [
|
||||
AccountType::DEFAULT,
|
||||
AccountType::CASH,
|
||||
AccountType::ASSET,
|
||||
AccountType::EXPENSE,
|
||||
AccountType::REVENUE,
|
||||
AccountType::INITIAL_BALANCE,
|
||||
AccountType::BENEFICIARY,
|
||||
AccountType::IMPORT,
|
||||
AccountType::RECONCILIATION,
|
||||
AccountType::LOAN,
|
||||
AccountType::DEBT,
|
||||
AccountType::MORTGAGE,
|
||||
],
|
||||
'asset' => [AccountType::DEFAULT, AccountType::ASSET],
|
||||
'cash' => [AccountType::CASH],
|
||||
'expense' => [AccountType::EXPENSE, AccountType::BENEFICIARY],
|
||||
'revenue' => [AccountType::REVENUE],
|
||||
'special' => [AccountType::CASH, AccountType::INITIAL_BALANCE, AccountType::IMPORT, AccountType::RECONCILIATION],
|
||||
'hidden' => [AccountType::INITIAL_BALANCE, AccountType::IMPORT, AccountType::RECONCILIATION],
|
||||
'liability' => [AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD],
|
||||
'liabilities' => [AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD],
|
||||
AccountType::DEFAULT => [AccountType::DEFAULT],
|
||||
AccountType::CASH => [AccountType::CASH],
|
||||
AccountType::ASSET => [AccountType::ASSET],
|
||||
AccountType::EXPENSE => [AccountType::EXPENSE],
|
||||
AccountType::REVENUE => [AccountType::REVENUE],
|
||||
AccountType::INITIAL_BALANCE => [AccountType::INITIAL_BALANCE],
|
||||
AccountType::BENEFICIARY => [AccountType::BENEFICIARY],
|
||||
AccountType::IMPORT => [AccountType::IMPORT],
|
||||
AccountType::RECONCILIATION => [AccountType::RECONCILIATION],
|
||||
AccountType::LOAN => [AccountType::LOAN],
|
||||
AccountType::MORTGAGE => [AccountType::MORTGAGE],
|
||||
AccountType::DEBT => [AccountType::DEBT],
|
||||
AccountType::CREDITCARD => [AccountType::CREDITCARD],
|
||||
'default account' => [AccountType::DEFAULT],
|
||||
'cash account' => [AccountType::CASH],
|
||||
'asset account' => [AccountType::ASSET],
|
||||
'expense account' => [AccountType::EXPENSE],
|
||||
'revenue account' => [AccountType::REVENUE],
|
||||
'initial balance account' => [AccountType::INITIAL_BALANCE],
|
||||
'reconciliation' => [AccountType::RECONCILIATION],
|
||||
'loan' => [AccountType::LOAN],
|
||||
'mortgage' => [AccountType::MORTGAGE],
|
||||
'debt' => [AccountType::DEBT],
|
||||
'credit card' => [AccountType::CREDITCARD],
|
||||
'credit-card' => [AccountType::CREDITCARD],
|
||||
'creditcard' => [AccountType::CREDITCARD],
|
||||
'cc' => [AccountType::CREDITCARD],
|
||||
];
|
||||
|
||||
return $types[$type] ?? $types['all'];
|
||||
return $this->types[$type] ?? $this->types['all'];
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\JsonApi;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Support\Http\Api\AccountFilter;
|
||||
use Illuminate\Contracts\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use LaravelJsonApi\Core\Query\FilterParameters;
|
||||
use LaravelJsonApi\Core\Query\SortFields;
|
||||
|
||||
@ -51,35 +53,80 @@ trait ExpandsQuery
|
||||
return $query;
|
||||
}
|
||||
|
||||
private function parseAccountTypeFilter(array $value): array
|
||||
{
|
||||
$return = [];
|
||||
foreach ($value as $entry) {
|
||||
$return = array_merge($return, $this->mapAccountTypes($entry));
|
||||
}
|
||||
return array_unique($return);
|
||||
}
|
||||
|
||||
private function parseAllFilters(string $class, FilterParameters $filters): array
|
||||
{
|
||||
$config = config('api.valid_api_filters')[$class];
|
||||
$parsed = [];
|
||||
foreach ($filters->all() as $filter) {
|
||||
$key = $filter->key();
|
||||
if (!in_array($key, $config, true)) {
|
||||
continue;
|
||||
}
|
||||
// make array if not array:
|
||||
$value = $filter->value();
|
||||
if (null === $value) {
|
||||
continue;
|
||||
}
|
||||
if (!is_array($value)) {
|
||||
$value = [$value];
|
||||
}
|
||||
|
||||
switch ($filter->key()) {
|
||||
case 'name':
|
||||
$parsed['name'] = $value;
|
||||
break;
|
||||
case 'type':
|
||||
$parsed['type'] = $this->parseAccountTypeFilter($value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
final protected function addFilterParams(string $class, Builder $query, ?FilterParameters $filters): Builder
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
if (null === $filters) {
|
||||
return $query;
|
||||
}
|
||||
$config = config(sprintf('firefly.valid_query_filters.%s', $class)) ?? [];
|
||||
if (0 === count($filters->all())) {
|
||||
return $query;
|
||||
}
|
||||
$query->where(function (Builder $q) use ($config, $filters): void {
|
||||
foreach ($filters->all() as $filter) {
|
||||
if (in_array($filter->key(), $config, true)) {
|
||||
foreach ($filter->value() as $value) {
|
||||
$q->where($filter->key(), 'LIKE', sprintf('%%%s%%', $value));
|
||||
// parse filters valid for this class.
|
||||
$parsed = $this->parseAllFilters($class, $filters);
|
||||
|
||||
// expand query for each query filter
|
||||
$config = config('api.valid_query_filters')[$class];
|
||||
$query->where(function (Builder $q) use ($config, $parsed): void {
|
||||
foreach ($parsed as $key => $filter) {
|
||||
if (in_array($key, $config, true)) {
|
||||
Log::debug(sprintf('Add query filter "%s"', $key));
|
||||
// add type to query:
|
||||
foreach ($filter as $value) {
|
||||
$q->where($key, 'LIKE', sprintf('%%%s%%', $value));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// some filters are special, i.e. the account type filter.
|
||||
$typeFilters = $filters->value('type', false);
|
||||
if (false !== $typeFilters && count($typeFilters) > 0) {
|
||||
$query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
|
||||
foreach ($typeFilters as $typeFilter) {
|
||||
$types = $this->mapAccountTypes($typeFilter);
|
||||
$query->whereIn('account_types.type', $types);
|
||||
// TODO this is special treatment, but alas, unavoidable right now.
|
||||
if ($class === Account::class && array_key_exists('type', $parsed)) {
|
||||
if (count($parsed['type']) > 0) {
|
||||
$query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
|
||||
$query->whereIn('account_types.type', $parsed['type']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ trait ValidateSortParameters
|
||||
return false;
|
||||
}
|
||||
|
||||
$config = config(sprintf('firefly.full_data_set.%s', $class)) ?? [];
|
||||
$config = config(sprintf('api.full_data_set.%s', $class)) ?? [];
|
||||
|
||||
foreach ($params->all() as $field) {
|
||||
if (in_array($field->name(), $config, true)) {
|
||||
|
58
config/api.php
Normal file
58
config/api.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/*
|
||||
* api.php
|
||||
* Copyright (c) 2024 james@firefly-iii.org.
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
|
||||
return [
|
||||
|
||||
// allowed filters (search) for APIs
|
||||
'filters' => [
|
||||
'allowed' => [
|
||||
'accounts' => [
|
||||
'name' => 'string',
|
||||
'active' => 'boolean',
|
||||
'iban' => 'iban',
|
||||
'balance' => 'numeric',
|
||||
'last_activity' => 'date',
|
||||
'balance_difference' => 'numeric',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
// allowed sort columns for APIs
|
||||
'sorting' => [
|
||||
'allowed' => [
|
||||
'transactions' => ['description', 'amount'],
|
||||
'accounts' => ['name', 'active', 'iban', 'balance', 'last_activity', 'balance_difference', 'current_debt'],
|
||||
],
|
||||
],
|
||||
'full_data_set' => [
|
||||
'account' => ['last_activity', 'balance_difference', 'current_balance', 'current_debt'],
|
||||
],
|
||||
'valid_query_filters' => [
|
||||
Account::class => ['id','name', 'iban', 'active'],
|
||||
],
|
||||
'valid_api_filters' => [
|
||||
Account::class => ['id', 'name', 'iban', 'active', 'type'],
|
||||
],
|
||||
];
|
@ -920,31 +920,4 @@ return [
|
||||
// preselected account lists possibilities:
|
||||
'preselected_accounts' => ['all', 'assets', 'liabilities'],
|
||||
|
||||
// allowed filters (search) for APIs
|
||||
'filters' => [
|
||||
'allowed' => [
|
||||
'accounts' => [
|
||||
'name' => 'string',
|
||||
'active' => 'boolean',
|
||||
'iban' => 'iban',
|
||||
'balance' => 'numeric',
|
||||
'last_activity' => 'date',
|
||||
'balance_difference' => 'numeric',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
// allowed sort columns for APIs
|
||||
'sorting' => [
|
||||
'allowed' => [
|
||||
'transactions' => ['description', 'amount'],
|
||||
'accounts' => ['name', 'active', 'iban', 'balance', 'last_activity', 'balance_difference', 'current_debt'],
|
||||
],
|
||||
],
|
||||
'full_data_set' => [
|
||||
'account' => ['last_activity', 'balance_difference', 'current_balance', 'current_debt'],
|
||||
],
|
||||
'valid_query_filters' => [
|
||||
'account' => ['name', 'iban', 'active'],
|
||||
],
|
||||
];
|
||||
|
Loading…
Reference in New Issue
Block a user