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

aggregated_output:
<?php

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

use App\Http\Controllers\Controller;
use App\Http\Resources\Admin\V1\DiscountResource;
use App\Models\Discount;
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\Carbon;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;

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

        $validated = $request->validate([
            'type' => ['nullable', Rule::in(['code', 'automatic'])],
            'status' => ['nullable', Rule::in(['active', 'expired', 'scheduled'])],
            'per_page' => ['nullable', 'integer', 'min:1', 'max:100'],
        ]);

        $discounts = Discount::withoutGlobalScopes()
            ->where('store_id', $store->getKey())
            ->when(data_get($validated, 'type'), fn (Builder $query, string $type) => $query->where('type', $type))
            ->when(data_get($validated, 'status'), fn (Builder $query, string $status) => $this->applyComputedStatus($query, $status))
            ->latest('created_at')
            ->latest('id')
            ->paginate((int) data_get($validated, 'per_page', 25));

        return DiscountResource::collection($discounts);
    }

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

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

        $discount = Discount::withoutGlobalScopes()->create($this->attributesForCreate($validated, $store));

        return DiscountResource::make($discount)
            ->response()
            ->setStatusCode(201);
    }

    public function update(Request $request, Store $store, Discount $discount): DiscountResource
    {
        $this->authorizeStore($request, $store);
        $this->abortUnlessDiscountBelongsToStore($discount, $store);

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

        $discount->update($this->attributesForUpdate($validated, $discount));

        return DiscountResource::make($discount->refresh());
    }

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

        $discount->delete();

        return response()->json(['message' => 'Discount 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 abortUnlessDiscountBelongsToStore(Discount $discount, Store $store): void
    {
        abort_unless((int) $discount->store_id === $store->getKey(), 404);
    }

    private function applyComputedStatus(Builder $query, string $status): void
    {
        match ($status) {
            'active' => $query
                ->where('status', 'active')
                ->where('starts_at', '<=', now())
                ->where(fn (Builder $query) => $query->whereNull('ends_at')->orWhere('ends_at', '>', now())),
            'expired' => $query->where(fn (Builder $query) => $query->where('status', 'expired')->orWhere('ends_at', '<=', now())),
            'scheduled' => $query->where('starts_at', '>', now()),
        };
    }

    /**
     * @return array<string, mixed>
     */
    private function validatePayload(Request $request, Store $store, ?Discount $discount = null): array
    {
        $creating = $discount === null;
        $type = (string) $request->input('type', $discount?->type->value ?? 'code');
        $valueType = (string) $request->input('value_type', $discount?->value_type->value ?? 'percent');

        $validated = $request->validate([
            'type' => [$creating ? 'required' : 'sometimes', Rule::in(['code', 'automatic'])],
            'code' => [
                Rule::requiredIf($creating && $type === 'code'),
                'nullable',
                'string',
                'max:50',
                function (string $attribute, mixed $value, \Closure $fail) use ($store, $discount): void {
                    if ($value === null || trim((string) $value) === '') {
                        return;
                    }

                    $normalized = Str::upper(trim((string) $value));

                    if ($discount instanceof Discount && $normalized !== (string) $discount->code) {
                        $fail(__('The discount code cannot be changed after creation.'));

                        return;
                    }

                    $exists = Discoun…1077 chars truncated…            }

                    $startsAt = $request->input('starts_at', $discount?->starts_at ?? now());

                    if (Carbon::parse($value)->lte(Carbon::parse($startsAt))) {
                        $fail(__('The end date must be after the start date.'));
                    }
                },
            ],
            'usage_limit' => ['sometimes', 'nullable', 'integer', 'min:1'],
            'status' => ['sometimes', Rule::in(['draft', 'active', 'expired', 'disabled'])],
            'rules_json' => ['sometimes', 'array'],
            'rules_json.minimum_purchase_amount' => ['nullable', 'integer', 'min:0'],
            'rules_json.min_purchase_amount' => ['nullable', 'integer', 'min:0'],
            'rules_json.applicable_product_ids' => ['nullable', 'array'],
            'rules_json.applicable_product_ids.*' => ['integer', Rule::exists('products', 'id')->where('store_id', $store->getKey())],
            'rules_json.applicable_collection_ids' => ['nullable', 'array'],
            'rules_json.applicable_collection_ids.*' => ['integer', Rule::exists('collections', 'id')->where('store_id', $store->getKey())],
            'rules_json.customer_eligibility' => ['nullable', Rule::in(['all', 'specific_customers', 'specific_segments'])],
            'rules_json.once_per_customer' => ['nullable', 'boolean'],
            'rules_json.one_per_customer' => ['nullable', 'boolean'],
        ]);

        return $validated;
    }

    /**
     * @return list<mixed>
     */
    private function valueAmountRules(bool $creating, string $valueType): array
    {
        $presence = $creating ? 'required' : 'sometimes';

        return match ($valueType) {
            'percent' => [$presence, 'integer', 'min:1', 'max:100'],
            'free_shipping' => ['sometimes', 'nullable', 'integer', 'min:0'],
            default => [$presence, 'integer', 'min:1'],
        };
    }

    /**
     * @param  array<string, mixed>  $validated
     * @return array<string, mixed>
     */
    private function attributesForCreate(array $validated, Store $store): array
    {
        $type = (string) $validated['type'];
        $valueType = (string) $validated['value_type'];

        return [
            'store_id' => $store->getKey(),
            'type' => $type,
            'code' => $type === 'code' ? Str::upper(trim((string) $validated['code'])) : null,
            'value_type' => $valueType,
            'value_amount' => $valueType === 'free_shipping' ? 0 : (int) $validated['value_amount'],
            'starts_at' => $validated['starts_at'] ?? now(),
            'ends_at' => $validated['ends_at'] ?? null,
            'usage_limit' => $validated['usage_limit'] ?? null,
            'usage_count' => 0,
            'rules_json' => $this->rulesPayload($validated['rules_json'] ?? []),
            'status' => $validated['status'] ?? 'active',
        ];
    }

    /**
     * @param  array<string, mixed>  $validated
     * @return array<string, mixed>
     */
    private function attributesForUpdate(array $validated, Discount $discount): array
    {
        $attributes = [];
        $valueType = (string) data_get($validated, 'value_type', $discount->value_type->value);

        foreach (['type', 'value_type', 'starts_at', 'ends_at', 'usage_limit', 'status'] as $field) {
            if (array_key_exists($field, $validated)) {
                $attributes[$field] = $validated[$field];
            }
        }

        if (array_key_exists('value_amount', $validated)) {
            $attributes['value_amount'] = $valueType === 'free_shipping' ? 0 : (int) $validated['value_amount'];
        } elseif (array_key_exists('value_type', $validated) && $valueType === 'free_shipping') {
            $attributes['value_amount'] = 0;
        }

        if (array_key_exists('rules_json', $validated)) {
            $attributes['rules_json'] = $this->rulesPayload($validated['rules_json']);
        }

        return $attributes;
    }

    /**
     * @param  array<string, mixed>  $rules
     * @return array<string, mixed>
     */
    private function rulesPayload(array $rules): array
    {
        return [
            'min_purchase_amount' => (int) data_get($rules, 'min_purchase_amount', data_get($rules, 'minimum_purchase_amount', 0)),
            'applicable_product_ids' => $this->integerList(data_get($rules, 'applicable_product_ids', [])),
            'applicable_collection_ids' => $this->integerList(data_get($rules, 'applicable_collection_ids', [])),
            'customer_eligibility' => data_get($rules, 'customer_eligibility', 'all'),
            'one_per_customer' => (bool) data_get($rules, 'one_per_customer', data_get($rules, 'once_per_customer', false)),
        ];
    }

    /**
     * @return list<int>
     */
    private function integerList(mixed $value): array
    {
        return collect(is_array($value) ? $value : [])
            ->map(fn (mixed $id): int => (int) $id)
            ->unique()
            ->values()
            ->all();
    }
}


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

CollectionController.php

type: read

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');
    }
}

status: completed


output:
Chunk ID: 2e7e36
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: 4cff20
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2770
Output:
<?php

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

use App\Http\Controllers\Controller;
use App\Http\Resources\Admin\V1\DiscountResource;
use App\Models\Discount;
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\Carbon;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;

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

        $validated = $request->validate([
            'type' => ['nullable', Rule::in(['code', 'automatic'])],
            'status' => ['nullable', Rule::in(['active', 'expired', 'scheduled'])],
            'per_page' => ['nullable', 'integer', 'min:1', 'max:100'],
        ]);

        $discounts = Discount::withoutGlobalScopes()
            ->where('store_id', $store->getKey())
            ->when(data_get($validated, 'type'), fn (Builder $query, string $type) => $query->where('type', $type))
            ->when(data_get($validated, 'status'), fn (Builder $query, string $status) => $this->applyComputedStatus($query, $status))
            ->latest('created_at')
            ->latest('id')
            ->paginate((int) data_get($validated, 'per_page', 25));

        return DiscountResource::collection($discounts);
    }

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

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

        $discount = Discount::withoutGlobalScopes()->create($this->attributesForCreate($validated, $store));

        return DiscountResource::make($discount)
            ->response()
            ->setStatusCode(201);
    }

    public function update(Request $request, Store $store, Discount $discount): DiscountResource
    {
        $this->authorizeStore($request, $store);
        $this->abortUnlessDiscountBelongsToStore($discount, $store);

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

        $discount->update($this->attributesForUpdate($validated, $discount));

        return DiscountResource::make($discount->refresh());
    }

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

        $discount->delete();

        return response()->json(['message' => 'Discount 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 abortUnlessDiscountBelongsToStore(Discount $discount, Store $store): void
    {
        abort_unless((int) $discount->store_id === $store->getKey(), 404);
    }

    private function applyComputedStatus(Builder $query, string $status): void
    {
        match ($status) {
            'active' => $query
                ->where('status', 'active')
                ->where('starts_at', '<=', now())
                ->where(fn (Builder $query) => $query->whereNull('ends_at')->orWhere('ends_at', '>', now())),
            'expired' => $query->where(fn (Builder $query) => $query->where('status', 'expired')->orWhere('ends_at', '<=', now())),
            'scheduled' => $query->where('starts_at', '>', now()),
        };
    }

    /**
     * @return array<string, mixed>
     */
    private function validatePayload(Request $request, Store $store, ?Discount $discount = null): array
    {
        $creating = $discount === null;
        $type = (string) $request->input('type', $discount?->type->value ?? 'code');
        $valueType = (string) $request->input('value_type', $discount?->value_type->value ?? 'percent');

        $validated = $request->validate([
            'type' => [$creating ? 'required' : 'sometimes', Rule::in(['code', 'automatic'])],
            'code' => [
                Rule::requiredIf($creating && $type === 'code'),
                'nullable',
                'string',
                'max:50',
                function (string $attribute, mixed $value, \Closure $fail) use ($store, $discount): void {
                    if ($value === null || trim((string) $value) === '') {
                        return;
                    }

                    $normalized = Str::upper(trim((string) $value));

                    if ($discount instanceof Discount && $normalized !== (string) $discount->code) {
                        $fail(__('The discount code cannot be changed after creation.'));

                        return;
                    }

                    $exists = Discount::withoutGlobalScopes()
                        ->where('store_id', $store->getKey())
                        ->whereRaw('lower(code) = ?', [Str::lower($normalized)])
                        ->when($discount instanceof Discount, fn (Builder $query) => $query->whereKeyNot($discount->getKey()))
                        ->exists();

                    if ($exists) {
                        $fail(__('The discount code has already been taken.'));
                    }
                },
            ],
            'value_type' => [$creating ? 'required' : 'sometimes', Rule::in(['fixed', 'percent', 'free_shipping'])],
            'value_amount' => $this->valueAmountRules($creating, $valueType),
            'starts_at' => ['sometimes', 'nullable', 'date'],
            'ends_at' => [
                'sometimes',
                'nullable',
                'date',
                function (string $attribute, mixed $value, \Closure $fail) use ($request, $discount): void {
                    if ($value === null || $value === '') {
                        return;
                    }

                    $startsAt = $request->input('starts_at', $discount?->starts_at ?? now());

                    if (Carbon::parse($value)->lte(Carbon::parse($startsAt))) {
                        $fail(__('The end date must be after the start date.'));
                    }
                },
            ],
            'usage_limit' => ['sometimes', 'nullable', 'integer', 'min:1'],
            'status' => ['sometimes', Rule::in(['draft', 'active', 'expired', 'disabled'])],
            'rules_json' => ['sometimes', 'array'],
            'rules_json.minimum_purchase_amount' => ['nullable', 'integer', 'min:0'],
            'rules_json.min_purchase_amount' => ['nullable', 'integer', 'min:0'],
            'rules_json.applicable_product_ids' => ['nullable', 'array'],
            'rules_json.applicable_product_ids.*' => ['integer', Rule::exists('products', 'id')->where('store_id', $store->getKey())],
            'rules_json.applicable_collection_ids' => ['nullable', 'array'],
            'rules_json.applicable_collection_ids.*' => ['integer', Rule::exists('collections', 'id')->where('store_id', $store->getKey())],
            'rules_json.customer_eligibility' => ['nullable', Rule::in(['all', 'specific_customers', 'specific_segments'])],
            'rules_json.once_per_customer' => ['nullable', 'boolean'],
            'rules_json.one_per_customer' => ['nullable', 'boolean'],
        ]);

        return $validated;
    }

    /**
     * @return list<mixed>
     */
    private function valueAmountRules(bool $creating, string $valueType): array
    {
        $presence = $creating ? 'required' : 'sometimes';

        return match ($valueType) {
            'percent' => [$presence, 'integer', 'min:1', 'max:100'],
            'free_shipping' => ['sometimes', 'nullable', 'integer', 'min:0'],
            default => [$presence, 'integer', 'min:1'],
        };
    }

    /**
     * @param  array<string, mixed>  $validated
     * @return array<string, mixed>
     */
    private function attributesForCreate(array $validated, Store $store): array
    {
        $type = (string) $validated['type'];
        $valueType = (string) $validated['value_type'];

        return [
            'store_id' => $store->getKey(),
            'type' => $type,
            'code' => $type === 'code' ? Str::upper(trim((string) $validated['code'])) : null,
            'value_type' => $valueType,
            'value_amount' => $valueType === 'free_shipping' ? 0 : (int) $validated['value_amount'],
            'starts_at' => $validated['starts_at'] ?? now(),
            'ends_at' => $validated['ends_at'] ?? null,
            'usage_limit' => $validated['usage_limit'] ?? null,
            'usage_count' => 0,
            'rules_json' => $this->rulesPayload($validated['rules_json'] ?? []),
            'status' => $validated['status'] ?? 'active',
        ];
    }

    /**
     * @param  array<string, mixed>  $validated
     * @return array<string, mixed>
     */
    private function attributesForUpdate(array $validated, Discount $discount): array
    {
        $attributes = [];
        $valueType = (string) data_get($validated, 'value_type', $discount->value_type->value);

        foreach (['type', 'value_type', 'starts_at', 'ends_at', 'usage_limit', 'status'] as $field) {
            if (array_key_exists($field, $validated)) {
                $attributes[$field] = $validated[$field];
            }
        }

        if (array_key_exists('value_amount', $validated)) {
            $attributes['value_amount'] = $valueType === 'free_shipping' ? 0 : (int) $validated['value_amount'];
        } elseif (array_key_exists('value_type', $validated) && $valueType === 'free_shipping') {
            $attributes['value_amount'] = 0;
        }

        if (array_key_exists('rules_json', $validated)) {
            $attributes['rules_json'] = $this->rulesPayload($validated['rules_json']);
        }

        return $attributes;
    }

    /**
     * @param  array<string, mixed>  $rules
     * @return array<string, mixed>
     */
    private function rulesPayload(array $rules): array
    {
        return [
            'min_purchase_amount' => (int) data_get($rules, 'min_purchase_amount', data_get($rules, 'minimum_purchase_amount', 0)),
            'applicable_product_ids' => $this->integerList(data_get($rules, 'applicable_product_ids', [])),
            'applicable_collection_ids' => $this->integerList(data_get($rules, 'applicable_collection_ids', [])),
            'customer_eligibility' => data_get($rules, 'customer_eligibility', 'all'),
            'one_per_customer' => (bool) data_get($rules, 'one_per_customer', data_get($rules, 'once_per_customer', false)),
        ];
    }

    /**
     * @return list<int>
     */
    private function integerList(mixed $value): array
    {
        return collect(is_array($value) ? $value : [])
            ->map(fn (mixed $id): int => (int) $id)
            ->unique()
            ->values()
            ->all();
    }
}


command: find app/Http/Requests -maxdepth 5 -type f | sort

path: Requests


output:
Chunk ID: 508141
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 277
Output:
app/Http/Requests/Api/Admin/V1/CreateOrderFulfillmentRequest.php
app/Http/Requests/Api/Admin/V1/CreateOrderRefundRequest.php
app/Http/Requests/Api/Storefront/V1/ApplyCheckoutDiscountRequest.php
app/Http/Requests/Api/Storefront/V1/CompleteCheckoutPaymentRequest.php
app/Http/Requests/Api/Storefront/V1/DestroyCartLineRequest.php
app/Http/Requests/Api/Storefront/V1/SearchProductsRequest.php
app/Http/Requests/Api/Storefront/V1/SelectCheckoutPaymentRequest.php
app/Http/Requests/Api/Storefront/V1/SetCheckoutAddressRequest.php
app/Http/Requests/Api/Storefront/V1/SetCheckoutShippingRequest.php
app/Http/Requests/Api/Storefront/V1/StoreAnalyticsEventsRequest.php
app/Http/Requests/Api/Storefront/V1/StoreCartLineRequest.php
app/Http/Requests/Api/Storefront/V1/StoreCartRequest.php
app/Http/Requests/Api/Storefront/V1/StoreCheckoutRequest.php
app/Http/Requests/Api/Storefront/V1/SuggestProductsRequest.php
app/Http/Requests/Api/Storefront/V1/UpdateCartLineRequest.php
app/Http/Requests/Storefront/Auth/RequestCustomerPasswordResetRequest.php
app/Http/Requests/Storefront/Auth/ResetCustomerPasswordRequest.php


command: find app/Enums -maxdepth 1 -type f -print | sort

path: Enums

aggregated_output:
app/Enums/AnalyticsEventType.php
app/Enums/AppInstallationStatus.php
app/Enums/AppStatus.php
app/Enums/CartStatus.php
app/Enums/CheckoutStatus.php
app/Enums/CollectionStatus.php
app/Enums/CollectionType.php
app/Enums/DiscountStatus.php
app/Enums/DiscountType.php
app/Enums/DiscountValueType.php
app/Enums/ExportStatus.php
app/Enums/FinancialStatus.php
app/Enums/FulfillmentShipmentStatus.php
app/Enums/FulfillmentStatus.php
app/Enums/InventoryPolicy.php
app/Enums/MediaStatus.php
app/Enums/MediaType.php
app/Enums/NavigationItemType.php
app/Enums/OrderStatus.php
app/Enums/PageStatus.php
app/Enums/PaymentMethod.php
app/Enums/PaymentStatus.php
app/Enums/ProductStatus.php
app/Enums/RefundStatus.php
app/Enums/ShippingRateType.php
app/Enums/StoreDomainType.php
app/Enums/StoreStatus.php
app/Enums/StoreUserRole.php
app/Enums/TaxMode.php
app/Enums/ThemeStatus.php
app/Enums/VariantStatus.php
app/Enums/WebhookDeliveryStatus.php
app/Enums/WebhookEventType.php
app/Enums/WebhookSubscriptionStatus.php


output:
Chunk ID: 315612
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 250
Output:
app/Enums/AnalyticsEventType.php
app/Enums/AppInstallationStatus.php
app/Enums/AppStatus.php
app/Enums/CartStatus.php
app/Enums/CheckoutStatus.php
app/Enums/CollectionStatus.php
app/Enums/CollectionType.php
app/Enums/DiscountStatus.php
app/Enums/DiscountType.php
app/Enums/DiscountValueType.php
app/Enums/ExportStatus.php
app/Enums/FinancialStatus.php
app/Enums/FulfillmentShipmentStatus.php
app/Enums/FulfillmentStatus.php
app/Enums/InventoryPolicy.php
app/Enums/MediaStatus.php
app/Enums/MediaType.php
app/Enums/NavigationItemType.php
app/Enums/OrderStatus.php
app/Enums/PageStatus.php
app/Enums/PaymentMethod.php
app/Enums/PaymentStatus.php
app/Enums/ProductStatus.php
app/Enums/RefundStatus.php
app/Enums/ShippingRateType.php
app/Enums/StoreDomainType.php
app/Enums/StoreStatus.php
app/Enums/StoreUserRole.php
app/Enums/TaxMode.php
app/Enums/ThemeStatus.php
app/Enums/VariantStatus.php
app/Enums/WebhookDeliveryStatus.php
app/Enums/WebhookEventType.php
app/Enums/WebhookSubscriptionStatus.php


cmd: sed -n '1,220p' app/Http/Requests/Api/Admin/V1/CreateOrderFulfillmentRequest.php


cmd: sed -n '1,240p' app/Http/Controllers/Api/Admin/V1/OrderFulfillmentController.php


cmd: sed -n '1,220p' tests/Feature/Api/AdminOrderApiTest.php


cmd: sed -n '1,180p' app/Enums/InventoryPolicy.php && sed -n '1,160p' app/Enums/ProductStatus.php && sed -n '1,160p' app/Enums/VariantStatus.php && sed -n '1,120p' app/Enums/MediaType.php && sed -n '1,120p' app/Enums/MediaStatus.php


command: sed -n '1,220p' app/Http/Requests/Api/Admin/V1/CreateOrderFulfillmentRequest.php

aggregated_output:
<?php

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

use Illuminate\Foundation\Http\FormRequest;

class CreateOrderFulfillmentRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    /**
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'line_items' => ['required', 'array', 'min:1'],
            'line_items.*.order_line_id' => ['required', 'integer'],
            'line_items.*.quantity' => ['required', 'integer', 'min:1'],
            'tracking_company' => ['nullable', 'string', 'max:255'],
            'tracking_number' => ['nullable', 'string', 'max:255'],
            'tracking_url' => ['nullable', 'url', 'max:2048'],
        ];
    }

    /**
     * @return array<int, int>
     */
    public function lineQuantities(): array
    {
        return collect($this->validated('line_items'))
            ->mapWithKeys(fn (array $line): array => [(int) $line['order_line_id'] => (int) $line['quantity']])
            ->all();
    }
}


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

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

use Illuminate\Foundation\Http\FormRequest;

class CreateOrderFulfillmentRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    /**
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'line_items' => ['required', 'array', 'min:1'],
            'line_items.*.order_line_id' => ['required', 'integer'],
            'line_items.*.quantity' => ['required', 'integer', 'min:1'],
            'tracking_company' => ['nullable', 'string', 'max:255'],
            'tracking_number' => ['nullable', 'string', 'max:255'],
            'tracking_url' => ['nullable', 'url', 'max:2048'],
        ];
    }

    /**
     * @return array<int, int>
     */
    public function lineQuantities(): array
    {
        return collect($this->validated('line_items'))
            ->mapWithKeys(fn (array $line): array => [(int) $line['order_line_id'] => (int) $line['quantity']])
            ->all();
    }
}


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

aggregated_output:
<?php

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

use App\Exceptions\InvalidFulfillmentOperationException;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\Admin\V1\CreateOrderFulfillmentRequest;
use App\Http\Resources\Admin\V1\FulfillmentResource;
use App\Models\Order;
use App\Models\Store;
use App\Services\FulfillmentService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class OrderFulfillmentController extends Controller
{
    public function store(CreateOrderFulfillmentRequest $request, Store $store, Order $order, FulfillmentService $fulfillments): FulfillmentResource|JsonResponse
    {
        $this->authorizeStore($request, $store);
        $this->abortUnlessOrderBelongsToStore($order, $store);

        try {
            $fulfillment = $fulfillments->create($order, $request->lineQuantities(), [
                'tracking_company' => $request->validated('tracking_company'),
                'tracking_number' => $request->validated('tracking_number'),
                'tracking_url' => $request->validated('tracking_url'),
            ]);

            return FulfillmentResource::make($fulfillment->load('lines'))
                ->response()
                ->setStatusCode(201);
        } catch (InvalidFulfillmentOperationException $exception) {
            return response()->json(['message' => $exception->getMessage()], 409);
        }
    }

    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 abortUnlessOrderBelongsToStore(Order $order, Store $store): void
    {
        abort_unless((int) $order->store_id === $store->getKey(), 404);
    }
}


command: sed -n '1,220p' tests/Feature/Api/AdminOrderApiTest.php

aggregated_output:
<?php

use App\Enums\FinancialStatus;
use App\Enums\FulfillmentStatus;
use App\Enums\PaymentStatus;
use App\Models\Fulfillment;
use App\Models\InventoryItem;
use App\Models\Order;
use App\Models\OrderLine;
use App\Models\Payment;
use App\Models\Product;
use App\Models\ProductVariant;
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 adminOrderApiStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

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

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

/**
 * @return array{0: Order, 1: OrderLine, 2: ProductVariant}
 */
function adminOrderApiOrder(Store $store, array $orderAttributes = [], int $quantity = 2, int $unitPrice = 2500): array
{
    $product = Product::factory()
        ->withDefaultVariant($unitPrice)
        ->create(['store_id' => $store->getKey()]);
    $variant = ProductVariant::withoutGlobalScopes()
        ->where('product_id', $product->getKey())
        ->firstOrFail();

    InventoryItem::withoutGlobalScopes()
        ->where('variant_id', $variant->getKey())
        ->update([
            'quantity_on_hand' => 10,
            'quantity_reserved' => 0,
        ]);

    $total = $quantity * $unitPrice;
    $order = Order::factory()->paid()->create(array_merge([
        'store_id' => $store->getKey(),
        'subtotal_amount' => $total,
        'shipping_amount' => 0,
        'tax_amount' => 0,
        'total_amount' => $total,
        'email' => 'buyer@example.test',
    ], $orderAttributes));
    $line = OrderLine::factory()->create([
        'order_id' => $order->getKey(),
        'product_id' => $product->getKey(),
        'variant_id' => $variant->getKey(),
        'title_snapshot' => 'Admin API Product',
        'sku_snapshot' => 'ADMIN-API-001',
        'quantity' => $quantity,
        'unit_price_amount' => $unitPrice,
        'total_amount' => $total,
    ]);

    Payment::factory()->create([
        'order_id' => $order->getKey(),
        'status' => $order->financial_status === FinancialStatus::Pending ? PaymentStatus::Pending : PaymentStatus::Captured,
        'amount' => $total,
        'currency' => $order->currency,
    ]);

    return [$order, $line, $variant];
}

test('admin order api lists and shows store scoped orders', function (): void {
    $store = adminOrderApiStore();
    [$order] = adminOrderApiOrder($store, [
        'order_number' => '#8001',
        'email' => 'alpha@example.test',
    ]);
    $otherStore = Store::factory()->create();
    adminOrderApiOrder($otherStore, [
        'order_number' => '#9001',
        'email' => 'other@example.test',
    ]);

    $this->getJson("/api/admin/v1/stores/{$store->getKey()}/orders")
        ->assertUnauthorized();

    $this->actingAs(adminOrderApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders?query=alpha")
        ->assertOk()
        ->assertJsonPath('data.0.order_number', '#8001')
        ->assertJsonMissing(['order_number' => '#9001']);

    $this->actingAs(adminOrderApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}")
        ->assertOk()
        ->assertJsonPath('data.order_number', '#8001')
        ->assertJsonPath('data.lines.0.title', 'Admin API Product');
});

test('admin order api creates refunds and fulfillments', function (): void {
    $store = adminOrderApiStore();
    [$order, $line] = adminOrderApiOrder($store, quantity: 2, unitPrice: 2500);
    $user = adminOrderApiUser();

    $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/refunds", [
            'amount' => 1000,
            'reason' => 'Customer return',
        ])
        ->assertCreated()
        ->assertJsonPath('data.amount', 1000)
        ->assertJsonPath('data.status', 'processed');

    expect($order->refresh()->financial_status)->toBe(FinancialStatus::PartiallyRefunded);

    $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/fulfillments", [
            'line_items' => [
                ['order_line_id' => $line->getKey(), 'quantity' => 1],
            ],
            'tracking_company' => 'DHL',
            'tracking_number' => 'DHL123',
        ])
        ->assertCreated()
        ->assertJsonPath('data.status', 'pending')
        ->assertJsonPath('data.lines.0.quantity', 1);

    $fulfillment = Fulfillment::query()->where('order_id', $order->getKey())->firstOrFail();

    expect($order->refresh()->fulfillment_status)->toBe(FulfillmentStatus::Partial)
        ->and($fulfillment->tracking_number)->toBe('DHL123');
});

test('admin order api rejects orders outside the requested store', function (): void {
    $store = adminOrderApiStore();
    $otherStore = Store::factory()->create();
    [$otherOrder] = adminOrderApiOrder($otherStore, [
        'order_number' => '#9001',
    ]);

    $this->actingAs(adminOrderApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$otherOrder->getKey()}")
        ->assertNotFound();
});

test('admin order api accepts scoped bearer tokens', function (): void {
    $store = adminOrderApiStore();
    adminOrderApiOrder($store, [
        'order_number' => '#8101',
        'email' => 'token@example.test',
    ]);
    $result = adminOrderApiToken($store, ['read-orders']);

    $this->withToken($result['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders?query=token")
        ->assertOk()
        ->assertJsonPath('data.0.order_number', '#8101');

    expect($result['token']->refresh()->last_used_at)->not->toBeNull();
});

test('admin order api enforces token store scope and abilities', function (): void {
    $store = adminOrderApiStore();
    [$order] = adminOrderApiOrder($store);
    $readOnly = adminOrderApiToken($store, ['read-orders']);
    $writeToken = adminOrderApiToken($store, ['write-orders']);
    $otherStore = Store::factory()->create();
    $otherStoreToken = adminOrderApiToken($otherStore, ['read-orders']);

    $this->withToken($readOnly['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/refunds", [
            'amount' => 500,
            'reason' => 'Read-only token',
        ])
        ->assertForbidden();

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

    $this->withToken($writeToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/refunds", [
            'amount' => 500,
            'reason' => 'Write token',
        ])
        ->assertCreated()
        ->assertJsonPath('data.amount', 500);
});

test('admin order api rejects expired bearer tokens', function (): void {
    $store = adminOrderApiStore();
    adminOrderApiOrder($store);
    $result = adminOrderApiToken($store, ['read-orders']);
    $result['token']->forceFill(['expires_at' => now()->subMinute()])->save();

    $this->withToken($result['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders")
        ->assertUnauthorized();


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

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

use App\Exceptions\InvalidFulfillmentOperationException;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\Admin\V1\CreateOrderFulfillmentRequest;
use App\Http\Resources\Admin\V1\FulfillmentResource;
use App\Models\Order;
use App\Models\Store;
use App\Services\FulfillmentService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class OrderFulfillmentController extends Controller
{
    public function store(CreateOrderFulfillmentRequest $request, Store $store, Order $order, FulfillmentService $fulfillments): FulfillmentResource|JsonResponse
    {
        $this->authorizeStore($request, $store);
        $this->abortUnlessOrderBelongsToStore($order, $store);

        try {
            $fulfillment = $fulfillments->create($order, $request->lineQuantities(), [
                'tracking_company' => $request->validated('tracking_company'),
                'tracking_number' => $request->validated('tracking_number'),
                'tracking_url' => $request->validated('tracking_url'),
            ]);

            return FulfillmentResource::make($fulfillment->load('lines'))
                ->response()
                ->setStatusCode(201);
        } catch (InvalidFulfillmentOperationException $exception) {
            return response()->json(['message' => $exception->getMessage()], 409);
        }
    }

    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 abortUnlessOrderBelongsToStore(Order $order, Store $store): void
    {
        abort_unless((int) $order->store_id === $store->getKey(), 404);
    }
}


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

use App\Enums\FinancialStatus;
use App\Enums\FulfillmentStatus;
use App\Enums\PaymentStatus;
use App\Models\Fulfillment;
use App\Models\InventoryItem;
use App\Models\Order;
use App\Models\OrderLine;
use App\Models\Payment;
use App\Models\Product;
use App\Models\ProductVariant;
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 adminOrderApiStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

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

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

/**
 * @return array{0: Order, 1: OrderLine, 2: ProductVariant}
 */
function adminOrderApiOrder(Store $store, array $orderAttributes = [], int $quantity = 2, int $unitPrice = 2500): array
{
    $product = Product::factory()
        ->withDefaultVariant($unitPrice)
        ->create(['store_id' => $store->getKey()]);
    $variant = ProductVariant::withoutGlobalScopes()
        ->where('product_id', $product->getKey())
        ->firstOrFail();

    InventoryItem::withoutGlobalScopes()
        ->where('variant_id', $variant->getKey())
        ->update([
            'quantity_on_hand' => 10,
            'quantity_reserved' => 0,
        ]);

    $total = $quantity * $unitPrice;
    $order = Order::factory()->paid()->create(array_merge([
        'store_id' => $store->getKey(),
        'subtotal_amount' => $total,
        'shipping_amount' => 0,
        'tax_amount' => 0,
        'total_amount' => $total,
        'email' => 'buyer@example.test',
    ], $orderAttributes));
    $line = OrderLine::factory()->create([
        'order_id' => $order->getKey(),
        'product_id' => $product->getKey(),
        'variant_id' => $variant->getKey(),
        'title_snapshot' => 'Admin API Product',
        'sku_snapshot' => 'ADMIN-API-001',
        'quantity' => $quantity,
        'unit_price_amount' => $unitPrice,
        'total_amount' => $total,
    ]);

    Payment::factory()->create([
        'order_id' => $order->getKey(),
        'status' => $order->financial_status === FinancialStatus::Pending ? PaymentStatus::Pending : PaymentStatus::Captured,
        'amount' => $total,
        'currency' => $order->currency,
    ]);

    return [$order, $line, $variant];
}

test('admin order api lists and shows store scoped orders', function (): void {
    $store = adminOrderApiStore();
    [$order] = adminOrderApiOrder($store, [
        'order_number' => '#8001',
        'email' => 'alpha@example.test',
    ]);
    $otherStore = Store::factory()->create();
    adminOrderApiOrder($otherStore, [
        'order_number' => '#9001',
        'email' => 'other@example.test',
    ]);

    $this->getJson("/api/admin/v1/stores/{$store->getKey()}/orders")
        ->assertUnauthorized();

    $this->actingAs(adminOrderApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders?query=alpha")
        ->assertOk()
        ->assertJsonPath('data.0.order_number', '#8001')
        ->assertJsonMissing(['order_number' => '#9001']);

    $this->actingAs(adminOrderApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}")
        ->assertOk()
        ->assertJsonPath('data.order_number', '#8001')
        ->assertJsonPath('data.lines.0.title', 'Admin API Product');
});

test('admin order api creates refunds and fulfillments', function (): void {
    $store = adminOrderApiStore();
    [$order, $line] = adminOrderApiOrder($store, quantity: 2, unitPrice: 2500);
    $user = adminOrderApiUser();

    $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/refunds", [
            'amount' => 1000,
            'reason' => 'Customer return',
        ])
        ->assertCreated()
        ->assertJsonPath('data.amount', 1000)
        ->assertJsonPath('data.status', 'processed');

    expect($order->refresh()->financial_status)->toBe(FinancialStatus::PartiallyRefunded);

    $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/fulfillments", [
            'line_items' => [
                ['order_line_id' => $line->getKey(), 'quantity' => 1],
            ],
            'tracking_company' => 'DHL',
            'tracking_number' => 'DHL123',
        ])
        ->assertCreated()
        ->assertJsonPath('data.status', 'pending')
        ->assertJsonPath('data.lines.0.quantity', 1);

    $fulfillment = Fulfillment::query()->where('order_id', $order->getKey())->firstOrFail();

    expect($order->refresh()->fulfillment_status)->toBe(FulfillmentStatus::Partial)
        ->and($fulfillment->tracking_number)->toBe('DHL123');
});

test('admin order api rejects orders outside the requested store', function (): void {
    $store = adminOrderApiStore();
    $otherStore = Store::factory()->create();
    [$otherOrder] = adminOrderApiOrder($otherStore, [
        'order_number' => '#9001',
    ]);

    $this->actingAs(adminOrderApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$otherOrder->getKey()}")
        ->assertNotFound();
});

test('admin order api accepts scoped bearer tokens', function (): void {
    $store = adminOrderApiStore();
    adminOrderApiOrder($store, [
        'order_number' => '#8101',
        'email' => 'token@example.test',
    ]);
    $result = adminOrderApiToken($store, ['read-orders']);

    $this->withToken($result['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders?query=token")
        ->assertOk()
        ->assertJsonPath('data.0.order_number', '#8101');

    expect($result['token']->refresh()->last_used_at)->not->toBeNull();
});

test('admin order api enforces token store scope and abilities', function (): void {
    $store = adminOrderApiStore();
    [$order] = adminOrderApiOrder($store);
    $readOnly = adminOrderApiToken($store, ['read-orders']);
    $writeToken = adminOrderApiToken($store, ['write-orders']);
    $otherStore = Store::factory()->create();
    $otherStoreToken = adminOrderApiToken($otherStore, ['read-orders']);

    $this->withToken($readOnly['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/refunds", [
            'amount' => 500,
            'reason' => 'Read-only token',
        ])
        ->assertForbidden();

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

    $this->withToken($writeToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/refunds", [
            'amount' => 500,
            'reason' => 'Write token',
        ])
        ->assertCreated()
        ->assertJsonPath('data.amount', 500);
});

test('admin order api rejects expired bearer tokens', function (): void {
    $store = adminOrderApiStore();
    adminOrderApiOrder($store);
    $result = adminOrderApiToken($store, ['read-orders']);
    $result['token']->forceFill(['expires_at' => now()->subMinute()])->save();

    $this->withToken($result['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders")
        ->assertUnauthorized();


command: sed -n '1,180p' app/Enums/InventoryPolicy.php && sed -n '1,160p' app/Enums/ProductStatus.php && sed -n '1,160p' app/Enums/VariantStatus.php && sed -n '1,120p' app/Enums/MediaType.php && sed -n '1,120p' app/Enums/MediaStatus.php

MediaType.php

cmd: sed -n '1,120p' app/Enums/MediaType.php

MediaStatus.php

aggregated_output:
<?php

namespace App\Enums;

enum InventoryPolicy: string
{
    case Deny = 'deny';
    case Continue = 'continue';
}
<?php

namespace App\Enums;

enum ProductStatus: string
{
    case Draft = 'draft';
    case Active = 'active';
    case Archived = 'archived';
}
<?php

namespace App\Enums;

enum VariantStatus: string
{
    case Active = 'active';
    case Archived = 'archived';
}
<?php

namespace App\Enums;

enum MediaType: string
{
    case Image = 'image';
    case Video = 'video';
}
<?php

namespace App\Enums;

enum MediaStatus: string
{
    case Processing = 'processing';
    case Ready = 'ready';
    case Failed = 'failed';
}


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

namespace App\Enums;

enum InventoryPolicy: string
{
    case Deny = 'deny';
    case Continue = 'continue';
}
<?php

namespace App\Enums;

enum ProductStatus: string
{
    case Draft = 'draft';
    case Active = 'active';
    case Archived = 'archived';
}
<?php

namespace App\Enums;

enum VariantStatus: string
{
    case Active = 'active';
    case Archived = 'archived';
}
<?php

namespace App\Enums;

enum MediaType: string
{
    case Image = 'image';
    case Video = 'video';
}
<?php

namespace App\Enums;

enum MediaStatus: string
{
    case Processing = 'processing';
    case Ready = 'ready';
    case Failed = 'failed';
}


cmd: rg -n "class .*Admin.*Api|admin.api|read-products|write-products|abilities" app bootstrap routes tests -S


cmd: sed -n '1,220p' database/migrations/2026_05_03_213851_create_products_table.php


cmd: sed -n '1,220p' database/migrations/2026_05_03_213853_create_product_variants_table.php


cmd: sed -n '1,220p' database/migrations/2026_05_03_213852_create_product_options_table.php


command: sed -n '1,220p' database/migrations/2026_05_03_213853_create_product_variants_table.php

aggregated_output: sed: database/migrations/2026_05_03_213853_create_product_variants_table.php: No such file or directory

status: failed


command: sed -n '1,220p' database/migrations/2026_05_03_213851_create_products_table.php

aggregated_output: sed: database/migrations/2026_05_03_213851_create_products_table.php: No such file or directory


command: sed -n '1,220p' database/migrations/2026_05_03_213852_create_product_options_table.php

aggregated_output: sed: database/migrations/2026_05_03_213852_create_product_options_table.php: No such file or directory


command: rg -n "class .*Admin.*Api|admin.api|read-products|write-products|abilities" app bootstrap routes tests -S

type: search

cmd: rg -n 'class .*Admin.*Api|admin.api|read-products|write-products|abilities' app bootstrap routes tests -S

query: class .*Admin.*Api|admin.api|read-products|write-products|abilities

path: app

aggregated_output:
routes/api.php:67:        Route::middleware('admin.api:read-products')->group(function (): void {
routes/api.php:72:        Route::middleware('admin.api:read-customers')->group(function (): void {
routes/api.php:77:        Route::middleware('admin.api:read-collections')->group(function (): void {
routes/api.php:81:        Route::middleware('admin.api:write-collections')->group(function (): void {
routes/api.php:87:        Route::middleware('admin.api:read-discounts')->group(function (): void {
routes/api.php:91:        Route::middleware('admin.api:write-discounts')->group(function (): void {
routes/api.php:97:        Route::middleware('admin.api:read-content')->group(function (): void {
routes/api.php:101:        Route::middleware('admin.api:write-content')->group(function (): void {
routes/api.php:107:        Route::middleware('admin.api:read-settings')->group(function (): void {
routes/api.php:114:        Route::middleware('admin.api:write-settings')->group(function (): void {
routes/api.php:123:        Route::middleware('admin.api:write-themes')->group(function (): void {
routes/api.php:129:        Route::middleware('admin.api:read-analytics')->group(function (): void {
routes/api.php:133:        Route::middleware('admin.api:read-orders')->group(function (): void {
routes/api.php:140:        Route::middleware('admin.api:write-orders')->group(function (): void {
bootstrap/app.php:19:            'admin.api' => AuthenticateAdminApi::class,
app/Models/OauthToken.php:24:        'abilities_json',
app/Models/OauthToken.php:34:        'abilities_json' => '[]',
app/Models/OauthToken.php:56:            'abilities_json' => 'array',
app/Livewire/Admin/Developers/Index.php:38:        'read-products',
app/Livewire/Admin/Developers/Index.php:39:        'write-products',
app/Http/Controllers/Api/Admin/V1/PageController.php:98:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Http/Controllers/Api/Admin/V1/ThemeSettingsController.php:46:        if (! $request->attributes->has('admin_api_oauth_token')) {
tests/Feature/Api/AdminSearchIndexApiTest.php:31: * @param  list<string>  $abilities
tests/Feature/Api/AdminSearchIndexApiTest.php:34:function adminSearchIndexApiToken(Store $store, array $abilities): array
tests/Feature/Api/AdminSearchIndexApiTest.php:36:    return app(WebhookService::class)->createApiToken($store, 'Search integration', $abilities);
tests/Feature/Api/AdminSearchIndexApiTest.php:75:test('admin search index api enforces token abilities and store scope', function (): void {
app/Http/Controllers/Api/Admin/V1/ShippingZoneController.php:60:        if (! $request->attributes->has('admin_api_oauth_token')) {
tests/Feature/Api/AdminAnalyticsSummaryApiTest.php:46: * @param  list<string>  $abilities
tests/Feature/Api/AdminAnalyticsSummaryApiTest.php:49:function adminAnalyticsSummaryApiToken(Store $store, array $abilities): array
tests/Feature/Api/AdminAnalyticsSummaryApiTest.php:51:    return app(WebhookService::class)->createApiToken($store, 'Analytics integration', $abilities);
tests/Feature/Api/AdminAnalyticsSummaryApiTest.php:127:test('admin analytics summary api enforces token abilities and store scope', function (): void {
app/Http/Controllers/Api/Admin/V1/TaxSettingsController.php:59:        if (! $request->attributes->has('admin_api_oauth_token')) {
tests/Feature/Api/AdminStoreSettingsApiTest.php:28: * @param  list<string>  $abilities
tests/Feature/Api/AdminStoreSettingsApiTest.php:31:function adminStoreSettingsApiToken(Store $store, array $abilities): array
tests/Feature/Api/AdminStoreSettingsApiTest.php:33:    return app(WebhookService::class)->createApiToken($store, 'Store settings integration', $abilities);
tests/Feature/Api/AdminStoreSettingsApiTest.php:84:test('admin store settings api enforces token abilities and store scope', function (): void {
tests/Feature/Api/AdminPageApiTest.php:28: * @param  list<string>  $abilities
tests/Feature/Api/AdminPageApiTest.php:31:function adminPageApiToken(Store $store, array $abilities): array
tests/Feature/Api/AdminPageApiTest.php:33:    return app(WebhookService::class)->createApiToken($store, 'Content integration', $abilities);
tests/Feature/Api/AdminPageApiTest.php:91:test('admin page api enforces token abilities and store scope', function (): void {
tests/Feature/Api/AdminDiscountApiTest.php:31: * @param  list<string>  $abilities
tests/Feature/Api/AdminDiscountApiTest.php:34:function adminDiscountApiToken(Store $store, array $abilities): array
tests/Feature/Api/AdminDiscountApiTest.php:36:    return app(WebhookService::class)->createApiToken($store, 'Discount integration', $abilities);
tests/Feature/Api/AdminDiscountApiTest.php:96:test('admin discount api enforces token abilities and store scope', function (): void {
app/Http/Controllers/Api/Admin/V1/CustomerController.php:56:        if (! $request->attributes->has('admin_api_oauth_token')) {
tests/Feature/Api/AdminTaxSettingsApiTest.php:29: * @param  list<string>  $abilities
tests/Feature…1901 chars truncated…st.php:32: * @param  list<string>  $abilities
tests/Feature/Api/AdminOrderExportApiTest.php:35:function adminOrderExportApiToken(Store $store, array $abilities): array
tests/Feature/Api/AdminOrderExportApiTest.php:37:    return app(WebhookService::class)->createApiToken($store, 'Order export integration', $abilities);
tests/Feature/Api/AdminOrderExportApiTest.php:111:test('admin order export api enforces token abilities and store scope', function (): void {
tests/Feature/Api/AdminOrderExportApiTest.php:117:    $wrongAbilityToken = adminOrderExportApiToken($store, ['read-products']);
tests/Feature/Api/AdminOrderApiTest.php:37: * @param  list<string>  $abilities
tests/Feature/Api/AdminOrderApiTest.php:40:function adminOrderApiToken(Store $store, array $abilities): array
tests/Feature/Api/AdminOrderApiTest.php:42:    return app(WebhookService::class)->createApiToken($store, 'Order integration', $abilities);
tests/Feature/Api/AdminOrderApiTest.php:184:test('admin order api enforces token store scope and abilities', function (): void {
app/Http/Controllers/Api/Admin/V1/StoreSettingsController.php:90:        if (! $request->attributes->has('admin_api_oauth_token')) {
tests/Feature/Api/AdminShippingSettingsApiTest.php:30: * @param  list<string>  $abilities
tests/Feature/Api/AdminShippingSettingsApiTest.php:33:function adminShippingSettingsApiToken(Store $store, array $abilities): array
tests/Feature/Api/AdminShippingSettingsApiTest.php:35:    return app(WebhookService::class)->createApiToken($store, 'Shipping settings integration', $abilities);
tests/Feature/Api/AdminShippingSettingsApiTest.php:93:test('admin shipping settings api enforces token abilities and store scope', function (): void {
tests/Feature/Api/AdminCollectionApiTest.php:29: * @param  list<string>  $abilities
tests/Feature/Api/AdminCollectionApiTest.php:32:function adminCollectionApiToken(Store $store, array $abilities): array
tests/Feature/Api/AdminCollectionApiTest.php:34:    return app(WebhookService::class)->createApiToken($store, 'Collection integration', $abilities);
tests/Feature/Api/AdminCollectionApiTest.php:99:test('admin collection api enforces token abilities and store scope', function (): void {
app/Http/Controllers/Api/Admin/V1/ProductController.php:65:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Http/Controllers/Api/Admin/V1/ThemeController.php:67:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Http/Controllers/Api/Admin/V1/DiscountController.php:77:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Http/Controllers/Api/Admin/V1/OrderFulfillmentController.php:39:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Http/Controllers/Api/Admin/V1/ShippingRateController.php:54:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Http/Controllers/Api/Admin/V1/OrderController.php:62:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Http/Controllers/Api/Admin/V1/OrderExportController.php:50:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Http/Controllers/Api/Admin/V1/AnalyticsSummaryController.php:66:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Http/Controllers/Api/Admin/V1/SearchIndexController.php:68:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Http/Controllers/Api/Admin/V1/CollectionController.php:105:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Services/WebhookService.php:64:     * @param  list<string>  $abilities
app/Services/WebhookService.php:67:    public function createApiToken(Store $store, string $name, array $abilities): array
app/Services/WebhookService.php:83:                'scopes_json' => $abilities,
app/Services/WebhookService.php:95:            'abilities_json' => $abilities,
app/Http/Middleware/AuthenticateAdminApi.php:12:class AuthenticateAdminApi
app/Http/Middleware/AuthenticateAdminApi.php:19:    public function handle(Request $request, Closure $next, string ...$abilities): Response
app/Http/Middleware/AuthenticateAdminApi.php:23:        app()->forgetInstance('admin_api_oauth_token');
app/Http/Middleware/AuthenticateAdminApi.php:57:        if (! $this->hasAbilities($token, $abilities)) {
app/Http/Middleware/AuthenticateAdminApi.php:63:        $request->attributes->set('admin_api_oauth_token', $token);
app/Http/Middleware/AuthenticateAdminApi.php:64:        app()->instance('admin_api_oauth_token', $token);
app/Http/Middleware/AuthenticateAdminApi.php:81:     * @param  list<string>  $abilities
app/Http/Middleware/AuthenticateAdminApi.php:83:    private function hasAbilities(OauthToken $token, array $abilities): bool
app/Http/Middleware/AuthenticateAdminApi.php:85:        if ($abilities === []) {
app/Http/Middleware/AuthenticateAdminApi.php:89:        $tokenAbilities = $token->abilities_json ?? [];
app/Http/Middleware/AuthenticateAdminApi.php:95:        foreach ($abilities as $ability) {


output:
Chunk ID: 911e3a
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2976
Output:
routes/api.php:67:        Route::middleware('admin.api:read-products')->group(function (): void {
routes/api.php:72:        Route::middleware('admin.api:read-customers')->group(function (): void {
routes/api.php:77:        Route::middleware('admin.api:read-collections')->group(function (): void {
routes/api.php:81:        Route::middleware('admin.api:write-collections')->group(function (): void {
routes/api.php:87:        Route::middleware('admin.api:read-discounts')->group(function (): void {
routes/api.php:91:        Route::middleware('admin.api:write-discounts')->group(function (): void {
routes/api.php:97:        Route::middleware('admin.api:read-content')->group(function (): void {
routes/api.php:101:        Route::middleware('admin.api:write-content')->group(function (): void {
routes/api.php:107:        Route::middleware('admin.api:read-settings')->group(function (): void {
routes/api.php:114:        Route::middleware('admin.api:write-settings')->group(function (): void {
routes/api.php:123:        Route::middleware('admin.api:write-themes')->group(function (): void {
routes/api.php:129:        Route::middleware('admin.api:read-analytics')->group(function (): void {
routes/api.php:133:        Route::middleware('admin.api:read-orders')->group(function (): void {
routes/api.php:140:        Route::middleware('admin.api:write-orders')->group(function (): void {
bootstrap/app.php:19:            'admin.api' => AuthenticateAdminApi::class,
app/Models/OauthToken.php:24:        'abilities_json',
app/Models/OauthToken.php:34:        'abilities_json' => '[]',
app/Models/OauthToken.php:56:            'abilities_json' => 'array',
app/Livewire/Admin/Developers/Index.php:38:        'read-products',
app/Livewire/Admin/Developers/Index.php:39:        'write-products',
app/Http/Controllers/Api/Admin/V1/PageController.php:98:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Http/Controllers/Api/Admin/V1/ThemeSettingsController.php:46:        if (! $request->attributes->has('admin_api_oauth_token')) {
tests/Feature/Api/AdminSearchIndexApiTest.php:31: * @param  list<string>  $abilities
tests/Feature/Api/AdminSearchIndexApiTest.php:34:function adminSearchIndexApiToken(Store $store, array $abilities): array
tests/Feature/Api/AdminSearchIndexApiTest.php:36:    return app(WebhookService::class)->createApiToken($store, 'Search integration', $abilities);
tests/Feature/Api/AdminSearchIndexApiTest.php:75:test('admin search index api enforces token abilities and store scope', function (): void {
app/Http/Controllers/Api/Admin/V1/ShippingZoneController.php:60:        if (! $request->attributes->has('admin_api_oauth_token')) {
tests/Feature/Api/AdminAnalyticsSummaryApiTest.php:46: * @param  list<string>  $abilities
tests/Feature/Api/AdminAnalyticsSummaryApiTest.php:49:function adminAnalyticsSummaryApiToken(Store $store, array $abilities): array
tests/Feature/Api/AdminAnalyticsSummaryApiTest.php:51:    return app(WebhookService::class)->createApiToken($store, 'Analytics integration', $abilities);
tests/Feature/Api/AdminAnalyticsSummaryApiTest.php:127:test('admin analytics summary api enforces token abilities and store scope', function (): void {
app/Http/Controllers/Api/Admin/V1/TaxSettingsController.php:59:        if (! $request->attributes->has('admin_api_oauth_token')) {
tests/Feature/Api/AdminStoreSettingsApiTest.php:28: * @param  list<string>  $abilities
tests/Feature/Api/AdminStoreSettingsApiTest.php:31:function adminStoreSettingsApiToken(Store $store, array $abilities): array
tests/Feature/Api/AdminStoreSettingsApiTest.php:33:    return app(WebhookService::class)->createApiToken($store, 'Store settings integration', $abilities);
tests/Feature/Api/AdminStoreSettingsApiTest.php:84:test('admin store settings api enforces token abilities and store scope', function (): void {
tests/Feature/Api/AdminPageApiTest.php:28: * @param  list<string>  $abilities
tests/Feature/Api/AdminPageApiTest.php:31:function adminPageApiToken(Store $store, array $abilities): array
tests/Feature/Api/AdminPageApiTest.php:33:    return app(WebhookService::class)->createApiToken($store, 'Content integration', $abilities);
tests/Feature/Api/AdminPageApiTest.php:91:test('admin page api enforces token abilities and store scope', function (): void {
tests/Feature/Api/AdminDiscountApiTest.php:31: * @param  list<string>  $abilities
tests/Feature/Api/AdminDiscountApiTest.php:34:function adminDiscountApiToken(Store $store, array $abilities): array
tests/Feature/Api/AdminDiscountApiTest.php:36:    return app(WebhookService::class)->createApiToken($store, 'Discount integration', $abilities);
tests/Feature/Api/AdminDiscountApiTest.php:96:test('admin discount api enforces token abilities and store scope', function (): void {
app/Http/Controllers/Api/Admin/V1/CustomerController.php:56:        if (! $request->attributes->has('admin_api_oauth_token')) {
tests/Feature/Api/AdminTaxSettingsApiTest.php:29: * @param  list<string>  $abilities
tests/Feature/Api/AdminTaxSettingsApiTest.php:32:function adminTaxSettingsApiToken(Store $store, array $abilities): array
tests/Feature/Api/AdminTaxSettingsApiTest.php:34:    return app(WebhookService::class)->createApiToken($store, 'Tax settings integration', $abilities);
tests/Feature/Api/AdminTaxSettingsApiTest.php:79:test('admin tax settings api enforces token abilities and store scope', function (): void {
tests/Feature/Api/AdminCatalogApiTest.php:32: * @param  list<string>  $abilities
tests/Feature/Api/AdminCatalogApiTest.php:35:function adminCatalogApiToken(Store $store, array $abilities): array
tests/Feature/Api/AdminCatalogApiTest.php:37:    return app(WebhookService::class)->createApiToken($store, 'Catalog integration', $abilities);
tests/Feature/Api/AdminCatalogApiTest.php:47:            'handle' => 'admin-api-jacket',
tests/Feature/Api/AdminCatalogApiTest.php:115:test('admin catalog api accepts scoped tokens and enforces abilities', function (): void {
tests/Feature/Api/AdminCatalogApiTest.php:125:    $productToken = adminCatalogApiToken($store, ['read-products']);
tests/Feature/Api/AdminCatalogApiTest.php:165:        ->getJson('/oauth/authorize?client_id=test&redirect_uri=https://example.test/callback&response_type=code&scope=read-products&state=state')
tests/Feature/Api/AdminThemeApiTest.php:33: * @param  list<string>  $abilities
tests/Feature/Api/AdminThemeApiTest.php:36:function adminThemeApiToken(Store $store, array $abilities): array
tests/Feature/Api/AdminThemeApiTest.php:38:    return app(WebhookService::class)->createApiToken($store, 'Theme integration', $abilities);
tests/Feature/Api/AdminThemeApiTest.php:156:test('admin theme api enforces token abilities and store scope', function (): void {
app/Http/Controllers/Api/Admin/V1/OrderRefundController.php:45:        if (! $request->attributes->has('admin_api_oauth_token')) {
tests/Feature/Api/AdminOrderExportApiTest.php:32: * @param  list<string>  $abilities
tests/Feature/Api/AdminOrderExportApiTest.php:35:function adminOrderExportApiToken(Store $store, array $abilities): array
tests/Feature/Api/AdminOrderExportApiTest.php:37:    return app(WebhookService::class)->createApiToken($store, 'Order export integration', $abilities);
tests/Feature/Api/AdminOrderExportApiTest.php:111:test('admin order export api enforces token abilities and store scope', function (): void {
tests/Feature/Api/AdminOrderExportApiTest.php:117:    $wrongAbilityToken = adminOrderExportApiToken($store, ['read-products']);
tests/Feature/Api/AdminOrderApiTest.php:37: * @param  list<string>  $abilities
tests/Feature/Api/AdminOrderApiTest.php:40:function adminOrderApiToken(Store $store, array $abilities): array
tests/Feature/Api/AdminOrderApiTest.php:42:    return app(WebhookService::class)->createApiToken($store, 'Order integration', $abilities);
tests/Feature/Api/AdminOrderApiTest.php:184:test('admin order api enforces token store scope and abilities', function (): void {
app/Http/Controllers/Api/Admin/V1/StoreSettingsController.php:90:        if (! $request->attributes->has('admin_api_oauth_token')) {
tests/Feature/Api/AdminShippingSettingsApiTest.php:30: * @param  list<string>  $abilities
tests/Feature/Api/AdminShippingSettingsApiTest.php:33:function adminShippingSettingsApiToken(Store $store, array $abilities): array
tests/Feature/Api/AdminShippingSettingsApiTest.php:35:    return app(WebhookService::class)->createApiToken($store, 'Shipping settings integration', $abilities);
tests/Feature/Api/AdminShippingSettingsApiTest.php:93:test('admin shipping settings api enforces token abilities and store scope', function (): void {
tests/Feature/Api/AdminCollectionApiTest.php:29: * @param  list<string>  $abilities
tests/Feature/Api/AdminCollectionApiTest.php:32:function adminCollectionApiToken(Store $store, array $abilities): array
tests/Feature/Api/AdminCollectionApiTest.php:34:    return app(WebhookService::class)->createApiToken($store, 'Collection integration', $abilities);
tests/Feature/Api/AdminCollectionApiTest.php:99:test('admin collection api enforces token abilities and store scope', function (): void {
app/Http/Controllers/Api/Admin/V1/ProductController.php:65:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Http/Controllers/Api/Admin/V1/ThemeController.php:67:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Http/Controllers/Api/Admin/V1/DiscountController.php:77:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Http/Controllers/Api/Admin/V1/OrderFulfillmentController.php:39:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Http/Controllers/Api/Admin/V1/ShippingRateController.php:54:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Http/Controllers/Api/Admin/V1/OrderController.php:62:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Http/Controllers/Api/Admin/V1/OrderExportController.php:50:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Http/Controllers/Api/Admin/V1/AnalyticsSummaryController.php:66:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Http/Controllers/Api/Admin/V1/SearchIndexController.php:68:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Http/Controllers/Api/Admin/V1/CollectionController.php:105:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Services/WebhookService.php:64:     * @param  list<string>  $abilities
app/Services/WebhookService.php:67:    public function createApiToken(Store $store, string $name, array $abilities): array
app/Services/WebhookService.php:83:                'scopes_json' => $abilities,
app/Services/WebhookService.php:95:            'abilities_json' => $abilities,
app/Http/Middleware/AuthenticateAdminApi.php:12:class AuthenticateAdminApi
app/Http/Middleware/AuthenticateAdminApi.php:19:    public function handle(Request $request, Closure $next, string ...$abilities): Response
app/Http/Middleware/AuthenticateAdminApi.php:23:        app()->forgetInstance('admin_api_oauth_token');
app/Http/Middleware/AuthenticateAdminApi.php:57:        if (! $this->hasAbilities($token, $abilities)) {
app/Http/Middleware/AuthenticateAdminApi.php:63:        $request->attributes->set('admin_api_oauth_token', $token);
app/Http/Middleware/AuthenticateAdminApi.php:64:        app()->instance('admin_api_oauth_token', $token);
app/Http/Middleware/AuthenticateAdminApi.php:81:     * @param  list<string>  $abilities
app/Http/Middleware/AuthenticateAdminApi.php:83:    private function hasAbilities(OauthToken $token, array $abilities): bool
app/Http/Middleware/AuthenticateAdminApi.php:85:        if ($abilities === []) {
app/Http/Middleware/AuthenticateAdminApi.php:89:        $tokenAbilities = $token->abilities_json ?? [];
app/Http/Middleware/AuthenticateAdminApi.php:95:        foreach ($abilities as $ability) {


output:
Chunk ID: 6c40d8
Wall time: 0.0000 seconds
Process exited with code 1
Original token count: 24
Output:
sed: database/migrations/2026_05_03_213851_create_products_table.php: No such file or directory


output:
Chunk ID: 786962
Wall time: 0.0000 seconds
Process exited with code 1
Original token count: 26
Output:
sed: database/migrations/2026_05_03_213853_create_product_variants_table.php: No such file or directory


output:
Chunk ID: 97ae22
Wall time: 0.0000 seconds
Process exited with code 1
Original token count: 26
Output:
sed: database/migrations/2026_05_03_213852_create_product_options_table.php: No such file or directory


cmd: rg --files database/migrations | rg 'product|inventory|collection_products|variant_option'


cmd: sed -n '1,140p' app/Http/Middleware/AuthenticateAdminApi.php


command: sed -n '1,140p' app/Http/Middleware/AuthenticateAdminApi.php

aggregated_output:
<?php

namespace App\Http\Middleware;

use App\Enums\AppInstallationStatus;
use App\Models\OauthToken;
use App\Models\Store;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class AuthenticateAdminApi
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next, string ...$abilities): Response
    {
        $store = $this->storeFromRoute($request);

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

        if ($request->user() !== null) {
            if ($request->user()->stores()->whereKey($store->getKey())->exists()) {
                return $next($request);
            }

            return response()->json(['message' => 'Forbidden.'], 403);
        }

        $plainTextToken = $request->bearerToken();

        if (! is_string($plainTextToken) || $plainTextToken === '') {
            return response()->json(['message' => 'Unauthenticated.'], 401);
        }

        $token = OauthToken::query()
            ->with('installation')
            ->where('access_token_hash', hash('sha256', $plainTextToken))
            ->first();

        if (! $token instanceof OauthToken || $token->isExpired()) {
            return response()->json(['message' => 'Unauthenticated.'], 401);
        }

        $installation = $token->installation;

        if ($installation === null
            || $installation->status !== AppInstallationStatus::Active
            || (int) $installation->store_id !== $store->getKey()) {
            return response()->json(['message' => 'Forbidden.'], 403);
        }

        if (! $this->hasAbilities($token, $abilities)) {
            return response()->json(['message' => 'Forbidden.'], 403);
        }

        $token->forceFill(['last_used_at' => now()])->save();

        $request->attributes->set('admin_api_oauth_token', $token);
        app()->instance('admin_api_oauth_token', $token);

        return $next($request);
    }

    private function storeFromRoute(Request $request): Store
    {
        $store = $request->route('store');

        if ($store instanceof Store) {
            return $store;
        }

        return Store::query()->findOrFail($store);
    }

    /**
     * @param  list<string>  $abilities
     */
    private function hasAbilities(OauthToken $token, array $abilities): bool
    {
        if ($abilities === []) {
            return true;
        }

        $tokenAbilities = $token->abilities_json ?? [];

        if (in_array('*', $tokenAbilities, true)) {
            return true;
        }

        foreach ($abilities as $ability) {
            if (! in_array($ability, $tokenAbilities, true)) {
                return false;
            }
        }

        return true;
    }
}


command: rg --files database/migrations | rg 'product|inventory|collection_products|variant_option'

cmd: rg --files database/migrations

path: migrations

type: search

query: product|inventory|collection_products|variant_option


output:
Chunk ID: ae6913
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 161
Output:
database/migrations/2026_05_04_030848_create_products_fts_table.php
database/migrations/2026_05_03_213900_create_variant_option_values_table.php
database/migrations/2026_05_03_213901_create_inventory_items_table.php
database/migrations/2026_05_03_213904_create_product_media_table.php
database/migrations/2026_05_03_213856_create_products_table.php
database/migrations/2026_05_03_213858_create_product_option_values_table.php
database/migrations/2026_05_03_213857_create_product_options_table.php
database/migrations/2026_05_03_213903_create_collection_products_table.php
database/migrations/2026_05_03_213859_create_product_variants_table.php


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

namespace App\Http\Middleware;

use App\Enums\AppInstallationStatus;
use App\Models\OauthToken;
use App\Models\Store;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class AuthenticateAdminApi
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next, string ...$abilities): Response
    {
        $store = $this->storeFromRoute($request);

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

        if ($request->user() !== null) {
            if ($request->user()->stores()->whereKey($store->getKey())->exists()) {
                return $next($request);
            }

            return response()->json(['message' => 'Forbidden.'], 403);
        }

        $plainTextToken = $request->bearerToken();

        if (! is_string($plainTextToken) || $plainTextToken === '') {
            return response()->json(['message' => 'Unauthenticated.'], 401);
        }

        $token = OauthToken::query()
            ->with('installation')
            ->where('access_token_hash', hash('sha256', $plainTextToken))
            ->first();

        if (! $token instanceof OauthToken || $token->isExpired()) {
            return response()->json(['message' => 'Unauthenticated.'], 401);
        }

        $installation = $token->installation;

        if ($installation === null
            || $installation->status !== AppInstallationStatus::Active
            || (int) $installation->store_id !== $store->getKey()) {
            return response()->json(['message' => 'Forbidden.'], 403);
        }

        if (! $this->hasAbilities($token, $abilities)) {
            return response()->json(['message' => 'Forbidden.'], 403);
        }

        $token->forceFill(['last_used_at' => now()])->save();

        $request->attributes->set('admin_api_oauth_token', $token);
        app()->instance('admin_api_oauth_token', $token);

        return $next($request);
    }

    private function storeFromRoute(Request $request): Store
    {
        $store = $request->route('store');

        if ($store instanceof Store) {
            return $store;
        }

        return Store::query()->findOrFail($store);
    }

    /**
     * @param  list<string>  $abilities
     */
    private function hasAbilities(OauthToken $token, array $abilities): bool
    {
        if ($abilities === []) {
            return true;
        }

        $tokenAbilities = $token->abilities_json ?? [];

        if (in_array('*', $tokenAbilities, true)) {
            return true;
        }

        foreach ($abilities as $ability) {
            if (! in_array($ability, $tokenAbilities, true)) {
                return false;
            }
        }

        return true;
    }
}


cmd: sed -n '1,220p' database/migrations/2026_05_03_213856_create_products_table.php


cmd: sed -n '1,220p' database/migrations/2026_05_03_213857_create_product_options_table.php


cmd: sed -n '1,220p' database/migrations/2026_05_03_213858_create_product_option_values_table.php


cmd: sed -n '1,240p' database/migrations/2026_05_03_213859_create_product_variants_table.php


cmd: sed -n '1,220p' database/migrations/2026_05_03_213901_create_inventory_items_table.php


cmd: sed -n '1,180p' database/migrations/2026_05_03_213900_create_variant_option_values_table.php


command: sed -n '1,220p' database/migrations/2026_05_03_213857_create_product_options_table.php

aggregated_output:
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('product_options', function (Blueprint $table) {
            $table->id();
            $table->foreignId('product_id')->constrained()->cascadeOnDelete();
            $table->string('name');
            $table->unsignedInteger('position')->default(0);

            $table->index('product_id');
            $table->unique(['product_id', 'position']);
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('product_options');
    }
};