From d19dd2a8b26d9a357814b65fbab91e02c9af38e9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 26 Jul 2024 18:50:41 +0200 Subject: [PATCH] Clean up some code, clean routes. [skip ci] --- .../Controllers/JsonApi/AccountController.php | 112 ++++++++++++++++++ .../Api/V2/Controllers/AccountController.php | 43 ------- app/JsonApi/V2/Accounts/AccountRepository.php | 12 ++ app/JsonApi/V2/Accounts/AccountResource.php | 21 ++-- app/JsonApi/V2/Accounts/AccountSchema.php | 24 ++-- .../V2/Accounts/Capabilities/AccountQuery.php | 28 +++-- app/JsonApi/V2/Server.php | 7 +- app/Models/Transaction.php | 2 + .../JsonApi/Enrichments/AccountEnrichment.php | 31 +++-- config/laravel-model-caching.php | 9 +- routes/api.php | 2 +- 11 files changed, 206 insertions(+), 85 deletions(-) create mode 100644 app/Api/V2/Controllers/JsonApi/AccountController.php delete mode 100644 app/Http/Controllers/Api/V2/Controllers/AccountController.php diff --git a/app/Api/V2/Controllers/JsonApi/AccountController.php b/app/Api/V2/Controllers/JsonApi/AccountController.php new file mode 100644 index 0000000000..734c7afd3f --- /dev/null +++ b/app/Api/V2/Controllers/JsonApi/AccountController.php @@ -0,0 +1,112 @@ +resourceType() + ); + + $response = null; + + if (method_exists($this, 'searching')) { + $response = $this->searching($request); + } + + if ($response) { + return $response; + } + + $data = $store + ->queryAll($resourceType) + ->withRequest($request) + ->firstOrPaginate($request->page()); + + if (method_exists($this, 'searched')) { + $response = $this->searched($data, $request); + } + + return $response ?: DataResponse::make($data)->withQueryParameters($request); + } + +// public function readAccountBalances(AnonymousQuery $query, AccountBalanceSchema $schema, Account $account): Responsable +// { +// $schema = JsonApi::server()->schemas()->schemaFor('account-balances'); +// +// $models = $schema +// ->repository() +// ->queryAll() +// ->withRequest($query) +// ->withAccount($account) +// ->get() +// ; +// +// return DataResponse::make($models); +// } +} diff --git a/app/Http/Controllers/Api/V2/Controllers/AccountController.php b/app/Http/Controllers/Api/V2/Controllers/AccountController.php deleted file mode 100644 index 7639cee1a3..0000000000 --- a/app/Http/Controllers/Api/V2/Controllers/AccountController.php +++ /dev/null @@ -1,43 +0,0 @@ -schemas()->schemaFor('account-balances'); -// -// $models = $schema -// ->repository() -// ->queryAll() -// ->withRequest($query) -// ->withAccount($account) -// ->get() -// ; -// -// return DataResponse::make($models); -// } -} diff --git a/app/JsonApi/V2/Accounts/AccountRepository.php b/app/JsonApi/V2/Accounts/AccountRepository.php index d73ddd75a2..ca3df2fe13 100644 --- a/app/JsonApi/V2/Accounts/AccountRepository.php +++ b/app/JsonApi/V2/Accounts/AccountRepository.php @@ -28,6 +28,16 @@ use FireflyIII\Support\JsonApi\Concerns\UsergroupAware; use LaravelJsonApi\Contracts\Store\QueriesAll; use LaravelJsonApi\NonEloquent\AbstractRepository; +/** + * Class AccountRepository + * + * The repository collects a single or many (account) objects from the database and returns them to the + * account resource. The account resource links all account properties to the JSON properties. + * + * For the queryAll thing, a separate query is constructed that does the actual querying of the database. + * This is necessary because the user can't just query all accounts (it would return other user's data) + * and because we also need to collect all kinds of metadata, like the currency and user info. + */ class AccountRepository extends AbstractRepository implements QueriesAll { use UsergroupAware; @@ -39,11 +49,13 @@ class AccountRepository extends AbstractRepository implements QueriesAll public function find(string $resourceId): ?object { + die('query single?'); return Account::find((int) $resourceId); } public function queryAll(): Capabilities\AccountQuery { + die('query all?'); return Capabilities\AccountQuery::make() ->withUserGroup($this->userGroup) ->withServer($this->server) diff --git a/app/JsonApi/V2/Accounts/AccountResource.php b/app/JsonApi/V2/Accounts/AccountResource.php index b809641bb5..bbb5cceb18 100644 --- a/app/JsonApi/V2/Accounts/AccountResource.php +++ b/app/JsonApi/V2/Accounts/AccountResource.php @@ -10,6 +10,11 @@ use LaravelJsonApi\Core\Resources\JsonApiResource; /** * @property Account $resource + * + * This class collects the resources attributes, the account in this case. + * Generally speaking, each property here is directly related to a property on the account object itself. + * However, many properties are collected from other sources, like the user or the currency. + * As a result, the account repository is where it's at, which is where the collection takes place and is optimised. */ class AccountResource extends JsonApiResource { @@ -21,14 +26,14 @@ class AccountResource extends JsonApiResource public function attributes($request): iterable { return [ - 'created_at' => $this->resource->created_at, - 'updated_at' => $this->resource->updated_at, - 'name' => $this->resource->name, - 'iban' => '' === $this->resource->iban ? null : $this->resource->iban, - 'active' => $this->resource->active, - 'last_activity' => $this->resource->last_activity, - 'type' => $this->resource->type, - 'account_role' => $this->resource->account_role, + 'created_at' => $this->resource->created_at, + 'updated_at' => $this->resource->updated_at, + 'name' => $this->resource->name, +// 'iban' => '' === $this->resource->iban ? null : $this->resource->iban, +// 'active' => $this->resource->active, +// 'last_activity' => $this->resource->last_activity, +// 'type' => $this->resource->type, +// 'account_role' => $this->resource->account_role, // 'virtual_balance' => $this->resource->virtual_balance, // 'native_balance' => $this->resource->native_balance, diff --git a/app/JsonApi/V2/Accounts/AccountSchema.php b/app/JsonApi/V2/Accounts/AccountSchema.php index bb50bd600b..130cbdda59 100644 --- a/app/JsonApi/V2/Accounts/AccountSchema.php +++ b/app/JsonApi/V2/Accounts/AccountSchema.php @@ -17,6 +17,13 @@ use LaravelJsonApi\Eloquent\Filters\WhereIdIn; use LaravelJsonApi\Eloquent\Pagination\PagePagination; use LaravelJsonApi\Eloquent\Schema; +/** + * Class AccountSchema + * + * This is the schema of all fields that an account exposes to the world. + * Fields do not have to have a relation to the actual model. + * Fields mentioned here still need to be filled in by the AccountResource. + */ class AccountSchema extends Schema { /** @@ -34,18 +41,19 @@ class AccountSchema extends Schema DateTime::make('created_at')->sortable()->readOnly(), DateTime::make('updated_at')->sortable()->readOnly(), Str::make('name')->sortable(), - Str::make('account_type'), - Str::make('virtual_balance'), - Str::make('iban'), - Boolean::make('active'), - Number::make('order'), - HasOne::make('user'), - HasMany::make('account_balances'), +// Str::make('account_type'), +// Str::make('virtual_balance'), +// Str::make('iban'), +// Boolean::make('active'), +// Number::make('order'), + HasOne::make('user')->readOnly(), + //HasMany::make('account_balances'), ]; } /** - * Get the resource filters. + * Filters mentioned here can be used to filter the results. + * TODO write down exactly how this works. */ public function filters(): array { diff --git a/app/JsonApi/V2/Accounts/Capabilities/AccountQuery.php b/app/JsonApi/V2/Accounts/Capabilities/AccountQuery.php index c52d97d921..6015b1a0a6 100644 --- a/app/JsonApi/V2/Accounts/Capabilities/AccountQuery.php +++ b/app/JsonApi/V2/Accounts/Capabilities/AccountQuery.php @@ -33,7 +33,7 @@ use LaravelJsonApi\Contracts\Store\HasPagination; use LaravelJsonApi\NonEloquent\Capabilities\QueryAll; use LaravelJsonApi\NonEloquent\Concerns\PaginatesEnumerables; -class AccountQuery extends QueryAll implements HasPagination +class AccountQuery implements HasPagination { use ExpandsQuery; use FiltersPagination; @@ -42,32 +42,44 @@ class AccountQuery extends QueryAll implements HasPagination use UsergroupAware; use ValidateSortParameters; - #[\Override] - public function get(): iterable + /** + * This method returns all accounts, given a bunch of filters and sort fields, together with pagination. + */ + public function queryAll(): iterable { + // collect filters $filters = $this->queryParameters->filter(); + // collect sort options $sort = $this->queryParameters->sortFields(); + // collect pagination based on the page $pagination = $this->filtersPagination($this->queryParameters->page()); + // check if we need all accounts, regardless of pagination + // This is necessary when the user wants to sort on specific params. $needsAll = $this->validateParams('account', $sort); + + // start the query $query = $this->userGroup->accounts(); + // add pagination to the query, limiting the results. if (!$needsAll) { $query = $this->addPagination($query, $pagination); } + + // add sort and filter parameters to the query. $query = $this->addSortParams($query, $sort); $query = $this->addFilterParams('account', $query, $filters); + // collect the result. $collection = $query->get(['accounts.*']); - // enrich data + // enrich the collected data $enrichment = new AccountEnrichment(); $collection = $enrichment->enrich($collection); - // add filters after the query + // TODO add filters after the query, if there are filters that cannot be applied to the database but only + // to the enriched results. - // add sort after the query + // sort the data after the query, and return it right away. return $this->sortCollection($collection, $sort); - // var_dump($filters->value('name')); - // exit; } } diff --git a/app/JsonApi/V2/Server.php b/app/JsonApi/V2/Server.php index a0c5d97356..be8daec9cf 100644 --- a/app/JsonApi/V2/Server.php +++ b/app/JsonApi/V2/Server.php @@ -9,6 +9,11 @@ use FireflyIII\JsonApi\V2\AccountBalances\AccountBalanceSchema; use FireflyIII\JsonApi\V2\Users\UserSchema; use LaravelJsonApi\Core\Server\Server as BaseServer; +/** + * Class Server + * + * This class serves as a generic class for the v2 API "server". + */ class Server extends BaseServer { /** @@ -32,7 +37,7 @@ class Server extends BaseServer return [ AccountSchema::class, UserSchema::class, - AccountBalanceSchema::class, + //AccountBalanceSchema::class, ]; } } diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index 4626848073..9e8bf2132b 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -26,6 +26,7 @@ namespace FireflyIII\Models; use Carbon\Carbon; use Eloquent; use FireflyIII\Support\Models\ReturnsIntegerIdTrait; +use GeneaLabs\LaravelModelCaching\Traits\Cachable; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Collection; @@ -96,6 +97,7 @@ class Transaction extends Model use HasFactory; use ReturnsIntegerIdTrait; use SoftDeletes; + use Cachable; protected $casts = [ diff --git a/app/Support/JsonApi/Enrichments/AccountEnrichment.php b/app/Support/JsonApi/Enrichments/AccountEnrichment.php index 68e996f50c..cbe3aeb929 100644 --- a/app/Support/JsonApi/Enrichments/AccountEnrichment.php +++ b/app/Support/JsonApi/Enrichments/AccountEnrichment.php @@ -32,32 +32,43 @@ use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Log; +/** + * Class AccountEnrichment + * + * This class "enriches" accounts and adds data from other tables and models to each account model. + */ class AccountEnrichment implements EnrichmentInterface { private Collection $collection; private array $currencies; #[\Override] + /** + * Do the actual enrichment. + */ public function enrich(Collection $collection): Collection { + Log::debug(sprintf('Now doing account enrichment for %d account(s)', $collection->count())); + // prep local fields $this->collection = $collection; $this->currencies = []; + // do everything here: $this->getLastActivity(); // $this->getMetaBalances(); $this->collectAccountTypes(); $this->collectMetaData(); - $this->collection->transform(function (Account $account) { - $account->user_array = ['id' => 1, 'bla bla' => 'bla']; - $account->balances = collect([ - ['balance_id' => 1, 'balance' => 5], - ['balance_id' => 2, 'balance' => 5], - ['balance_id' => 3, 'balance' => 5], - ]); - - return $account; - }); +// $this->collection->transform(function (Account $account) { +// $account->user_array = ['id' => 1, 'bla bla' => 'bla']; +// $account->balances = collect([ +// ['balance_id' => 1, 'balance' => 5], +// ['balance_id' => 2, 'balance' => 5], +// ['balance_id' => 3, 'balance' => 5], +// ]); +// +// return $account; +// }); return $this->collection; } diff --git a/config/laravel-model-caching.php b/config/laravel-model-caching.php index cda02fbe7a..5729e7e01e 100644 --- a/config/laravel-model-caching.php +++ b/config/laravel-model-caching.php @@ -1,11 +1,8 @@ '', - - 'enabled' => env('MODEL_CACHE_ENABLED', true), - + 'cache-prefix' => '', + 'enabled' => env('MODEL_CACHE_ENABLED', true), 'use-database-keying' => env('MODEL_CACHE_USE_DATABASE_KEYING', true), - - 'store' => env('MODEL_CACHE_STORE'), + 'store' => env('MODEL_CACHE_STORE'), ]; diff --git a/routes/api.php b/routes/api.php index be94da8681..534fc847a2 100644 --- a/routes/api.php +++ b/routes/api.php @@ -22,7 +22,7 @@ declare(strict_types=1); -use FireflyIII\Http\Controllers\Api\V2\Controllers\AccountController; +use FireflyIII\Api\V2\Controllers\JsonApi\AccountController; use LaravelJsonApi\Laravel\Facades\JsonApiRoute; use LaravelJsonApi\Laravel\Http\Controllers\JsonApiController; use LaravelJsonApi\Laravel\Routing\Relationships;