output:
Wall time: 3.3552 seconds
Output:
[{"type":"text","text":"# Search Results\n\n## Validation Available Validation Rules date_format:_format_,...\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### date_format:_format_,...\n\nThe field under validation must match one of the given _formats_. You should use **either** `date` or `date_format` when validating a field, not both. This validation rule supports all formats supported by PHP's [DateTime](https://www.php.net/manual/en/class.datetime.php) class.\n\nFor convenience, date-based rules may be constructed using the fluent `date` rule builder:\n\n```php\nuse Illuminate\\Validation\\Rule;\n\n'start_date' => [\n    'required',\n    Rule::date()->format('Y-m-d'),\n],\n```\n\n---\n\n## Expectations `json()`\nExpectations | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/expectations.md\n\n\n### `json()`\n\nThe `json()` modifier decodes the current expectation `$value` from JSON to an array.\n\n```php\nexpect('{\"name\":\"Nuno\",\"credit\":1000.00}')\n    ->json()\n    ->toHaveCount(2)\n    ->name->toBe('Nuno')\n    ->credit->toBeFloat();\n\nexpect('not-a-json')->json(); //Fails\n```\n\n---\n\n## Http Tests Validation Assertions assertInvalid\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n#### assertInvalid\n\nAssert that the response has validation errors for the given keys. This method may be used for asserting against responses where the validation errors are returned as a JSON structure or where the validation errors have been flashed to the session:\n\n```php\n$response->assertInvalid(['name', 'email']);\n```\n\nYou may also assert that a given key has a particular validation error message. When doing so, you may provide the entire message or only a small portion of the message:\n\n```php\n$response->assertInvalid([\n    'name' => 'The name field is required.',\n    'email' => 'valid email address',\n]);\n```\n\n---\n\n## Validation Form Request Validation Preparing Input for Validation\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n### Preparing Input for Validation\n\nIf you need to prepare or sanitize any data from the request before you apply your validation rules, you may use the `prepareForValidation` method:\n\n```php\nuse Illuminate\\Support\\Str;\n\n/**\n * Prepare the data for validation.\n */\nprotected function prepareForValidation(): void\n{\n    $this->merge([\n        'slug' => Str::slug($this->slug),\n    ]);\n}\n```\n\nLikewise, if you need to normalize any request data after validation is complete, you may use the `passedValidation` method:\n\n```php\n/**\n * Handle a passed validation attempt.\n */\nprotected function passedValidation(): void\n{\n    $this->replace(['name' => 'Taylor']);\n}\n```\n\n---\n\n## Validation Manually Creating Validators Performing Additional Validation\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n### Performing Additional Validation\n\nSometimes you need to perform additional validation after your initial validation is complete. You can accomplish this using the validator's `after` method. The `after` method accepts a closure or an array of callables which will be invoked after validation is complete. The given callables will receive an `Illuminate\\Validation\\Validator` instance, allowing you to raise additional error messages if necessary:\n\n```php\nuse Illuminate\\Support\\Facades\\Validator;\n\n$validator = Validator::make(/* ... */);\n\n$validator->after(function ($validator) {\n    if ($this->somethingElseIsInvalid()) {\n        $validator->errors()->add(\n            'field', 'Something is wrong with this field!'\n        );\n    }\n});\n\nif ($validator->fails()) {\n    // ...\n}\n```\n\nAs noted, the `after` method also accepts an array of callables, which is particularly convenient if your \"after validation\" logic is encapsulated in invokable classes, which will receive an `Illuminate\\Validation\\Validator` instance via their `__invoke` method:\n\n```php\nuse App\\Validation\\ValidateShippingTime;\nuse App\\Validation\\ValidateUserStatus;\n\n$validator->after([\n    new ValidateUserStatus,\n    new ValidateShippingTime,\n    function ($validator) {\n        // ...\n    },\n]);\n```\n\n---\n\n## Validation Custom Validation Rules Implicit Rules\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n### Implicit Rules\n\nBy default, when an attribute being validated is not present or contains an empty string, normal validation rules, including custom rules, are not run. For example, the [unique](#rule-unique) rule will not be run against an empty string:\n\n```php\nuse Illuminate\\Support\\Facades\\Validator;\n\n$rules = ['name' => 'unique:users,name'];\n\n$input = ['name' => ''];\n\nValidator::make($input, $rules)->passes(); // true\n```\n\nFor a custom rule to run even when an attribute is empty, the rule must imply that the attribute is required. To quickly generate a new implicit rule object, you may use the `make:rule` Artisan command with the `--implicit` option:\n\n```shell\nphp artisan make:rule Uppercase --implicit\n```\n\n> [!WARNING]\n> An \"implicit\" rule only _implies_ that the attribute is required. Whether it actually invalidates a missing or empty attribute is up to you.\n\n---\n\n## Expectations `toBeJson()`\nExpectations | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/expectations.md\n\n\n### `toBeJson()`\n\nThis expectation ensures that `$value` is a JSON string.\n\n```php\nexpect('{\"hello\":\"world\"}')->toBeJson();\n```\n\n---\n\n## Http Tests Available Assertions Response Assertions assertJsonValidationErrors\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n#### assertJsonValidationErrors\n\nAssert that the response has the given JSON validation errors for the given keys. This method should be used when asserting against responses where the validation errors are returned as a JSON structure instead of being flashed to the session:\n\n```php\n$response->assertJsonValidationErrors(array $data, $responseKey = 'errors');\n```\n\n> [!NOTE]\n> The more generic [assertInvalid](#assert-invalid) method may be used to assert that a response has validation errors returned as JSON **or** that errors were flashed to session storage.\n\n---\n\n## Validation Available Validation Rules exclude\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### exclude\n\nThe field under validation will be excluded from the request data returned by the `validate` and `validated` methods.\n\n---\n\n## Validation Available Validation Rules json\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### json\n\nThe field under validation must be a valid JSON string.\n\n---\n\n## Validation Custom Validation Rules Using Rule Objects\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n### Using Rule Objects\n\nLaravel provides a variety of helpful validation rules; however, you may wish to specify some of your own. One method of registering custom validation rules is using rule objects. To generate a new rule object, you may use the `make:rule` Artisan command. Let's use this command to generate a rule that verifies a string is uppercase. Laravel will place the new rule in the `app/Rules` directory. If this directory does not exist, Laravel will create it when you execute the Artisan command to create your rule:\n\n```shell\nphp artisan make:rule Uppercase\n```\n\nOnce the rule has been created, we are ready to define its behavior. A rule object contains a single method: `validate`. This method receives the attribute name, its value, and a callback that should be invoked on failure with the validation error message:\n\n```php\n<?php\n\nnamespace App\\Rules;\n\nuse Closure;\nuse Illuminate\\Contracts\\Validation\\ValidationRule;\n\nclass Uppercase implements ValidationRule\n{\n    /**\n     * Run the validation rule.\n     */\n    public function validate(string $attribute, mixed $value, Closure $fail): void\n    {\n        if (strtoupper($value) !== $value) {\n            $fail('The :attribute must be uppercase.');\n        }\n    }\n}\n```\n\nOnce the rule has been defined, you may attach it to a validator by passing an instance of the rule object with your other validation rules:\n\n```php\nuse App\\Rules\\Uppercase;\n\n$request->validate([\n    'name' => ['required', 'string', new Uppercase],\n]);\n```\n\n#### Translating Validation Messages\n\nInstead of providing a literal error message to the `$fail` closure, you may also provide a [translation string key](/docs/{{version}}/localization) and instruct Laravel to translate the error message:\n\n```php\nif (strtoupper($value) !== $value) {\n    $fail('validation.uppercase')->translate();\n}\n```\n\nIf necessary, you may provide placeholder replacements and the preferred language as the first and second arguments to the `translate` method:\n\n```php\n$fail('validation.location')->translate([\n    'value' => $this->value,\n], 'fr');\n```\n\n#### Accessing Additional Data\n\nIf your custom validation rule class needs to access all of the other data undergoing validation, your rule class may implement the `Illuminate\\Contracts\\Validation\\DataAwareRule` interface. This interface requires your class to define a `setData` method. This method will automatically be invoked by Laravel (before validation proceeds) with all of the data under validation:\n\n```php\n<?php\n\nnamespace App\\Rules;\n\nuse Illuminate\\Contracts\\Validation\\DataAwareRule;\nuse Illuminate\\Contracts\\Validation\\ValidationRule;\n\nclass Uppercase implements DataAwareRule, ValidationRule\n{\n    /**\n     * All of the data under validation.\n     *\n     * @var array<string, mixed>\n     */\n    protected $data = [];\n\n    // ...\n\n    /**\n     * Set the data under validation.\n     *\n     * @param  array<string, mixed>  $data\n     */\n    public function setData(array $data): static\n    {\n        $this->data = $data;\n\n        return $this;\n    }\n}\n```\n\nOr, if your validation rule requires access to the validator instance performing the validation, you may implement the `ValidatorAwareRule` interface:\n\n```php\n<?php\n\nnamespace App\\Rules;\n\nuse Illuminate\\Contracts\\Validation\\ValidationRule;\nuse Illuminate\\Contracts\\Validation\\ValidatorAwareRule;\nuse Illuminate\\Validation\\Validator;\n\nclass Uppercase implements ValidationRule, ValidatorAwareRule\n{\n    /**\n     * The validator instance.\n     *\n     * @var \\Illuminate\\Validation\\Validator\n     */\n    protected $validator;\n\n    // ...\n\n    /**\n     * Set the current validator.\n     */\n    public function setValidator(Validator $validator): static\n    {\n        $this->validator = $validator;\n\n        return $this;\n    }\n}\n```\n\n---\n\n## Validation Form Request Validation Customizing the Error Messages Customizing the Validation Attributes\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### Customizing the Validation Attributes\n\nMany of Laravel's built-in validation rule error messages contain an `:attribute` placeholder. If you would like the `:attribute` placeholder of your validation message to be replaced with a custom attribute name, you may specify the custom names by overriding the `attributes` method. This method should return an array of attribute / name pairs:\n\n```php\n/**\n * Get custom attributes for validator errors.\n *\n * @return array<string, string>\n */\npublic function attributes(): array\n{\n    return [\n        'email' => 'email address',\n    ];\n}\n```\n\n---\n\n## Validation Form Request Validation\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n## Form Request Validation\n\n---\n\n## Strings Fluent Strings `isJson` {.collection-method}\nStrings | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/strings.md\n\n\n#### `isJson` {.collection-method}\n\nThe `isJson` method determines if a given string is valid JSON:\n\n```php\nuse Illuminate\\Support\\Str;\n\n$result = Str::of('[1,2,3]')->isJson();\n\n// true\n\n$result = Str::of('{\"first\": \"John\", \"last\": \"Doe\"}')->isJson();\n\n// true\n\n$result = Str::of('{first: \"John\", last: \"Doe\"}')->isJson();\n\n// false\n```\n\n---\n\n## Validation Form Request Validation Customizing the Error Messages\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n### Customizing the Error Messages\n\nYou may customize the error messages used by the form request by overriding the `messages` method. This method should return an array of attribute / rule pairs and their corresponding error messages:\n\n```php\n/**\n * Get the error messages for the defined validation rules.\n *\n * @return array<string, string>\n */\npublic function messages(): array\n{\n    return [\n        'title.required' => 'A title is required',\n        'body.required' => 'A message is required',\n    ];\n}\n```\n\n---\n\n## Validation Custom Validation Rules Using Closures\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n### Using Closures\n\nIf you only need the functionality of a custom rule once throughout your application, you may use a closure instead of a rule object. The closure receives the attribute's name, the attribute's value, and a `$fail` callback that should be called if validation fails:\n\n```php\nuse Illuminate\\Support\\Facades\\Validator;\nuse Closure;\n\n$validator = Validator::make($request->all(), [\n    'title' => [\n        'required',\n        'max:255',\n        function (string $attribute, mixed $value, Closure $fail) {\n            if ($value === 'foo') {\n                $fail(\"The {$attribute} is invalid.\");\n            }\n        },\n    ],\n]);\n```\n\n---\n\n## Validation Form Request Validation Creating Form Requests Customizing the Redirect Location\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### Customizing the Redirect Location\n\nWhen form request validation fails, a redirect response will be generated to send the user back to their previous location. However, you are free to customize this behavior. To do so, define a `$redirect` property on your form request:\n\n```php\n/**\n * The URI that users should be redirected to if validation fails.\n *\n * @var string\n */\nprotected $redirect = '/dashboard';\n```\n\nOr, if you would like to redirect users to a named route, you may define a `$redirectRoute` property instead:\n\n```php\n/**\n * The route that users should be redirected to if validation fails.\n *\n * @var string\n */\nprotected $redirectRoute = 'dashboard';\n```\n\n---\n\n## Validation Form Request Validation Creating Form Requests\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n### Creating Form Requests\n\nFor more complex validation scenarios, you may wish to create a \"form request\". Form requests are custom request classes that encapsulate their own validation and authorization logic. To create a form request class, you may use the `make:request` Artisan CLI command:\n\n```shell\nphp artisan make:request StorePostRequest\n```\n\nThe generated form request class will be placed in the `app/Http/Requests` directory. If this directory does not exist, it will be created when you run the `make:request` command. Each form request generated by Laravel has two methods: `authorize` and `rules`.\n\nAs you might have guessed, the `authorize` method is responsible for determining if the currently authenticated user can perform the action represented by the request, while the `rules` method returns the validation rules that should apply to the request's data:\n\n```php\n/**\n * Get the validation rules that apply to the request.\n *\n * @return array<string, \\Illuminate\\Contracts\\Validation\\ValidationRule|array<mixed>|string>\n */\npublic function rules(): array\n{\n    return [\n        'title' => 'required|unique:posts|max:255',\n        'body' => 'required',\n    ];\n}\n```\n\n> [!NOTE]\n> You may type-hint any dependencies you require within the `rules` method's signature. They will automatically be resolved via the Laravel [service container](/docs/{{version}}/container).\n\nSo, how are the validation rules evaluated? All you need to do is type-hint the request on your controller method. The incoming form request is validated before the controller method is called, meaning you do not need to clutter your controller with any validation logic:\n\n```php\n/**\n * Store a new blog post.\n */\npublic function store(StorePostRequest $request): RedirectResponse\n{\n    // The incoming request is valid...\n\n    // Retrieve the validated input data...\n    $validated = $request->validated();\n\n    // Retrieve a portion of the validated input data...\n    $validated = $request->safe()->only(['name', 'email']);\n    $validated = $request->safe()->except(['name', 'email']);\n\n    // Store the blog post...\n\n    return redirect('/posts');\n}\n```\n\nIf validation fails, a redirect response will be generated to send the user back to their previous location. The errors will also be flashed to the session so they are available for display. If the request was an XHR request, an HTTP response with a 422 status code will be returned to the user including a [JSON representation of the validation errors](#validation-error-response-format).\n\n> [!NOTE]\n> Need to add real-time form request validation to your Inertia powered Laravel frontend? Check out [Laravel Precognition](/docs/{{version}}/precognition).\n\n---\n\n## Http Tests Testing JSON APIs\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n## Testing JSON APIs\n\nLaravel also provides several helpers for testing JSON APIs and their responses. For example, the `json`, `getJson`, `postJson`, `putJson`, `patchJson`, `deleteJson`, and `optionsJson` methods may be used to issue JSON requests with various HTTP verbs. You may also easily pass data and headers to these methods. To get started, let's write a test to make a `POST` request to `/api/user` and assert that the expected JSON data was returned:\n\n```php tab=Pest\n<?php\n\ntest('making an api request', function () {\n    $response = $this->postJson('/api/user', ['name' => 'Sally']);\n\n    $response\n        ->assertStatus(201)\n        ->assertJson([\n            'created' => true,\n        ]);\n});\n```\n\n```php tab=PHPUnit\n<?php\n\nnamespace Tests\\Feature;\n\nuse Tests\\TestCase;\n\nclass ExampleTest extends TestCase\n{\n    /**\n     * A basic functional test example.\n     */\n    public function test_making_an_api_request(): void\n    {\n        $response = $this->postJson('/api/user', ['name' => 'Sally']);\n\n        $response\n            ->assertStatus(201)\n            ->assertJson([\n                'created' => true,\n            ]);\n    }\n}\n```\n\nIn addition, JSON response data may be accessed as array variables on the response, making it convenient for you to inspect the individual values returned within a JSON response:\n\n```php tab=Pest\nexpect($response['created'])->toBeTrue();\n```\n\n```php tab=PHPUnit\n$this->assertTrue($response['created']);\n```\n\n> [!NOTE]\n> The `assertJson` method converts the response to an array to verify that the given array exists within the JSON response returned by the application. So, if there are other properties in the JSON response, this test will still pass as long as the given fragment is present.\n\n---\n\n## Validation Custom Validation Rules\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n## Custom Validation Rules\n\n---\n\n## Urls URLs for Named Routes Signed URLs Validating Signed Route Requests\nUrls | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/urls.md\n\n\n#### Validating Signed Route Requests\n\nTo verify that an incoming request has a valid signature, you should call the `hasValidSignature` method on the incoming `Illuminate\\Http\\Request` instance:\n\n```php\nuse Illuminate\\Http\\Request;\n\nRoute::get('/unsubscribe/{user}', function (Request $request) {\n    if (! $request->hasValidSignature()) {\n        abort(401);\n    }\n\n    // ...\n})->name('unsubscribe');\n```\n\nSometimes, you may need to allow your application's frontend to append data to a signed URL, such as when performing client-side pagination. Therefore, you can specify request query parameters that should be ignored when validating a signed URL using the `hasValidSignatureWhileIgnoring` method. Remember, ignoring parameters allows anyone to modify those parameters on the request:\n\n```php\nif (! $request->hasValidSignatureWhileIgnoring(['page', 'order'])) {\n    abort(401);\n}\n```\n\nInstead of validating signed URLs using the incoming request instance, you may assign the `signed` (`Illuminate\\Routing\\Middleware\\ValidateSignature`) [middleware](/docs/{{version}}/middleware) to the route. If the incoming request does not have a valid signature, the middleware will automatically return a `403` HTTP response:\n\n```php\nRoute::post('/unsubscribe/{user}', function (Request $request) {\n    // ...\n})->name('unsubscribe')->middleware('signed');\n```\n\nIf your signed URLs do not include the domain in the URL hash, you should provide the `relative` argument to the middleware:\n\n```php\nRoute::post('/unsubscribe/{user}', function (Request $request) {\n    // ...\n})->name('unsubscribe')->middleware('signed:relative');\n```\n\n---\n\n## Strings Strings `Str::isJson()` {.collection-method}\nStrings | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/strings.md\n\n\n#### `Str::isJson()` {.collection-method}\n\nThe `Str::isJson` method determines if the given string is valid JSON:\n\n```php\nuse Illuminate\\Support\\Str;\n\n$result = Str::isJson('[1,2,3]');\n\n// true\n\n$result = Str::isJson('{\"first\": \"John\", \"last\": \"Doe\"}');\n\n// true\n\n$result = Str::isJson('{first: \"John\", last: \"Doe\"}');\n\n// false\n```\n\n---\n\n## Validation Form Request Validation Authorizing Form Requests\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n### Authorizing Form Requests\n\nThe form request class also contains an `authorize` method. Within this method, you may determine if the authenticated user actually has the authority to update a given resource. For example, you may determine if a user actually owns a blog comment they are attempting to update. Most likely, you will interact with your [authorization gates and policies](/docs/{{version}}/authorization) within this method:\n\n```php\nuse App\\Models\\Comment;\n\n/**\n * Determine if the user is authorized to make this request.\n */\npublic function authorize(): bool\n{\n    $comment = Comment::find($this->route('comment'));\n\n    return $comment && $this->user()->can('update', $comment);\n}\n```\n\nSince all form requests extend the base Laravel request class, we may use the `user` method to access the currently authenticated user. Also, note the call to the `route` method in the example above. This method grants you access to the URI parameters defined on the route being called, such as the `{comment}` parameter in the example below:\n\n```php\nRoute::post('/comment/{comment}');\n```\n\nTherefore, if your application is taking advantage of [route model binding](/docs/{{version}}/routing#route-model-binding), your code may be made even more succinct by accessing the resolved model as a property of the request:\n\n```php\nreturn $this->user()->can('update', $this->comment);\n```\n\nIf the `authorize` method returns `false`, an HTTP response with a 403 status code will automatically be returned and your controller method will not execute.\n\nIf you plan to handle authorization logic for the request in another part of your application, you may remove the `authorize` method completely, or simply return `true`:\n\n```php\n/**\n * Determine if the user is authorized to make this request.\n */\npublic function authorize(): bool\n{\n    return true;\n}\n```\n\n> [!NOTE]\n> You may type-hint any dependencies you need within the `authorize` method's signature. They will automatically be resolved via the Laravel [service container](/docs/{{version}}/container).\n\n---\n\n## Validation Form Request Validation Creating Form Requests Stopping on the First Validation Failure\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### Stopping on the First Validation Failure\n\nBy adding a `stopOnFirstFailure` property to your request class, you may inform the validator that it should stop validating all attributes once a single validation failure has occurred:\n\n```php\n/**\n * Indicates if the validator should stop on the first rule failure.\n *\n * @var bool\n */\nprotected $stopOnFirstFailure = true;\n```\n\n---\n\n## Responses Streamed Responses Streamed JSON Responses\nResponses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md\n\n\n### Streamed JSON Responses\n\nIf you need to stream JSON data incrementally, you may utilize the `streamJson` method. This method is especially useful for large datasets that need to be sent progressively to the browser in a format that can be easily parsed by JavaScript:\n\n```php\nuse App\\Models\\User;\n\nRoute::get('/users.json', function () {\n    return response()->streamJson([\n        'users' => User::cursor(),\n    ]);\n});\n```\n\nThe `useJsonStream` hook is identical to the [useStream hook](#consuming-streamed-responses) except that it will attempt to parse the data as JSON once it has finished streaming:\n\n```tsx tab=React\nimport { useJsonStream } from \"@laravel/stream-react\";\n\ntype User = {\n    id: number;\n    name: string;\n    email: string;\n};\n\nfunction App() {\n    const { data, send } = useJsonStream<{ users: User[] }>(\"users\");\n\n    const loadUsers = () => {\n        send({\n            query: \"taylor\",\n        });\n    };\n\n    return (\n        <div>\n            <ul>\n                {data?.users.map((user) => (\n                    <li>\n                        {user.id}: {user.name}\n                    </li>\n                ))}\n            </ul>\n            <button onClick={loadUsers}>Load Users</button>\n        </div>\n    );\n}\n```\n\n```vue tab=Vue\n<script setup lang=\"ts\">\nimport { useJsonStream } from \"@laravel/stream-vue\";\n\ntype User = {\n    id: number;\n    name: string;\n    email: string;\n};\n\nconst { data, send } = useJsonStream<{ users: User[] }>(\"users\");\n\nconst loadUsers = () => {\n    send({\n        query: \"taylor\",\n    });\n};\n</script>\n\n<template>\n    <div>\n        <ul>\n            <li v-for=\"user in data?.users\" :key=\"user.id\">\n                {{ user.id }}: {{ user.name }}\n            </li>\n        </ul>\n        <button @click=\"loadUsers\">Load Users</button>\n    </div>\n</template>\n```\n\n```svelte tab=Svelte\n<script>\nimport { useJsonStream } from \"@laravel/stream-svelte\";\n\nconst stream = useJsonStream(\"users\");\n\nconst loadUsers = () => {\n    stream.send({\n        query: \"taylor\",\n    });\n};\n</script>\n\n<div>\n    <ul>\n        {#if $stream.data?.users}\n            {#each $stream.data.users as user (user.id)}\n                <li>{user.id}: {user.name}</li>\n            {/each}\n        {/if}\n    </ul>\n    <button onclick={loadUsers}>Load Users</button>\n</div>\n```\n\n---\n\n## Http Tests Testing JSON APIs Fluent JSON Testing Scoping JSON Collection Assertions\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n#### Scoping JSON Collection Assertions\n\nSometimes, your application's routes will return JSON collections that are assigned named keys:\n\n```php\nRoute::get('/users', function () {\n    return [\n        'meta' => [...],\n        'users' => User::all(),\n    ];\n})\n```\n\nWhen testing these routes, you may use the `has` method to assert against the number of items in the collection. In addition, you may use the `has` method to scope a chain of assertions:\n\n```php\n$response\n    ->assertJson(fn (AssertableJson $json) =>\n        $json->has('meta')\n            ->has('users', 3)\n            ->has('users.0', fn (AssertableJson $json) =>\n                $json->where('id', 1)\n                    ->where('name', 'Victoria Faith')\n                    ->where('email', fn (string $email) => str($email)->is('victoria@gmail.com'))\n                    ->missing('password')\n                    ->etc()\n            )\n    );\n```\n\nHowever, instead of making two separate calls to the `has` method to assert against the `users` collection, you may make a single call which provides a closure as its third parameter. When doing so, the closure will automatically be invoked and scoped to the first item in the collection:\n\n```php\n$response\n    ->assertJson(fn (AssertableJson $json) =>\n        $json->has('meta')\n            ->has('users', 3, fn (AssertableJson $json) =>\n                $json->where('id', 1)\n                    ->where('name', 'Victoria Faith')\n                    ->where('email', fn (string $email) => str($email)->is('victoria@gmail.com'))\n                    ->missing('password')\n                    ->etc()\n            )\n    );\n```\n\n---\n\n## Validation Validating Passwords Defining Default Password Rules\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### Defining Default Password Rules\n\nYou may find it convenient to specify the default validation rules for passwords in a single location of your application. You can easily accomplish this using the `Password::defaults` method, which accepts a closure. The closure given to the `defaults` method should return the default configuration of the Password rule. Typically, the `defaults` rule should be called within the `boot` method of one of your application's service providers:\n\n```php\nuse Illuminate\\Validation\\Rules\\Password;\n\n/**\n * Bootstrap any application services.\n */\npublic function boot(): void\n{\n    Password::defaults(function () {\n        $rule = Password::min(8);\n\n        return $this->app->isProduction()\n            ? $rule->mixedCase()->uncompromised()\n            : $rule;\n    });\n}\n```\n\nThen, when you would like to apply the default rules to a particular password undergoing validation, you may invoke the `defaults` method with no arguments:\n\n```php\n'password' => ['required', Password::defaults()],\n```\n\nOccasionally, you may want to attach additional validation rules to your default password validation rules. You may use the `rules` method to accomplish this:\n\n```php\nuse App\\Rules\\ZxcvbnRule;\n\nPassword::defaults(function () {\n    $rule = Password::min(8)->rules([new ZxcvbnRule]);\n\n    // ...\n});\n```\n\n---\n\n## Validation Conditionally Adding Rules Skipping Validation When Fields Have Certain Values\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### Skipping Validation When Fields Have Certain Values\n\nYou may occasionally wish to not validate a given field if another field has a given value. You may accomplish this using the `exclude_if` validation rule. In this example, the `appointment_date` and `doctor_name` fields will not be validated if the `has_appointment` field has a value of `false`:\n\n```php\nuse Illuminate\\Support\\Facades\\Validator;\n\n$validator = Validator::make($data, [\n    'has_appointment' => 'required|boolean',\n    'appointment_date' => 'exclude_if:has_appointment,false|required|date',\n    'doctor_name' => 'exclude_if:has_appointment,false|required|string',\n]);\n```\n\nAlternatively, you may use the `exclude_unless` rule to not validate a given field unless another field has a given value:\n\n```php\n$validator = Validator::make($data, [\n    'has_appointment' => 'required|boolean',\n    'appointment_date' => 'exclude_unless:has_appointment,true|required|date',\n    'doctor_name' => 'exclude_unless:has_appointment,true|required|string',\n]);\n```\n\n_[Truncated]_"}]


server: laravel-boost

tool: database-schema

filter: pages

type: text

engine: sqlite

type: integer

type: varchar

default: 'draft'

type: datetime

foreign_schema: main

foreign_table: stores

on_update: no action

on_delete: cascade


command: sed -n '1,240p' app/Http/Resources/Admin/V1/CollectionResource.php

CollectionResource.php

type: read

aggregated_output:
<?php

namespace App\Http\Resources\Admin\V1;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class CollectionResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'store_id' => $this->store_id,
            'title' => $this->title,
            'handle' => $this->handle,
            'description_html' => $this->description_html,
            'type' => $this->type?->value,
            'status' => $this->status?->value,
            'products_count' => $this->whenCounted('products'),
            'products' => $this->whenLoaded('products', fn () => $this->products->map(fn ($product): array => [
                'id' => $product->id,
                'title' => $product->title,
                'handle' => $product->handle,
                'position' => $product->pivot?->position,
            ])->values()),
            'created_at' => $this->created_at?->toIso8601String(),
            'updated_at' => $this->updated_at?->toIso8601String(),
        ];
    }
}

status: completed


command:
/bin/zsh
-lc
sed -n '1,260p' app/Models/Page.php

Page.php

cmd: sed -n '1,260p' app/Models/Page.php

aggregated_output:
<?php

namespace App\Models;

use App\Enums\PageStatus;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Page extends Model
{
    /** @use HasFactory<\Database\Factories\PageFactory> */
    use BelongsToStore, HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'title',
        'handle',
        'body_html',
        'status',
        'published_at',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'status' => 'draft',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    public function isPublished(): bool
    {
        return $this->status === PageStatus::Published && $this->published_at !== null;
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'status' => PageStatus::class,
            'published_at' => 'datetime',
        ];
    }
}


command: sed -n '1,260p' app/Http/Controllers/Api/Admin/V1/CollectionController.php

CollectionController.php

aggregated_output:
<?php

namespace App\Http\Controllers\Api\Admin\V1;

use App\Http\Controllers\Controller;
use App\Http\Resources\Admin\V1\CollectionResource;
use App\Models\Collection;
use App\Models\Store;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;

class CollectionController extends Controller
{
    public function index(Request $request, Store $store): AnonymousResourceCollection
    {
        $this->authorizeStore($request, $store);

        $validated = $request->validate([
            'status' => ['nullable', Rule::in(['draft', 'active', 'archived'])],
            'query' => ['nullable', 'string', 'max:255'],
            'per_page' => ['nullable', 'integer', 'min:1', 'max:100'],
        ]);

        $collections = Collection::withoutGlobalScopes()
            ->withCount('products')
            ->where('store_id', $store->getKey())
            ->when(data_get($validated, 'status'), fn (Builder $query, string $status) => $query->where('status', $status))
            ->when(data_get($validated, 'query'), function (Builder $query, string $search): void {
                $query->where('title', 'like', '%'.$search.'%');
            })
            ->latest('updated_at')
            ->latest('id')
            ->paginate((int) data_get($validated, 'per_page', 25));

        return CollectionResource::collection($collections);
    }

    public function store(Request $request, Store $store): JsonResponse
    {
        $this->authorizeStore($request, $store);

        $validated = $this->validatePayload($request, $store);

        $collection = DB::transaction(function () use ($store, $validated): Collection {
            $collection = Collection::withoutGlobalScopes()->create([
                'store_id' => $store->getKey(),
                ...$this->attributesForCreate($validated),
            ]);

            if (array_key_exists('product_ids', $validated)) {
                $this->replaceProducts($collection, $validated['product_ids']);
            }

            return $collection;
        });

        return CollectionResource::make($this->loadCollection($collection))
            ->response()
            ->setStatusCode(201);
    }

    public function update(Request $request, Store $store, Collection $collection): CollectionResource
    {
        $this->authorizeStore($request, $store);
        $this->abortUnlessCollectionBelongsToStore($collection, $store);

        $validated = $this->validatePayload($request, $store, $collection);

        DB::transaction(function () use ($collection, $validated): void {
            $attributes = $this->attributesForUpdate($validated);

            if ($attributes !== []) {
                $collection->update($attributes);
            }

            if (array_key_exists('product_ids', $validated)) {
                $this->replaceProducts($collection, $validated['product_ids']);
            } else {
                $this->addProducts($collection, $validated['add_product_ids'] ?? []);
                $this->removeProducts($collection, $validated['remove_product_ids'] ?? []);
            }
        });

        return CollectionResource::make($this->loadCollection($collection->refresh()));
    }

    public function destroy(Request $request, Store $store, Collection $collection): JsonResponse
    {
        $this->authorizeStore($request, $store);
        $this->abortUnlessCollectionBelongsToStore($collection, $store);

        $collection->delete();

        return response()->json(['message' => 'Collection deleted']);
    }

    private function authorizeStore(Request $request, Store $store): void
    {
        if (! $request->attributes->has('admin_api_oauth_token')) {
            abort_unless($request->user()?->stores()->whereKey($store->getKey())->exists(), 403);
        }

        app()->instance('current_store', $store);
    }

    private function abortUnlessCollectionBelongsToStore(Collection $collection, Store $store): void
    {
        abort_unless((int) $collection->store_id === $store->getKey(), 404);
    }

    /**
     * @return array<string, mixed>
     */
    private function validatePayload(Request $request, Store $store, ?Collection $collection = null): array
    {
        $creating = $collection === null;

        return $request->validate([
            'title' => [$creating ? 'required' : 'sometimes', 'string', 'max:255'],
            'handle' => [
                'sometimes',
                'nullable',
                'string',
                'max:255',
                Rule::unique('collections', 'handle')
                    ->where('store_id', $store->getKey())
                    ->ignore($collection?->getKey()),
            ],
            'description_html' => ['sometimes', 'nullable', 'string', 'max:65535'],
            'type' => [$creating ? 'required' : 'sometimes', Rule::in(['manual', 'automated'])],
            'status' => ['sometimes', Rule::in(['draft', 'active', 'archived'])],
            'product_ids' => ['sometimes', 'array'],
            'product_ids.*' => ['integer', Rule::exists('products', 'id')->where('store_id', $store->getKey())],
            'add_product_ids' => ['sometimes', 'array'],
            'add_product_ids.*' => ['integer', Rule::exists('products', 'id')->where('store_id', $store->getKey())],
            'remove_product_ids' => ['sometimes', 'array'],
            'remove_product_ids.*' => ['integer', Rule::exists('products', 'id')->where('store_id', $store->getKey())],
        ]);
    }

    /**
     * @param  array<string, mixed>  $validated
     * @return array<string, mixed>
     */
    private function attributesForCreate(array $validated): array
    {
        $title = (string) $validated['title'];
        $handle = filled($validated['handle'] ?? null) ? (string) $validated['handle'] : $title;

        return [
            'title' => $title,
            'handle' => Str::slug($handle),
            'description_html' => $validated['description_html'] ?? null,
            'type' => $validated['type'],
            'status' => $validated['status'] ?? 'active',
        ];
    }

    /**
     * @param  array<string, mixed>  $validated
     * @return array<string, mixed>
     */
    private function attributesForUpdate(array $validated): array
    {
        $attributes = Arr::only($validated, ['title', 'description_html', 'type', 'status']);

        if (array_key_exists('handle', $validated) && filled($validated['handle'])) {
            $attributes['handle'] = Str::slug((string) $validated['handle']);
        }

        return $attributes;
    }

    /**
     * @param  array<int, int>  $productIds
     */
    private function replaceProducts(Collection $collection, array $productIds): void
    {
        $collection->products()->sync($this->positionedProductIds($productIds));
    }

    /**
     * @param  array<int, int>  $productIds
     */
    private function addProducts(Collection $collection, array $productIds): void
    {
        if ($productIds === []) {
            return;
        }

        $startPosition = (int) $collection->products()->max('collection_products.position') + 1;

        $collection->products()->syncWithoutDetaching($this->positionedProductIds($productIds, $startPosition));
    }

    /**
     * @param  array<int, int>  $productIds
     */
    private function removeProducts(Collection $collection, array $productIds): void
    {
        if ($productIds === []) {
            return;
        }

        $collection->products()->detach($productIds);
    }

    /**
     * @param  array<int, int>  $productIds
     * @return array<int, array{position: int}>
     */
    private function positionedProductIds(array $productIds, int $startPosition = 0): array
    {
        return collect($productIds)
            ->unique()
            ->values()
            ->mapWithKeys(fn (int $productId, int $position): array => [$productId => ['position' => $startPosition + $position]])
            ->all();
    }

    private function loadCollection(Collection $collection): Collection
    {
        return $collection->load('products')->loadCount('products');
    }
}


output:
Chunk ID: dcd8b4
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2085
Output:
<?php

namespace App\Http\Controllers\Api\Admin\V1;

use App\Http\Controllers\Controller;
use App\Http\Resources\Admin\V1\CollectionResource;
use App\Models\Collection;
use App\Models\Store;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;

class CollectionController extends Controller
{
    public function index(Request $request, Store $store): AnonymousResourceCollection
    {
        $this->authorizeStore($request, $store);

        $validated = $request->validate([
            'status' => ['nullable', Rule::in(['draft', 'active', 'archived'])],
            'query' => ['nullable', 'string', 'max:255'],
            'per_page' => ['nullable', 'integer', 'min:1', 'max:100'],
        ]);

        $collections = Collection::withoutGlobalScopes()
            ->withCount('products')
            ->where('store_id', $store->getKey())
            ->when(data_get($validated, 'status'), fn (Builder $query, string $status) => $query->where('status', $status))
            ->when(data_get($validated, 'query'), function (Builder $query, string $search): void {
                $query->where('title', 'like', '%'.$search.'%');
            })
            ->latest('updated_at')
            ->latest('id')
            ->paginate((int) data_get($validated, 'per_page', 25));

        return CollectionResource::collection($collections);
    }

    public function store(Request $request, Store $store): JsonResponse
    {
        $this->authorizeStore($request, $store);

        $validated = $this->validatePayload($request, $store);

        $collection = DB::transaction(function () use ($store, $validated): Collection {
            $collection = Collection::withoutGlobalScopes()->create([
                'store_id' => $store->getKey(),
                ...$this->attributesForCreate($validated),
            ]);

            if (array_key_exists('product_ids', $validated)) {
                $this->replaceProducts($collection, $validated['product_ids']);
            }

            return $collection;
        });

        return CollectionResource::make($this->loadCollection($collection))
            ->response()
            ->setStatusCode(201);
    }

    public function update(Request $request, Store $store, Collection $collection): CollectionResource
    {
        $this->authorizeStore($request, $store);
        $this->abortUnlessCollectionBelongsToStore($collection, $store);

        $validated = $this->validatePayload($request, $store, $collection);

        DB::transaction(function () use ($collection, $validated): void {
            $attributes = $this->attributesForUpdate($validated);

            if ($attributes !== []) {
                $collection->update($attributes);
            }

            if (array_key_exists('product_ids', $validated)) {
                $this->replaceProducts($collection, $validated['product_ids']);
            } else {
                $this->addProducts($collection, $validated['add_product_ids'] ?? []);
                $this->removeProducts($collection, $validated['remove_product_ids'] ?? []);
            }
        });

        return CollectionResource::make($this->loadCollection($collection->refresh()));
    }

    public function destroy(Request $request, Store $store, Collection $collection): JsonResponse
    {
        $this->authorizeStore($request, $store);
        $this->abortUnlessCollectionBelongsToStore($collection, $store);

        $collection->delete();

        return response()->json(['message' => 'Collection deleted']);
    }

    private function authorizeStore(Request $request, Store $store): void
    {
        if (! $request->attributes->has('admin_api_oauth_token')) {
            abort_unless($request->user()?->stores()->whereKey($store->getKey())->exists(), 403);
        }

        app()->instance('current_store', $store);
    }

    private function abortUnlessCollectionBelongsToStore(Collection $collection, Store $store): void
    {
        abort_unless((int) $collection->store_id === $store->getKey(), 404);
    }

    /**
     * @return array<string, mixed>
     */
    private function validatePayload(Request $request, Store $store, ?Collection $collection = null): array
    {
        $creating = $collection === null;

        return $request->validate([
            'title' => [$creating ? 'required' : 'sometimes', 'string', 'max:255'],
            'handle' => [
                'sometimes',
                'nullable',
                'string',
                'max:255',
                Rule::unique('collections', 'handle')
                    ->where('store_id', $store->getKey())
                    ->ignore($collection?->getKey()),
            ],
            'description_html' => ['sometimes', 'nullable', 'string', 'max:65535'],
            'type' => [$creating ? 'required' : 'sometimes', Rule::in(['manual', 'automated'])],
            'status' => ['sometimes', Rule::in(['draft', 'active', 'archived'])],
            'product_ids' => ['sometimes', 'array'],
            'product_ids.*' => ['integer', Rule::exists('products', 'id')->where('store_id', $store->getKey())],
            'add_product_ids' => ['sometimes', 'array'],
            'add_product_ids.*' => ['integer', Rule::exists('products', 'id')->where('store_id', $store->getKey())],
            'remove_product_ids' => ['sometimes', 'array'],
            'remove_product_ids.*' => ['integer', Rule::exists('products', 'id')->where('store_id', $store->getKey())],
        ]);
    }

    /**
     * @param  array<string, mixed>  $validated
     * @return array<string, mixed>
     */
    private function attributesForCreate(array $validated): array
    {
        $title = (string) $validated['title'];
        $handle = filled($validated['handle'] ?? null) ? (string) $validated['handle'] : $title;

        return [
            'title' => $title,
            'handle' => Str::slug($handle),
            'description_html' => $validated['description_html'] ?? null,
            'type' => $validated['type'],
            'status' => $validated['status'] ?? 'active',
        ];
    }

    /**
     * @param  array<string, mixed>  $validated
     * @return array<string, mixed>
     */
    private function attributesForUpdate(array $validated): array
    {
        $attributes = Arr::only($validated, ['title', 'description_html', 'type', 'status']);

        if (array_key_exists('handle', $validated) && filled($validated['handle'])) {
            $attributes['handle'] = Str::slug((string) $validated['handle']);
        }

        return $attributes;
    }

    /**
     * @param  array<int, int>  $productIds
     */
    private function replaceProducts(Collection $collection, array $productIds): void
    {
        $collection->products()->sync($this->positionedProductIds($productIds));
    }

    /**
     * @param  array<int, int>  $productIds
     */
    private function addProducts(Collection $collection, array $productIds): void
    {
        if ($productIds === []) {
            return;
        }

        $startPosition = (int) $collection->products()->max('collection_products.position') + 1;

        $collection->products()->syncWithoutDetaching($this->positionedProductIds($productIds, $startPosition));
    }

    /**
     * @param  array<int, int>  $productIds
     */
    private function removeProducts(Collection $collection, array $productIds): void
    {
        if ($productIds === []) {
            return;
        }

        $collection->products()->detach($productIds);
    }

    /**
     * @param  array<int, int>  $productIds
     * @return array<int, array{position: int}>
     */
    private function positionedProductIds(array $productIds, int $startPosition = 0): array
    {
        return collect($productIds)
            ->unique()
            ->values()
            ->mapWithKeys(fn (int $productId, int $position): array => [$productId => ['position' => $startPosition + $position]])
            ->all();
    }

    private function loadCollection(Collection $collection): Collection
    {
        return $collection->load('products')->loadCount('products');
    }
}


output:
Chunk ID: e41327
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 299
Output:
<?php

namespace App\Http\Resources\Admin\V1;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class CollectionResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'store_id' => $this->store_id,
            'title' => $this->title,
            'handle' => $this->handle,
            'description_html' => $this->description_html,
            'type' => $this->type?->value,
            'status' => $this->status?->value,
            'products_count' => $this->whenCounted('products'),
            'products' => $this->whenLoaded('products', fn () => $this->products->map(fn ($product): array => [
                'id' => $product->id,
                'title' => $product->title,
                'handle' => $product->handle,
                'position' => $product->pivot?->position,
            ])->values()),
            'created_at' => $this->created_at?->toIso8601String(),
            'updated_at' => $this->updated_at?->toIso8601String(),
        ];
    }
}


output:
Chunk ID: 22098d
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 299
Output:
<?php

namespace App\Models;

use App\Enums\PageStatus;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Page extends Model
{
    /** @use HasFactory<\Database\Factories\PageFactory> */
    use BelongsToStore, HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'title',
        'handle',
        'body_html',
        'status',
        'published_at',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'status' => 'draft',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    public function isPublished(): bool
    {
        return $this->status === PageStatus::Published && $this->published_at !== null;
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'status' => PageStatus::class,
            'published_at' => 'datetime',
        ];
    }
}


command: rg -n "Page::|pages" database tests app/Livewire routes resources -g '*.php' -g '*.blade.php'

type: search

cmd: rg -n 'Page::|pages' database tests app/Livewire routes resources -g '*.php' -g '*.blade.php'

query: Page::|pages

path: database

aggregated_output:
routes/web.php:60:    Route::livewire('pages/{handle}', StorefrontPageShow::class)->name('pages.show');
routes/web.php:100:    Route::livewire('pages', AdminPagesIndex::class)->name('pages.index');
routes/web.php:101:    Route::livewire('pages/create', AdminPageForm::class)->name('pages.create');
routes/web.php:102:    Route::livewire('pages/{page}/edit', AdminPageForm::class)->name('pages.edit');
resources/views/layouts/storefront.blade.php:105:                        About
app/Livewire/Admin/Navigation/Index.php:264:            NavigationItemType::Page->value => Page::withoutGlobalScopes()
app/Livewire/Admin/Navigation/Index.php:284:            NavigationItemType::Page->value => 'page: '.$this->resourceTitle(Page::class, $item['resource_id']),
app/Livewire/Admin/Navigation/Index.php:393:            NavigationItemType::Page->value => Page::withoutGlobalScopes()->where('store_id', $this->storeId)->whereKey($resourceId)->exists(),
resources/views/layouts/app/sidebar.blade.php:61:                    <flux:sidebar.item icon="document-text" :href="route('admin.pages.index')" :current="request()->routeIs('admin.pages.*')" wire:navigate>
app/Livewire/Admin/Pages/Form.php:48:        $this->authorize('create', Page::class);
app/Livewire/Admin/Pages/Form.php:77:                Rule::unique('pages', 'handle')
app/Livewire/Admin/Pages/Form.php:97:            : Page::withoutGlobalScopes()->create($this->payload($store, $publishedAt));
app/Livewire/Admin/Pages/Form.php:120:        $this->redirectRoute('admin.pages.index', navigate: true);
app/Livewire/Admin/Pages/Form.php:125:        return view('livewire.admin.pages.form', [
app/Livewire/Admin/Pages/Form.php:164:        $this->authorize('create', Page::class);
app/Livewire/Admin/Pages/Index.php:31:        $this->authorize('viewAny', Page::class);
app/Livewire/Admin/Pages/Index.php:41:    public function pages(): LengthAwarePaginator
app/Livewire/Admin/Pages/Index.php:43:        return Page::withoutGlobalScopes()
app/Livewire/Admin/Pages/Index.php:70:        return view('livewire.admin.pages.index', [
app/Livewire/Admin/Pages/Index.php:71:            'pages' => $this->pages(),
app/Livewire/Storefront/Pages/Show.php:19:        $page = Page::query()
app/Livewire/Storefront/Pages/Show.php:32:        return view('livewire.storefront.pages.show')
resources/views/livewire/admin/pages/index.blade.php:5:            <flux:text class="mt-1">Static storefront content pages.</flux:text>
resources/views/livewire/admin/pages/index.blade.php:8:        @can('create', App\Models\Page::class)
resources/views/livewire/admin/pages/index.blade.php:9:            <flux:button :href="route('admin.pages.create')" wire:navigate variant="primary" icon="plus">
resources/views/livewire/admin/pages/index.blade.php:15:    <flux:input wire:model.live.debounce.300ms="search" icon="magnifying-glass" placeholder="Search pages..." aria-label="Search pages" />
resources/views/livewire/admin/pages/index.blade.php:29:                    @forelse ($pages as $page)
resources/views/livewire/admin/pages/index.blade.php:32:
resources/views/livewire/admin/pages/index.blade.php:44:                            No pages found.
resources/views/livewire/admin/pages/index.blade.php:52:    {{ $pages->links() }}
resources/views/livewire/admin/pages/form.blade.php:12:            <flux:button :href="route('admin.pages.index')" wire:navigate variant="filled" icon="arrow-left">Pages</flux:button>
resources/views/livewire/admin/pages/form.blade.php:56:                <flux:button :href="route('admin.pages.index')" wire:navigate variant="ghost">Discard</flux:button>
tests/Feature/Storefront/ThemeDataTest.php:30:        ->and(Schema::hasColumns('pages', ['store_id', 'title', 'handle', 'body_html', 'status', 'published_at']))->toBeTrue()
tests/Feature/Storefront/ThemeDataTest.php:36:        ->and(Page::withoutGlobalScopes()->count())->toBe(6)
tests/Feature/Storefront/ThemeDataTest.php:47:    Page::factory()->published()->create(['store_id' => $firstStore->getKey(), 'handle' => 'first-page']);
tests/Feature/Storefront/ThemeDataTest.php:48:    Page::factory()->published()->create(['store_id' => $secondStore->getKey(), 'handle' => 'second-page']);
tests/Feature/Storefront/ThemeDataTest.php:55:        ->and(Page::query()->pluck('handle')->all())->toBe(['first-page'])
tests/Feature/Storefront/ThemeDataTest.php:109:        ->and(collect($items)->firstWhere('label', 'About')['url'])->toBe('/pages/about');
tests/Feature/Storefront/ThemeDataTest.php:112:test('storefront pages render published database content only', function () {
tests/Feature/Storefront/ThemeDataTest.php:118:    Page::withoutGlobalScopes()->create([
tests/Feature/Storefront/ThemeDataTest.php:128:        ->get('/pages/about')
tests/Feature/Storefront/ThemeDataTest.php:134:        ->get('/pages/private-draft')
resources/views/livewire/admin/settings/checkout.blade.php:48:                    <flux:input wire:model="termsUrl" label="Terms URL" placeholder="https://shop.example.test/pages/terms" />
tests/Feature/Foundation/AuthorizationTest.php:110:            'view' => Page::class,
tests/Feature/Foundation/AuthorizationTest.php:111:            'update' => Page::class,
tests/Feature/Foundation/AuthorizationTest.php:112:            'delete' => Page::class,
tests/Feature/Auth/CustomerPasswordResetTest.php:25:test('customer password reset pages render for the resolved storefront store', function (): void {
tests/Feature/Admin/ContentManagementTest.php:55:test('content routes render store scoped pages navigation and themes', function (): void {
tests/Feature/Admin/ContentManagementTest.php:58:    Page::factory()->create([
tests/Feature/Admin/ContentManagementTest.php:64:    $this->get('/admin/pages')->assertRedirect('/admin/login');
tests/Feature/Admin/ContentManagementTest.php:68:        ->get('/admin/pages')
tests/Feature/Admin/ContentManagementTest.php:75:        ->get('/admin/pages/create')
tests/Feature/Admin/ContentManagementTest.php:98:test('admin pages can be created searched edited and deleted', function (): void {
tests/Feature/Admin/ContentManagementTest.php:111:    $page = Page::withoutGlobalScopes()
tests/Feature/Admin/ContentManagementTest.php:138:        ->assertRedirect(route('admin.pages.index', absolute: false));
tests/Feature/Admin/ContentManagementTest.php:140:    expect(Page::withoutGlobalScopes()->whereKey($page->getKey())->exists())->toBeFalse();
tests/Feature/Admin/ContentManagementTest.php:261:    $otherPage = Page::factory()->create([
tests/Browser/SmokeTest.php:27:test('storefront core pages render without javascript errors', function (): void {
tests/Browser/SmokeTest.php:37:    $page = Page::withoutGlobalScopes()
tests/Browser/SmokeTest.php:42:    $pages = visit([
tests/Browser/SmokeTest.php:48:        "/pages/{$page->handle}",
tests/Browser/SmokeTest.php:52:    $pages->assertNoJavaScriptErrors();
tests/Browser/SmokeTest.php:54:    [$home, $collections, $collectionPage, $productPage, $search, $contentPage, $cart] = $pages;
tests/Browser/SmokeTest.php:65:test('admin core pages render for an authenticated store user', function (): void {
tests/Browser/SmokeTest.php:72:    $pages = visit([
tests/Browser/SmokeTest.php:80:    $pages->assertNoJavaScriptErrors();
tests/Browser/SmokeTest.php:82:    [$dashboard, $products, $orders, $customers, $navigation] = $pages;
tests/Feature/Admin/SettingsManagementTest.php:151:        ->set('termsUrl', 'https://shop.test/pages/terms')
tests/Feature/Admin/SettingsManagementTest.php:166:        ->and(data_get($settings, 'checkout.terms_url'))->toBe('https://shop.test/pages/terms')
database/migrations/2026_05_03_223841_create_pages_table.php:14:        Schema::create('pages', function (Blueprint $table) {
database/migrations/2026_05_03_223841_create_pages_table.php:35:        Schema::dropIfExists('pages');
database/seeders/PageSeeder.php:17:            foreach ($this->pagesFor($store) as $page) {
database/seeders/PageSeeder.php:18:                Page::withoutGlobalScopes()->updateOrCreate(
database/seeders/PageSeeder.php:37:    private function pagesFor(Store $store): array
database/seeders/NavigationItemSeeder.php:124:        return Page::withoutGlobalScopes()


output:
Chunk ID: e3e877
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2123
Output:
routes/web.php:60:    Route::livewire('pages/{handle}', StorefrontPageShow::class)->name('pages.show');
routes/web.php:100:    Route::livewire('pages', AdminPagesIndex::class)->name('pages.index');
routes/web.php:101:    Route::livewire('pages/create', AdminPageForm::class)->name('pages.create');
routes/web.php:102:    Route::livewire('pages/{page}/edit', AdminPageForm::class)->name('pages.edit');
resources/views/layouts/storefront.blade.php:105:                        About
app/Livewire/Admin/Navigation/Index.php:264:            NavigationItemType::Page->value => Page::withoutGlobalScopes()
app/Livewire/Admin/Navigation/Index.php:284:            NavigationItemType::Page->value => 'page: '.$this->resourceTitle(Page::class, $item['resource_id']),
app/Livewire/Admin/Navigation/Index.php:393:            NavigationItemType::Page->value => Page::withoutGlobalScopes()->where('store_id', $this->storeId)->whereKey($resourceId)->exists(),
resources/views/layouts/app/sidebar.blade.php:61:                    <flux:sidebar.item icon="document-text" :href="route('admin.pages.index')" :current="request()->routeIs('admin.pages.*')" wire:navigate>
app/Livewire/Admin/Pages/Form.php:48:        $this->authorize('create', Page::class);
app/Livewire/Admin/Pages/Form.php:77:                Rule::unique('pages', 'handle')
app/Livewire/Admin/Pages/Form.php:97:            : Page::withoutGlobalScopes()->create($this->payload($store, $publishedAt));
app/Livewire/Admin/Pages/Form.php:120:        $this->redirectRoute('admin.pages.index', navigate: true);
app/Livewire/Admin/Pages/Form.php:125:        return view('livewire.admin.pages.form', [
app/Livewire/Admin/Pages/Form.php:164:        $this->authorize('create', Page::class);
app/Livewire/Admin/Pages/Index.php:31:        $this->authorize('viewAny', Page::class);
app/Livewire/Admin/Pages/Index.php:41:    public function pages(): LengthAwarePaginator
app/Livewire/Admin/Pages/Index.php:43:        return Page::withoutGlobalScopes()
app/Livewire/Admin/Pages/Index.php:70:        return view('livewire.admin.pages.index', [
app/Livewire/Admin/Pages/Index.php:71:            'pages' => $this->pages(),
app/Livewire/Storefront/Pages/Show.php:19:        $page = Page::query()
app/Livewire/Storefront/Pages/Show.php:32:        return view('livewire.storefront.pages.show')
resources/views/livewire/admin/pages/index.blade.php:5:            <flux:text class="mt-1">Static storefront content pages.</flux:text>
resources/views/livewire/admin/pages/index.blade.php:8:        @can('create', App\Models\Page::class)
resources/views/livewire/admin/pages/index.blade.php:9:            <flux:button :href="route('admin.pages.create')" wire:navigate variant="primary" icon="plus">
resources/views/livewire/admin/pages/index.blade.php:15:    <flux:input wire:model.live.debounce.300ms="search" icon="magnifying-glass" placeholder="Search pages..." aria-label="Search pages" />
resources/views/livewire/admin/pages/index.blade.php:29:                    @forelse ($pages as $page)
resources/views/livewire/admin/pages/index.blade.php:32:
resources/views/livewire/admin/pages/index.blade.php:44:                            No pages found.
resources/views/livewire/admin/pages/index.blade.php:52:    {{ $pages->links() }}
resources/views/livewire/admin/pages/form.blade.php:12:            <flux:button :href="route('admin.pages.index')" wire:navigate variant="filled" icon="arrow-left">Pages</flux:button>
resources/views/livewire/admin/pages/form.blade.php:56:                <flux:button :href="route('admin.pages.index')" wire:navigate variant="ghost">Discard</flux:button>
tests/Feature/Storefront/ThemeDataTest.php:30:        ->and(Schema::hasColumns('pages', ['store_id', 'title', 'handle', 'body_html', 'status', 'published_at']))->toBeTrue()
tests/Feature/Storefront/ThemeDataTest.php:36:        ->and(Page::withoutGlobalScopes()->count())->toBe(6)
tests/Feature/Storefront/ThemeDataTest.php:47:    Page::factory()->published()->create(['store_id' => $firstStore->getKey(), 'handle' => 'first-page']);
tests/Feature/Storefront/ThemeDataTest.php:48:    Page::factory()->published()->create(['store_id' => $secondStore->getKey(), 'handle' => 'second-page']);
tests/Feature/Storefront/ThemeDataTest.php:55:        ->and(Page::query()->pluck('handle')->all())->toBe(['first-page'])
tests/Feature/Storefront/ThemeDataTest.php:109:        ->and(collect($items)->firstWhere('label', 'About')['url'])->toBe('/pages/about');
tests/Feature/Storefront/ThemeDataTest.php:112:test('storefront pages render published database content only', function () {
tests/Feature/Storefront/ThemeDataTest.php:118:    Page::withoutGlobalScopes()->create([
tests/Feature/Storefront/ThemeDataTest.php:128:        ->get('/pages/about')
tests/Feature/Storefront/ThemeDataTest.php:134:        ->get('/pages/private-draft')
resources/views/livewire/admin/settings/checkout.blade.php:48:                    <flux:input wire:model="termsUrl" label="Terms URL" placeholder="https://shop.example.test/pages/terms" />
tests/Feature/Foundation/AuthorizationTest.php:110:            'view' => Page::class,
tests/Feature/Foundation/AuthorizationTest.php:111:            'update' => Page::class,
tests/Feature/Foundation/AuthorizationTest.php:112:            'delete' => Page::class,
tests/Feature/Auth/CustomerPasswordResetTest.php:25:test('customer password reset pages render for the resolved storefront store', function (): void {
tests/Feature/Admin/ContentManagementTest.php:55:test('content routes render store scoped pages navigation and themes', function (): void {
tests/Feature/Admin/ContentManagementTest.php:58:    Page::factory()->create([
tests/Feature/Admin/ContentManagementTest.php:64:    $this->get('/admin/pages')->assertRedirect('/admin/login');
tests/Feature/Admin/ContentManagementTest.php:68:        ->get('/admin/pages')
tests/Feature/Admin/ContentManagementTest.php:75:        ->get('/admin/pages/create')
tests/Feature/Admin/ContentManagementTest.php:98:test('admin pages can be created searched edited and deleted', function (): void {
tests/Feature/Admin/ContentManagementTest.php:111:    $page = Page::withoutGlobalScopes()
tests/Feature/Admin/ContentManagementTest.php:138:        ->assertRedirect(route('admin.pages.index', absolute: false));
tests/Feature/Admin/ContentManagementTest.php:140:    expect(Page::withoutGlobalScopes()->whereKey($page->getKey())->exists())->toBeFalse();
tests/Feature/Admin/ContentManagementTest.php:261:    $otherPage = Page::factory()->create([
tests/Browser/SmokeTest.php:27:test('storefront core pages render without javascript errors', function (): void {
tests/Browser/SmokeTest.php:37:    $page = Page::withoutGlobalScopes()
tests/Browser/SmokeTest.php:42:    $pages = visit([
tests/Browser/SmokeTest.php:48:        "/pages/{$page->handle}",
tests/Browser/SmokeTest.php:52:    $pages->assertNoJavaScriptErrors();
tests/Browser/SmokeTest.php:54:    [$home, $collections, $collectionPage, $productPage, $search, $contentPage, $cart] = $pages;
tests/Browser/SmokeTest.php:65:test('admin core pages render for an authenticated store user', function (): void {
tests/Browser/SmokeTest.php:72:    $pages = visit([
tests/Browser/SmokeTest.php:80:    $pages->assertNoJavaScriptErrors();
tests/Browser/SmokeTest.php:82:    [$dashboard, $products, $orders, $customers, $navigation] = $pages;
tests/Feature/Admin/SettingsManagementTest.php:151:        ->set('termsUrl', 'https://shop.test/pages/terms')
tests/Feature/Admin/SettingsManagementTest.php:166:        ->and(data_get($settings, 'checkout.terms_url'))->toBe('https://shop.test/pages/terms')
database/migrations/2026_05_03_223841_create_pages_table.php:14:        Schema::create('pages', function (Blueprint $table) {
database/migrations/2026_05_03_223841_create_pages_table.php:35:        Schema::dropIfExists('pages');
database/seeders/PageSeeder.php:17:            foreach ($this->pagesFor($store) as $page) {
database/seeders/PageSeeder.php:18:                Page::withoutGlobalScopes()->updateOrCreate(
database/seeders/PageSeeder.php:37:    private function pagesFor(Store $store): array
database/seeders/NavigationItemSeeder.php:124:        return Page::withoutGlobalScopes()


cmd: sed -n '1,240p' database/factories/PageFactory.php


cmd: sed -n '1,220p' app/Enums/PageStatus.php


cmd: sed -n '1,240p' app/Livewire/Admin/Pages/Form.php


cmd: sed -n '1,220p' app/Livewire/Admin/Pages/Index.php


cmd: sed -n '1,260p' tests/Feature/Api/AdminCollectionApiTest.php


command: sed -n '1,220p' app/Enums/PageStatus.php

aggregated_output:
<?php

namespace App\Enums;

enum PageStatus: string
{
    case Draft = 'draft';
    case Published = 'published';
    case Archived = 'archived';
}


command: sed -n '1,240p' database/factories/PageFactory.php

aggregated_output:
<?php

namespace Database\Factories;

use App\Enums\PageStatus;
use App\Models\Store;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Page>
 */
class PageFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        $title = Str::title(fake()->words(2, true));

        return [
            'store_id' => Store::factory(),
            'title' => $title,
            'handle' => Str::slug($title).'-'.fake()->unique()->numberBetween(1000, 9999),
            'body_html' => '<p>'.fake()->sentence().'</p>',
            'status' => PageStatus::Draft,
            'published_at' => null,
        ];
    }

    public function published(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => PageStatus::Published,
            'published_at' => now(),
        ]);
    }

    public function archived(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => PageStatus::Archived,
            'published_at' => now()->subDay(),
        ]);
    }
}


output:
Chunk ID: 7294cc
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 316
Output:
<?php

namespace Database\Factories;

use App\Enums\PageStatus;
use App\Models\Store;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Page>
 */
class PageFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        $title = Str::title(fake()->words(2, true));

        return [
            'store_id' => Store::factory(),
            'title' => $title,
            'handle' => Str::slug($title).'-'.fake()->unique()->numberBetween(1000, 9999),
            'body_html' => '<p>'.fake()->sentence().'</p>',
            'status' => PageStatus::Draft,
            'published_at' => null,
        ];
    }

    public function published(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => PageStatus::Published,
            'published_at' => now(),
        ]);
    }

    public function archived(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => PageStatus::Archived,
            'published_at' => now()->subDay(),
        ]);
    }
}


output:
Chunk ID: 613193
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 38
Output:
<?php

namespace App\Enums;

enum PageStatus: string
{
    case Draft = 'draft';
    case Published = 'published';
    case Archived = 'archived';
}


command: sed -n '1,220p' app/Livewire/Admin/Pages/Index.php

aggregated_output:
<?php

namespace App\Livewire\Admin\Pages;

use App\Enums\PageStatus;
use App\Models\Page;
use App\Models\Store;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Attributes\Locked;
use Livewire\Component;
use Livewire\WithPagination;

class Index extends Component
{
    use AuthorizesRequests;
    use WithPagination;

    #[Locked]
    public int $storeId;

    public string $search = '';

    public function mount(): void
    {
        $store = app('current_store');

        abort_unless($store instanceof Store, 404);

        $this->authorize('viewAny', Page::class);

        $this->storeId = $store->getKey();
    }

    public function updatedSearch(): void
    {
        $this->resetPage();
    }

    public function pages(): LengthAwarePaginator
    {
        return Page::withoutGlobalScopes()
            ->where('store_id', $this->storeId)
            ->when($this->search !== '', function (Builder $query): void {
                $search = '%'.$this->search.'%';

                $query->where(function (Builder $query) use ($search): void {
                    $query
                        ->where('title', 'like', $search)
                        ->orWhere('handle', 'like', $search);
                });
            })
            ->latest('updated_at')
            ->latest('id')
            ->paginate(20);
    }

    public function statusColor(PageStatus $status): string
    {
        return match ($status) {
            PageStatus::Published => 'green',
            PageStatus::Archived => 'red',
            default => 'zinc',
        };
    }

    public function render(): mixed
    {
        return view('livewire.admin.pages.index', [
            'pages' => $this->pages(),
        ])->layout('layouts.app', [
            'title' => __('Pages'),
        ]);
    }
}


command: sed -n '1,260p' tests/Feature/Api/AdminCollectionApiTest.php

aggregated_output:
<?php

use App\Models\Collection;
use App\Models\Product;
use App\Models\Store;
use App\Models\User;
use App\Services\WebhookService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminCollectionApiStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

function adminCollectionApiUser(): User
{
    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
}

/**
 * @param  list<string>  $abilities
 * @return array{token: \App\Models\OauthToken, plain_text: string}
 */
function adminCollectionApiToken(Store $store, array $abilities): array
{
    return app(WebhookService::class)->createApiToken($store, 'Collection integration', $abilities);
}

test('admin collection api lists creates updates and deletes collections', function (): void {
    $store = adminCollectionApiStore();
    $products = Product::factory()
        ->count(3)
        ->withDefaultVariant()
        ->create(['store_id' => $store->getKey()]);
    Collection::factory()->create([
        'store_id' => Store::factory()->create()->getKey(),
        'title' => 'Other Store Collection',
    ]);
    $user = adminCollectionApiUser();

    $this->actingAs($user)
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/collections?query=New")
        ->assertOk()
        ->assertJsonMissing(['title' => 'Other Store Collection']);

    $createResponse = $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/collections", [
            'title' => 'API Winter',
            'description_html' => '
Cold weather goods.
',
            'type' => 'manual',
            'status' => 'active',
            'product_ids' => $products->take(2)->pluck('id')->all(),
        ])
        ->assertCreated()
        ->assertJsonPath('data.title', 'API Winter')
        ->assertJsonPath('data.handle', 'api-winter')
        ->assertJsonPath('data.products_count', 2);

    $collectionId = $createResponse->json('data.id');
    $collection = Collection::withoutGlobalScopes()->findOrFail($collectionId);

    expect($collection->products()->pluck('collection_products.position', 'products.id')->all())
        ->toBe([
            $products[0]->getKey() => 0,
            $products[1]->getKey() => 1,
        ]);

    $this->actingAs($user)
        ->putJson("/api/admin/v1/stores/{$store->getKey()}/collections/{$collection->getKey()}", [
            'title' => 'API Winter Edit',
            'status' => 'draft',
            'add_product_ids' => [$products[2]->getKey()],
            'remove_product_ids' => [$products[0]->getKey()],
        ])
        ->assertOk()
        ->assertJsonPath('data.title', 'API Winter Edit')
        ->assertJsonPath('data.status', 'draft')
        ->assertJsonPath('data.products_count', 2);

    expect($collection->refresh()->products()->pluck('products.id')->all())
        ->toBe([$products[1]->getKey(), $products[2]->getKey()]);

    $this->actingAs($user)
        ->deleteJson("/api/admin/v1/stores/{$store->getKey()}/collections/{$collection->getKey()}")
        ->assertOk()
        ->assertJsonPath('message', 'Collection deleted');

    expect(Collection::withoutGlobalScopes()->whereKey($collection->getKey())->exists())->toBeFalse();
});

test('admin collection api enforces token abilities and store scope', function (): void {
    $store = adminCollectionApiStore();
    $otherStore = Store::factory()->create();
    $collection = Collection::factory()->create([
        'store_id' => $store->getKey(),
        'title' => 'Token Collection',
    ]);
    $readToken = adminCollectionApiToken($store, ['read-collections']);
    $writeToken = adminCollectionApiToken($store, ['write-collections']);
    $otherStoreToken = adminCollectionApiToken($otherStore, ['read-collections']);

    $this->withToken($readToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/collections?query=Token")
        ->assertOk()
        ->assertJsonPath('data.0.title', 'Token Collection');

    $this->withToken($readToken['plain_text'])
        ->deleteJson("/api/admin/v1/stores/{$store->getKey()}/collections/{$collection->getKey()}")
        ->assertForbidden();

    $this->withToken($otherStoreToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/collections")
        ->assertForbidden();

    $this->withToken($writeToken['plain_text'])
        ->deleteJson("/api/admin/v1/stores/{$store->getKey()}/collections/{$collection->getKey()}")
        ->assertOk();
});

test('admin collection api validates handles and product store ownership', function (): void {
    $store = adminCollectionApiStore();
    $existing = Collection::factory()->create([
        'store_id' => $store->getKey(),
        'handle' => 'existing-api-collection',
    ]);
    $otherStoreProduct = Product::factory()
        ->withDefaultVariant()
        ->create(['store_id' => Store::factory()->create()->getKey()]);

    $this->actingAs(adminCollectionApiUser())
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/collections", [
            'title' => 'Invalid API Collection',
            'handle' => $existing->handle,
            'type' => 'manual',
            'product_ids' => [$otherStoreProduct->getKey()],
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['handle', 'product_ids.0']);
});


command: sed -n '1,240p' app/Livewire/Admin/Pages/Form.php

aggregated_output:
<?php

namespace App\Livewire\Admin\Pages;

use App\Enums\PageStatus;
use App\Models\NavigationMenu;
use App\Models\Page;
use App\Models\Store;
use App\Services\NavigationService;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Livewire\Component;

class Form extends Component
{
    use AuthorizesRequests;

    public ?Page $page = null;

    public string $title = '';

    public string $handle = '';

    public string $bodyHtml = '';

    public string $status = 'draft';

    public string $publishedAt = '';

    public function mount(?Page $page = null): void
    {
        $store = app('current_store');

        abort_unless($store instanceof Store, 404);

        if ($page?->exists) {
            abort_unless((int) $page->store_id === $store->getKey(), 404);

            $this->authorize('update', $page);

            $this->page = $page;
            $this->fillFromPage($page);

            return;
        }

        $this->authorize('create', Page::class);
    }

    public function updatedTitle(string $title): void
    {
        if ($this->page instanceof Page || $this->handle !== '') {
            return;
        }

        $this->handle = Str::slug($title);
    }

    public function save(NavigationService $navigation): void
    {
        $store = app('current_store');

        abort_unless($store instanceof Store, 404);

        $this->authorizeSave();

        $this->handle = Str::slug($this->handle);

        $this->validate([
            'title' => ['required', 'string', 'max:255'],
            'handle' => [
                'required',
                'string',
                'max:255',
                'alpha_dash',
                Rule::unique('pages', 'handle')
                    ->where('store_id', $store->getKey())
                    ->ignore($this->page?->getKey()),
            ],
            'bodyHtml' => ['nullable', 'string'],
            'status' => ['required', Rule::in(array_column(PageStatus::cases(), 'value'))],
            'publishedAt' => ['nullable', 'date'],
        ], [], [
            'bodyHtml' => 'body',
            'publishedAt' => 'published at',
        ]);

        $publishedAt = $this->publishedAt !== '' ? $this->publishedAt : null;

        if ($this->status === PageStatus::Published->value && $publishedAt === null) {
            $publishedAt = now();
        }

        $page = $this->page instanceof Page
            ? tap($this->page)->update($this->payload($store, $publishedAt))
            : Page::withoutGlobalScopes()->create($this->payload($store, $publishedAt));

        $this->page = $page->refresh();
        $this->fillFromPage($this->page);
        $this->forgetNavigation($store, $navigation);

        session()->flash('status', 'Page saved');
        $this->dispatch('toast', type: 'success', message: __('Page saved'));
    }

    public function deletePage(NavigationService $navigation): void
    {
        abort_unless($this->page instanceof Page, 404);

        $store = app('current_store');

        abort_unless($store instanceof Store, 404);

        $this->authorize('delete', $this->page);

        $this->page->delete();
        $this->forgetNavigation($store, $navigation);

        $this->redirectRoute('admin.pages.index', navigate: true);
    }

    public function render(): mixed
    {
        return view('livewire.admin.pages.form', [
            'isEditing' => $this->page instanceof Page,
        ])->layout('layouts.app', [
            'title' => $this->page ? __('Edit page') : __('Create page'),
        ]);
    }

    private function fillFromPage(Page $page): void
    {
        $this->title = $page->title;
        $this->handle = $page->handle;
        $this->bodyHtml = (string) $page->body_html;
        $this->status = $page->status->value;
        $this->publishedAt = $page->published_at?->format('Y-m-d\TH:i') ?? '';
    }

    /**
     * @return array<string, mixed>
     */
    private function payload(Store $store, mixed $publishedAt): array
    {
        return [
            'store_id' => $store->getKey(),
            'title' => $this->title,
            'handle' => $this->handle,
            'body_html' => $this->bodyHtml,
            'status' => PageStatus::from($this->status),
            'published_at' => $publishedAt,
        ];
    }

    private function authorizeSave(): void
    {
        if ($this->page instanceof Page) {
            $this->authorize('update', $this->page);

            return;
        }

        $this->authorize('create', Page::class);
    }

    private function forgetNavigation(Store $store, NavigationService $navigation): void
    {
        NavigationMenu::withoutGlobalScopes()
            ->where('store_id', $store->getKey())
            ->get()
            ->each(fn (NavigationMenu $menu): mixed => $navigation->forget($menu));
    }
}


command:
/bin/zsh
-lc
sed -n '1,160p' routes/api.php

api.php

cmd: sed -n '1,160p' routes/api.php

aggregated_output:
<?php

use App\Http\Controllers\Api\Admin\V1\CollectionController as AdminCollectionController;
use App\Http\Controllers\Api\Admin\V1\CustomerController as AdminCustomerController;
use App\Http\Controllers\Api\Admin\V1\DiscountController as AdminDiscountController;
use App\Http\Controllers\Api\Admin\V1\OrderController as AdminOrderController;
use App\Http\Controllers\Api\Admin\V1\OrderFulfillmentController as AdminOrderFulfillmentController;
use App\Http\Controllers\Api\Admin\V1\OrderRefundController as AdminOrderRefundController;
use App\Http\Controllers\Api\Admin\V1\ProductController as AdminProductController;
use App\Http\Controllers\Api\Apps\V1\DeferredEndpointController as DeferredAppEndpointController;
use App\Http\Controllers\Api\Storefront\V1\AnalyticsEventController as StorefrontAnalyticsEventController;
use App\Http\Controllers\Api\Storefront\V1\CartController;
use App\Http\Controllers\Api\Storefront\V1\CartLineController;
use App\Http\Controllers\Api\Storefront\V1\CheckoutController;
use App\Http\Controllers\Api\Storefront\V1\OrderController as StorefrontOrderController;
use App\Http\Controllers\Api\Storefront\V1\SearchController as StorefrontSearchController;
use Illuminate\Support\Facades\Route;

Route::middleware('store.resolve')
    ->prefix('storefront/v1')
    ->name('api.storefront.v1.')
    ->group(function (): void {
        Route::middleware('throttle:api.storefront')->group(function (): void {
            Route::post('carts', [CartController::class, 'store'])->name('carts.store');
            Route::get('carts/{cart}', [CartController::class, 'show'])->name('carts.show');
            Route::post('carts/{cart}/lines', [CartLineController::class, 'store'])->name('carts.lines.store');
            Route::put('carts/{cart}/lines/{cartLine}', [CartLineController::class, 'update'])->name('carts.lines.update');
            Route::delete('carts/{cart}/lines/{cartLine}', [CartLineController::class, 'destroy'])->name('carts.lines.destroy');
        });

        Route::middleware('throttle:search')->group(function (): void {
            Route::get('search', [StorefrontSearchController::class, 'index'])->name('search.index');
            Route::get('search/suggest', [StorefrontSearchController::class, 'suggest'])->name('search.suggest');
        });

        Route::middleware('throttle:analytics')->group(function (): void {
            Route::post('analytics/events', [StorefrontAnalyticsEventController::class, 'store'])->name('analytics.events.store');
        });

        Route::middleware('throttle:checkout')->group(function (): void {
            Route::post('checkouts', [CheckoutController::class, 'store'])->name('checkouts.store');
            Route::get('checkouts/{checkout}', [CheckoutController::class, 'show'])->name('checkouts.show');
            Route::put('checkouts/{checkout}/address', [CheckoutController::class, 'address'])->name('checkouts.address');
            Route::put('checkouts/{checkout}/shipping-method', [CheckoutController::class, 'shippingMethod'])->name('checkouts.shipping-method');
            Route::post('checkouts/{checkout}/apply-discount', [CheckoutController::class, 'applyDiscount'])->name('checkouts.apply-discount');
            Route::delete('checkouts/{checkout}/discount', [CheckoutController::class, 'destroyDiscount'])->name('checkouts.discount.destroy');
            Route::put('checkouts/{checkout}/payment-method', [CheckoutController::class, 'paymentMethod'])->name('checkouts.payment-method');
            Route::post('checkouts/{checkout}/pay', [CheckoutController::class, 'pay'])->name('checkouts.pay');
            Route::get('orders/{orderNumber}', [StorefrontOrderController::class, 'show'])->name('orders.show');
        });
    });

Route::middleware('throttle:60,1')
    ->prefix('admin/v1/stores/{store}')
    ->name('api.admin.v1.')
    ->group(function (): void {
        Route::middleware('admin.api:read-products')->group(function (): void {
            Route::get('products', [AdminProductController::class, 'index'])->name('products.index');
            Route::get('products/{product}', [AdminProductController::class, 'show'])->name('products.show');
        });

        Route::middleware('admin.api:read-customers')->group(function (): void {
            Route::get('customers', [AdminCustomerController::class, 'index'])->name('customers.index');
            Route::get('customers/{customer}', [AdminCustomerController::class, 'show'])->name('customers.show');
        });

        Route::middleware('admin.api:read-collections')->group(function (): void {
            Route::get('collections', [AdminCollectionController::class, 'index'])->name('collections.index');
        });

        Route::middleware('admin.api:write-collections')->group(function (): void {
            Route::post('collections', [AdminCollectionController::class, 'store'])->name('collections.store');
            Route::put('collections/{collection}', [AdminCollectionController::class, 'update'])->name('collections.update');
            Route::delete('collections/{collection}', [AdminCollectionController::class, 'destroy'])->name('collections.destroy');
        });

        Route::middleware('admin.api:read-discounts')->group(function (): void {
            Route::get('discounts', [AdminDiscountController::class, 'index'])->name('discounts.index');
        });

        Route::middleware('admin.api:write-discounts')->group(function (): void {
            Route::post('discounts', [AdminDiscountController::class, 'store'])->name('discounts.store');
            Route::put('discounts/{discount}', [AdminDiscountController::class, 'update'])->name('discounts.update');
            Route::delete('discounts/{discount}', [AdminDiscountController::class, 'destroy'])->name('discounts.destroy');
        });

        Route::middleware('admin.api:read-orders')->group(function (): void {
            Route::get('orders', [AdminOrderController::class, 'index'])->name('orders.index');
            Route::get('orders/{order}', [AdminOrderController::class, 'show'])->name('orders.show');
        });

        Route::middleware('admin.api:write-orders')->group(function (): void {
            Route::post('orders/{order}/fulfillments', [AdminOrderFulfillmentController::class, 'store'])->name('orders.fulfillments.store');
            Route::post('orders/{order}/refunds', [AdminOrderRefundController::class, 'store'])->name('orders.refunds.store');
        });
    });

Route::middleware('throttle:60,1')
    ->prefix('apps/v1/stores/{store}')
    ->name('api.apps.v1.')
    ->group(function (): void {
        Route::get('products', DeferredAppEndpointController::class)->name('products.index');
        Route::get('orders', DeferredAppEndpointController::class)->name('orders.index');
        Route::get('customers', DeferredAppEndpointController::class)->name('customers.index');
    });