output:
Chunk ID: 162048
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 767
Output:
## 7. Webhook Signature Verification

### 7.1 Inbound Payment Webhooks

> **Not applicable.** All payment processing happens in-process via the Mock PSP. There are no inbound payment webhooks from external providers (no Stripe, no PayPal). This section is preserved for reference only -- if a real PSP is integrated in the future, inbound webhook verification would follow the patterns described in the outbound webhook section below (HMAC signature verification).

---

### 7.2 Platform Webhooks (Outbound)

Service: `App\Services\WebhookDispatcher`

When the platform sends webhooks to external subscribers (third-party apps, integrations).

```mermaid
sequenceDiagram
    participant App as Platform
    participant Sub as Subscriber URL

    App->>App: Serialize payload as JSON
    App->>App: Generate Unix timestamp
    App->>App: Concatenate: "{timestamp}.{json_body}"
    App->>App: HMAC-SHA256(signing_secret, concatenated_message)
    App->>Sub: POST to subscriber URL
    Note right of Sub: Headers: X-Platform-Signature, X-Platform-Timestamp, Content-Type: application/json
    Sub-->>App: 200 OK (acknowledged)
```

**Requirements:**

1. The payload is serialized as JSON.
2. A Unix timestamp is generated (current time).
3. The message to sign is: `"{timestamp}.{json_body}"` (timestamp, dot, JSON string).
4. The signature is computed as HMAC-SHA256 using the per-subscription `signing_secret` (decrypted from the `signing_secret_encrypted` column).
5. The HTTP request to the subscriber includes three headers:
   - `X-Platform-Signature`: the hex-encoded HMAC digest
   - `X-Platform-Timestamp`: the Unix timestamp as a string
   - `Content-Type`: `application/json`
6. Replay protection guidance for recipients: reject any request where `X-Platform-Timestamp` is more than 5 minutes in the past.

---

## 8. Audit Logging

### 8.1 Logging Channel

A dedicated `audit` channel must be added to `config/logging.php`.

| Setting | Value |
|---------|-------|
| Driver | `daily` (file-based, daily rotation) |
| Path | `storage/logs/audit.log` |
| Level | `info` |
| Retention | 90 days |

---

### 8.2 Log Entry Format

All audit log entries are structured JSON objects with the following schema:

| Field | Type | Required | Description |
|-------|------|:--------:|-------------|
| `timestamp` | string (ISO-8601) | Y | When the event occurred |
| `event` | string | Y | Event identifier (e.g., `product.updated`, `auth.login`) |
| `user_id` | integer or null | N | ID of the admin user who triggered the event |
| `store_id` | integer or null | N | ID of the store context |
| `resource_type` | string or null | N | Type of resource affected (e.g., `product`, `order`) |
| `resource_id` | integer or null | N | ID of the affected resource |
| `ip` | string | Y | Client IP address |
| `user_agent` | string | Y | Client user agent string |
| `changes` | object or null | N | For update events: object mapping field names to `[old_value, new_value]` tuples |
| (additional keys) | varies | N | Event-specific extra data |

---

### 8.3 Events to Log


output:
Chunk ID: 9d45f4
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 239
Output:

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/navigation` | Navigation menus management | `Admin\Navigation\Index` |

#### Apps

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/apps` | Apps directory - list installed and available apps | `Admin\Apps\Index` |
| GET | `/admin/apps/{installation}` | Installed app detail - scopes, webhooks, usage | `Admin\Apps\Show` |

#### Developers

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/developers` | Developer settings - API keys/tokens, webhook management | `Admin\Developers\Index` |

> **Note:** Webhook subscription management (CRUD) is available exclusively through the admin UI at `/admin/developers`. There are no REST API endpoints for programmatic webhook subscription management in the initial implementation.

#### Analytics


server: laravel-boost

tool: database-schema

type: text

engine: sqlite

store_id: integer

date: varchar

orders_count: integer

revenue_amount: integer

aov_amount: integer

visits_count: integer

add_to_cart_count: integer

checkout_started_count: integer

checkout_completed_count: integer

type: varchar

session_id: varchar

customer_id: integer

properties_json: text

client_event_id: varchar

occurred_at: datetime

created_at: datetime

key: varchar

value: text

expiration: integer

owner: varchar

cart_id: integer

variant_id: integer

quantity: integer

unit_price_amount: integer

line_subtotal_amount: integer

line_discount_amount: integer

line_total_amount: integer

currency: varchar

cart_version: integer

status: varchar

updated_at: datetime

payment_method: varchar

email: varchar

shipping_address_json: text

billing_address_json: text

shipping_method_id: integer

discount_code: varchar

tax_provider_snapshot_json: text

totals_json: text

expires_at: datetime

collection_id: integer

product_id: integer

position: integer

title: varchar

handle: varchar

description_html: text

label: varchar

address_json: text

token: varchar

varchar

password_hash: varchar

code: varchar

value_type: varchar

value_amount: integer

starts_at: datetime

ends_at: datetime

usage_limit: integer

usage_count: integer

rules_json: text

connection: text

queue: text

payload: text

exception: text

failed_at: datetime

fulfillment_id: integer

order_line_id: integer

order_id: integer

title: varchar

title: varchar

title: varchar

query: text


cmd: rg --files app/Enums app/Models app/Services app/Jobs app/Livewire/Admin app/Policies database/factories database/seeders tests/Feature | sed -n '1,220p'


cmd: sed -n '1,220p' app/Models/Concerns/BelongsToStore.php


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


cmd: sed -n '1,260p' resources/views/livewire/admin/search/settings.blade.php


cmd: sed -n '1,240p' app/Providers/AppServiceProvider.php


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

Store.php

type: read

cmd: sed -n '1,240p' app/Models/Store.php

aggregated_output:
<?php

namespace App\Models;

use App\Enums\StoreStatus;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;

class Store extends Model
{
    /** @use HasFactory<\Database\Factories\StoreFactory> */
    use HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'organization_id',
        'name',
        'handle',
        'status',
        'default_currency',
        'default_locale',
        'timezone',
    ];

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

    /**
     * @return HasMany<StoreDomain, $this>
     */
    public function domains(): HasMany
    {
        return $this->hasMany(StoreDomain::class);
    }

    /**
     * @return BelongsToMany<User, $this>
     */
    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class, 'store_users')
            ->using(StoreUser::class)
            ->withPivot('role');
    }

    /**
     * @return HasOne<StoreSettings, $this>
     */
    public function settings(): HasOne
    {
        return $this->hasOne(StoreSettings::class);
    }

    /**
     * @return HasMany<Theme, $this>
     */
    public function themes(): HasMany
    {
        return $this->hasMany(Theme::class);
    }

    /**
     * @return HasMany<Page, $this>
     */
    public function pages(): HasMany
    {
        return $this->hasMany(Page::class);
    }

    /**
     * @return HasMany<NavigationMenu, $this>
     */
    public function navigationMenus(): HasMany
    {
        return $this->hasMany(NavigationMenu::class);
    }

    /**
     * @return HasMany<Cart, $this>
     */
    public function carts(): HasMany
    {
        return $this->hasMany(Cart::class);
    }

    /**
     * @return HasMany<Checkout, $this>
     */
    public function checkouts(): HasMany
    {
        return $this->hasMany(Checkout::class);
    }

    /**
     * @return HasMany<ShippingZone, $this>
     */
    public function shippingZones(): HasMany
    {
        return $this->hasMany(ShippingZone::class);
    }

    /**
     * @return HasOne<TaxSettings, $this>
     */
    public function taxSettings(): HasOne
    {
        return $this->hasOne(TaxSettings::class);
    }

    /**
     * @return HasMany<Discount, $this>
     */
    public function discounts(): HasMany
    {
        return $this->hasMany(Discount::class);
    }

    /**
     * @return HasMany<Order, $this>
     */
    public function orders(): HasMany
    {
        return $this->hasMany(Order::class);
    }

    public function isSuspended(): bool
    {
        return $this->status === StoreStatus::Suspended;
    }

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

status: completed


command: sed -n '1,220p' app/Models/Concerns/BelongsToStore.php

aggregated_output:
<?php

namespace App\Models\Concerns;

use App\Models\Scopes\StoreScope;
use App\Models\Store;

trait BelongsToStore
{
    public static function bootBelongsToStore(): void
    {
        static::addGlobalScope(new StoreScope);

        static::creating(function (object $model): void {
            if ($model->store_id || ! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if ($store instanceof Store) {
                $model->store_id = $store->getKey();
            }
        });
    }
}


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

aggregated_output:
<?php

namespace App\Livewire\Admin\Search;

use App\Models\SearchSettings;
use App\Models\Store;
use App\Services\SearchService;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Attributes\Locked;
use Livewire\Component;

class Settings extends Component
{
    use AuthorizesRequests;

    #[Locked]
    public int $storeId;

    /**
     * @var array<int, string>
     */
    public array $synonymGroups = [];

    public string $stopWords = '';

    public ?string $lastIndexedAt = null;

    public bool $isReindexing = false;

    public ?int $reindexProgress = null;

    public function mount(): void
    {
        $store = $this->store();

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

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

        $settings = $this->settings();
        $this->synonymGroups = collect($settings->synonyms_json ?? [])
            ->map(fn (array $group): string => implode(', ', $group))
            ->values()
            ->all();
        $this->stopWords = implode(', ', $settings->stop_words_json ?? []);
        $this->lastIndexedAt = $settings->updated_at?->toDayDateTimeString();
    }

    public function addSynonymGroup(): void
    {
        $this->synonymGroups[] = '';
    }

    public function removeSynonymGroup(int $index): void
    {
        unset($this->synonymGroups[$index]);
        $this->synonymGroups = array_values($this->synonymGroups);
    }

    public function save(): void
    {
        $this->authorize('update', $this->scopedStore());

        $this->validate([
            'synonymGroups' => ['array', 'max:50'],
            'synonymGroups.*' => ['nullable', 'string', 'max:500'],
            'stopWords' => ['nullable', 'string', 'max:2000'],
        ], [], [
            'synonymGroups.*' => 'synonym group',
            'stopWords' => 'stop words',
        ]);

        $settings = $this->settings();
        $settings->forceFill([
            'synonyms_json' => $this->parseSynonyms(),
            'stop_words_json' => $this->parseWordList($this->stopWords),
        ])->save();

        $this->lastIndexedAt = $settings->updated_at?->toDayDateTimeString();

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

    public function triggerReindex(SearchService $search): void
    {
        $store = $this->scopedStore();

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

        $this->isReindexing = true;
        $this->reindexProgress = 10;

        $count = $search->reindex($store);

        $settings = $this->settings();
        $settings->touch();

        $this->lastIndexedAt = $settings->refresh()->updated_at?->toDayDateTimeString();
        $this->reindexProgress = 100;
        $this->isReindexing = false;

        session()->flash('status', __('Search index rebuilt for :count products', ['count' => $count]));
        $this->dispatch('toast', type: 'success', message: __('Search index rebuilt'));
    }

    public function pollReindexStatus(): void
    {
        $this->reindexProgress = $this->isReindexing ? $this->reindexProgress : null;
    }

    public function render(): mixed
    {
        return view('livewire.admin.search.settings')->layout('layouts.app', [
            'title' => __('Search settings'),
        ]);
    }

    /**
     * @return list<list<string>>
     */
    private function parseSynonyms(): array
    {
        return collect($this->synonymGroups)
            ->map(fn (string $group): array => $this->parseWordList($group))
            ->filter(fn (array $group): bool => count($group) >= 2)
            ->values()
            ->all();
    }

    /**
     * @return list<string>
     */
    private function parseWordList(string $words): array
    {
        return collect(explode(',', mb_strtolower($words)))
            ->map(fn (string $word): string => trim($word))
            ->filter()
            ->unique()
            ->values()
            ->all();
    }

    private function settings(): SearchSettings
    {
        return SearchSettings::withoutGlobalScopes()->firstOrCreate([
            'store_id' => $this->storeId ?? $this->store()->getKey(),
        ]);
    }

    private function store(): Store
    {
        $store = app('current_store');

        abort_unless($store instanceof Store, 404);

        return $store;
    }

    private function scopedStore(): Store
    {
        return Store::query()->findOrFail($this->storeId);
    }
}


command: rg --files app/Enums app/Models app/Services app/Jobs app/Livewire/Admin app/Policies database/factories database/seeders tests/Feature | sed -n '1,220p'

path: Enums

aggregated_output:
app/Policies/DiscountPolicy.php
app/Policies/OrderPolicy.php
app/Policies/PagePolicy.php
app/Policies/CustomerPolicy.php
app/Policies/RefundPolicy.php
app/Policies/StorePolicy.php
app/Policies/FulfillmentPolicy.php
app/Policies/ProductPolicy.php
app/Policies/ThemePolicy.php
app/Policies/CollectionPolicy.php
app/Jobs/ProcessMediaUpload.php
app/Jobs/CancelUnpaidBankTransferOrders.php
app/Jobs/AggregateAnalytics.php
app/Jobs/CleanupAbandonedCarts.php
app/Jobs/ExpireAbandonedCheckouts.php
app/Services/InventoryService.php
app/Services/VariantMatrixService.php
app/Services/SearchService.php
app/Services/DiscountService.php
app/Services/PricingEngine.php
app/Services/CartService.php
app/Services/AnalyticsService.php
app/Services/ShippingCalculator.php
app/Services/ProductService.php
app/Services/RefundService.php
app/Services/NavigationService.php
app/Models/ThemeSettings.php
app/Models/Fulfillment.php
app/Models/User.php
app/Models/ProductMedia.php
app/Models/Theme.php
app/Models/StoreDomain.php
app/Models/SearchSettings.php
app/Models/Store.php
app/Models/Order.php
app/Models/Product.php
app/Models/Discount.php
app/Models/Cart.php
app/Models/CartLine.php
app/Models/NavigationMenu.php
app/Models/InventoryItem.php
app/Models/Refund.php
app/Models/NavigationItem.php
app/Models/ProductOption.php
app/Models/ThemeFile.php
app/Models/OrderLine.php
app/Models/Page.php
app/Models/Collection.php
app/Models/ShippingZone.php
app/Models/TaxSettings.php
app/Models/ProductOptionValue.php
app/Models/ProductVariant.php
app/Models/Customer.php
app/Models/AnalyticsDaily.php
app/Models/Payment.php
app/Models/Checkout.php
app/Models/StoreUser.php
app/Models/CustomerAddress.php
app/Models/SearchQuery.php
app/Models/StoreSettings.php
app/Models/ShippingRate.php
app/Models/AnalyticsEvent.php
app/Models/FulfillmentLine.php
app/Models/Concerns/BelongsToStore.php
app/Models/Organization.php
app/Enums/VariantStatus.php
app/Enums/DiscountType.php
app/Enums/PageStatus.php
app/Enums/OrderStatus.php
app/Enums/MediaType.php
app/Enums/FulfillmentShipmentStatus.php
app/Enums/TaxMode.php
app/Enums/CollectionType.php
app/Enums/CartStatus.php
app/Enums/RefundStatus.php
app/Enums/InventoryPolicy.php
app/Enums/StoreUserRole.php
app/Enums/AnalyticsEventType.php
app/Enums/PaymentMethod.php
app/Enums/FinancialStatus.php
app/Enums/ShippingRateType.php
app/Enums/MediaStatus.php
app/Enums/CollectionStatus.php
app/Enums/DiscountValueType.php
app/Enums/DiscountStatus.php
app/Enums/NavigationItemType.php
app/Enums/FulfillmentStatus.php
app/Enums/ProductStatus.php
app/Enums/StoreDomainType.php
app/Enums/CheckoutStatus.php
app/Enums/ThemeStatus.php
app/Enums/StoreStatus.php
app/Enums/PaymentStatus.php
app/Livewire/Admin/Discounts/Form.php
app/Livewire/Admin/Discounts/Index.php
app/Services/Tax/StripeTaxProvider.php
app/Services/Tax/ManualTaxProvider.php
tests/Feature/Analytics/AnalyticsServiceTest.php
app/Livewire/Admin/Navigation/Index.php
app/Services/ThemeSettingsService.php
app/Services/TaxCalculator.php
app/Services/FulfillmentService.php
app/Services/CheckoutService.php
app/Services/PaymentService.php
app/Models/Scopes/StoreScope.php
tests/Feature/Admin/AnalyticsDashboardTest.php
tests/Feature/Admin/CustomerManagementTest.php
tests/Feature/Admin/OrderManagementTest.php
tests/Feature/Admin/ContentManagementTest.php
tests/Feature/Admin/DiscountManagementTest.php
tests/Feature/Admin/DashboardTest.php
tests/Feature/Admin/SettingsManagementTest.php
tests/Feature/Admin/SearchSettingsTest.php
app/Livewire/Admin/Auth/Login.php
tests/Feature/Orders/OrderServiceTest.php
tests/Feature/Orders/BankTransferOrderTest.php
tests/Feature/Orders/RefundServiceTest.php
tests/Feature/Orders/FulfillmentServiceTest.php
tests/Feature/DashboardTest.php
tests/Feature/Catalog/ProductServiceTest.php
tests/Feature/Catalog/CatalogFoundationTest.php
tests/Feature/Catalog/InventoryServiceTest.php
tests/Feature/Catalog/VariantMatrixServiceTest.php
tests/Feature/Catalog/CatalogUiTest.php
tests/Feature/Catalog/MediaProcessingTest.php
app/Livewire/Admin/Products/Form.php
app/Livewire/Admin/Products/Index.php
app/Livewire/Admin/Dashboard.php
tests/Feature/Api/StorefrontCheckoutApiTest.php
tests/Feature/Api/StorefrontSearchApiTest.php
tests/Feature/Api/StorefrontAnalyticsApiTest.php
tests/Feature/Api/StorefrontOrderApiTest.php
tests/Feature/Api/AdminOrderApiTest.php
tests/Feature/Api/StorefrontCartApiTest.php
database/seeders/ProductVariantSeeder.php
database/seeders/ThemeFileSeeder.php
database/seeders/CustomerSeeder.php
database/seeders/AnalyticsSeeder.php
database/seeders/OrderLineSeeder.php
database/seeders/PaymentSeeder.php
database/seeders/ThemeSeeder.php
database/seeders/UserSeeder.php
database/seeders/NavigationMenuSeeder.php
database/seeders/ProductSeeder.php
database/seeders/FulfillmentSeeder.php
database/seeders/StoreSeeder.php
database/seeders/CheckoutSeeder.php
database/seeders/CollectionSeeder.php
database/seeders/ShippingRateSeeder.php
database/seeders/StoreUserSeeder.php
database/seeders/ProductOptionSeeder.php
database/seeders/StoreDomainSeeder.php
database/seeders/TaxSettingsSeeder.php
database/seeders/ThemeSettingsSeeder.php
database/seeders/CartSeeder.php
database/seeders/ShippingZoneSeeder.php
database/seeders/OrganizationSeeder.php
database/seeders/ProductMediaSeeder.php
database/seeders/NavigationItemSeeder.php
database/seeders/InventoryItemSeeder.php
database/seeders/RefundSeeder.php
database/seeders/StoreSettingsSeeder.php
database/seeders/CustomerAddressSeeder.php
database/seeders/ProductOptionValueSeeder.php
database/seeders/SearchSettingsSeeder.php
database/seeders/FulfillmentLineSeeder.php
database/seeders/CartLineSeeder.php
database/seeders/OrderSeeder.php
database/seeders/PageSeeder.php
database/seeders/DiscountSeeder.php
database/seeders/DatabaseSeeder.php
database/factories/CartFactory.php
database/factories/CollectionFactory.php
database/factories/StoreDomainFactory.php
database/factories/PageFactory.php
database/factories/CheckoutFactory.php
database/factories/NavigationItemFactory.php
database/factories/FulfillmentFactory.php
database/factories/ProductOptionFactory.php
database/factories/CartLineFactory.php
database/factories/InventoryItemFactory.php
database/factories/PaymentFactory.php
database/factories/ProductOptionValueFactory.php
database/factories/OrderFactory.php
database/factories/ProductMediaFactory.php
database/factories/ThemeFactory.php
database/factories/OrganizationFactory.php
database/factories/OrderLineFactory.php
database/factories/StoreFactory.php
database/factories/ThemeSettingsFactory.php
database/factories/ShippingRateFactory.php
database/factories/StoreSettingsFactory.php
database/factories/SearchQueryFactory.php
database/factories/UserFactory.php
database/factories/DiscountFactory.php
database/factories/AnalyticsEventFactory.php
database/factories/TaxSettingsFactory.php
database/factories/ThemeFileFactory.php
database/factories/CustomerFactory.php
database/factories/ProductFactory.php
database/factories/AnalyticsDailyFactory.php
database/factories/CustomerAddressFactory.php
database/factories/RefundFactory.php
database/factories/NavigationMenuFactory.php
database/factories/ProductVariantFactory.php
database/factories/SearchSettingsFactory.php
database/factories/ShippingZoneFactory.php
database/factories/FulfillmentLineFactory.php
app/Services/Payments/MockPaymentProvider.php
tests/Feature/Cart/CartServiceTest.php
tests/Feature/Auth/PasswordResetTest.php
tests/Feature/Auth/AuthenticationTest.php
tests/Feature/Auth/RegistrationTest.php
tests/Feature/Auth/TwoFactorChallengeTest.php
tests/Feature/Auth/EmailVerificationTest.php
app/Services/OrderService.php
tests/Feature/Auth/PasswordConfirmationTest.php
tests/Feature/ExampleTest.php
app/Livewire/Admin/Analytics/Index.php
app/Livewire/Admin/Settings/Shipping.php


output:
Chunk ID: 7629d9
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1957
Output:
app/Policies/DiscountPolicy.php
app/Policies/OrderPolicy.php
app/Policies/PagePolicy.php
app/Policies/CustomerPolicy.php
app/Policies/RefundPolicy.php
app/Policies/StorePolicy.php
app/Policies/FulfillmentPolicy.php
app/Policies/ProductPolicy.php
app/Policies/ThemePolicy.php
app/Policies/CollectionPolicy.php
app/Jobs/ProcessMediaUpload.php
app/Jobs/CancelUnpaidBankTransferOrders.php
app/Jobs/AggregateAnalytics.php
app/Jobs/CleanupAbandonedCarts.php
app/Jobs/ExpireAbandonedCheckouts.php
app/Services/InventoryService.php
app/Services/VariantMatrixService.php
app/Services/SearchService.php
app/Services/DiscountService.php
app/Services/PricingEngine.php
app/Services/CartService.php
app/Services/AnalyticsService.php
app/Services/ShippingCalculator.php
app/Services/ProductService.php
app/Services/RefundService.php
app/Services/NavigationService.php
app/Models/ThemeSettings.php
app/Models/Fulfillment.php
app/Models/User.php
app/Models/ProductMedia.php
app/Models/Theme.php
app/Models/StoreDomain.php
app/Models/SearchSettings.php
app/Models/Store.php
app/Models/Order.php
app/Models/Product.php
app/Models/Discount.php
app/Models/Cart.php
app/Models/CartLine.php
app/Models/NavigationMenu.php
app/Models/InventoryItem.php
app/Models/Refund.php
app/Models/NavigationItem.php
app/Models/ProductOption.php
app/Models/ThemeFile.php
app/Models/OrderLine.php
app/Models/Page.php
app/Models/Collection.php
app/Models/ShippingZone.php
app/Models/TaxSettings.php
app/Models/ProductOptionValue.php
app/Models/ProductVariant.php
app/Models/Customer.php
app/Models/AnalyticsDaily.php
app/Models/Payment.php
app/Models/Checkout.php
app/Models/StoreUser.php
app/Models/CustomerAddress.php
app/Models/SearchQuery.php
app/Models/StoreSettings.php
app/Models/ShippingRate.php
app/Models/AnalyticsEvent.php
app/Models/FulfillmentLine.php
app/Models/Concerns/BelongsToStore.php
app/Models/Organization.php
app/Enums/VariantStatus.php
app/Enums/DiscountType.php
app/Enums/PageStatus.php
app/Enums/OrderStatus.php
app/Enums/MediaType.php
app/Enums/FulfillmentShipmentStatus.php
app/Enums/TaxMode.php
app/Enums/CollectionType.php
app/Enums/CartStatus.php
app/Enums/RefundStatus.php
app/Enums/InventoryPolicy.php
app/Enums/StoreUserRole.php
app/Enums/AnalyticsEventType.php
app/Enums/PaymentMethod.php
app/Enums/FinancialStatus.php
app/Enums/ShippingRateType.php
app/Enums/MediaStatus.php
app/Enums/CollectionStatus.php
app/Enums/DiscountValueType.php
app/Enums/DiscountStatus.php
app/Enums/NavigationItemType.php
app/Enums/FulfillmentStatus.php
app/Enums/ProductStatus.php
app/Enums/StoreDomainType.php
app/Enums/CheckoutStatus.php
app/Enums/ThemeStatus.php
app/Enums/StoreStatus.php
app/Enums/PaymentStatus.php
app/Livewire/Admin/Discounts/Form.php
app/Livewire/Admin/Discounts/Index.php
app/Services/Tax/StripeTaxProvider.php
app/Services/Tax/ManualTaxProvider.php
tests/Feature/Analytics/AnalyticsServiceTest.php
app/Livewire/Admin/Navigation/Index.php
app/Services/ThemeSettingsService.php
app/Services/TaxCalculator.php
app/Services/FulfillmentService.php
app/Services/CheckoutService.php
app/Services/PaymentService.php
app/Models/Scopes/StoreScope.php
tests/Feature/Admin/AnalyticsDashboardTest.php
tests/Feature/Admin/CustomerManagementTest.php
tests/Feature/Admin/OrderManagementTest.php
tests/Feature/Admin/ContentManagementTest.php
tests/Feature/Admin/DiscountManagementTest.php
tests/Feature/Admin/DashboardTest.php
tests/Feature/Admin/SettingsManagementTest.php
tests/Feature/Admin/SearchSettingsTest.php
app/Livewire/Admin/Auth/Login.php
tests/Feature/Orders/OrderServiceTest.php
tests/Feature/Orders/BankTransferOrderTest.php
tests/Feature/Orders/RefundServiceTest.php
tests/Feature/Orders/FulfillmentServiceTest.php
tests/Feature/DashboardTest.php
tests/Feature/Catalog/ProductServiceTest.php
tests/Feature/Catalog/CatalogFoundationTest.php
tests/Feature/Catalog/InventoryServiceTest.php
tests/Feature/Catalog/VariantMatrixServiceTest.php
tests/Feature/Catalog/CatalogUiTest.php
tests/Feature/Catalog/MediaProcessingTest.php
app/Livewire/Admin/Products/Form.php
app/Livewire/Admin/Products/Index.php
app/Livewire/Admin/Dashboard.php
tests/Feature/Api/StorefrontCheckoutApiTest.php
tests/Feature/Api/StorefrontSearchApiTest.php
tests/Feature/Api/StorefrontAnalyticsApiTest.php
tests/Feature/Api/StorefrontOrderApiTest.php
tests/Feature/Api/AdminOrderApiTest.php
tests/Feature/Api/StorefrontCartApiTest.php
database/seeders/ProductVariantSeeder.php
database/seeders/ThemeFileSeeder.php
database/seeders/CustomerSeeder.php
database/seeders/AnalyticsSeeder.php
database/seeders/OrderLineSeeder.php
database/seeders/PaymentSeeder.php
database/seeders/ThemeSeeder.php
database/seeders/UserSeeder.php
database/seeders/NavigationMenuSeeder.php
database/seeders/ProductSeeder.php
database/seeders/FulfillmentSeeder.php
database/seeders/StoreSeeder.php
database/seeders/CheckoutSeeder.php
database/seeders/CollectionSeeder.php
database/seeders/ShippingRateSeeder.php
database/seeders/StoreUserSeeder.php
database/seeders/ProductOptionSeeder.php
database/seeders/StoreDomainSeeder.php
database/seeders/TaxSettingsSeeder.php
database/seeders/ThemeSettingsSeeder.php
database/seeders/CartSeeder.php
database/seeders/ShippingZoneSeeder.php
database/seeders/OrganizationSeeder.php
database/seeders/ProductMediaSeeder.php
database/seeders/NavigationItemSeeder.php
database/seeders/InventoryItemSeeder.php
database/seeders/RefundSeeder.php
database/seeders/StoreSettingsSeeder.php
database/seeders/CustomerAddressSeeder.php
database/seeders/ProductOptionValueSeeder.php
database/seeders/SearchSettingsSeeder.php
database/seeders/FulfillmentLineSeeder.php
database/seeders/CartLineSeeder.php
database/seeders/OrderSeeder.php
database/seeders/PageSeeder.php
database/seeders/DiscountSeeder.php
database/seeders/DatabaseSeeder.php
database/factories/CartFactory.php
database/factories/CollectionFactory.php
database/factories/StoreDomainFactory.php
database/factories/PageFactory.php
database/factories/CheckoutFactory.php
database/factories/NavigationItemFactory.php
database/factories/FulfillmentFactory.php
database/factories/ProductOptionFactory.php
database/factories/CartLineFactory.php
database/factories/InventoryItemFactory.php
database/factories/PaymentFactory.php
database/factories/ProductOptionValueFactory.php
database/factories/OrderFactory.php
database/factories/ProductMediaFactory.php
database/factories/ThemeFactory.php
database/factories/OrganizationFactory.php
database/factories/OrderLineFactory.php
database/factories/StoreFactory.php
database/factories/ThemeSettingsFactory.php
database/factories/ShippingRateFactory.php
database/factories/StoreSettingsFactory.php
database/factories/SearchQueryFactory.php
database/factories/UserFactory.php
database/factories/DiscountFactory.php
database/factories/AnalyticsEventFactory.php
database/factories/TaxSettingsFactory.php
database/factories/ThemeFileFactory.php
database/factories/CustomerFactory.php
database/factories/ProductFactory.php
database/factories/AnalyticsDailyFactory.php
database/factories/CustomerAddressFactory.php
database/factories/RefundFactory.php
database/factories/NavigationMenuFactory.php
database/factories/ProductVariantFactory.php
database/factories/SearchSettingsFactory.php
database/factories/ShippingZoneFactory.php
database/factories/FulfillmentLineFactory.php
app/Services/Payments/MockPaymentProvider.php
tests/Feature/Cart/CartServiceTest.php
tests/Feature/Auth/PasswordResetTest.php
tests/Feature/Auth/AuthenticationTest.php
tests/Feature/Auth/RegistrationTest.php
tests/Feature/Auth/TwoFactorChallengeTest.php
tests/Feature/Auth/EmailVerificationTest.php
app/Services/OrderService.php
tests/Feature/Auth/PasswordConfirmationTest.php
tests/Feature/ExampleTest.php
app/Livewire/Admin/Analytics/Index.php
app/Livewire/Admin/Settings/Shipping.php


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

namespace App\Models;

use App\Enums\StoreStatus;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;

class Store extends Model
{
    /** @use HasFactory<\Database\Factories\StoreFactory> */
    use HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'organization_id',
        'name',
        'handle',
        'status',
        'default_currency',
        'default_locale',
        'timezone',
    ];

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

    /**
     * @return HasMany<StoreDomain, $this>
     */
    public function domains(): HasMany
    {
        return $this->hasMany(StoreDomain::class);
    }

    /**
     * @return BelongsToMany<User, $this>
     */
    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class, 'store_users')
            ->using(StoreUser::class)
            ->withPivot('role');
    }

    /**
     * @return HasOne<StoreSettings, $this>
     */
    public function settings(): HasOne
    {
        return $this->hasOne(StoreSettings::class);
    }

    /**
     * @return HasMany<Theme, $this>
     */
    public function themes(): HasMany
    {
        return $this->hasMany(Theme::class);
    }

    /**
     * @return HasMany<Page, $this>
     */
    public function pages(): HasMany
    {
        return $this->hasMany(Page::class);
    }

    /**
     * @return HasMany<NavigationMenu, $this>
     */
    public function navigationMenus(): HasMany
    {
        return $this->hasMany(NavigationMenu::class);
    }

    /**
     * @return HasMany<Cart, $this>
     */
    public function carts(): HasMany
    {
        return $this->hasMany(Cart::class);
    }

    /**
     * @return HasMany<Checkout, $this>
     */
    public function checkouts(): HasMany
    {
        return $this->hasMany(Checkout::class);
    }

    /**
     * @return HasMany<ShippingZone, $this>
     */
    public function shippingZones(): HasMany
    {
        return $this->hasMany(ShippingZone::class);
    }

    /**
     * @return HasOne<TaxSettings, $this>
     */
    public function taxSettings(): HasOne
    {
        return $this->hasOne(TaxSettings::class);
    }

    /**
     * @return HasMany<Discount, $this>
     */
    public function discounts(): HasMany
    {
        return $this->hasMany(Discount::class);
    }

    /**
     * @return HasMany<Order, $this>
     */
    public function orders(): HasMany
    {
        return $this->hasMany(Order::class);
    }

    public function isSuspended(): bool
    {
        return $this->status === StoreStatus::Suspended;
    }

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


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

namespace App\Models\Concerns;

use App\Models\Scopes\StoreScope;
use App\Models\Store;

trait BelongsToStore
{
    public static function bootBelongsToStore(): void
    {
        static::addGlobalScope(new StoreScope);

        static::creating(function (object $model): void {
            if ($model->store_id || ! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if ($store instanceof Store) {
                $model->store_id = $store->getKey();
            }
        });
    }
}


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

namespace App\Livewire\Admin\Search;

use App\Models\SearchSettings;
use App\Models\Store;
use App\Services\SearchService;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Attributes\Locked;
use Livewire\Component;

class Settings extends Component
{
    use AuthorizesRequests;

    #[Locked]
    public int $storeId;

    /**
     * @var array<int, string>
     */
    public array $synonymGroups = [];

    public string $stopWords = '';

    public ?string $lastIndexedAt = null;

    public bool $isReindexing = false;

    public ?int $reindexProgress = null;

    public function mount(): void
    {
        $store = $this->store();

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

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

        $settings = $this->settings();
        $this->synonymGroups = collect($settings->synonyms_json ?? [])
            ->map(fn (array $group): string => implode(', ', $group))
            ->values()
            ->all();
        $this->stopWords = implode(', ', $settings->stop_words_json ?? []);
        $this->lastIndexedAt = $settings->updated_at?->toDayDateTimeString();
    }

    public function addSynonymGroup(): void
    {
        $this->synonymGroups[] = '';
    }

    public function removeSynonymGroup(int $index): void
    {
        unset($this->synonymGroups[$index]);
        $this->synonymGroups = array_values($this->synonymGroups);
    }

    public function save(): void
    {
        $this->authorize('update', $this->scopedStore());

        $this->validate([
            'synonymGroups' => ['array', 'max:50'],
            'synonymGroups.*' => ['nullable', 'string', 'max:500'],
            'stopWords' => ['nullable', 'string', 'max:2000'],
        ], [], [
            'synonymGroups.*' => 'synonym group',
            'stopWords' => 'stop words',
        ]);

        $settings = $this->settings();
        $settings->forceFill([
            'synonyms_json' => $this->parseSynonyms(),
            'stop_words_json' => $this->parseWordList($this->stopWords),
        ])->save();

        $this->lastIndexedAt = $settings->updated_at?->toDayDateTimeString();

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

    public function triggerReindex(SearchService $search): void
    {
        $store = $this->scopedStore();

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

        $this->isReindexing = true;
        $this->reindexProgress = 10;

        $count = $search->reindex($store);

        $settings = $this->settings();
        $settings->touch();

        $this->lastIndexedAt = $settings->refresh()->updated_at?->toDayDateTimeString();
        $this->reindexProgress = 100;
        $this->isReindexing = false;

        session()->flash('status', __('Search index rebuilt for :count products', ['count' => $count]));
        $this->dispatch('toast', type: 'success', message: __('Search index rebuilt'));
    }

    public function pollReindexStatus(): void
    {
        $this->reindexProgress = $this->isReindexing ? $this->reindexProgress : null;
    }

    public function render(): mixed
    {
        return view('livewire.admin.search.settings')->layout('layouts.app', [
            'title' => __('Search settings'),
        ]);
    }

    /**
     * @return list<list<string>>
     */
    private function parseSynonyms(): array
    {
        return collect($this->synonymGroups)
            ->map(fn (string $group): array => $this->parseWordList($group))
            ->filter(fn (array $group): bool => count($group) >= 2)
            ->values()
            ->all();
    }

    /**
     * @return list<string>
     */
    private function parseWordList(string $words): array
    {
        return collect(explode(',', mb_strtolower($words)))
            ->map(fn (string $word): string => trim($word))
            ->filter()
            ->unique()
            ->values()
            ->all();
    }

    private function settings(): SearchSettings
    {
        return SearchSettings::withoutGlobalScopes()->firstOrCreate([
            'store_id' => $this->storeId ?? $this->store()->getKey(),
        ]);
    }

    private function store(): Store
    {
        $store = app('current_store');

        abort_unless($store instanceof Store, 404);

        return $store;
    }

    private function scopedStore(): Store
    {
        return Store::query()->findOrFail($this->storeId);
    }
}


command: sed -n '1,260p' resources/views/livewire/admin/search/settings.blade.php

aggregated_output:
            <flux:heading size="xl">Search settings</flux:heading>
            <flux:text class="mt-2">Tune storefront search behavior and rebuild the local SQLite index.</flux:text>

        <flux:button wire:click="triggerReindex" icon="arrow-path" variant="ghost">
            Reindex now
        </flux:button>

    @if (session('status'))
        <flux:callout color="green" icon="check-circle">
            {{ session('status') }}
        </flux:callout>
    @endif

    <form wire:submit="save" class="space-y-8">

                <flux:heading size="lg">Synonyms</flux:heading>
                <flux:text>Group equivalent search terms with comma-separated words.</flux:text>

                @foreach ($synonymGroups as $index => $group)

                        <flux:input wire:model="synonymGroups.{{ $index }}" placeholder="t-shirt, tee, tshirt" aria-label="Synonym group {{ $index + 1 }}" />
                        <flux:button wire:click="removeSynonymGroup({{ $index }})" type="button" icon="trash" variant="ghost" aria-label="Remove synonym group {{ $index + 1 }}" />

                    <flux:error name="synonymGroups.{{ $index }}" />
                @endforeach

            <flux:button wire:click="addSynonymGroup" type="button" icon="plus" variant="ghost">
                Add synonym group
            </flux:button>

        <flux:separator />

                <flux:heading size="lg">Stop words</flux:heading>
                <flux:text>Separate excluded words with commas.</flux:text>

            <flux:textarea wire:model="stopWords" rows="5" placeholder="the, a, an, is, are" />
            <flux:error name="stopWords" />

        <flux:separator />

                    <flux:heading size="lg">Search index</flux:heading>
                    <flux:text>Last indexed: {{ $lastIndexedAt ?? 'Never' }}</flux:text>

                @if ($reindexProgress !== null)
                    <flux:badge color="blue">{{ $reindexProgress }}% complete</flux:badge>
                @endif

            <flux:button type="submit" variant="primary">
                Save
            </flux:button>

    </form>


output:
Chunk ID: 5514d9
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 728
Output:

            <flux:heading size="xl">Search settings</flux:heading>
            <flux:text class="mt-2">Tune storefront search behavior and rebuild the local SQLite index.</flux:text>

        <flux:button wire:click="triggerReindex" icon="arrow-path" variant="ghost">
            Reindex now
        </flux:button>

    @if (session('status'))
        <flux:callout color="green" icon="check-circle">
            {{ session('status') }}
        </flux:callout>
    @endif

    <form wire:submit="save" class="space-y-8">

                <flux:heading size="lg">Synonyms</flux:heading>
                <flux:text>Group equivalent search terms with comma-separated words.</flux:text>

                @foreach ($synonymGroups as $index => $group)

                        <flux:input wire:model="synonymGroups.{{ $index }}" placeholder="t-shirt, tee, tshirt" aria-label="Synonym group {{ $index + 1 }}" />
                        <flux:button wire:click="removeSynonymGroup({{ $index }})" type="button" icon="trash" variant="ghost" aria-label="Remove synonym group {{ $index + 1 }}" />

                    <flux:error name="synonymGroups.{{ $index }}" />
                @endforeach

            <flux:button wire:click="addSynonymGroup" type="button" icon="plus" variant="ghost">
                Add synonym group
            </flux:button>

        <flux:separator />

                <flux:heading size="lg">Stop words</flux:heading>
                <flux:text>Separate excluded words with commas.</flux:text>

            <flux:textarea wire:model="stopWords" rows="5" placeholder="the, a, an, is, are" />
            <flux:error name="stopWords" />

        <flux:separator />

                    <flux:heading size="lg">Search index</flux:heading>
                    <flux:text>Last indexed: {{ $lastIndexedAt ?? 'Never' }}</flux:text>

                @if ($reindexProgress !== null)
                    <flux:badge color="blue">{{ $reindexProgress }}% complete</flux:badge>
                @endif

            <flux:button type="submit" variant="primary">
                Save
            </flux:button>

    </form>


command: sed -n '1,240p' app/Providers/AppServiceProvider.php

aggregated_output:
<?php

namespace App\Providers;

use App\Auth\CustomerUserProvider;
use App\Contracts\PaymentProvider;
use App\Http\Middleware\CheckStoreRole;
use App\Http\Middleware\EnsureUserEmailIsVerified;
use App\Http\Middleware\ResolveStore;
use App\Models\Product;
use App\Models\Store;
use App\Observers\ProductObserver;
use App\Services\AnalyticsService;
use App\Services\NavigationService;
use App\Services\Payments\MockPaymentProvider;
use App\Services\SearchService;
use App\Services\ThemeSettingsService;
use Carbon\CarbonImmutable;
use Illuminate\Auth\Middleware\Authenticate;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rules\Password;
use Illuminate\View\View as ViewInstance;
use Livewire\Livewire;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        $this->app->bind(PaymentProvider::class, MockPaymentProvider::class);

        Auth::provider('store_scoped_eloquent', function ($app, array $config): CustomerUserProvider {
            return new CustomerUserProvider($app['hash'], $config['model']);
        });

        $this->app->singleton(ThemeSettingsService::class);
        $this->app->singleton(NavigationService::class);
        $this->app->singleton(SearchService::class);
        $this->app->singleton(AnalyticsService::class);
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        $this->configureDefaults();
        $this->configureLivewireMiddleware();
        $this->configureStorefrontViewData();
    }

    /**
     * Configure default behaviors for production-ready applications.
     */
    protected function configureDefaults(): void
    {
        Date::use(CarbonImmutable::class);

        DB::prohibitDestructiveCommands(
            app()->isProduction(),
        );

        Password::defaults(fn (): ?Password => app()->isProduction()
            ? Password::min(12)
                ->mixedCase()
                ->letters()
                ->numbers()
                ->symbols()
                ->uncompromised()
            : null
        );

        RateLimiter::for('api.storefront', function (Request $request): Limit {
            return Limit::perMinute(120)->by($request->ip());
        });

        RateLimiter::for('checkout', function (Request $request): Limit {
            $sessionId = $request->hasSession() ? $request->session()->getId() : null;

            return Limit::perMinute(10)->by($sessionId ?: $request->ip());
        });

        RateLimiter::for('search', function (Request $request): Limit {
            return Limit::perMinute(30)->by($request->ip());
        });

        RateLimiter::for('analytics', function (Request $request): Limit {
            return Limit::perMinute(60)->by($request->ip());
        });

        Product::observe(ProductObserver::class);

        Authenticate::redirectUsing(function (Request $request): string {
            if ($request->is('admin*')) {
                return route('admin.login');
            }

            if ($request->is('account*')) {
                return route('account.login');
            }

            return route('login');
        });
    }

    protected function configureStorefrontViewData(): void
    {
        View::composer('layouts.storefront', function (ViewInstance $view): void {
            $store = app()->bound('current_store') ? app('current_store') : null;

            if (! $store instanceof Store) {
                $view->with([
                    'themeSettings' => [],
                    'mainNavigation' => [],
                    'footerNavigation' => [],
                ]);

                return;
            }

            $themeSettings = app(ThemeSettingsService::class)->forStore($store);
            $navigation = app(NavigationService::class);

            $view->with([
                'themeSettings' => $themeSettings,
                'mainNavigation' => $navigation->forHandle($store, data_get($themeSettings, 'header.main_menu', 'main-menu')),
                'footerNavigation' => $navigation->forHandle($store, data_get($themeSettings, 'footer.menu', 'footer-menu')),
            ]);
        });
    }

    protected function configureLivewireMiddleware(): void
    {
        Livewire::addPersistentMiddleware([
            EnsureUserEmailIsVerified::class,
            ResolveStore::class,
            CheckStoreRole::class,
        ]);
    }
}


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

namespace App\Providers;

use App\Auth\CustomerUserProvider;
use App\Contracts\PaymentProvider;
use App\Http\Middleware\CheckStoreRole;
use App\Http\Middleware\EnsureUserEmailIsVerified;
use App\Http\Middleware\ResolveStore;
use App\Models\Product;
use App\Models\Store;
use App\Observers\ProductObserver;
use App\Services\AnalyticsService;
use App\Services\NavigationService;
use App\Services\Payments\MockPaymentProvider;
use App\Services\SearchService;
use App\Services\ThemeSettingsService;
use Carbon\CarbonImmutable;
use Illuminate\Auth\Middleware\Authenticate;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rules\Password;
use Illuminate\View\View as ViewInstance;
use Livewire\Livewire;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        $this->app->bind(PaymentProvider::class, MockPaymentProvider::class);

        Auth::provider('store_scoped_eloquent', function ($app, array $config): CustomerUserProvider {
            return new CustomerUserProvider($app['hash'], $config['model']);
        });

        $this->app->singleton(ThemeSettingsService::class);
        $this->app->singleton(NavigationService::class);
        $this->app->singleton(SearchService::class);
        $this->app->singleton(AnalyticsService::class);
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        $this->configureDefaults();
        $this->configureLivewireMiddleware();
        $this->configureStorefrontViewData();
    }

    /**
     * Configure default behaviors for production-ready applications.
     */
    protected function configureDefaults(): void
    {
        Date::use(CarbonImmutable::class);

        DB::prohibitDestructiveCommands(
            app()->isProduction(),
        );

        Password::defaults(fn (): ?Password => app()->isProduction()
            ? Password::min(12)
                ->mixedCase()
                ->letters()
                ->numbers()
                ->symbols()
                ->uncompromised()
            : null
        );

        RateLimiter::for('api.storefront', function (Request $request): Limit {
            return Limit::perMinute(120)->by($request->ip());
        });

        RateLimiter::for('checkout', function (Request $request): Limit {
            $sessionId = $request->hasSession() ? $request->session()->getId() : null;

            return Limit::perMinute(10)->by($sessionId ?: $request->ip());
        });

        RateLimiter::for('search', function (Request $request): Limit {
            return Limit::perMinute(30)->by($request->ip());
        });

        RateLimiter::for('analytics', function (Request $request): Limit {
            return Limit::perMinute(60)->by($request->ip());
        });

        Product::observe(ProductObserver::class);

        Authenticate::redirectUsing(function (Request $request): string {
            if ($request->is('admin*')) {
                return route('admin.login');
            }

            if ($request->is('account*')) {
                return route('account.login');
            }

            return route('login');
        });
    }

    protected function configureStorefrontViewData(): void
    {
        View::composer('layouts.storefront', function (ViewInstance $view): void {
            $store = app()->bound('current_store') ? app('current_store') : null;

            if (! $store instanceof Store) {
                $view->with([
                    'themeSettings' => [],
                    'mainNavigation' => [],
                    'footerNavigation' => [],
                ]);

                return;
            }

            $themeSettings = app(ThemeSettingsService::class)->forStore($store);
            $navigation = app(NavigationService::class);

            $view->with([
                'themeSettings' => $themeSettings,
                'mainNavigation' => $navigation->forHandle($store, data_get($themeSettings, 'header.main_menu', 'main-menu')),
                'footerNavigation' => $navigation->forHandle($store, data_get($themeSettings, 'footer.menu', 'footer-menu')),
            ]);
        });
    }

    protected function configureLivewireMiddleware(): void
    {
        Livewire::addPersistentMiddleware([
            EnsureUserEmailIsVerified::class,
            ResolveStore::class,
            CheckStoreRole::class,
        ]);
    }
}


cmd: sed -n '1,220p' app/Models/SearchSettings.php


cmd: sed -n '1,240p' app/Models/AnalyticsEvent.php


cmd: sed -n '1,220p' app/Jobs/AggregateAnalytics.php


cmd: sed -n '1,240p' app/Policies/StorePolicy.php


command: sed -n '1,220p' app/Models/SearchSettings.php

aggregated_output:
<?php

namespace App\Models;

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

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

    public $incrementing = false;

    public const CREATED_AT = null;

    protected $primaryKey = 'store_id';

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'synonyms_json',
        'stop_words_json',
    ];

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

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

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'synonyms_json' => 'array',
            'stop_words_json' => 'array',
            'updated_at' => 'datetime',
        ];
    }
}


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

namespace App\Models;

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

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

    public $incrementing = false;

    public const CREATED_AT = null;

    protected $primaryKey = 'store_id';

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'synonyms_json',
        'stop_words_json',
    ];

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

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

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'synonyms_json' => 'array',
            'stop_words_json' => 'array',
            'updated_at' => 'datetime',
        ];
    }
}


command: sed -n '1,240p' app/Models/AnalyticsEvent.php

aggregated_output:
<?php

namespace App\Models;

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

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

    public const UPDATED_AT = null;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'type',
        'session_id',
        'customer_id',
        'properties_json',
        'client_event_id',
        'occurred_at',
        'created_at',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'properties_json' => '{}',
    ];

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

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

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'type' => AnalyticsEventType::class,
            'properties_json' => 'array',
            'occurred_at' => 'datetime',
            'created_at' => 'datetime',
        ];
    }
}


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

namespace App\Models;

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

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

    public const UPDATED_AT = null;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'type',
        'session_id',
        'customer_id',
        'properties_json',
        'client_event_id',
        'occurred_at',
        'created_at',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'properties_json' => '{}',
    ];

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

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

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'type' => AnalyticsEventType::class,
            'properties_json' => 'array',
            'occurred_at' => 'datetime',
            'created_at' => 'datetime',
        ];
    }
}


command: sed -n '1,220p' app/Jobs/AggregateAnalytics.php

aggregated_output:
<?php

namespace App\Jobs;

use App\Services\AnalyticsService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Carbon;

class AggregateAnalytics implements ShouldQueue
{
    use Queueable;

    public function __construct(public ?string $date = null) {}

    public function handle(AnalyticsService $analytics): void
    {
        $analytics->aggregate($this->date ? Carbon::parse($this->date) : null);
    }
}


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

namespace App\Jobs;

use App\Services\AnalyticsService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Carbon;

class AggregateAnalytics implements ShouldQueue
{
    use Queueable;

    public function __construct(public ?string $date = null) {}

    public function handle(AnalyticsService $analytics): void
    {
        $analytics->aggregate($this->date ? Carbon::parse($this->date) : null);
    }
}


command: sed -n '1,240p' app/Policies/StorePolicy.php

aggregated_output:
<?php

namespace App\Policies;

use App\Models\Store;
use App\Models\User;
use App\Traits\ChecksStoreRole;

class StorePolicy
{
    use ChecksStoreRole;

    public function view(User $user, Store $store): bool
    {
        return $this->isAnyRole($user, $store->getKey());
    }

    public function update(User $user, Store $store): bool
    {
        return $this->isOwnerOrAdmin($user, $store->getKey());
    }

    public function delete(User $user, Store $store): bool
    {
        return $this->isOwnerOnly($user, $store->getKey());
    }
}


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

namespace App\Policies;

use App\Models\Store;
use App\Models\User;
use App\Traits\ChecksStoreRole;

class StorePolicy
{
    use ChecksStoreRole;

    public function view(User $user, Store $store): bool
    {
        return $this->isAnyRole($user, $store->getKey());
    }

    public function update(User $user, Store $store): bool
    {
        return $this->isOwnerOrAdmin($user, $store->getKey());
    }

    public function delete(User $user, Store $store): bool
    {
        return $this->isOwnerOnly($user, $store->getKey());
    }
}


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

web.php

cmd: sed -n '1,260p' routes/web.php

aggregated_output:
<?php

use App\Http\Middleware\EnsureUserEmailIsVerified;
use App\Livewire\Admin\Analytics\Index as AdminAnalyticsIndex;
use App\Livewire\Admin\Auth\Login as AdminLogin;
use App\Livewire\Admin\Collections\Form as AdminCollectionForm;
use App\Livewire\Admin\Collections\Index as AdminCollectionsIndex;
use App\Livewire\Admin\Customers\Index as AdminCustomersIndex;
use App\Livewire\Admin\Customers\Show as AdminCustomerShow;
use App\Livewire\Admin\Dashboard as AdminDashboard;
use App\Livewire\Admin\Discounts\Form as AdminDiscountForm;
use App\Livewire\Admin\Discounts\Index as AdminDiscountsIndex;
use App\Livewire\Admin\Inventory\Index as AdminInventoryIndex;
use App\Livewire\Admin\Navigation\Index as AdminNavigationIndex;
use App\Livewire\Admin\Orders\Index as AdminOrdersIndex;
use App\Livewire\Admin\Orders\Show as AdminOrderShow;
use App\Livewire\Admin\Pages\Form as AdminPageForm;
use App\Livewire\Admin\Pages\Index as AdminPagesIndex;
use App\Livewire\Admin\Products\Form as AdminProductForm;
use App\Livewire\Admin\Products\Index as AdminProductsIndex;
use App\Livewire\Admin\Search\Settings as AdminSearchSettings;
use App\Livewire\Admin\Settings\Index as AdminSettingsIndex;
use App\Livewire\Admin\Settings\Shipping as AdminSettingsShipping;
use App\Livewire\Admin\Settings\Taxes as AdminSettingsTaxes;
use App\Livewire\Admin\Themes\Editor as AdminThemeEditor;
use App\Livewire\Admin\Themes\Index as AdminThemesIndex;
use App\Livewire\Storefront\Account\Auth\Login as CustomerLogin;
use App\Livewire\Storefront\Account\Auth\Register as CustomerRegister;
use App\Livewire\Storefront\Account\Orders\Index as CustomerOrdersIndex;
use App\Livewire\Storefront\Account\Orders\Show as CustomerOrderShow;
use App\Livewire\Storefront\Cart\Show as StorefrontCartShow;
use App\Livewire\Storefront\Checkout\Confirmation as StorefrontCheckoutConfirmation;
use App\Livewire\Storefront\Checkout\Show as StorefrontCheckoutShow;
use App\Livewire\Storefront\Collections\Index as StorefrontCollectionsIndex;
use App\Livewire\Storefront\Collections\Show as StorefrontCollectionShow;
use App\Livewire\Storefront\Home as StorefrontHome;
use App\Livewire\Storefront\Pages\Show as StorefrontPageShow;
use App\Livewire\Storefront\Products\Show as StorefrontProductShow;
use App\Livewire\Storefront\Search\Index as StorefrontSearchIndex;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;

Route::middleware(['storefront'])->group(function (): void {
    Route::livewire('/', StorefrontHome::class)->name('home');
    Route::livewire('collections', StorefrontCollectionsIndex::class)->name('collections.index');
    Route::livewire('collections/{handle}', StorefrontCollectionShow::class)->name('collections.show');
    Route::livewire('products/{handle}', StorefrontProductShow::class)->name('products.show');
    Route::livewire('cart', StorefrontCartShow::class)->name('cart.show');
    Route::livewire('checkout', StorefrontCheckoutShow::class)->name('checkout.show');
    Route::livewire('checkout/confirmation/{order}', StorefrontCheckoutConfirmation::class)->name('checkout.confirmation');
    Route::livewire('search', StorefrontSearchIndex::class)->name('search.index');
    Route::livewire('pages/{handle}', StorefrontPageShow::class)->name('pages.show');
});

Route::livewire('admin/login', AdminLogin::class)
    ->middleware('guest')
    ->name('admin.login');

Route::post('admin/logout', function () {
    Auth::guard('web')->logout();

    request()->session()->invalidate();
    request()->session()->regenerateToken();

    return redirect()->route('admin.login');
})->middleware('auth')->name('admin.logout');

Route::middleware(['auth', EnsureUserEmailIsVerified::class, 'admin'])->prefix('admin')->name('admin.')->group(function (): void {
    Route::livewire('/', AdminDashboard::class)->name('dashboard');
    Route::livewire('analytics', AdminAnalyticsIndex::class)->name('analytics.index');
    Route::livewire('products', AdminProductsIndex::class)->name('products.index');
    Route::livewire('products/create', AdminProductForm::class)->name('products.create');
    Route::livewire('products/{product}/edit', AdminProductForm::class)->name('products.edit');
    Route::livewire('inventory', AdminInventoryIndex::class)->name('inventory.index');
    Route::livewire('orders', AdminOrdersIndex::class)->name('orders.index');
    Route::livewire('orders/{order}', AdminOrderShow::class)->name('orders.show');
    Route::livewire('customers', AdminCustomersIndex::class)->name('customers.index');
    Route::livewire('customers/{customer}', AdminCustomerShow::class)->name('customers.show');
    Route::livewire('discounts', AdminDiscountsIndex::class)->name('discounts.index');
    Route::livewire('discounts/create', AdminDiscountForm::class)->name('discounts.create');
    Route::livewire('discounts/{discount}/edit', AdminDiscountForm::class)->name('discounts.edit');
    Route::livewire('pages', AdminPagesIndex::class)->name('pages.index');
    Route::livewire('pages/create', AdminPageForm::class)->name('pages.create');
    Route::livewire('pages/{page}/edit', AdminPageForm::class)->name('pages.edit');
    Route::livewire('navigation', AdminNavigationIndex::class)->name('navigation.index');
    Route::livewire('themes', AdminThemesIndex::class)->name('themes.index');
    Route::livewire('themes/{theme}/editor', AdminThemeEditor::class)->name('themes.editor');
    Route::livewire('settings', AdminSettingsIndex::class)->name('settings.index');
    Route::livewire('settings/shipping', AdminSettingsShipping::class)->name('settings.shipping');
    Route::livewire('settings/taxes', AdminSettingsTaxes::class)->name('settings.taxes');
    Route::livewire('search/settings', AdminSearchSettings::class)->name('search.settings');
    Route::livewire('collections', AdminCollectionsIndex::class)->name('collections.index');
    Route::livewire('collections/create', AdminCollectionForm::class)->name('collections.create');
    Route::livewire('collections/{collection}/edit', AdminCollectionForm::class)->name('collections.edit');
});

Route::middleware(['storefront'])->group(function (): void {
    Route::livewire('account/login', CustomerLogin::class)
        ->middleware('guest:customer')
        ->name('account.login');

    Route::livewire('account/register', CustomerRegister::class)
        ->middleware('guest:customer')
        ->name('account.register');

    Route::livewire('account', CustomerOrdersIndex::class)
        ->middleware('auth:customer')
        ->name('account.dashboard');

    Route::livewire('account/orders/{order}', CustomerOrderShow::class)
        ->middleware('auth:customer')
        ->name('account.orders.show');
});

Route::redirect('dashboard', 'admin')
    ->middleware(['auth', 'verified'])
    ->name('dashboard');

require __DIR__.'/settings.php';


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

api.php

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

aggregated_output:
<?php

use App\Http\Controllers\Api\Admin\V1\OrderController as AdminOrderController;
use App\Http\Controllers\Api\Admin\V1\OrderFulfillmentController as AdminOrderFulfillmentController;
use App\Http\Controllers\Api\Admin\V1\OrderRefundController as AdminOrderRefundController;
use App\Http\Controllers\Api\Storefront\V1\AnalyticsEventController as StorefrontAnalyticsEventController;
use App\Http\Controllers\Api\Storefront\V1\CartController;
use App\Http\Controllers\Api\Storefront\V1\CartLineController;
use App\Http\Controllers\Api\Storefront\V1\CheckoutController;
use App\Http\Controllers\Api\Storefront\V1\OrderController as StorefrontOrderController;
use App\Http\Controllers\Api\Storefront\V1\SearchController as StorefrontSearchController;
use Illuminate\Support\Facades\Route;

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

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

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

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

Route::middleware(['auth', 'throttle:60,1'])
    ->prefix('admin/v1/stores/{store}')
    ->name('api.admin.v1.')
    ->group(function (): void {
        Route::get('orders', [AdminOrderController::class, 'index'])->name('orders.index');
        Route::get('orders/{order}', [AdminOrderController::class, 'show'])->name('orders.show');
        Route::post('orders/{order}/fulfillments', [AdminOrderFulfillmentController::class, 'store'])->name('orders.fulfillments.store');
        Route::post('orders/{order}/refunds', [AdminOrderRefundController::class, 'store'])->name('orders.refunds.store');
    });


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

use App\Http\Middleware\EnsureUserEmailIsVerified;
use App\Livewire\Admin\Analytics\Index as AdminAnalyticsIndex;
use App\Livewire\Admin\Auth\Login as AdminLogin;
use App\Livewire\Admin\Collections\Form as AdminCollectionForm;
use App\Livewire\Admin\Collections\Index as AdminCollectionsIndex;
use App\Livewire\Admin\Customers\Index as AdminCustomersIndex;
use App\Livewire\Admin\Customers\Show as AdminCustomerShow;
use App\Livewire\Admin\Dashboard as AdminDashboard;
use App\Livewire\Admin\Discounts\Form as AdminDiscountForm;
use App\Livewire\Admin\Discounts\Index as AdminDiscountsIndex;
use App\Livewire\Admin\Inventory\Index as AdminInventoryIndex;
use App\Livewire\Admin\Navigation\Index as AdminNavigationIndex;
use App\Livewire\Admin\Orders\Index as AdminOrdersIndex;
use App\Livewire\Admin\Orders\Show as AdminOrderShow;
use App\Livewire\Admin\Pages\Form as AdminPageForm;
use App\Livewire\Admin\Pages\Index as AdminPagesIndex;
use App\Livewire\Admin\Products\Form as AdminProductForm;
use App\Livewire\Admin\Products\Index as AdminProductsIndex;
use App\Livewire\Admin\Search\Settings as AdminSearchSettings;
use App\Livewire\Admin\Settings\Index as AdminSettingsIndex;
use App\Livewire\Admin\Settings\Shipping as AdminSettingsShipping;
use App\Livewire\Admin\Settings\Taxes as AdminSettingsTaxes;
use App\Livewire\Admin\Themes\Editor as AdminThemeEditor;
use App\Livewire\Admin\Themes\Index as AdminThemesIndex;
use App\Livewire\Storefront\Account\Auth\Login as CustomerLogin;
use App\Livewire\Storefront\Account\Auth\Register as CustomerRegister;
use App\Livewire\Storefront\Account\Orders\Index as CustomerOrdersIndex;
use App\Livewire\Storefront\Account\Orders\Show as CustomerOrderShow;
use App\Livewire\Storefront\Cart\Show as StorefrontCartShow;
use App\Livewire\Storefront\Checkout\Confirmation as StorefrontCheckoutConfirmation;
use App\Livewire\Storefront\Checkout\Show as StorefrontCheckoutShow;
use App\Livewire\Storefront\Collections\Index as StorefrontCollectionsIndex;
use App\Livewire\Storefront\Collections\Show as StorefrontCollectionShow;
use App\Livewire\Storefront\Home as StorefrontHome;
use App\Livewire\Storefront\Pages\Show as StorefrontPageShow;
use App\Livewire\Storefront\Products\Show as StorefrontProductShow;
use App\Livewire\Storefront\Search\Index as StorefrontSearchIndex;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;

Route::middleware(['storefront'])->group(function (): void {
    Route::livewire('/', StorefrontHome::class)->name('home');
    Route::livewire('collections', StorefrontCollectionsIndex::class)->name('collections.index');
    Route::livewire('collections/{handle}', StorefrontCollectionShow::class)->name('collections.show');
    Route::livewire('products/{handle}', StorefrontProductShow::class)->name('products.show');
    Route::livewire('cart', StorefrontCartShow::class)->name('cart.show');
    Route::livewire('checkout', StorefrontCheckoutShow::class)->name('checkout.show');
    Route::livewire('checkout/confirmation/{order}', StorefrontCheckoutConfirmation::class)->name('checkout.confirmation');
    Route::livewire('search', StorefrontSearchIndex::class)->name('search.index');
    Route::livewire('pages/{handle}', StorefrontPageShow::class)->name('pages.show');
});

Route::livewire('admin/login', AdminLogin::class)
    ->middleware('guest')
    ->name('admin.login');

Route::post('admin/logout', function () {
    Auth::guard('web')->logout();

    request()->session()->invalidate();
    request()->session()->regenerateToken();

    return redirect()->route('admin.login');
})->middleware('auth')->name('admin.logout');

Route::middleware(['auth', EnsureUserEmailIsVerified::class, 'admin'])->prefix('admin')->name('admin.')->group(function (): void {
    Route::livewire('/', AdminDashboard::class)->name('dashboard');
    Route::livewire('analytics', AdminAnalyticsIndex::class)->name('analytics.index');
    Route::livewire('products', AdminProductsIndex::class)->name('products.index');
    Route::livewire('products/create', AdminProductForm::class)->name('products.create');
    Route::livewire('products/{product}/edit', AdminProductForm::class)->name('products.edit');
    Route::livewire('inventory', AdminInventoryIndex::class)->name('inventory.index');
    Route::livewire('orders', AdminOrdersIndex::class)->name('orders.index');
    Route::livewire('orders/{order}', AdminOrderShow::class)->name('orders.show');
    Route::livewire('customers', AdminCustomersIndex::class)->name('customers.index');
    Route::livewire('customers/{customer}', AdminCustomerShow::class)->name('customers.show');
    Route::livewire('discounts', AdminDiscountsIndex::class)->name('discounts.index');
    Route::livewire('discounts/create', AdminDiscountForm::class)->name('discounts.create');
    Route::livewire('discounts/{discount}/edit', AdminDiscountForm::class)->name('discounts.edit');
    Route::livewire('pages', AdminPagesIndex::class)->name('pages.index');
    Route::livewire('pages/create', AdminPageForm::class)->name('pages.create');
    Route::livewire('pages/{page}/edit', AdminPageForm::class)->name('pages.edit');
    Route::livewire('navigation', AdminNavigationIndex::class)->name('navigation.index');
    Route::livewire('themes', AdminThemesIndex::class)->name('themes.index');
    Route::livewire('themes/{theme}/editor', AdminThemeEditor::class)->name('themes.editor');
    Route::livewire('settings', AdminSettingsIndex::class)->name('settings.index');
    Route::livewire('settings/shipping', AdminSettingsShipping::class)->name('settings.shipping');
    Route::livewire('settings/taxes', AdminSettingsTaxes::class)->name('settings.taxes');
    Route::livewire('search/settings', AdminSearchSettings::class)->name('search.settings');
    Route::livewire('collections', AdminCollectionsIndex::class)->name('collections.index');
    Route::livewire('collections/create', AdminCollectionForm::class)->name('collections.create');
    Route::livewire('collections/{collection}/edit', AdminCollectionForm::class)->name('collections.edit');
});

Route::middleware(['storefront'])->group(function (): void {
    Route::livewire('account/login', CustomerLogin::class)
        ->middleware('guest:customer')
        ->name('account.login');

    Route::livewire('account/register', CustomerRegister::class)
        ->middleware('guest:customer')
        ->name('account.register');

    Route::livewire('account', CustomerOrdersIndex::class)
        ->middleware('auth:customer')
        ->name('account.dashboard');

    Route::livewire('account/orders/{order}', CustomerOrderShow::class)
        ->middleware('auth:customer')
        ->name('account.orders.show');
});

Route::redirect('dashboard', 'admin')
    ->middleware(['auth', 'verified'])
    ->name('dashboard');

require __DIR__.'/settings.php';


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

use App\Http\Controllers\Api\Admin\V1\OrderController as AdminOrderController;
use App\Http\Controllers\Api\Admin\V1\OrderFulfillmentController as AdminOrderFulfillmentController;
use App\Http\Controllers\Api\Admin\V1\OrderRefundController as AdminOrderRefundController;
use App\Http\Controllers\Api\Storefront\V1\AnalyticsEventController as StorefrontAnalyticsEventController;
use App\Http\Controllers\Api\Storefront\V1\CartController;
use App\Http\Controllers\Api\Storefront\V1\CartLineController;
use App\Http\Controllers\Api\Storefront\V1\CheckoutController;
use App\Http\Controllers\Api\Storefront\V1\OrderController as StorefrontOrderController;
use App\Http\Controllers\Api\Storefront\V1\SearchController as StorefrontSearchController;
use Illuminate\Support\Facades\Route;

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

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

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

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

Route::middleware(['auth', 'throttle:60,1'])
    ->prefix('admin/v1/stores/{store}')
    ->name('api.admin.v1.')
    ->group(function (): void {
        Route::get('orders', [AdminOrderController::class, 'index'])->name('orders.index');
        Route::get('orders/{order}', [AdminOrderController::class, 'show'])->name('orders.show');
        Route::post('orders/{order}/fulfillments', [AdminOrderFulfillmentController::class, 'store'])->name('orders.fulfillments.store');
        Route::post('orders/{order}/refunds', [AdminOrderRefundController::class, 'store'])->name('orders.refunds.store');
    });


cmd: sed -n '200,260p' specs/06-AUTH-AND-SECURITY.md


cmd: sed -n '2897,3025p' specs/02-API-ROUTES.md


cmd: sed -n '296,385p' specs/06-AUTH-AND-SECURITY.md


cmd: sed -n '535,565p' specs/06-AUTH-AND-SECURITY.md


cmd: sed -n '1,220p' app/Traits/ChecksStoreRole.php


cmd: sed -n '1,220p' app/Http/Middleware/CheckStoreRole.php


command: sed -n '2897,3025p' specs/02-API-ROUTES.md

aggregated_output:
## 4. Webhook Receiver Endpoints

> **Note:** No inbound webhook endpoints are needed. All payment processing happens in-process via the Mock PSP -- there are no external payment provider callbacks. The webhook infrastructure defined in this section is reserved for **outbound** app webhooks (see Section 5, OAuth / App Ecosystem).

---

## 5. OAuth Endpoints (App Ecosystem)

> **Scope note:** OAuth and app ecosystem endpoints are deferred for initial implementation. The route structure below is defined for future use but implementations should return 501 Not Implemented.

These endpoints implement OAuth2 authorization for third-party apps, powered by Laravel Passport.

---

### GET /oauth/authorize

Display the authorization prompt to the merchant.

- **Middleware:** `web`, `auth`

**Query parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `client_id` | string | Yes | The app's OAuth client ID |
| `redirect_uri` | string | Yes | Must match one of the registered redirect URIs |
| `response_type` | string | Yes | Must be `code` |
| `scope` | string | Yes | Space-separated list of requested scopes |
| `state` | string | Yes | CSRF token for the client |

**Flow:** Merchant reviews requested scopes and approves/denies. On approval, redirects to `redirect_uri` with `code` and `state` parameters. Authorization code is single-use.

---

### POST /oauth/token

Exchange an authorization code for access and refresh tokens.

- **Middleware:** None (public endpoint)

**Authorization code exchange request:**

```json
{
  "grant_type": "authorization_code",
  "client_id": "app_client_abc123",
  "client_secret": "secret_xyz",
  "redirect_uri": "https://myapp.example.com/callback",
  "code": "auth_code_abc123"
}
```

**Success response:** `200 OK`

```json
{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJ...",
  "refresh_token": "def..."
}
```

**Error responses:**
- `400` - Invalid grant (code already used, expired, etc.)
- `401` - Invalid client credentials

**Refresh token request:**

```json
{
  "grant_type": "refresh_token",
  "client_id": "app_client_abc123",
  "client_secret": "secret_xyz",
  "refresh_token": "def..."
}
```

---

### App API Endpoints

Third-party apps use OAuth tokens to access store data via scoped endpoints.

| Method | Path | Scope Required | Description |
|--------|------|----------------|-------------|
| GET | `/api/apps/v1/stores/{storeId}/products` | `read-products` | List products (same schema as admin API) |
| GET | `/api/apps/v1/stores/{storeId}/orders` | `read-orders` | List orders |
| GET | `/api/apps/v1/stores/{storeId}/customers` | `read-customers` | List customers |

Token scopes are enforced on every endpoint. Requests with insufficient scopes return `403 Forbidden`.

---

## 6. Middleware Stack

### 6.1 Route Group Middleware

#### Web (default)

Applied to all web routes via the `web` middleware group:

| Middleware | Purpose |
|-----------|---------|
| `EncryptCookies` | Encrypt/decrypt cookies |
| `AddQueuedCookiesToResponse` | Attach queued cookies |
| `StartSession` | Session handling |
| `ShareErrorsFromSession` | Flash validation errors |
| `VerifyCsrfToken` | CSRF protection on POST/PUT/PATCH/DELETE |
| `SubstituteBindings` | Route model binding |

#### Admin

Applied to `/admin/*` routes in addition to `web`:

| Middleware | Purpose |
|-----------|---------|
| `auth` | Require authenticated user (Laravel's built-in guard) |
| `verified` | Require verified email address |
| `store.resolve` | Resolve current store from session/domain and set `app.store_id` |
| `role.check` | Verify user has required role for the current store |

#### Storefront

Applied to storefront routes in addition to `web`:

| Middleware | Purpose |
|-----------|---------|
| `store.resolve` | Resolve store from request hostname via `store_domains` table |


command: sed -n '200,260p' specs/06-AUTH-AND-SECURITY.md

aggregated_output:
- Token expiration: 60 minutes.
- Throttle: one reset email per 60 seconds.
- Generic response on the "forgot password" form (do not reveal whether email exists in the store).

---

### 1.3 API Authentication (Sanctum)

| Setting | Value |
|---------|-------|
| Guard name | `sanctum` |
| Provider | `users` (admin users only) |
| Token type | Personal access tokens |
| Token prefix | `shop_` |
| Hashing | SHA-256 |
| Default expiration | 1 year |
| Storage | `personal_access_tokens` table |

#### Token Lifecycle

```mermaid
stateDiagram-v2
    [*] --> Created: Admin creates token in Developers section
    Created --> Active: Plain-text shown once, hash stored
    Active --> InUse: Token sent as Bearer header
    InUse --> Active: Request processed
    Active --> Revoked: Admin revokes token
    Active --> Expired: Expiration date reached
    Revoked --> [*]: Token row deleted
    Expired --> [*]: Token row deleted (cleanup job)
```

**Requirements:**

- Tokens are created in the admin panel, under the Developers section.
- Each token has: a human-readable name, an optional expiration date, and a list of abilities (scopes).
- On creation, the plain-text token (prefixed with `shop_`) is displayed exactly once. Only the SHA-256 hash is persisted.
- Tokens are sent as `Authorization: Bearer shop_xxxxxxxx`.
- Sanctum middleware validates the hash and resolves the associated user.
- Ability checks are performed per-endpoint (e.g., the products list endpoint requires `read-products`).
- Revoking a token deletes its row from the `personal_access_tokens` table.

#### Token Abilities (Scopes)

| Ability | Description |
|---------|-------------|
| `read-products` | List and view products |
| `write-products` | Create, update, delete products |
| `read-orders` | List and view orders |
| `write-orders` | Update orders, create fulfillments |
| `read-customers` | List and view customers |
| `write-customers` | Update customers |
| `read-collections` | List and view collections |
| `write-collections` | Create, update, delete collections |
| `read-discounts` | List and view discounts |
| `write-discounts` | Create, update, delete discounts |
| `read-analytics` | View analytics data |
| `read-settings` | View store settings (general, shipping, taxes, checkout, notifications) |
| `write-settings` | Update store settings (replaces the legacy `manage-store`, `manage-shipping`, `manage-taxes` scopes) |
| `read-themes` | View themes and theme files |
| `write-themes` | Create, update, delete, publish themes |


output:
Chunk ID: ffbd17
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 633
Output:
- Token expiration: 60 minutes.
- Throttle: one reset email per 60 seconds.
- Generic response on the "forgot password" form (do not reveal whether email exists in the store).

---

### 1.3 API Authentication (Sanctum)

| Setting | Value |
|---------|-------|
| Guard name | `sanctum` |
| Provider | `users` (admin users only) |
| Token type | Personal access tokens |
| Token prefix | `shop_` |
| Hashing | SHA-256 |
| Default expiration | 1 year |
| Storage | `personal_access_tokens` table |

#### Token Lifecycle

```mermaid
stateDiagram-v2
    [*] --> Created: Admin creates token in Developers section
    Created --> Active: Plain-text shown once, hash stored
    Active --> InUse: Token sent as Bearer header
    InUse --> Active: Request processed
    Active --> Revoked: Admin revokes token
    Active --> Expired: Expiration date reached
    Revoked --> [*]: Token row deleted
    Expired --> [*]: Token row deleted (cleanup job)
```

**Requirements:**

- Tokens are created in the admin panel, under the Developers section.
- Each token has: a human-readable name, an optional expiration date, and a list of abilities (scopes).
- On creation, the plain-text token (prefixed with `shop_`) is displayed exactly once. Only the SHA-256 hash is persisted.
- Tokens are sent as `Authorization: Bearer shop_xxxxxxxx`.
- Sanctum middleware validates the hash and resolves the associated user.
- Ability checks are performed per-endpoint (e.g., the products list endpoint requires `read-products`).
- Revoking a token deletes its row from the `personal_access_tokens` table.

#### Token Abilities (Scopes)

| Ability | Description |
|---------|-------------|
| `read-products` | List and view products |
| `write-products` | Create, update, delete products |
| `read-orders` | List and view orders |
| `write-orders` | Update orders, create fulfillments |
| `read-customers` | List and view customers |
| `write-customers` | Update customers |
| `read-collections` | List and view collections |
| `write-collections` | Create, update, delete collections |
| `read-discounts` | List and view discounts |
| `write-discounts` | Create, update, delete discounts |
| `read-analytics` | View analytics data |
| `read-settings` | View store settings (general, shipping, taxes, checkout, notifications) |
| `write-settings` | Update store settings (replaces the legacy `manage-store`, `manage-shipping`, `manage-taxes` scopes) |
| `read-themes` | View themes and theme files |
| `write-themes` | Create, update, delete, publish themes |


output:
Chunk ID: f9da75
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 978
Output:
## 4. Webhook Receiver Endpoints

> **Note:** No inbound webhook endpoints are needed. All payment processing happens in-process via the Mock PSP -- there are no external payment provider callbacks. The webhook infrastructure defined in this section is reserved for **outbound** app webhooks (see Section 5, OAuth / App Ecosystem).

---

## 5. OAuth Endpoints (App Ecosystem)

> **Scope note:** OAuth and app ecosystem endpoints are deferred for initial implementation. The route structure below is defined for future use but implementations should return 501 Not Implemented.

These endpoints implement OAuth2 authorization for third-party apps, powered by Laravel Passport.

---

### GET /oauth/authorize

Display the authorization prompt to the merchant.

- **Middleware:** `web`, `auth`

**Query parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `client_id` | string | Yes | The app's OAuth client ID |
| `redirect_uri` | string | Yes | Must match one of the registered redirect URIs |
| `response_type` | string | Yes | Must be `code` |
| `scope` | string | Yes | Space-separated list of requested scopes |
| `state` | string | Yes | CSRF token for the client |

**Flow:** Merchant reviews requested scopes and approves/denies. On approval, redirects to `redirect_uri` with `code` and `state` parameters. Authorization code is single-use.

---

### POST /oauth/token

Exchange an authorization code for access and refresh tokens.

- **Middleware:** None (public endpoint)

**Authorization code exchange request:**

```json
{
  "grant_type": "authorization_code",
  "client_id": "app_client_abc123",
  "client_secret": "secret_xyz",
  "redirect_uri": "https://myapp.example.com/callback",
  "code": "auth_code_abc123"
}
```

**Success response:** `200 OK`

```json
{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJ...",
  "refresh_token": "def..."
}
```

**Error responses:**
- `400` - Invalid grant (code already used, expired, etc.)
- `401` - Invalid client credentials

**Refresh token request:**

```json
{
  "grant_type": "refresh_token",
  "client_id": "app_client_abc123",
  "client_secret": "secret_xyz",
  "refresh_token": "def..."
}
```

---

### App API Endpoints

Third-party apps use OAuth tokens to access store data via scoped endpoints.

| Method | Path | Scope Required | Description |
|--------|------|----------------|-------------|
| GET | `/api/apps/v1/stores/{storeId}/products` | `read-products` | List products (same schema as admin API) |
| GET | `/api/apps/v1/stores/{storeId}/orders` | `read-orders` | List orders |
| GET | `/api/apps/v1/stores/{storeId}/customers` | `read-customers` | List customers |

Token scopes are enforced on every endpoint. Requests with insufficient scopes return `403 Forbidden`.

---

## 6. Middleware Stack

### 6.1 Route Group Middleware

#### Web (default)

Applied to all web routes via the `web` middleware group:

| Middleware | Purpose |
|-----------|---------|
| `EncryptCookies` | Encrypt/decrypt cookies |
| `AddQueuedCookiesToResponse` | Attach queued cookies |
| `StartSession` | Session handling |
| `ShareErrorsFromSession` | Flash validation errors |
| `VerifyCsrfToken` | CSRF protection on POST/PUT/PATCH/DELETE |
| `SubstituteBindings` | Route model binding |

#### Admin

Applied to `/admin/*` routes in addition to `web`:

| Middleware | Purpose |
|-----------|---------|
| `auth` | Require authenticated user (Laravel's built-in guard) |
| `verified` | Require verified email address |
| `store.resolve` | Resolve current store from session/domain and set `app.store_id` |
| `role.check` | Verify user has required role for the current store |

#### Storefront

Applied to storefront routes in addition to `web`:

| Middleware | Purpose |
|-----------|---------|
| `store.resolve` | Resolve store from request hostname via `store_domains` table |