diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 57cc00af8f..7277fce1b0 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -37,6 +37,7 @@ use Illuminate\Support\Arr; use Illuminate\Validation\ValidationException as LaravelValidationException; use Laravel\Passport\Exceptions\OAuthServerException as LaravelOAuthException; use LaravelJsonApi\Core\Exceptions\JsonApiException; +use LaravelJsonApi\Exceptions\ExceptionParser; use League\OAuth2\Server\Exception\OAuthServerException; use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException; use Symfony\Component\HttpFoundation\Response; @@ -67,6 +68,19 @@ class Handler extends ExceptionHandler JsonApiException::class, ]; + + /** + * Register the exception handling callbacks for the application. + * + * @return void + */ + public function register() + { + $this->renderable( + ExceptionParser::make()->renderable() + ); + } + /** * Render an exception into an HTTP response. It's complex but lucky for us, we never use it because * Firefly III never crashes. @@ -81,15 +95,21 @@ class Handler extends ExceptionHandler public function render($request, \Throwable $e): Response { $expectsJson = $request->expectsJson(); - // if the user requests anything /api/, assume the user wants to see JSON. - if (str_starts_with($request->getRequestUri(), '/api/')) { - app('log')->debug('API endpoint, always assume user wants JSON.'); - $expectsJson = true; - } app('log')->debug('Now in Handler::render()'); + + if($e instanceof JsonApiException) { + // ignore it: controller will handle it. + + app('log')->debug(sprintf('Return to parent to handle JsonApiException(%d)', $e->getCode() + )); + + return parent::render($request, $e); + } + if ($e instanceof LaravelValidationException && $expectsJson) { // ignore it: controller will handle it. + app('log')->debug(sprintf('Return to parent to handle LaravelValidationException(%d)', $e->status)); return parent::render($request, $e); diff --git a/app/Helpers/Collector/GroupCollector.php b/app/Helpers/Collector/GroupCollector.php index dd2d09357f..ba86169775 100644 --- a/app/Helpers/Collector/GroupCollector.php +++ b/app/Helpers/Collector/GroupCollector.php @@ -805,7 +805,7 @@ class GroupCollector implements GroupCollectorInterface return 'zzz'; } - exit('here we are'); + exit('here we are 2'); }); } diff --git a/app/JsonApi/V2/Accounts/AccountRepository.php b/app/JsonApi/V2/Accounts/AccountRepository.php index a6366d9ab0..357c2c5c1b 100644 --- a/app/JsonApi/V2/Accounts/AccountRepository.php +++ b/app/JsonApi/V2/Accounts/AccountRepository.php @@ -54,6 +54,7 @@ class AccountRepository extends AbstractRepository implements QueriesAll, Create + /** * SiteRepository constructor. */ @@ -101,6 +102,12 @@ class AccountRepository extends AbstractRepository implements QueriesAll, Create return Capabilities\CrudAccount::make(); } + /** + * TODO piggy banks + * TODO transactions + * + * @return CrudRelations + */ protected function relations(): CrudRelations { Log::debug(__METHOD__); diff --git a/app/JsonApi/V2/Accounts/AccountRequest.php b/app/JsonApi/V2/Accounts/AccountRequest.php index 82b2607982..c0b6e05c49 100644 --- a/app/JsonApi/V2/Accounts/AccountRequest.php +++ b/app/JsonApi/V2/Accounts/AccountRequest.php @@ -2,15 +2,18 @@ namespace FireflyIII\JsonApi\V2\Accounts; -use FireflyIII\Rules\BelongsUser; +use FireflyIII\Rules\Account\IsUniqueAccount; +use FireflyIII\Rules\IsBoolean; +use FireflyIII\Rules\IsValidPositiveAmount; +use FireflyIII\Rules\UniqueAccountNumber; +use FireflyIII\Rules\UniqueIban; +use FireflyIII\Support\Request\ConvertsDataTypes; use Illuminate\Support\Facades\Log; -use Illuminate\Validation\Rule; use LaravelJsonApi\Laravel\Http\Requests\ResourceRequest; -use LaravelJsonApi\Validation\Rule as JsonApiRule; class AccountRequest extends ResourceRequest { - + use ConvertsDataTypes; /** * Get the validation rules for the resource. * @@ -19,16 +22,36 @@ class AccountRequest extends ResourceRequest public function rules(): array { Log::debug(__METHOD__); - die('am i used'); + $accountRoles = implode(',', config('firefly.accountRoles')); + $ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes'))); + $types = implode(',', array_keys(config('firefly.subTitlesByIdentifier'))); + $type = $this->convertString('type'); + //var_dump($types);exit; + return [ - 'type' => [ - new BelongsUser() - ], - 'name' => [ - 'nullable', - 'string', - 'max:255', - ], + 'name' => ['required', 'max:1024', 'min:1'], // , new IsUniqueAccount() + 'account_type' => ['required','max:1024','min:1', sprintf('in:%s', $types)], +// 'iban' => ['iban', 'nullable', new UniqueIban(null, $type)], +// 'bic' => 'bic|nullable', +// 'account_number' => ['min:1', 'max:255', 'nullable', new UniqueAccountNumber(null, $type)], +// 'opening_balance' => 'numeric|required_with:opening_balance_date|nullable', +// 'opening_balance_date' => 'date|required_with:opening_balance|nullable', +// 'virtual_balance' => 'numeric|nullable', +// 'order' => 'numeric|nullable', +// 'currency_id' => 'numeric|exists:transaction_currencies,id', +// 'currency_code' => 'min:3|max:3|exists:transaction_currencies,code', +// 'active' => [new IsBoolean()], +// 'include_net_worth' => [new IsBoolean()], +// 'account_role' => sprintf('nullable|in:%s|required_if:type,asset', $accountRoles), +// 'credit_card_type' => sprintf('nullable|in:%s|required_if:account_role,ccAsset', $ccPaymentTypes), +// 'monthly_payment_date' => 'nullable|date|required_if:account_role,ccAsset|required_if:credit_card_type,monthlyFull', +// 'liability_type' => 'nullable|required_if:type,liability|required_if:type,liabilities|in:loan,debt,mortgage', +// 'liability_amount' => ['required_with:liability_start_date', new IsValidPositiveAmount()], +// 'liability_start_date' => 'required_with:liability_amount|date', +// 'liability_direction' => 'nullable|required_if:type,liability|required_if:type,liabilities|in:credit,debit', +// 'interest' => 'min:0|max:100|numeric', +// 'interest_period' => sprintf('nullable|in:%s', implode(',', config('firefly.interest_periods'))), +// 'notes' => 'min:0|max:32768', ]; } diff --git a/app/JsonApi/V2/Accounts/AccountResource.php b/app/JsonApi/V2/Accounts/AccountResource.php index 0a4e4727dc..166be08486 100644 --- a/app/JsonApi/V2/Accounts/AccountResource.php +++ b/app/JsonApi/V2/Accounts/AccountResource.php @@ -6,7 +6,6 @@ namespace FireflyIII\JsonApi\V2\Accounts; use FireflyIII\Models\Account; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Log; use LaravelJsonApi\Core\Resources\JsonApiResource; /** @@ -38,7 +37,7 @@ class AccountResource extends JsonApiResource 'active' => $this->resource->active, 'order' => $this->resource->order, 'iban' => $this->resource->iban, - 'type' => $this->resource->account_type_string, + 'account_type' => $this->resource->account_type_string, 'account_role' => $this->resource->account_role, 'account_number' => '' === $this->resource->account_number ? null : $this->resource->account_number, diff --git a/app/JsonApi/V2/Accounts/AccountSchema.php b/app/JsonApi/V2/Accounts/AccountSchema.php index 1374ded384..495376b7dc 100644 --- a/app/JsonApi/V2/Accounts/AccountSchema.php +++ b/app/JsonApi/V2/Accounts/AccountSchema.php @@ -40,7 +40,7 @@ class AccountSchema extends Schema Attribute::make('active')->sortable(), Attribute::make('order')->sortable(), Attribute::make('iban')->sortable(), - Attribute::make('type'), + Attribute::make('account_type'), Attribute::make('account_role'), Attribute::make('account_number')->sortable(), @@ -60,7 +60,9 @@ class AccountSchema extends Schema Attribute::make('liability_direction'), Attribute::make('interest'), Attribute::make('interest_period'), - Attribute::make('current_debt')->sortable(), + //Attribute::make('current_debt')->sortable(), + + // TODO credit card fields. // dynamic data Attribute::make('last_activity')->sortable(), diff --git a/app/JsonApi/V2/Accounts/Capabilities/CrudAccount.php b/app/JsonApi/V2/Accounts/Capabilities/CrudAccount.php index 01ac1db16b..ce750fd0fa 100644 --- a/app/JsonApi/V2/Accounts/Capabilities/CrudAccount.php +++ b/app/JsonApi/V2/Accounts/Capabilities/CrudAccount.php @@ -53,6 +53,7 @@ class CrudAccount extends CrudResource } public function create(array $validatedData): Account { - die('here we are'); + var_dump($validatedData);exit; + die('in create method.'); } } diff --git a/app/Policies/AccountPolicy.php b/app/Policies/AccountPolicy.php index 8bedfaef0e..c67bd80894 100644 --- a/app/Policies/AccountPolicy.php +++ b/app/Policies/AccountPolicy.php @@ -37,6 +37,10 @@ class AccountPolicy return auth()->check() && $user->id === $account->user_id; } + public function create(): bool { + return auth()->check(); + } + /** * Everybody can do this, but selection should limit to user. * diff --git a/app/Rules/Account/IsUniqueAccount.php b/app/Rules/Account/IsUniqueAccount.php new file mode 100644 index 0000000000..e8fba9ab04 --- /dev/null +++ b/app/Rules/Account/IsUniqueAccount.php @@ -0,0 +1,214 @@ +fail = $fail; + // because a user does not have to be logged in (tests and what-not). + if (!auth()->check()) { + app('log')->debug('validateUniqueAccountForUser::anon'); + $fail('validation.nog_logged_in')->translate(); + return; + } + if (array_key_exists('type', $this->data)) { + app('log')->debug('validateUniqueAccountForUser::typeString'); + + $this->validateByAccountTypeString($value, $parameters, (string) $this->data['type']); + } + if (array_key_exists('account_type_id', $this->data)) { + app('log')->debug('validateUniqueAccountForUser::typeId'); + + $this->validateByAccountTypeId($value, $parameters); + } + $parameterId = $parameters[0] ?? null; + if (null !== $parameterId) { + app('log')->debug('validateUniqueAccountForUser::paramId'); + + $this->validateByParameterId((int) $parameterId, $value); + } + if (array_key_exists('id', $this->data)) { + app('log')->debug('validateUniqueAccountForUser::accountId'); + + $this->validateByAccountId($value); + } + + // without type, just try to validate the name. + app('log')->debug('validateUniqueAccountForUser::accountName'); + + $this->validateByAccountName($value); + + } + + /** + * TODO duplicate from old validation class. + * + * @param string $value + * @param array $parameters + * @param string $type + * + * @return bool + */ + private function validateByAccountTypeString(string $value, array $parameters, string $type): bool + { + /** @var null|array $search */ + $search = config('firefly.accountTypeByIdentifier.%s', $type); + + if (null === $search) { + return false; + } + + $accountTypes = AccountType::whereIn('type', $search)->get(); + $ignore = (int) ($parameters[0] ?? 0.0); + $accountTypeIds = $accountTypes->pluck('id')->toArray(); + + /** @var null|Account $result */ + $result = auth()->user()->accounts()->whereIn('account_type_id', $accountTypeIds)->where('id', '!=', $ignore) + ->where('name', $value) + ->first(); + + return null === $result; + } + + /** + * TODO duplicate from old validation class. + * + * @return void + */ + private function validateAccountAnonymously(): void + { + if (!array_key_exists('user_id', $this->data)) { + $this->fail('No user ID provided.'); + } + + /** @var User $user */ + $user = User::find($this->data['user_id']); + $type = AccountType::find($this->data['account_type_id'])->first(); + $value = $this->data['name']; + + /** @var null|Account $result */ + $result = $user->accounts()->where('account_type_id', $type->id)->where('name', $value)->first(); + + return null === $result; + } + + /** + * TODO Duplicate from old validation class. + * + * @param mixed $value + * @param mixed $parameters + */ + private function validateByAccountTypeId($value, $parameters): bool + { + $type = AccountType::find($this->data['account_type_id'])->first(); + $ignore = (int) ($parameters[0] ?? 0.0); + + /** @var null|Account $result */ + $result = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore) + ->where('name', $value) + ->first(); + + return null === $result; + } + + /** + * TODO Duplicate from old validation class. + * + * @param mixed $value + */ + private function validateByParameterId(int $accountId, $value): bool + { + /** @var Account $existingAccount */ + $existingAccount = Account::find($accountId); + + $type = $existingAccount->accountType; + $ignore = $existingAccount->id; + + $entry = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore) + ->where('name', $value) + ->first(); + + return null === $entry; + } + + /** + * TODO Duplicate from old validation class. + * + * @param mixed $value + */ + private function validateByAccountId($value): bool + { + /** @var Account $existingAccount */ + $existingAccount = Account::find($this->data['id']); + + $type = $existingAccount->accountType; + $ignore = $existingAccount->id; + + $entry = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore) + ->where('name', $value) + ->first(); + + return null === $entry; + } + + /** + * TODO is duplicate + * TODO does not take group into account. Must be made group aware. + * + * @param string $value + * + * @return bool + */ + private function validateByAccountName(string $value): bool + { + return 0 === auth()->user()->accounts()->where('name', $value)->count(); + } + + /** + * @inheritDoc + */ + #[\Override] public function setData(array $data): void + { + $this->data = $data; + } +} diff --git a/app/Support/Models/AccountBalanceCalculator.php b/app/Support/Models/AccountBalanceCalculator.php index 162e010ccf..c23e816fc0 100644 --- a/app/Support/Models/AccountBalanceCalculator.php +++ b/app/Support/Models/AccountBalanceCalculator.php @@ -306,7 +306,7 @@ class AccountBalanceCalculator private function getStartAmounts(Account $account, TransactionJournal $journal): array { - exit('here we are'); + exit('here we are 1'); return []; } diff --git a/app/Support/Steam.php b/app/Support/Steam.php index 00b50458f6..10150da8b9 100644 --- a/app/Support/Steam.php +++ b/app/Support/Steam.php @@ -44,7 +44,7 @@ class Steam */ public function balanceIgnoreVirtual(Account $account, Carbon $date): string { - Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__)); + //Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__)); /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); $repository->setUser($account->user); @@ -93,7 +93,7 @@ class Steam */ public function balanceInRange(Account $account, Carbon $start, Carbon $end, ?TransactionCurrency $currency = null): array { - Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__)); +// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__)); $cache = new CacheProperties(); $cache->addProperty($account->id); $cache->addProperty('balance-in-range'); @@ -214,7 +214,7 @@ class Steam */ public function balance(Account $account, Carbon $date, ?TransactionCurrency $currency = null): string { - Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__)); +// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__)); // abuse chart properties: $cache = new CacheProperties(); $cache->addProperty($account->id); @@ -261,7 +261,7 @@ class Steam */ public function balanceInRangeConverted(Account $account, Carbon $start, Carbon $end, TransactionCurrency $native): array { - Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__)); +// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__)); $cache = new CacheProperties(); $cache->addProperty($account->id); $cache->addProperty('balance-in-range-converted'); @@ -386,7 +386,7 @@ class Steam */ public function balanceConverted(Account $account, Carbon $date, TransactionCurrency $native): string { - Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__)); +// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__)); Log::debug(sprintf('Now in balanceConverted (%s) for account #%d, converting to %s', $date->format('Y-m-d'), $account->id, $native->code)); $cache = new CacheProperties(); $cache->addProperty($account->id); @@ -526,7 +526,7 @@ class Steam */ public function balancesByAccounts(Collection $accounts, Carbon $date): array { - Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__)); +// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__)); $ids = $accounts->pluck('id')->toArray(); // cache this property. $cache = new CacheProperties(); @@ -557,7 +557,7 @@ class Steam */ public function balancesByAccountsConverted(Collection $accounts, Carbon $date): array { - Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__)); +// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__)); $ids = $accounts->pluck('id')->toArray(); // cache this property. $cache = new CacheProperties(); @@ -591,7 +591,7 @@ class Steam */ public function balancesPerCurrencyByAccounts(Collection $accounts, Carbon $date): array { - Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__)); +// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__)); $ids = $accounts->pluck('id')->toArray(); // cache this property. $cache = new CacheProperties(); @@ -617,7 +617,7 @@ class Steam public function balancePerCurrency(Account $account, Carbon $date): array { - Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__)); +// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__)); // abuse chart properties: $cache = new CacheProperties(); $cache->addProperty($account->id); diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index bcdd07207e..2a9df099fc 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -28,6 +28,7 @@ return [ 'filter_must_be_in' => 'Filter ":filter" must be one of: :values', 'filter_not_string' => 'Filter ":filter" is expected to be a string of text', 'bad_api_filter' => 'This API endpoint does not support ":filter" as a filter.', + 'nog_logged_in' => 'You are not logged in.', 'bad_type_source' => 'Firefly III can\'t determine the transaction type based on this source account.', 'bad_type_destination' => 'Firefly III can\'t determine the transaction type based on this destination account.', 'missing_where' => 'Array is missing "where"-clause', diff --git a/routes/api.php b/routes/api.php index e64211bebf..74ac5b7dcb 100644 --- a/routes/api.php +++ b/routes/api.php @@ -241,19 +241,18 @@ Route::group( // V2 JSON API ROUTES JsonApiRoute::server('v2')->prefix('v2') - ->resources(function (ResourceRegistrar $server): void { - // ACCOUNTS - $server->resource('accounts', AccountController::class)->relationships(function (Relationships $relations): void { - $relations->hasOne('user')->readOnly(); - }); - // $server->resource('accounts', AccountController::class)->readOnly(); + ->resources(function (ResourceRegistrar $server): void { + // ACCOUNTS + $server->resource('accounts', AccountController::class) + ->relationships(function (Relationships $relations): void { + $relations->hasOne('user')->readOnly(); + }); - // USERS - $server->resource('users', JsonApiController::class)->readOnly()->relationships(function (Relationships $relations): void { - $relations->hasMany('accounts')->readOnly(); - }); - }) -; + // USERS + $server->resource('users', JsonApiController::class)->readOnly()->relationships(function (Relationships $relations): void { + $relations->hasMany('accounts')->readOnly(); + }); + }); /* * ____ ____ __ .______ ______ __ __ .___________. _______ _______.