From cc63a86a1cf0b16e5f55a4970d2d4c241af8a1fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Falc=C3=B3n?= Date: Wed, 3 Jun 2026 17:33:54 +0200 Subject: [PATCH] refactor(requests): extract user-owned exists rules to trait (#477) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What The `Rule::exists('x', 'id')->where(fn => user_id)` ownership closure was repeated **19×** across 10 Form Requests (categories, labels, accounts, typed accounts). New `app/Http/Requests/Concerns/ValidatesUserOwnedResources` trait: - `$this->userOwned('categories' | 'labels' | 'accounts')` - `$this->userOwnedAccountOfType(AccountType::Loan | RealEstate)` `UpdateAutomationRuleRequest` intentionally untouched — #476 makes it extend Store. ## Stats - **-110 / +63 lines** ## Checks - `php artisan test --filter="Transaction|Budget|Account|AutomationRule|RealEstate|Loan"` — 641 passed (3052 assertions), 1 skipped - `vendor/bin/pint --dirty` — pass Part of duplication-removal series (#475, #476). --- .../BulkUpdateTransactionsRequest.php | 36 ++++--------------- .../Concerns/ValidatesUserOwnedResources.php | 26 ++++++++++++++ .../OpenBanking/MapAccountsRequest.php | 6 ++-- .../Requests/Settings/StoreAccountRequest.php | 13 +++---- .../Settings/StoreAutomationRuleRequest.php | 12 +++---- .../Settings/UpdateAccountRequest.php | 8 ++--- app/Http/Requests/StoreBudgetRequest.php | 9 ++--- .../Requests/StoreRealEstateDetailRequest.php | 8 ++--- app/Http/Requests/StoreTransactionRequest.php | 25 ++++--------- .../UpdateRealEstateDetailRequest.php | 8 ++--- .../Requests/UpdateTransactionRequest.php | 20 +++-------- 11 files changed, 75 insertions(+), 96 deletions(-) create mode 100644 app/Http/Requests/Concerns/ValidatesUserOwnedResources.php diff --git a/app/Http/Requests/BulkUpdateTransactionsRequest.php b/app/Http/Requests/BulkUpdateTransactionsRequest.php index e1ca93c0..b6f6f27d 100644 --- a/app/Http/Requests/BulkUpdateTransactionsRequest.php +++ b/app/Http/Requests/BulkUpdateTransactionsRequest.php @@ -2,11 +2,13 @@ namespace App\Http\Requests; +use App\Http\Requests\Concerns\ValidatesUserOwnedResources; use Illuminate\Foundation\Http\FormRequest; -use Illuminate\Validation\Rule; class BulkUpdateTransactionsRequest extends FormRequest { + use ValidatesUserOwnedResources; + public function authorize(): bool { return true; @@ -23,44 +25,20 @@ class BulkUpdateTransactionsRequest extends FormRequest 'filters.amount_min' => ['nullable', 'numeric'], 'filters.amount_max' => ['nullable', 'numeric'], 'filters.category_ids' => ['nullable', 'array'], - 'filters.category_ids.*' => [ - 'string', - 'uuid', - Rule::exists('categories', 'id')->where(function ($query) { - $query->where('user_id', $this->user()->id); - }), - ], + 'filters.category_ids.*' => ['string', 'uuid', $this->userOwned('categories')], 'filters.account_ids' => ['nullable', 'array'], 'filters.account_ids.*' => ['string', 'uuid'], 'filters.label_ids' => ['nullable', 'array'], - 'filters.label_ids.*' => [ - 'string', - 'uuid', - Rule::exists('labels', 'id')->where(function ($query) { - $query->where('user_id', $this->user()->id); - }), - ], + 'filters.label_ids.*' => ['string', 'uuid', $this->userOwned('labels')], 'filters.creditor_name' => ['nullable', 'string'], 'filters.debtor_name' => ['nullable', 'string'], 'filters.search' => ['nullable', 'string'], 'filters.search_text' => ['nullable', 'string'], - 'category_id' => [ - 'nullable', - Rule::exists('categories', 'id')->where(function ($query) { - $query->where('user_id', $this->user()->id); - }), - ], + 'category_id' => ['nullable', $this->userOwned('categories')], 'notes' => ['nullable', 'string'], 'notes_iv' => ['nullable', 'string', 'size:16'], 'label_ids' => ['nullable', 'array'], - 'label_ids.*' => [ - 'required', - 'string', - 'uuid', - Rule::exists('labels', 'id')->where(function ($query) { - $query->where('user_id', $this->user()->id); - }), - ], + 'label_ids.*' => ['required', 'string', 'uuid', $this->userOwned('labels')], ]; } diff --git a/app/Http/Requests/Concerns/ValidatesUserOwnedResources.php b/app/Http/Requests/Concerns/ValidatesUserOwnedResources.php new file mode 100644 index 00000000..16347000 --- /dev/null +++ b/app/Http/Requests/Concerns/ValidatesUserOwnedResources.php @@ -0,0 +1,26 @@ +where('user_id', $this->user()->id); + } + + /** + * Rule asserting the value references an account of the given type owned by the authenticated user. + */ + protected function userOwnedAccountOfType(AccountType $type): Exists + { + return $this->userOwned('accounts')->where('type', $type->value); + } +} diff --git a/app/Http/Requests/OpenBanking/MapAccountsRequest.php b/app/Http/Requests/OpenBanking/MapAccountsRequest.php index ee35efc8..41b71c34 100644 --- a/app/Http/Requests/OpenBanking/MapAccountsRequest.php +++ b/app/Http/Requests/OpenBanking/MapAccountsRequest.php @@ -2,11 +2,13 @@ namespace App\Http\Requests\OpenBanking; +use App\Http\Requests\Concerns\ValidatesUserOwnedResources; use Illuminate\Foundation\Http\FormRequest; -use Illuminate\Validation\Rule; class MapAccountsRequest extends FormRequest { + use ValidatesUserOwnedResources; + public function authorize(): bool { return $this->route('connection')->user_id === $this->user()->id; @@ -25,7 +27,7 @@ class MapAccountsRequest extends FormRequest 'nullable', 'uuid', 'required_if:mappings.*.action,link', - Rule::exists('accounts', 'id')->where('user_id', $this->user()->id), + $this->userOwned('accounts'), ], ]; } diff --git a/app/Http/Requests/Settings/StoreAccountRequest.php b/app/Http/Requests/Settings/StoreAccountRequest.php index 730d83d9..4c228c81 100644 --- a/app/Http/Requests/Settings/StoreAccountRequest.php +++ b/app/Http/Requests/Settings/StoreAccountRequest.php @@ -4,6 +4,7 @@ namespace App\Http\Requests\Settings; use App\Enums\AccountType; use App\Enums\PropertyType; +use App\Http\Requests\Concerns\ValidatesUserOwnedResources; use App\Models\Account; use App\Services\CurrencyOptions; use Illuminate\Contracts\Validation\ValidationRule; @@ -12,6 +13,8 @@ use Illuminate\Validation\Rule; class StoreAccountRequest extends FormRequest { + use ValidatesUserOwnedResources; + /** * Determine if the user is authorized to make this request. */ @@ -64,10 +67,7 @@ class StoreAccountRequest extends FormRequest 'linked_loan_account_id' => [ 'nullable', 'string', - Rule::exists('accounts', 'id')->where(function ($query) { - $query->where('user_id', $this->user()->id) - ->where('type', AccountType::Loan->value); - }), + $this->userOwnedAccountOfType(AccountType::Loan), ], 'notes' => ['nullable', 'string', 'max:2000'], 'revaluation_percentage' => ['nullable', 'numeric', 'min:-100', 'max:100'], @@ -85,10 +85,7 @@ class StoreAccountRequest extends FormRequest 'linked_real_estate_account_id' => [ 'nullable', 'string', - Rule::exists('accounts', 'id')->where(function ($query) { - $query->where('user_id', $this->user()->id) - ->where('type', AccountType::RealEstate->value); - }), + $this->userOwnedAccountOfType(AccountType::RealEstate), function (string $attribute, mixed $value, \Closure $fail): void { if (! is_string($value)) { return; diff --git a/app/Http/Requests/Settings/StoreAutomationRuleRequest.php b/app/Http/Requests/Settings/StoreAutomationRuleRequest.php index c849df87..bed453d0 100644 --- a/app/Http/Requests/Settings/StoreAutomationRuleRequest.php +++ b/app/Http/Requests/Settings/StoreAutomationRuleRequest.php @@ -2,12 +2,14 @@ namespace App\Http\Requests\Settings; +use App\Http\Requests\Concerns\ValidatesUserOwnedResources; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; -use Illuminate\Validation\Rule; class StoreAutomationRuleRequest extends FormRequest { + use ValidatesUserOwnedResources; + /** * Determine if the user is authorized to make this request. */ @@ -35,9 +37,7 @@ class StoreAutomationRuleRequest extends FormRequest 'action_category_id' => [ 'nullable', 'string', - Rule::exists('categories', 'id')->where(function ($query) { - $query->where('user_id', auth()->id()); - }), + $this->userOwned('categories'), ], 'action_note' => ['nullable', 'string'], 'action_note_iv' => ['nullable', 'string', 'required_with:action_note'], @@ -46,9 +46,7 @@ class StoreAutomationRuleRequest extends FormRequest 'required', 'string', 'uuid', - Rule::exists('labels', 'id')->where(function ($query) { - $query->where('user_id', auth()->id()); - }), + $this->userOwned('labels'), ], ]; } diff --git a/app/Http/Requests/Settings/UpdateAccountRequest.php b/app/Http/Requests/Settings/UpdateAccountRequest.php index 54729218..d73ef6db 100644 --- a/app/Http/Requests/Settings/UpdateAccountRequest.php +++ b/app/Http/Requests/Settings/UpdateAccountRequest.php @@ -4,6 +4,7 @@ namespace App\Http\Requests\Settings; use App\Enums\AccountType; use App\Enums\PropertyType; +use App\Http\Requests\Concerns\ValidatesUserOwnedResources; use App\Services\CurrencyOptions; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; @@ -11,6 +12,8 @@ use Illuminate\Validation\Rule; class UpdateAccountRequest extends FormRequest { + use ValidatesUserOwnedResources; + /** * Determine if the user is authorized to make this request. */ @@ -59,10 +62,7 @@ class UpdateAccountRequest extends FormRequest 'linked_loan_account_id' => [ 'nullable', 'string', - Rule::exists('accounts', 'id')->where(function ($query) { - $query->where('user_id', $this->user()->id) - ->where('type', AccountType::Loan->value); - }), + $this->userOwnedAccountOfType(AccountType::Loan), ], 'notes' => ['nullable', 'string', 'max:2000'], 'revaluation_percentage' => ['nullable', 'numeric', 'min:-100', 'max:100'], diff --git a/app/Http/Requests/StoreBudgetRequest.php b/app/Http/Requests/StoreBudgetRequest.php index 447a0b05..f5e760eb 100644 --- a/app/Http/Requests/StoreBudgetRequest.php +++ b/app/Http/Requests/StoreBudgetRequest.php @@ -4,11 +4,14 @@ namespace App\Http\Requests; use App\Enums\BudgetPeriodType; use App\Enums\RolloverType; +use App\Http\Requests\Concerns\ValidatesUserOwnedResources; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; class StoreBudgetRequest extends FormRequest { + use ValidatesUserOwnedResources; + public function authorize(): bool { return true; @@ -16,16 +19,14 @@ class StoreBudgetRequest extends FormRequest public function rules(): array { - $userId = $this->user()->id; - return [ 'name' => ['required', 'string', 'max:255'], 'period_type' => ['required', Rule::enum(BudgetPeriodType::class)], 'period_start_day' => ['nullable', 'integer', 'min:0', 'max:31'], 'category_ids' => ['nullable', 'array'], - 'category_ids.*' => [Rule::exists('categories', 'id')->where('user_id', $userId)], + 'category_ids.*' => [$this->userOwned('categories')], 'label_ids' => ['nullable', 'array'], - 'label_ids.*' => [Rule::exists('labels', 'id')->where('user_id', $userId)], + 'label_ids.*' => [$this->userOwned('labels')], 'rollover_type' => ['required', Rule::enum(RolloverType::class)], 'allocated_amount' => ['required', 'integer', 'min:0'], ]; diff --git a/app/Http/Requests/StoreRealEstateDetailRequest.php b/app/Http/Requests/StoreRealEstateDetailRequest.php index 08bd60dc..9b6ea990 100644 --- a/app/Http/Requests/StoreRealEstateDetailRequest.php +++ b/app/Http/Requests/StoreRealEstateDetailRequest.php @@ -4,12 +4,15 @@ namespace App\Http\Requests; use App\Enums\AccountType; use App\Enums\PropertyType; +use App\Http\Requests\Concerns\ValidatesUserOwnedResources; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; class StoreRealEstateDetailRequest extends FormRequest { + use ValidatesUserOwnedResources; + /** * Determine if the user is authorized to make this request. */ @@ -39,10 +42,7 @@ class StoreRealEstateDetailRequest extends FormRequest 'linked_loan_account_id' => [ 'nullable', 'string', - Rule::exists('accounts', 'id')->where(function ($query) { - $query->where('user_id', $this->user()->id) - ->where('type', AccountType::Loan->value); - }), + $this->userOwnedAccountOfType(AccountType::Loan), ], 'notes' => ['nullable', 'string', 'max:2000'], ]; diff --git a/app/Http/Requests/StoreTransactionRequest.php b/app/Http/Requests/StoreTransactionRequest.php index 117fd718..2e9f2147 100644 --- a/app/Http/Requests/StoreTransactionRequest.php +++ b/app/Http/Requests/StoreTransactionRequest.php @@ -3,11 +3,14 @@ namespace App\Http\Requests; use App\Enums\TransactionSource; +use App\Http\Requests\Concerns\ValidatesUserOwnedResources; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; class StoreTransactionRequest extends FormRequest { + use ValidatesUserOwnedResources; + public function authorize(): bool { return true; @@ -17,18 +20,8 @@ class StoreTransactionRequest extends FormRequest { return [ 'id' => ['sometimes', 'uuid'], - 'account_id' => [ - 'required', - Rule::exists('accounts', 'id')->where(function ($query) { - $query->where('user_id', $this->user()->id); - }), - ], - 'category_id' => [ - 'nullable', - Rule::exists('categories', 'id')->where(function ($query) { - $query->where('user_id', $this->user()->id); - }), - ], + 'account_id' => ['required', $this->userOwned('accounts')], + 'category_id' => ['nullable', $this->userOwned('categories')], 'description' => ['required', 'string'], 'description_iv' => ['nullable', 'string', 'size:16'], 'transaction_date' => ['required', 'date'], @@ -40,13 +33,7 @@ class StoreTransactionRequest extends FormRequest 'debtor_name' => ['nullable', 'string', 'max:255'], 'source' => ['required', Rule::enum(TransactionSource::class)], 'label_ids' => ['nullable', 'array'], - 'label_ids.*' => [ - 'string', - 'uuid', - Rule::exists('labels', 'id')->where(function ($query) { - $query->where('user_id', $this->user()->id); - }), - ], + 'label_ids.*' => ['string', 'uuid', $this->userOwned('labels')], ]; } diff --git a/app/Http/Requests/UpdateRealEstateDetailRequest.php b/app/Http/Requests/UpdateRealEstateDetailRequest.php index fc7ba89f..d907d547 100644 --- a/app/Http/Requests/UpdateRealEstateDetailRequest.php +++ b/app/Http/Requests/UpdateRealEstateDetailRequest.php @@ -4,12 +4,15 @@ namespace App\Http\Requests; use App\Enums\AccountType; use App\Enums\PropertyType; +use App\Http\Requests\Concerns\ValidatesUserOwnedResources; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; class UpdateRealEstateDetailRequest extends FormRequest { + use ValidatesUserOwnedResources; + /** * Determine if the user is authorized to make this request. */ @@ -40,10 +43,7 @@ class UpdateRealEstateDetailRequest extends FormRequest 'linked_loan_account_id' => [ 'nullable', 'string', - Rule::exists('accounts', 'id')->where(function ($query) { - $query->where('user_id', $this->user()->id) - ->where('type', AccountType::Loan->value); - }), + $this->userOwnedAccountOfType(AccountType::Loan), ], 'notes' => ['nullable', 'string', 'max:2000'], 'revaluation_percentage' => ['nullable', 'numeric', 'min:-100', 'max:100'], diff --git a/app/Http/Requests/UpdateTransactionRequest.php b/app/Http/Requests/UpdateTransactionRequest.php index 70fc220e..1fca7a75 100644 --- a/app/Http/Requests/UpdateTransactionRequest.php +++ b/app/Http/Requests/UpdateTransactionRequest.php @@ -2,11 +2,13 @@ namespace App\Http\Requests; +use App\Http\Requests\Concerns\ValidatesUserOwnedResources; use Illuminate\Foundation\Http\FormRequest; -use Illuminate\Validation\Rule; class UpdateTransactionRequest extends FormRequest { + use ValidatesUserOwnedResources; + public function authorize(): bool { return true; @@ -15,12 +17,7 @@ class UpdateTransactionRequest extends FormRequest public function rules(): array { return [ - 'category_id' => [ - 'nullable', - Rule::exists('categories', 'id')->where(function ($query) { - $query->where('user_id', $this->user()->id); - }), - ], + 'category_id' => ['nullable', $this->userOwned('categories')], 'description' => ['sometimes', 'string'], 'description_iv' => ['nullable', 'string', 'size:16'], 'notes' => ['nullable', 'string'], @@ -28,14 +25,7 @@ class UpdateTransactionRequest extends FormRequest 'creditor_name' => ['nullable', 'string', 'max:255'], 'debtor_name' => ['nullable', 'string', 'max:255'], 'label_ids' => ['nullable', 'array'], - 'label_ids.*' => [ - 'required', - 'string', - 'uuid', - Rule::exists('labels', 'id')->where(function ($query) { - $query->where('user_id', $this->user()->id); - }), - ], + 'label_ids.*' => ['required', 'string', 'uuid', $this->userOwned('labels')], ]; }