command: rm -f .playwright-mcp/console-2026-05-03T23-28-42-443Z.log .playwright-mcp/console-2026-05-04T04-24-15-455Z.log .playwright-mcp/console-2026-05-04T05-58-46-727Z.log && git diff --check && git status --short

aggregated_output:
 M routes/api.php
 M specs/progress.md
?? app/Enums/ExportStatus.php
?? app/Http/Controllers/Api/Admin/V1/OrderExportController.php
?? app/Http/Resources/Admin/V1/DataExportResource.php
?? app/Models/DataExport.php
?? app/Services/OrderExportService.php
?? database/factories/DataExportFactory.php
?? database/migrations/2026_05_04_074802_create_data_exports_table.php
?? tests/Feature/Api/AdminOrderExportApiTest.php


output:
Chunk ID: 9803e1
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 105
Output:
 M routes/api.php
 M specs/progress.md
?? app/Enums/ExportStatus.php
?? app/Http/Controllers/Api/Admin/V1/OrderExportController.php
?? app/Http/Resources/Admin/V1/DataExportResource.php
?? app/Models/DataExport.php
?? app/Services/OrderExportService.php
?? database/factories/DataExportFactory.php
?? database/migrations/2026_05_04_074802_create_data_exports_table.php
?? tests/Feature/Api/AdminOrderExportApiTest.php


cmd: git add app/Enums/ExportStatus.php app/Http/Controllers/Api/Admin/V1/OrderExportController.php app/Http/Resources/Admin/V1/DataExportResource.php app/Models/DataExport.php app/Services/OrderExportService.php database/factories/DataExportFactory.php database/migrations/2026_05_04_074802_create_data_exports_table.php routes/api.php specs/progress.md tests/Feature/Api/AdminOrderExportApiTest.php && git diff --cached --stat


command: git add app/Enums/ExportStatus.php app/Http/Controllers/Api/Admin/V1/OrderExportController.php app/Http/Resources/Admin/V1/DataExportResource.php app/Models/DataExport.php app/Services/OrderExportService.php database/factories/DataExportFactory.php database/migrations/2026_05_04_074802_create_data_exports_table.php routes/api.php specs/progress.md tests/Feature/Api/AdminOrderExportApiTest.php && git diff --cached --stat

type: unknown

aggregated_output:
 app/Enums/ExportStatus.php                         |  11 ++
 .../Api/Admin/V1/OrderExportController.php         |  61 ++++++++
 app/Http/Resources/Admin/V1/DataExportResource.php |  48 ++++++
 app/Models/DataExport.php                          |  65 ++++++++
 app/Services/OrderExportService.php                | 143 ++++++++++++++++++
 database/factories/DataExportFactory.php           |  33 +++++
 ...2026_05_04_074802_create_data_exports_table.php |  41 +++++
 routes/api.php                                     |   3 +
 specs/progress.md                                  |  19 ++-
 tests/Feature/Api/AdminOrderExportApiTest.php      | 165 +++++++++++++++++++++
 10 files changed, 584 insertions(+), 5 deletions(-)

status: completed


output:
Chunk ID: 87f0df
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 180
Output:
 app/Enums/ExportStatus.php                         |  11 ++
 .../Api/Admin/V1/OrderExportController.php         |  61 ++++++++
 app/Http/Resources/Admin/V1/DataExportResource.php |  48 ++++++
 app/Models/DataExport.php                          |  65 ++++++++
 app/Services/OrderExportService.php                | 143 ++++++++++++++++++
 database/factories/DataExportFactory.php           |  33 +++++
 ...2026_05_04_074802_create_data_exports_table.php |  41 +++++
 routes/api.php                                     |   3 +
 specs/progress.md                                  |  19 ++-
 tests/Feature/Api/AdminOrderExportApiTest.php      | 165 +++++++++++++++++++++
 10 files changed, 584 insertions(+), 5 deletions(-)


command:
/bin/zsh
-lc
git status --short

cmd: git status --short

aggregated_output:
A  app/Enums/ExportStatus.php
A  app/Http/Controllers/Api/Admin/V1/OrderExportController.php
A  app/Http/Resources/Admin/V1/DataExportResource.php
A  app/Models/DataExport.php
A  app/Services/OrderExportService.php
A  database/factories/DataExportFactory.php
A  database/migrations/2026_05_04_074802_create_data_exports_table.php
M  routes/api.php
M  specs/progress.md
A  tests/Feature/Api/AdminOrderExportApiTest.php


output:
Chunk ID: e20715
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 105
Output:
A  app/Enums/ExportStatus.php
A  app/Http/Controllers/Api/Admin/V1/OrderExportController.php
A  app/Http/Resources/Admin/V1/DataExportResource.php
A  app/Models/DataExport.php
A  app/Services/OrderExportService.php
A  database/factories/DataExportFactory.php
A  database/migrations/2026_05_04_074802_create_data_exports_table.php
M  routes/api.php
M  specs/progress.md
A  tests/Feature/Api/AdminOrderExportApiTest.php


message: The export slice is staged: enum/model/migration/factory, service, controller/resource, routes, tests, and progress. I’m committing this as the final admin REST gap slice.

phase: commentary


type: message

role: assistant

text: The export slice is staged: enum/model/migration/factory, service, controller/resource, routes, tests, and progress. I’m committing this as the final admin REST gap slice.


cmd: git commit -m "Add admin order export API"


command: git commit -m "Add admin order export API"

aggregated_output:
[2026-05-04-codex-5-5-goal cf3288fe] Add admin order export API
 10 files changed, 584 insertions(+), 5 deletions(-)
 create mode 100644 app/Enums/ExportStatus.php
 create mode 100644 app/Http/Controllers/Api/Admin/V1/OrderExportController.php
 create mode 100644 app/Http/Resources/Admin/V1/DataExportResource.php
 create mode 100644 app/Models/DataExport.php
 create mode 100644 app/Services/OrderExportService.php
 create mode 100644 database/factories/DataExportFactory.php
 create mode 100644 database/migrations/2026_05_04_074802_create_data_exports_table.php
 create mode 100644 tests/Feature/Api/AdminOrderExportApiTest.php


output:
Chunk ID: 572593
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 158
Output:
[2026-05-04-codex-5-5-goal cf3288fe] Add admin order export API
 10 files changed, 584 insertions(+), 5 deletions(-)
 create mode 100644 app/Enums/ExportStatus.php
 create mode 100644 app/Http/Controllers/Api/Admin/V1/OrderExportController.php
 create mode 100644 app/Http/Resources/Admin/V1/DataExportResource.php
 create mode 100644 app/Models/DataExport.php
 create mode 100644 app/Services/OrderExportService.php
 create mode 100644 database/factories/DataExportFactory.php
 create mode 100644 database/migrations/2026_05_04_074802_create_data_exports_table.php
 create mode 100644 tests/Feature/Api/AdminOrderExportApiTest.php


output:
Chunk ID: 2e1196
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 0
Output:


message: Committed `cf3288fe Add admin order export API`; the worktree is clean. With the listed admin REST gap closed, I’m doing a remaining-gap audit against `specs/progress.md` and route coverage to decide whether there is another implementable slice or whether only deferred/verification items remain.


text: Committed `cf3288fe Add admin order export API`; the worktree is clean. With the listed admin REST gap closed, I’m doing a remaining-gap audit against `specs/progress.md` and route coverage to decide whether there is another implementable slice or whether only deferred/verification items remain.


cmd: rg -n "Not Implemented|501|deferred|TODO|missing|Open Issues|root paths|CHECK|OAuth|Passport|browser suite|full automated" specs app routes tests


command:
/bin/zsh
-lc
tail -n 80 specs/progress.md

progress.md

type: read

cmd: tail -n 80 specs/progress.md

aggregated_output:
- 2026-05-04: `php artisan route:list --path=api/admin/v1/stores/{store}/exports --except-vendor` confirmed 2 order export API routes: create and status.
- 2026-05-04: `php artisan migrate:fresh --seed --no-interaction` passed after the admin order export API changes and confirmed the `data_exports` migration runs with the seed suite.
- 2026-05-04: `php artisan test --compact` passed after the admin order export API changes: 242 tests, 1408 assertions.

## Decisions

- Follow the roadmap order strictly. Phase 1 is required before catalog, checkout, admin, storefront, and QA flows can be meaningfully implemented.
- Preserve the starter Fortify conventions where they do not conflict with the shop specs.
- Use integer minor units for all money columns and JSON text columns cast through Eloquent as specified.
- Keep `password_hash` as the database column for admin users and customers while exposing `password`/`getAuthPassword()` at the model layer for Fortify/Laravel auth compatibility.
- Seed both `shop.test` and `acme-fashion.test` as storefront domains for the first store so the goal URL and E2E spec URL can both resolve.
- Herd needs a site link for non-project-folder aliases; `acme-fashion.test` is linked to the same local project path as `shop.test` so the seeded domain can be verified in browser E2E flows.
- Customer Livewire auth components persist `storeId` from the initial storefront request because Livewire update requests do not run through the original storefront route middleware.
- The seed specification contains an arithmetic conflict: Acme Fashion's per-product variant table sums to 117 variants, while the prose says 107. The implementation follows the concrete per-product table, resulting in 117 Fashion variants and 10 Electronics variants, 127 total.
- Child catalog models without a `store_id` column are tenant-scoped through their parent product relationship. Inventory keeps its denormalized `store_id` and enforces that it matches the variant product store at save time.
- Storefront content pages now resolve from the `pages` table and only published pages are rendered.
- The catalog admin form intentionally blocks active products without a priced variant and duplicate in-store SKUs during UI edits, mirroring service-layer invariants where the current CRUD surface touches product variants directly.
- The product admin form generates variant rows from the option cartesian product, preserves matching variant edits by option combination, and syncs product option values plus variant pivots on save; stale variants are deleted or archived if order-line history exists.
- Navigation items use a nullable `parent_id` for one-level storefront dropdowns; `NavigationService::buildTree()` groups items recursively by parent while the admin editor enforces top-level parents for child items.
- Cart, checkout, and pricing runtime records are not seeded; only deterministic tax, shipping, and discount configuration is seeded. Runtime carts/checkouts are created by services and tests.
- The cart drawer reads the current session/customer cart without creating an empty cart on every storefront render; carts are created on first add-to-cart or checkout/cart service mutation.
- The checkout UI reserves inventory by selecting a payment method, then submits payment through `CheckoutService::completeCheckout()` so failed card payments can release reservations and return customers to payment selection.
- Order confirmation access is allowed for the authenticated owning customer or for the session that just placed the order via `last_order_id`.
- Customer account order list/detail routes use the `customer` guard and reload orders through explicit store/customer constraints.
- The cart REST API exposes `cart_version` as the public optimistic concurrency field while the service layer keeps its `expectedVersion` argument; `expected_version` remains accepted as a compatibility alias in API requests.
- Cart page discount codes are validated with `DiscountService`, saved in session, and applied to the checkout when the customer proceeds.
- Admin discount forms persist minimum purchase, product eligibility, collection eligibility, and `one_per_customer` in the existing `discounts.rules_json` keys consumed by `DiscountService`; code discounts now check customer order-line allocation history before allowing another use.
- `PricingEngine` applies an explicit checkout discount code first, then active automatic discounts returned by `DiscountService`.
- Order creation replays the same code-then-automatic discount sequence in memory so `order_lines.discount_allocations_json` keeps separate `{discount_id, code, amount}` entries for every line-item discount while preserving aggregate cart line totals.
- `orders.checkout_id` is intentionally added beyond the original schema table to enforce idempotent checkout completion without duplicate orders.
- Failed card payments release reserved inventory and move the checkout back to `shipping_sel…5193 chars truncated…uery time: per-store stop words are removed, synonym groups expand into sanitized SQLite FTS5 `OR` terms, and terms are joined with explicit `AND`; the admin page can rebuild the per-store index synchronously for this self-contained app.
- Search API rate limiting is registered as `search` (30/minute per IP), analytics ingestion uses the `analytics` limiter (60/minute per IP), and a `webhooks` limiter is registered for future inbound app endpoints.
- Analytics ingestion stores raw events with client-event deduplication and keeps aggregation separate in `analytics_daily`; admin analytics reads pre-aggregated daily metrics for KPIs and raw events for referrers.
- Seeded analytics metrics are deterministic demo data and are not tied to seeded orders because order/payment seed data remains intentionally absent.
- The admin analytics CSV export is generated as a data URL for the self-contained local app rather than creating persistent export files/jobs.
- OAuth/Passport app authorization remains deferred per the roadmap; `/oauth/*` and `/api/apps/v1/*` now return explicit `501 Not Implemented` responses, while the developer token UI uses the existing app installation and `oauth_tokens` schema as a self-contained local token store.
- Admin order API routes use the `admin.api` middleware, which accepts either the existing session-authenticated admin user or a hashed `oauth_tokens` bearer token scoped to the route store; token requests require `read-orders` for GET routes and `write-orders` for refund/fulfillment mutations and update `last_used_at` on successful use.
- Outbound webhook delivery jobs are forced onto the database queue and `webhooks` queue name so domain events enqueue delivery work instead of making external HTTP requests inline when the default queue connection is `sync`.
- Webhook signatures follow the security spec message shape of `{timestamp}.{json_body}` with HMAC-SHA256, while the delivery payload is wrapped with `id`, `api_version`, `event_type`, `store_id`, `occurred_at`, and `data`.
- Webhook circuit breaker state is derived from the latest five delivery rows for a subscription because the schema does not include a dedicated consecutive-failure counter column.
- Customer password resets use a custom store-scoped service instead of Laravel's stock password broker token repository because the `customer_password_reset_tokens` table includes `store_id` in the primary key and the stock repository does not write tenant columns.
- Customer password reset pages are routed under `/account/forgot-password` and `/account/reset-password/{token}` so the existing Fortify starter/admin root routes (`/forgot-password`, `/reset-password/{token}`) remain intact.
- Product media uploads use Livewire temporary file uploads on the existing product form, create `ProductMedia` rows in `processing` status, and dispatch the existing `ProcessMediaUpload` job instead of adding a second media-processing path.
- Resource policies now type concrete model parameters for Product, Collection, Customer, Discount, Order, Fulfillment, Refund, Page, and Theme actions; `ChecksStoreRole::storeIdForModel()` stays generic because it is the shared store-id extraction helper.

## Open Issues

- Customer password reset is implemented under `/account/forgot-password` and `/account/reset-password/{token}`; the exact spec root paths remain occupied by the existing Fortify starter/admin reset routes.
- `php artisan route:list --except-vendor` hides Livewire full-page routes because their controller is vendor-provided; path-filtered route-list commands are used as evidence for those routes.
- Full automated browser suites from Spec 08 are still incomplete beyond the initial Pest browser smoke coverage for storefront, admin, and mobile rendering.
- SQLite enum/check constraints from the schema spec are not yet explicitly enforced as database `CHECK` constraints; enum validation is currently enforced through casts/services/model invariants.

## Completion Summary

Not complete. Phase 1 foundation, Phase 2 catalog data/UI surfaces, product media processing/admin upload controls, Phase 3 storefront theme/content/navigation data, the Phase 4 cart/checkout/pricing backend foundation, cart/checkout storefront UI through order completion, cart-page estimates, cart/checkout REST APIs, Phase 5 order/payment backend foundation, Phase 5 refund/fulfillment services, customer order views, admin dashboard/order/customer/discount/content/settings/theme-file/navigation/search/analytics/apps/developers management, storefront search and analytics APIs, product/customer/collection/discount/content page/store settings/search/analytics/shipping/tax/theme/order/export API surfaces, deferred OAuth/app route stubs, initial automated browser smoke coverage, and outbound webhook delivery foundations are implemented, with known auth/token route compatibility, deferred OAuth/app ecosystem routes, full browser-suite, and database CHECK constraint gaps tracked above.


output:
Chunk ID: f39af4
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 3799
Output:
- 2026-05-04: `php artisan route:list --path=api/admin/v1/stores/{store}/exports --except-vendor` confirmed 2 order export API routes: create and status.
- 2026-05-04: `php artisan migrate:fresh --seed --no-interaction` passed after the admin order export API changes and confirmed the `data_exports` migration runs with the seed suite.
- 2026-05-04: `php artisan test --compact` passed after the admin order export API changes: 242 tests, 1408 assertions.

## Decisions

- Follow the roadmap order strictly. Phase 1 is required before catalog, checkout, admin, storefront, and QA flows can be meaningfully implemented.
- Preserve the starter Fortify conventions where they do not conflict with the shop specs.
- Use integer minor units for all money columns and JSON text columns cast through Eloquent as specified.
- Keep `password_hash` as the database column for admin users and customers while exposing `password`/`getAuthPassword()` at the model layer for Fortify/Laravel auth compatibility.
- Seed both `shop.test` and `acme-fashion.test` as storefront domains for the first store so the goal URL and E2E spec URL can both resolve.
- Herd needs a site link for non-project-folder aliases; `acme-fashion.test` is linked to the same local project path as `shop.test` so the seeded domain can be verified in browser E2E flows.
- Customer Livewire auth components persist `storeId` from the initial storefront request because Livewire update requests do not run through the original storefront route middleware.
- The seed specification contains an arithmetic conflict: Acme Fashion's per-product variant table sums to 117 variants, while the prose says 107. The implementation follows the concrete per-product table, resulting in 117 Fashion variants and 10 Electronics variants, 127 total.
- Child catalog models without a `store_id` column are tenant-scoped through their parent product relationship. Inventory keeps its denormalized `store_id` and enforces that it matches the variant product store at save time.
- Storefront content pages now resolve from the `pages` table and only published pages are rendered.
- The catalog admin form intentionally blocks active products without a priced variant and duplicate in-store SKUs during UI edits, mirroring service-layer invariants where the current CRUD surface touches product variants directly.
- The product admin form generates variant rows from the option cartesian product, preserves matching variant edits by option combination, and syncs product option values plus variant pivots on save; stale variants are deleted or archived if order-line history exists.
- Navigation items use a nullable `parent_id` for one-level storefront dropdowns; `NavigationService::buildTree()` groups items recursively by parent while the admin editor enforces top-level parents for child items.
- Cart, checkout, and pricing runtime records are not seeded; only deterministic tax, shipping, and discount configuration is seeded. Runtime carts/checkouts are created by services and tests.
- The cart drawer reads the current session/customer cart without creating an empty cart on every storefront render; carts are created on first add-to-cart or checkout/cart service mutation.
- The checkout UI reserves inventory by selecting a payment method, then submits payment through `CheckoutService::completeCheckout()` so failed card payments can release reservations and return customers to payment selection.
- Order confirmation access is allowed for the authenticated owning customer or for the session that just placed the order via `last_order_id`.
- Customer account order list/detail routes use the `customer` guard and reload orders through explicit store/customer constraints.
- The cart REST API exposes `cart_version` as the public optimistic concurrency field while the service layer keeps its `expectedVersion` argument; `expected_version` remains accepted as a compatibility alias in API requests.
- Cart page discount codes are validated with `DiscountService`, saved in session, and applied to the checkout when the customer proceeds.
- Admin discount forms persist minimum purchase, product eligibility, collection eligibility, and `one_per_customer` in the existing `discounts.rules_json` keys consumed by `DiscountService`; code discounts now check customer order-line allocation history before allowing another use.
- `PricingEngine` applies an explicit checkout discount code first, then active automatic discounts returned by `DiscountService`.
- Order creation replays the same code-then-automatic discount sequence in memory so `order_lines.discount_allocations_json` keeps separate `{discount_id, code, amount}` entries for every line-item discount while preserving aggregate cart line totals.
- `orders.checkout_id` is intentionally added beyond the original schema table to enforce idempotent checkout completion without duplicate orders.
- Failed card payments release reserved inventory and move the checkout back to `shipping_selected` so customers can retry payment selection.
- Bank transfer order completion creates a pending order and payment while keeping inventory reserved until the later admin payment-confirmation flow.
- Inventory commit/release/restock locks by primary key without the current-store global scope because scheduled jobs can operate across stores in one run.
- Admin order Livewire components persist locked `storeId`/`orderId` state and reload orders without global scopes but with explicit store constraints before each service-backed action.
- Admin order actions deliberately delegate to `OrderService`, `RefundService`, and `FulfillmentService` so UI validation cannot bypass payment, refund, fulfillment, or inventory guards.
- Storefront order lookup uses a deterministic HMAC access token derived from store/order identity so confirmation links can be token-gated without adding a new runtime column.
- Admin REST API routes use the `admin.api` middleware with session-authenticated store users or hashed `oauth_tokens` bearer tokens; the current implemented surfaces are read-only products/customers/orders, collection CRUD, discount CRUD, content page CRUD, search status/reindex, analytics summary, shipping zone/rate settings, tax settings read/update, and order fulfillment/refund mutations.
- Admin dashboard sales and top-product metrics are derived from paid or partially refunded orders; the dedicated analytics page now reads analytics event and daily aggregate tables.
- Customer addresses are scoped through the locked customer id because `customer_addresses` intentionally belongs to customers and has no direct `store_id` column.
- Admin/store Livewire route middleware is registered with Livewire persistent middleware so component POST requests keep `current_store` binding and store-role checks after the initial full-page route load.
- Admin routes use a custom `EnsureUserEmailIsVerified` middleware so verified-user gating works for full-page Livewire routes without relying on the stock redirect middleware path that fails during Livewire component redirects in this app.
- Admin settings are split across `/admin/settings`, `/admin/settings/shipping`, `/admin/settings/taxes`, `/admin/settings/checkout`, and `/admin/settings/notifications`; domains are managed on the general settings page because the current route surface does not need a separate domains route.
- The tax settings API accepts the spec-facing `default_tax_rate`/`tax_rates` payload shape and normalizes it to the existing internal `default_rate_bps`/`rates` config consumed by the calculator and admin UI.
- The shipping settings API accepts spec-facing `price_amount`/`tiers` rate config keys and normalizes them to the existing `amount`/`ranges` config consumed by `ShippingCalculator`.
- The theme upload API imports ZIP archives with `theme.json` or `manifest.json`, requires the same core template paths seeded for default themes, stores each imported file on the local disk under the new theme id, and seeds theme settings from the manifest or the store defaults.
- The theme publish API validates required files before switching the store to exactly one published theme and flushing cached theme settings.
- The store settings API exposes the general settings UI surface as `/api/admin/v1/stores/{store}/settings`: store name/currency/locale/timezone fields update the `stores` row, while `settings_json` patches are recursively merged into `store_settings` so checkout and notification defaults are preserved unless explicitly changed.
- The order export API adds a small `data_exports` table to support the spec's export status endpoint; this local app generates CSV synchronously into the local disk, marks the export `completed` inside the create request, and returns a data URL from the status response instead of a cloud presigned URL.
- Checkout and notification settings persist in `store_settings.settings_json`; `bank_transfer_cancel_days` remains a root-level key because the existing cancellation job already consumes it from that location.
- Admin navigation persists nested ordered menu items with Livewire `wire:sort`, up/down sibling controls, and a parent selector; children are saved under their selected top-level parent with per-parent positions.
- The theme editor uses the `ThemeSettingsService` default settings shape as the editable schema and writes back to `theme_settings.settings_json`.
- Theme files are seeded and edited through local disk storage at `theme_files.storage_key`; saving a file updates the stored contents plus `sha256` and `byte_size` metadata.
- Theme duplication copies each file's local disk contents to a new storage key for the copied theme instead of sharing the original file storage path.
- Search uses a local SQLite FTS5 virtual table (`products_fts`) with `SearchService` as the only query/sync boundary; product observer events keep the index synchronized for create/update/delete.
- Admin search reindex API runs the existing local `SearchService::reindex()` synchronously and returns `202 Accepted` with `completed` status because this self-contained app has no separate durable search job state table.
- Search settings apply at query time: per-store stop words are removed, synonym groups expand into sanitized SQLite FTS5 `OR` terms, and terms are joined with explicit `AND`; the admin page can rebuild the per-store index synchronously for this self-contained app.
- Search API rate limiting is registered as `search` (30/minute per IP), analytics ingestion uses the `analytics` limiter (60/minute per IP), and a `webhooks` limiter is registered for future inbound app endpoints.
- Analytics ingestion stores raw events with client-event deduplication and keeps aggregation separate in `analytics_daily`; admin analytics reads pre-aggregated daily metrics for KPIs and raw events for referrers.
- Seeded analytics metrics are deterministic demo data and are not tied to seeded orders because order/payment seed data remains intentionally absent.
- The admin analytics CSV export is generated as a data URL for the self-contained local app rather than creating persistent export files/jobs.
- OAuth/Passport app authorization remains deferred per the roadmap; `/oauth/*` and `/api/apps/v1/*` now return explicit `501 Not Implemented` responses, while the developer token UI uses the existing app installation and `oauth_tokens` schema as a self-contained local token store.
- Admin order API routes use the `admin.api` middleware, which accepts either the existing session-authenticated admin user or a hashed `oauth_tokens` bearer token scoped to the route store; token requests require `read-orders` for GET routes and `write-orders` for refund/fulfillment mutations and update `last_used_at` on successful use.
- Outbound webhook delivery jobs are forced onto the database queue and `webhooks` queue name so domain events enqueue delivery work instead of making external HTTP requests inline when the default queue connection is `sync`.
- Webhook signatures follow the security spec message shape of `{timestamp}.{json_body}` with HMAC-SHA256, while the delivery payload is wrapped with `id`, `api_version`, `event_type`, `store_id`, `occurred_at`, and `data`.
- Webhook circuit breaker state is derived from the latest five delivery rows for a subscription because the schema does not include a dedicated consecutive-failure counter column.
- Customer password resets use a custom store-scoped service instead of Laravel's stock password broker token repository because the `customer_password_reset_tokens` table includes `store_id` in the primary key and the stock repository does not write tenant columns.
- Customer password reset pages are routed under `/account/forgot-password` and `/account/reset-password/{token}` so the existing Fortify starter/admin root routes (`/forgot-password`, `/reset-password/{token}`) remain intact.
- Product media uploads use Livewire temporary file uploads on the existing product form, create `ProductMedia` rows in `processing` status, and dispatch the existing `ProcessMediaUpload` job instead of adding a second media-processing path.
- Resource policies now type concrete model parameters for Product, Collection, Customer, Discount, Order, Fulfillment, Refund, Page, and Theme actions; `ChecksStoreRole::storeIdForModel()` stays generic because it is the shared store-id extraction helper.

## Open Issues

- Customer password reset is implemented under `/account/forgot-password` and `/account/reset-password/{token}`; the exact spec root paths remain occupied by the existing Fortify starter/admin reset routes.
- `php artisan route:list --except-vendor` hides Livewire full-page routes because their controller is vendor-provided; path-filtered route-list commands are used as evidence for those routes.
- Full automated browser suites from Spec 08 are still incomplete beyond the initial Pest browser smoke coverage for storefront, admin, and mobile rendering.
- SQLite enum/check constraints from the schema spec are not yet explicitly enforced as database `CHECK` constraints; enum validation is currently enforced through casts/services/model invariants.

## Completion Summary

Not complete. Phase 1 foundation, Phase 2 catalog data/UI surfaces, product media processing/admin upload controls, Phase 3 storefront theme/content/navigation data, the Phase 4 cart/checkout/pricing backend foundation, cart/checkout storefront UI through order completion, cart-page estimates, cart/checkout REST APIs, Phase 5 order/payment backend foundation, Phase 5 refund/fulfillment services, customer order views, admin dashboard/order/customer/discount/content/settings/theme-file/navigation/search/analytics/apps/developers management, storefront search and analytics APIs, product/customer/collection/discount/content page/store settings/search/analytics/shipping/tax/theme/order/export API surfaces, deferred OAuth/app route stubs, initial automated browser smoke coverage, and outbound webhook delivery foundations are implemented, with known auth/token route compatibility, deferred OAuth/app ecosystem routes, full browser-suite, and database CHECK constraint gaps tracked above.


command:
/bin/zsh
-lc
git log --oneline -8

cmd: git log --oneline -8

aggregated_output:
cf3288fe Add admin order export API
b02a92c2 Add admin store settings API
b61e6322 Add admin theme API endpoints
95ad3071 Add admin shipping settings API
a8f0799e Add admin tax settings API
a5135d16 Add admin analytics summary API
99670769 Add admin search index API endpoints
d8fd4ebe Add admin page API endpoints


command: rg -n "Not Implemented|501|deferred|TODO|missing|Open Issues|root paths|CHECK|OAuth|Passport|browser suite|full automated" specs app routes tests

type: search

cmd: rg -n 'Not Implemented|501|deferred|TODO|missing|Open Issues|root paths|CHECK|OAuth|Passport|browser suite|full automated' specs app routes tests

query: Not Implemented|501|deferred|TODO|missing|Open Issues|root paths|CHECK|OAuth|Passport|browser suite|full automated

path: specs

aggregated_output:
routes/web.php:3:use App\Http\Controllers\Api\OAuthController;
routes/web.php:76:Route::get('oauth/authorize', [OAuthController::class, 'authorize'])
routes/web.php:80:Route::post('oauth/token', [OAuthController::class, 'token'])
tests/Feature/Checkout/PricingServicesTest.php:271:                ['min_g' => 501, 'max_g' => 2000, 'amount' => 899],
specs/01-DATABASE-SCHEMA.md:53:| status | TEXT | No | 'active' | CHECK IN ('active', 'suspended') | |
specs/01-DATABASE-SCHEMA.md:77:| type | TEXT | No | 'storefront' | CHECK IN ('storefront', 'admin', 'api') | Domain purpose |
specs/01-DATABASE-SCHEMA.md:79:| tls_mode | TEXT | No | 'managed' | CHECK IN ('managed', 'bring_your_own') | TLS certificate mode |
specs/01-DATABASE-SCHEMA.md:100:| status | TEXT | No | 'active' | CHECK IN ('active', 'disabled') | |
specs/01-DATABASE-SCHEMA.md:127:| role | TEXT | No | 'staff' | CHECK IN ('owner', 'admin', 'staff', 'support') | |
specs/01-DATABASE-SCHEMA.md:165:| status | TEXT | No | 'draft' | CHECK IN ('draft', 'active', 'archived') | |
specs/01-DATABASE-SCHEMA.md:240:| status | TEXT | No | 'active' | CHECK IN ('active', 'archived') | |
specs/01-DATABASE-SCHEMA.md:286:| policy | TEXT | No | 'deny' | CHECK IN ('deny', 'continue') | Oversell policy |
specs/01-DATABASE-SCHEMA.md:308:| type | TEXT | No | 'manual' | CHECK IN ('manual', 'automated') | Collection type: manual curation or rule-based automation |
specs/01-DATABASE-SCHEMA.md:309:| status | TEXT | No | 'active' | CHECK IN ('draft', 'active', 'archived') | |
specs/01-DATABASE-SCHEMA.md:348:| type | TEXT | No | 'image' | CHECK IN ('image', 'video') | Media type |
specs/01-DATABASE-SCHEMA.md:356:| status | TEXT | No | 'processing' | CHECK IN ('processing', 'ready', 'failed') | Processing status |
specs/01-DATABASE-SCHEMA.md:379:| status | TEXT | No | 'draft' | CHECK IN ('draft', 'published') | |
specs/01-DATABASE-SCHEMA.md:434:| status | TEXT | No | 'draft' | CHECK IN ('draft', 'published', 'archived') | |
specs/01-DATABASE-SCHEMA.md:475:| type | TEXT | No | 'link' | CHECK IN ('link', 'page', 'collection', 'product') | Item type |
specs/01-DATABASE-SCHEMA.md:539:| status | TEXT | No | 'active' | CHECK IN ('active', 'converted', 'abandoned') | |
specs/01-DATABASE-SCHEMA.md:587:| status | TEXT | No | 'started' | CHECK IN ('started', 'addressed', 'shipping_selected', 'payment_selected', 'completed', 'expired') | |
specs/01-DATABASE-SCHEMA.md:588:| payment_method | TEXT | Yes | NULL | CHECK IN ('credit_card', 'paypal', 'bank_transfer') | Selected payment method |
specs/01-DATABASE-SCHEMA.md:641:| type | TEXT | No | 'flat' | CHECK IN ('flat', 'weight', 'price', 'carrier') | Calculation method |
specs/01-DATABASE-SCHEMA.md:665:| mode | TEXT | No | 'manual' | CHECK IN ('manual', 'provider') | Tax calculation mode |
specs/01-DATABASE-SCHEMA.md:666:| provider | TEXT | No | 'none' | CHECK IN ('stripe_tax', 'none') | External tax provider |
specs/01-DATABASE-SCHEMA.md:680:| type | TEXT | No | 'code' | CHECK IN ('code', 'automatic') | Activation method |
specs/01-DATABASE-SCHEMA.md:682:| value_type | TEXT | No | - | CHECK IN ('fixed', 'percent', 'free_shipping') | Discount calculation type |
specs/01-DATABASE-SCHEMA.md:689:| status | TEXT | No | 'active' | CHECK IN ('draft', 'active', 'expired', 'disabled') | |
specs/01-DATABASE-SCHEMA.md:761:| payment_method | TEXT | No | - | CHECK IN ('credit_card', 'paypal', 'bank_transfer') | Payment method used for this order |
specs/01-DATABASE-SCHEMA.md:762:| status | TEXT | No | 'pending' | CHECK IN ('pending', 'paid', 'fulfilled', 'cancelled', 'refunded') | Overall order status |
specs/01-DATABASE-SCHEMA.md:763:| financial_status | TEXT | No | 'pending' | CHECK IN ('pending', 'authorized', 'paid', 'partially_refunded', 'refunded', 'voided') | Payment status |
specs/01-DATABASE-SCHEMA.md:764:| fulfillment_status | TEXT | No | 'unfulfilled' | CHECK IN ('unfulfilled', 'partial', 'fulfilled') | Shipping status |
specs/01-DATABASE-SCHEMA.md:826:| provider | TEXT | No | 'mock' | CHECK IN ('mock') | Payment provider (mock PSP only) |
specs/01-DATABASE-SCHEMA.md:827:| method | TEXT | No | - | CHECK IN ('credit_card', 'paypal', 'bank_transfer') | Payment method used |
specs/01-DATABASE-SCHEMA.md:829:| status | TEXT | No | 'pending' | CHECK IN ('pending', 'captured', 'failed', 'refunded') | |
specs/01-DATABASE-SCHEMA.md:857:| status | TEXT | No | 'pending' | CHECK IN ('pending', 'processed', 'failed') | |
specs/01-DATABASE-SCHEMA.md:877:| status | TEXT | No | 'pending' | CHECK IN ('pending', 'shipped', 'delivered') | |
specs/01-DATABASE-SCHEMA.md:979:| status | TEXT | No | 'active' | CHECK IN ('active', 'disabled') | |
specs/01-DATABASE-SCHEMA.md:998:| status | TEXT | No | 'active' | CHECK IN ('active', 'suspended', 'uninstalled') | |
specs/01-DATABASE-SCHEMA.md:1011:OAuth2 client credentials for apps.
specs/01-DATABASE-SCHEMA.md:1017:| client_id | TEXT | No | - | UNIQUE per table | OAuth2 client identifier |
specs/01-DATABASE-SCHEMA.md:1023:- `idx_o…11228 chars truncated… make:test Api/AdminCatalogApiTest --pest --no-interaction` created the admin catalog API controllers/resources, deferred app/OAuth controllers, and tests.
specs/progress.md:325:- 2026-05-04: `php artisan route:list --path=oauth --except-vendor` confirmed `/oauth/authorize` and `/oauth/token`; `php artisan route:list --path=api/apps/v1 --except-vendor` confirmed 3 deferred app API routes.
specs/progress.md:464:- OAuth/Passport app authorization remains deferred per the roadmap; `/oauth/*` and `/api/apps/v1/*` now return explicit `501 Not Implemented` responses, while the developer token UI uses the existing app installation and `oauth_tokens` schema as a self-contained local token store.
specs/progress.md:474:## Open Issues
specs/progress.md:476:- Customer password reset is implemented under `/account/forgot-password` and `/account/reset-password/{token}`; the exact spec root paths remain occupied by the existing Fortify starter/admin reset routes.
specs/progress.md:478:- Full automated browser suites from Spec 08 are still incomplete beyond the initial Pest browser smoke coverage for storefront, admin, and mobile rendering.
specs/progress.md:479:- SQLite enum/check constraints from the schema spec are not yet explicitly enforced as database `CHECK` constraints; enum validation is currently enforced through casts/services/model invariants.
specs/progress.md:483:Not complete. Phase 1 foundation, Phase 2 catalog data/UI surfaces, product media processing/admin upload controls, Phase 3 storefront theme/content/navigation data, the Phase 4 cart/checkout/pricing backend foundation, cart/checkout storefront UI through order completion, cart-page estimates, cart/checkout REST APIs, Phase 5 order/payment backend foundation, Phase 5 refund/fulfillment services, customer order views, admin dashboard/order/customer/discount/content/settings/theme-file/navigation/search/analytics/apps/developers management, storefront search and analytics APIs, product/customer/collection/discount/content page/store settings/search/analytics/shipping/tax/theme/order/export API surfaces, deferred OAuth/app route stubs, initial automated browser smoke coverage, and outbound webhook delivery foundations are implemented, with known auth/token route compatibility, deferred OAuth/app ecosystem routes, full browser-suite, and database CHECK constraint gaps tracked above.
specs/09-IMPLEMENTATION-ROADMAP.md:24:| OAuth (apps) | Passport | Deferred/Stubbed | Simplifies initial build, can add later |
specs/09-IMPLEMENTATION-ROADMAP.md:111:All monetary amounts stored as INTEGER in minor units (cents). All enums stored as TEXT with CHECK constraints. All timestamps as TEXT (ISO-8601) via Laravel.
specs/09-IMPLEMENTATION-ROADMAP.md:315:| `rebuildMatrix(Product $product): void` | Computes cartesian product, creates missing variants, archives orphaned ones |
specs/09-IMPLEMENTATION-ROADMAP.md:668:| `charge(Checkout $checkout, PaymentMethod $method, array $details): PaymentResult` | Processes payment in-process. For credit_card: validates magic card numbers, returns success/decline. For paypal: always succeeds. For bank_transfer: returns pending (deferred capture). |
specs/09-IMPLEMENTATION-ROADMAP.md:675:- Bank transfer: deferred capture, returns PaymentResult with status=pending
specs/09-IMPLEMENTATION-ROADMAP.md:1082:| calculates a weight-based rate | rate type: weight, config: ranges 0-500g=499, 501-2000g=899; cart total weight: 750g | shipping = 899 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1329:| rejects address transition with missing required fields | set address without city | validation error, status remains started | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1372:| calculates weight-based rate correctly | cart total weight 750g, rate range 501-2000g = 899 | shipping = 899 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1497:| validates required address fields | submit with missing address1 | validation error | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1660:| validates required address fields | PUT address with missing city | 422 response | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1732:| `OauthClient` | OAuth client for app |
specs/09-IMPLEMENTATION-ROADMAP.md:1733:| `OauthToken` | OAuth token for installation |
specs/04-STOREFRONT-UI.md:1107:No additional form fields. The button text changes to "Place order - {total}" (not "Pay now", since payment is deferred).
specs/08-PLAYWRIGHT-E2E-PLAN.md:176:**Purpose:** Hit every major public and admin page, assert they load without JavaScript errors. This is the fastest way to catch broken routes, missing views, or uncaught JS exceptions.
specs/08-PLAYWRIGHT-E2E-PLAN.md:1711:    Step2 -->|missing fields| Step2Error[Validation errors on address fields]
specs/05-BUSINESS-LOGIC.md:992:    else Bank Transfer (deferred)
specs/07-SEEDERS-AND-TEST-DATA.md:627:| `weightBased` | type = "weight", config_json has ranges array with tiers: 0-500g = 399, 501-2000g = 699, 2001g+ = 1299 |


output:
Chunk ID: 274ac1
Wall time: 0.1775 seconds
Process exited with code 0
Original token count: 1261
Output:

  POST      admin/logout ........................................ admin.logout
  GET|HEAD  api/admin/v1/stores/{store}/analytics/summary api.admin.v1.analyt…
  GET|HEAD  api/admin/v1/stores/{store}/collections api.admin.v1.collections.…
  POST      api/admin/v1/stores/{store}/collections api.admin.v1.collections.…
  PUT       api/admin/v1/stores/{store}/collections/{collection} api.admin.v1…
  DELETE    api/admin/v1/stores/{store}/collections/{collection} api.admin.v1…
  GET|HEAD  api/admin/v1/stores/{store}/customers api.admin.v1.customers.inde…
  GET|HEAD  api/admin/v1/stores/{store}/customers/{customer} api.admin.v1.cus…
  GET|HEAD  api/admin/v1/stores/{store}/discounts api.admin.v1.discounts.inde…
  POST      api/admin/v1/stores/{store}/discounts api.admin.v1.discounts.stor…
  PUT       api/admin/v1/stores/{store}/discounts/{discount} api.admin.v1.dis…
  DELETE    api/admin/v1/stores/{store}/discounts/{discount} api.admin.v1.dis…
  POST      api/admin/v1/stores/{store}/exports/orders api.admin.v1.exports.o…
  GET|HEAD  api/admin/v1/stores/{store}/exports/{dataExport} api.admin.v1.exp…
  GET|HEAD  api/admin/v1/stores/{store}/orders api.admin.v1.orders.index › Ap…
  GET|HEAD  api/admin/v1/stores/{store}/orders/{order} api.admin.v1.orders.sh…
  POST      api/admin/v1/stores/{store}/orders/{order}/fulfillments api.admin…
  POST      api/admin/v1/stores/{store}/orders/{order}/refunds api.admin.v1.o…
  GET|HEAD  api/admin/v1/stores/{store}/pages api.admin.v1.pages.index › Api\…
  POST      api/admin/v1/stores/{store}/pages api.admin.v1.pages.store › Api\…
  PUT       api/admin/v1/stores/{store}/pages/{page} api.admin.v1.pages.updat…
  DELETE    api/admin/v1/stores/{store}/pages/{page} api.admin.v1.pages.destr…
  GET|HEAD  api/admin/v1/stores/{store}/products api.admin.v1.products.index …
  GET|HEAD  api/admin/v1/stores/{store}/products/{product} api.admin.v1.produ…
  POST      api/admin/v1/stores/{store}/search/reindex api.admin.v1.search.re…
  GET|HEAD  api/admin/v1/stores/{store}/search/status api.admin.v1.search.sta…
  GET|HEAD  api/admin/v1/stores/{store}/settings api.admin.v1.settings.show  …
  PUT       api/admin/v1/stores/{store}/settings api.admin.v1.settings.update…
  GET|HEAD  api/admin/v1/stores/{store}/shipping/zones api.admin.v1.shipping.…
  POST      api/admin/v1/stores/{store}/shipping/zones api.admin.v1.shipping.…
  PUT       api/admin/v1/stores/{store}/shipping/zones/{shippingZone} api.adm…
  POST      api/admin/v1/stores/{store}/shipping/zones/{shippingZone}/rates a…
  GET|HEAD  api/admin/v1/stores/{store}/tax/settings api.admin.v1.tax.setting…
  PUT       api/admin/v1/stores/{store}/tax/settings api.admin.v1.tax.setting…
  POST      api/admin/v1/stores/{store}/themes api.admin.v1.themes.store › Ap…
  POST      api/admin/v1/stores/{store}/themes/{theme}/publish api.admin.v1.t…
  PUT       api/admin/v1/stores/{store}/themes/{theme}/settings api.admin.v1.…
  GET|HEAD  api/apps/v1/stores/{store}/customers api.apps.v1.customers.index …
  GET|HEAD  api/apps/v1/stores/{store}/orders api.apps.v1.orders.index › Api\…
  GET|HEAD  api/apps/v1/stores/{store}/products api.apps.v1.products.index › …
  POST      api/storefront/v1/analytics/events api.storefront.v1.analytics.ev…
  POST      api/storefront/v1/carts api.storefront.v1.carts.store › Api\Store…
  GET|HEAD  api/storefront/v1/carts/{cart} api.storefront.v1.carts.show › Api…
  POST      api/storefront/v1/carts/{cart}/lines api.storefront.v1.carts.line…
  PUT       api/storefront/v1/carts/{cart}/lines/{cartLine} api.storefront.v1…
  DELETE    api/storefront/v1/carts/{cart}/lines/{cartLine} api.storefront.v1…
  POST      api/storefront/v1/checkouts api.storefront.v1.checkouts.store › A…
  GET|HEAD  api/storefront/v1/checkouts/{checkout} api.storefront.v1.checkout…
  PUT       api/storefront/v1/checkouts/{checkout}/address api.storefront.v1.…
  POST      api/storefront/v1/checkouts/{checkout}/apply-discount api.storefr…
  DELETE    api/storefront/v1/checkouts/{checkout}/discount api.storefront.v1…
  POST      api/storefront/v1/checkouts/{checkout}/pay api.storefront.v1.chec…
  PUT       api/storefront/v1/checkouts/{checkout}/payment-method api.storefr…
  PUT       api/storefront/v1/checkouts/{checkout}/shipping-method api.storef…
  GET|HEAD  api/storefront/v1/orders/{orderNumber} api.storefront.v1.orders.s…
  GET|HEAD  api/storefront/v1/search api.storefront.v1.search.index › Api\Sto…
  GET|HEAD  api/storefront/v1/search/suggest api.storefront.v1.search.suggest…
  ANY       dashboard .... dashboard › Illuminate\Routing › RedirectController
  GET|HEAD  oauth/authorize .. oauth.authorize › Api\OAuthController@authorize
  POST      oauth/token .............. oauth.token › Api\OAuthController@token
  ANY       settings ................. Illuminate\Routing › RedirectController

                                                           Showing [61] routes


output:
Chunk ID: c12152
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 5307
Output:
routes/web.php:3:use App\Http\Controllers\Api\OAuthController;
routes/web.php:76:Route::get('oauth/authorize', [OAuthController::class, 'authorize'])
routes/web.php:80:Route::post('oauth/token', [OAuthController::class, 'token'])
tests/Feature/Checkout/PricingServicesTest.php:271:                ['min_g' => 501, 'max_g' => 2000, 'amount' => 899],
specs/01-DATABASE-SCHEMA.md:53:| status | TEXT | No | 'active' | CHECK IN ('active', 'suspended') | |
specs/01-DATABASE-SCHEMA.md:77:| type | TEXT | No | 'storefront' | CHECK IN ('storefront', 'admin', 'api') | Domain purpose |
specs/01-DATABASE-SCHEMA.md:79:| tls_mode | TEXT | No | 'managed' | CHECK IN ('managed', 'bring_your_own') | TLS certificate mode |
specs/01-DATABASE-SCHEMA.md:100:| status | TEXT | No | 'active' | CHECK IN ('active', 'disabled') | |
specs/01-DATABASE-SCHEMA.md:127:| role | TEXT | No | 'staff' | CHECK IN ('owner', 'admin', 'staff', 'support') | |
specs/01-DATABASE-SCHEMA.md:165:| status | TEXT | No | 'draft' | CHECK IN ('draft', 'active', 'archived') | |
specs/01-DATABASE-SCHEMA.md:240:| status | TEXT | No | 'active' | CHECK IN ('active', 'archived') | |
specs/01-DATABASE-SCHEMA.md:286:| policy | TEXT | No | 'deny' | CHECK IN ('deny', 'continue') | Oversell policy |
specs/01-DATABASE-SCHEMA.md:308:| type | TEXT | No | 'manual' | CHECK IN ('manual', 'automated') | Collection type: manual curation or rule-based automation |
specs/01-DATABASE-SCHEMA.md:309:| status | TEXT | No | 'active' | CHECK IN ('draft', 'active', 'archived') | |
specs/01-DATABASE-SCHEMA.md:348:| type | TEXT | No | 'image' | CHECK IN ('image', 'video') | Media type |
specs/01-DATABASE-SCHEMA.md:356:| status | TEXT | No | 'processing' | CHECK IN ('processing', 'ready', 'failed') | Processing status |
specs/01-DATABASE-SCHEMA.md:379:| status | TEXT | No | 'draft' | CHECK IN ('draft', 'published') | |
specs/01-DATABASE-SCHEMA.md:434:| status | TEXT | No | 'draft' | CHECK IN ('draft', 'published', 'archived') | |
specs/01-DATABASE-SCHEMA.md:475:| type | TEXT | No | 'link' | CHECK IN ('link', 'page', 'collection', 'product') | Item type |
specs/01-DATABASE-SCHEMA.md:539:| status | TEXT | No | 'active' | CHECK IN ('active', 'converted', 'abandoned') | |
specs/01-DATABASE-SCHEMA.md:587:| status | TEXT | No | 'started' | CHECK IN ('started', 'addressed', 'shipping_selected', 'payment_selected', 'completed', 'expired') | |
specs/01-DATABASE-SCHEMA.md:588:| payment_method | TEXT | Yes | NULL | CHECK IN ('credit_card', 'paypal', 'bank_transfer') | Selected payment method |
specs/01-DATABASE-SCHEMA.md:641:| type | TEXT | No | 'flat' | CHECK IN ('flat', 'weight', 'price', 'carrier') | Calculation method |
specs/01-DATABASE-SCHEMA.md:665:| mode | TEXT | No | 'manual' | CHECK IN ('manual', 'provider') | Tax calculation mode |
specs/01-DATABASE-SCHEMA.md:666:| provider | TEXT | No | 'none' | CHECK IN ('stripe_tax', 'none') | External tax provider |
specs/01-DATABASE-SCHEMA.md:680:| type | TEXT | No | 'code' | CHECK IN ('code', 'automatic') | Activation method |
specs/01-DATABASE-SCHEMA.md:682:| value_type | TEXT | No | - | CHECK IN ('fixed', 'percent', 'free_shipping') | Discount calculation type |
specs/01-DATABASE-SCHEMA.md:689:| status | TEXT | No | 'active' | CHECK IN ('draft', 'active', 'expired', 'disabled') | |
specs/01-DATABASE-SCHEMA.md:761:| payment_method | TEXT | No | - | CHECK IN ('credit_card', 'paypal', 'bank_transfer') | Payment method used for this order |
specs/01-DATABASE-SCHEMA.md:762:| status | TEXT | No | 'pending' | CHECK IN ('pending', 'paid', 'fulfilled', 'cancelled', 'refunded') | Overall order status |
specs/01-DATABASE-SCHEMA.md:763:| financial_status | TEXT | No | 'pending' | CHECK IN ('pending', 'authorized', 'paid', 'partially_refunded', 'refunded', 'voided') | Payment status |
specs/01-DATABASE-SCHEMA.md:764:| fulfillment_status | TEXT | No | 'unfulfilled' | CHECK IN ('unfulfilled', 'partial', 'fulfilled') | Shipping status |
specs/01-DATABASE-SCHEMA.md:826:| provider | TEXT | No | 'mock' | CHECK IN ('mock') | Payment provider (mock PSP only) |
specs/01-DATABASE-SCHEMA.md:827:| method | TEXT | No | - | CHECK IN ('credit_card', 'paypal', 'bank_transfer') | Payment method used |
specs/01-DATABASE-SCHEMA.md:829:| status | TEXT | No | 'pending' | CHECK IN ('pending', 'captured', 'failed', 'refunded') | |
specs/01-DATABASE-SCHEMA.md:857:| status | TEXT | No | 'pending' | CHECK IN ('pending', 'processed', 'failed') | |
specs/01-DATABASE-SCHEMA.md:877:| status | TEXT | No | 'pending' | CHECK IN ('pending', 'shipped', 'delivered') | |
specs/01-DATABASE-SCHEMA.md:979:| status | TEXT | No | 'active' | CHECK IN ('active', 'disabled') | |
specs/01-DATABASE-SCHEMA.md:998:| status | TEXT | No | 'active' | CHECK IN ('active', 'suspended', 'uninstalled') | |
specs/01-DATABASE-SCHEMA.md:1011:OAuth2 client credentials for apps.
specs/01-DATABASE-SCHEMA.md:1017:| client_id | TEXT | No | - | UNIQUE per table | OAuth2 client identifier |
specs/01-DATABASE-SCHEMA.md:1023:- `idx_oauth_clients_client_id` UNIQUE on (`client_id`) - OAuth2 client lookup
specs/01-DATABASE-SCHEMA.md:1060:| status | TEXT | No | 'active' | CHECK IN ('active', 'paused', 'disabled') | |
specs/01-DATABASE-SCHEMA.md:1080:| status | TEXT | No | 'pending' | CHECK IN ('pending', 'success', 'failed') | |
specs/01-DATABASE-SCHEMA.md:1657:2. **Unique constraints on business keys** - Handles, codes, email+store, and external identifiers (SKUs, barcodes, hostnames, OAuth client IDs) have unique indexes to enforce data integrity at the database level.
tests/Feature/Auth/CustomerPasswordResetTest.php:95:        ->set('email', 'missing@example.test')
tests/Feature/Catalog/VariantMatrixServiceTest.php:13:test('variant matrix rebuild creates missing cartesian combinations and inventory', function () {
app/Http/Controllers/Api/Apps/V1/DeferredEndpointController.php:13:            'message' => 'App API endpoints are deferred for initial implementation.',
app/Http/Controllers/Api/Apps/V1/DeferredEndpointController.php:14:        ], 501);
tests/Feature/Api/AdminCatalogApiTest.php:161:test('deferred oauth and app ecosystem routes return not implemented', function (): void {
tests/Feature/Api/AdminCatalogApiTest.php:166:        ->assertStatus(501)
tests/Feature/Api/AdminCatalogApiTest.php:167:        ->assertJsonPath('message', 'OAuth app ecosystem endpoints are deferred for initial implementation.');
tests/Feature/Api/AdminCatalogApiTest.php:176:        ->assertStatus(501)
tests/Feature/Api/AdminCatalogApiTest.php:177:        ->assertJsonPath('message', 'OAuth app ecosystem endpoints are deferred for initial implementation.');
tests/Feature/Api/AdminCatalogApiTest.php:180:        ->assertStatus(501)
tests/Feature/Api/AdminCatalogApiTest.php:181:        ->assertJsonPath('message', 'App API endpoints are deferred for initial implementation.');
app/Http/Controllers/Api/Admin/V1/ThemeController.php:85:        $missing = array_values(array_diff(ThemeArchiveInstaller::requiredPaths(), $paths));
app/Http/Controllers/Api/Admin/V1/ThemeController.php:87:        if ($missing !== []) {
app/Http/Controllers/Api/Admin/V1/ThemeController.php:89:                'theme' => __('The theme is missing required file: :path', ['path' => $missing[0]]),
specs/06-AUTH-AND-SECURITY.md:766:| OauthClient | `client_secret_encrypted` | `encrypted` | Third-party OAuth client secrets |
app/Jobs/ProcessMediaUpload.php:78:            throw new RuntimeException('Media file is missing from public storage.');
specs/02-API-ROUTES.md:451:    else Bank Transfer (deferred)
specs/02-API-ROUTES.md:657:- `422` - Validation errors (missing required fields, invalid country code, address not in any shipping zone)
specs/02-API-ROUTES.md:909:**Success response (Bank Transfer - deferred payment):** `200 OK`
specs/02-API-ROUTES.md:937:- `422` - Validation errors (missing card fields, invalid payment method)
specs/02-API-ROUTES.md:1012:- `401` - Invalid or missing token
specs/02-API-ROUTES.md:1543:- `422` - Validation errors (duplicate SKU, duplicate handle, missing required fields)
specs/02-API-ROUTES.md:2052:    "id": 501,
specs/02-API-ROUTES.md:2531:- `422` - Invalid theme archive (missing required templates, invalid manifest)
specs/02-API-ROUTES.md:2899:> **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).
specs/02-API-ROUTES.md:2903:## 5. OAuth Endpoints (App Ecosystem)
specs/02-API-ROUTES.md:2905:> **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.
specs/02-API-ROUTES.md:2907:These endpoints implement OAuth2 authorization for third-party apps, powered by Laravel Passport.
specs/02-API-ROUTES.md:2921:| `client_id` | string | Yes | The app's OAuth client ID |
specs/02-API-ROUTES.md:2979:Third-party apps use OAuth tokens to access store data via scoped endpoints.
specs/02-API-ROUTES.md:3054:- **Web (admin):** Reads `store_id` from session. If missing, redirects to store selector (for platform-level users with access to multiple stores).
specs/02-API-ROUTES.md:3145:    payment_selected --> completed : POST /checkouts/{id}/pay (bank_transfer: deferred, order created as pending)
specs/02-API-ROUTES.md:3161:| `payment_selected` | `completed` | POST /checkouts/{id}/pay - Mock PSP processes payment in-process (instant for card/paypal, deferred for bank_transfer) |
app/Services/ThemeArchiveInstaller.php:236:        $missing = array_values(array_diff(self::REQUIRED_PATHS, array_keys($files)));
app/Services/ThemeArchiveInstaller.php:238:        if ($missing !== []) {
app/Services/ThemeArchiveInstaller.php:240:                'file' => __('The theme archive is missing required file: :path', ['path' => $missing[0]]),
app/Http/Controllers/Api/OAuthController.php:8:class OAuthController extends Controller
app/Http/Controllers/Api/OAuthController.php:23:            'message' => 'OAuth app ecosystem endpoints are deferred for initial implementation.',
app/Http/Controllers/Api/OAuthController.php:24:        ], 501);
app/Services/CartService.php:261:            throw InvalidCartOperationException::because('Variant inventory is missing.');
specs/progress.md:30:| Routes/API | `specs/02-API-ROUTES.md` | partial | Phase 1 auth routes plus catalog/cart/order/customer/discount/content/settings/search/analytics/apps/developers UI routes exist: `/`, `/collections`, `/collections/{handle}`, `/products/{handle}`, `/cart`, `/checkout`, `/checkout/confirmation/{order}`, `/search`, `/pages/{handle}`, `/account/login`, `/account/register`, `/account/forgot-password`, `/account/reset-password/{token}`, `/account`, `/account/orders/{order}`, `/admin/products`, `/admin/products/create`, `/admin/products/{product}/edit`, `/admin/collections`, `/admin/collections/create`, `/admin/collections/{collection}/edit`, `/admin/inventory`, `/admin/orders`, `/admin/orders/{order}`, `/admin/customers`, `/admin/customers/{customer}`, `/admin/discounts`, `/admin/discounts/create`, `/admin/discounts/{discount}/edit`, `/admin/pages`, `/admin/pages/create`, `/admin/pages/{page}/edit`, `/admin/navigation`, `/admin/themes`, `/admin/themes/{theme}/editor`, `/admin/settings`, `/admin/settings/shipping`, `/admin/settings/taxes`, `/admin/settings/checkout`, `/admin/settings/notifications`, `/admin/search/settings`, `/admin/analytics`, `/admin/apps`, `/admin/apps/{installation}`, and `/admin/developers`. Storefront REST API now includes cart line CRUD, checkout address/shipping/discount/payment-method selection, checkout payment completion, token-gated order lookup, product search, search suggestions, and analytics event ingestion under `/api/storefront/v1`. Admin REST API now includes store-scoped product/customer/order list/detail, collection CRUD, discount CRUD, content page CRUD, store settings read/update, search status/reindex endpoints, analytics summary, order export creation/status, shipping zone/rate settings, tax settings read/update, theme upload/publish/settings update, fulfillment creation, and refund creation under `/api/admin/v1/stores/{store}` with session or scoped bearer-token authentication. `/oauth/authorize`, `/oauth/token`, and `/api/apps/v1/stores/{store}` app routes are registered as explicit `501 Not Implemented` stubs per the deferred OAuth/app ecosystem scope note. |
specs/progress.md:35:| Seed/test data | `specs/07-SEEDERS-AND-TEST-DATA.md` | partial | Seeders create Acme Fashion and Acme Electronics stores/domains/settings/admin access with checkout and notification defaults, 2 apps, 4 app installations, 4 OAuth clients, 4 OAuth tokens, 8 webhook subscriptions, 2 search settings rows, 25 indexed products, 60 analytics_daily rows, 210 analytics_events rows, 6 collections, 127 variants, 127 inventory rows, 206 variant option pivots, 2 themes, 6 theme files with local disk contents and metadata, 2 theme settings rows, 6 pages, 4 navigation menus, 21 navigation items including 6 child items, 2 tax settings rows, 2 shipping zones, 6 shipping rates, 6 discounts, and no product media/runtime carts. Order/payment seed data is still missing. |
specs/progress.md:36:| Browser E2E plan | `specs/08-PLAYWRIGHT-E2E-PLAN.md` | partial | Manual Playwright MCP smoke coverage has verified storefront catalog browsing on `shop.test` and `acme-fashion.test`, product detail, add-to-cart, cart drawer/page, checkout through order completion, order confirmation, customer order list/detail, SQLite-backed search and search API JSON, analytics event ingestion API, DB-backed content pages, seeded nested navigation dropdowns, admin dashboard on desktop/mobile, admin analytics, admin app list/detail, admin developers token/webhook flows, admin product/collection/inventory/order/customer/discount/content/navigation/settings/theme/search pages, admin checkout/notification settings pages, admin product media section rendering, admin analytics CSV export, admin customer address creation, admin discount creation/editing/filtering, admin page creation, admin nested navigation rendering/item creation/save, admin theme editor preview, admin theme file list/editor, admin search reindexing, admin fulfillment creation/shipping/delivery, and auth flows without console warnings/errors on successful pages. Automated Pest browser smoke coverage now checks storefront core pages, authenticated admin core pages, and mobile storefront rendering for JavaScript errors. Full Spec 08 browser suites are still incomplete. |
specs/progress.md:45:- 2026-05-03: Planning challenger sub-agent found the repo was still a starter kit and produced the acceptance checklist by vertical slice. Risks added here: missing Sanctum dependency, password_hash/Fortify coordination, route/API absence, and E2E count inconsistencies.
specs/progress.md:46:- 2026-05-03: `php artisan migrate:fresh --seed --no-interaction` passed on real `database/database.sqlite` after creating the missing SQLite file.
specs/progress.md:195:- 2026-05-04: `mcp__laravel_boost__.application_info` and schema summary reconfirmed Laravel 12.51.0/PHP 8.4/Livewire 4.1.4/Flux 2.12.0/Pest 4.3.2/SQLite and showed search/analytics/app tables were still missing before the search slice.
specs/progress.md:237:- 2026-05-04: `php artisan route:list | rg "account/(forgot|reset)| forgot-password | reset-password"` confirmed customer reset routes under `/account/*` and existing Fortify reset routes at the root paths remain registered separately.
specs/progress.md:322:- 2026-05-04: `php artisan make:controller Api/Admin/V1/ProductController --no-interaction`, `php artisan make:controller Api/Admin/V1/CustomerController --no-interaction`, `php artisan make:resource Admin/V1/ProductResource --no-interaction`, `php artisan make:resource Admin/V1/CustomerResource --no-interaction`, `php artisan make:controller Api/OAuthController --no-interaction`, `php artisan make:controller Api/Apps/V1/DeferredEndpointController --no-interaction`, and `php artisan make:test Api/AdminCatalogApiTest --pest --no-interaction` created the admin catalog API controllers/resources, deferred app/OAuth controllers, and tests.
specs/progress.md:325:- 2026-05-04: `php artisan route:list --path=oauth --except-vendor` confirmed `/oauth/authorize` and `/oauth/token`; `php artisan route:list --path=api/apps/v1 --except-vendor` confirmed 3 deferred app API routes.
specs/progress.md:464:- OAuth/Passport app authorization remains deferred per the roadmap; `/oauth/*` and `/api/apps/v1/*` now return explicit `501 Not Implemented` responses, while the developer token UI uses the existing app installation and `oauth_tokens` schema as a self-contained local token store.
specs/progress.md:474:## Open Issues
specs/progress.md:476:- Customer password reset is implemented under `/account/forgot-password` and `/account/reset-password/{token}`; the exact spec root paths remain occupied by the existing Fortify starter/admin reset routes.
specs/progress.md:478:- Full automated browser suites from Spec 08 are still incomplete beyond the initial Pest browser smoke coverage for storefront, admin, and mobile rendering.
specs/progress.md:479:- SQLite enum/check constraints from the schema spec are not yet explicitly enforced as database `CHECK` constraints; enum validation is currently enforced through casts/services/model invariants.
specs/progress.md:483:Not complete. Phase 1 foundation, Phase 2 catalog data/UI surfaces, product media processing/admin upload controls, Phase 3 storefront theme/content/navigation data, the Phase 4 cart/checkout/pricing backend foundation, cart/checkout storefront UI through order completion, cart-page estimates, cart/checkout REST APIs, Phase 5 order/payment backend foundation, Phase 5 refund/fulfillment services, customer order views, admin dashboard/order/customer/discount/content/settings/theme-file/navigation/search/analytics/apps/developers management, storefront search and analytics APIs, product/customer/collection/discount/content page/store settings/search/analytics/shipping/tax/theme/order/export API surfaces, deferred OAuth/app route stubs, initial automated browser smoke coverage, and outbound webhook delivery foundations are implemented, with known auth/token route compatibility, deferred OAuth/app ecosystem routes, full browser-suite, and database CHECK constraint gaps tracked above.
specs/09-IMPLEMENTATION-ROADMAP.md:24:| OAuth (apps) | Passport | Deferred/Stubbed | Simplifies initial build, can add later |
specs/09-IMPLEMENTATION-ROADMAP.md:111:All monetary amounts stored as INTEGER in minor units (cents). All enums stored as TEXT with CHECK constraints. All timestamps as TEXT (ISO-8601) via Laravel.
specs/09-IMPLEMENTATION-ROADMAP.md:315:| `rebuildMatrix(Product $product): void` | Computes cartesian product, creates missing variants, archives orphaned ones |
specs/09-IMPLEMENTATION-ROADMAP.md:668:| `charge(Checkout $checkout, PaymentMethod $method, array $details): PaymentResult` | Processes payment in-process. For credit_card: validates magic card numbers, returns success/decline. For paypal: always succeeds. For bank_transfer: returns pending (deferred capture). |
specs/09-IMPLEMENTATION-ROADMAP.md:675:- Bank transfer: deferred capture, returns PaymentResult with status=pending
specs/09-IMPLEMENTATION-ROADMAP.md:1082:| calculates a weight-based rate | rate type: weight, config: ranges 0-500g=499, 501-2000g=899; cart total weight: 750g | shipping = 899 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1329:| rejects address transition with missing required fields | set address without city | validation error, status remains started | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1372:| calculates weight-based rate correctly | cart total weight 750g, rate range 501-2000g = 899 | shipping = 899 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1497:| validates required address fields | submit with missing address1 | validation error | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1660:| validates required address fields | PUT address with missing city | 422 response | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1732:| `OauthClient` | OAuth client for app |
specs/09-IMPLEMENTATION-ROADMAP.md:1733:| `OauthToken` | OAuth token for installation |
specs/04-STOREFRONT-UI.md:1107:No additional form fields. The button text changes to "Place order - {total}" (not "Pay now", since payment is deferred).
specs/08-PLAYWRIGHT-E2E-PLAN.md:176:**Purpose:** Hit every major public and admin page, assert they load without JavaScript errors. This is the fastest way to catch broken routes, missing views, or uncaught JS exceptions.
specs/08-PLAYWRIGHT-E2E-PLAN.md:1711:    Step2 -->|missing fields| Step2Error[Validation errors on address fields]
specs/05-BUSINESS-LOGIC.md:992:    else Bank Transfer (deferred)
specs/07-SEEDERS-AND-TEST-DATA.md:627:| `weightBased` | type = "weight", config_json has ranges array with tiers: 0-500g = 399, 501-2000g = 699, 2001g+ = 1299 |


output:
Chunk ID: fd71db
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 79
Output:
cf3288fe Add admin order export API
b02a92c2 Add admin store settings API
b61e6322 Add admin theme API endpoints
95ad3071 Add admin shipping settings API
a8f0799e Add admin tax settings API
a5135d16 Add admin analytics summary API
99670769 Add admin search index API endpoints
d8fd4ebe Add admin page API endpoints


command:
/bin/zsh
-lc
php artisan route:list --except-vendor

cmd: php artisan route:list --except-vendor

aggregated_output:
  POST      admin/logout ........................................ admin.logout
  GET|HEAD  api/admin/v1/stores/{store}/analytics/summary api.admin.v1.analyt…
  GET|HEAD  api/admin/v1/stores/{store}/collections api.admin.v1.collections.…
  POST      api/admin/v1/stores/{store}/collections api.admin.v1.collections.…
  PUT       api/admin/v1/stores/{store}/collections/{collection} api.admin.v1…
  DELETE    api/admin/v1/stores/{store}/collections/{collection} api.admin.v1…
  GET|HEAD  api/admin/v1/stores/{store}/customers api.admin.v1.customers.inde…
  GET|HEAD  api/admin/v1/stores/{store}/customers/{customer} api.admin.v1.cus…
  GET|HEAD  api/admin/v1/stores/{store}/discounts api.admin.v1.discounts.inde…
  POST      api/admin/v1/stores/{store}/discounts api.admin.v1.discounts.stor…
  PUT       api/admin/v1/stores/{store}/discounts/{discount} api.admin.v1.dis…
  DELETE    api/admin/v1/stores/{store}/discounts/{discount} api.admin.v1.dis…
  POST      api/admin/v1/stores/{store}/exports/orders api.admin.v1.exports.o…
  GET|HEAD  api/admin/v1/stores/{store}/exports/{dataExport} api.admin.v1.exp…
  GET|HEAD  api/admin/v1/stores/{store}/orders api.admin.v1.orders.index › Ap…
  GET|HEAD  api/admin/v1/stores/{store}/orders/{order} api.admin.v1.orders.sh…
  POST      api/admin/v1/stores/{store}/orders/{order}/fulfillments api.admin…
  POST      api/admin/v1/stores/{store}/orders/{order}/refunds api.admin.v1.o…
  GET|HEAD  api/admin/v1/stores/{store}/pages api.admin.v1.pages.index › Api\…
  POST      api/admin/v1/stores/{store}/pages api.admin.v1.pages.store › Api\…
  PUT       api/admin/v1/stores/{store}/pages/{page} api.admin.v1.pages.updat…
  DELETE    api/admin/v1/stores/{store}/pages/{page} api.admin.v1.pages.destr…
  GET|HEAD  api/admin/v1/stores/{store}/products api.admin.v1.products.index …
  GET|HEAD  api/admin/v1/stores/{store}/products/{product} api.admin.v1.produ…
  POST      api/admin/v1/stores/{store}/search/reindex api.admin.v1.search.re…
  GET|HEAD  api/admin/v1/stores/{store}/search/status api.admin.v1.search.sta…
  GET|HEAD  api/admin/v1/stores/{store}/settings api.admin.v1.settings.show  …
  PUT       api/admin/v1/stores/{store}/settings api.admin.v1.settings.update…
  GET|HEAD  api/admin/v1/stores/{store}/shipping/zones api.admin.v1.shipping.…
  POST      api/admin/v1/stores/{store}/shipping/zones api.admin.v1.shipping.…
  PUT       api/admin/v1/stores/{store}/shipping/zones/{shippingZone} api.adm…
  POST      api/admin/v1/stores/{store}/shipping/zones/{shippingZone}/rates a…
  GET|HEAD  api/admin/v1/stores/{store}/tax/settings api.admin.v1.tax.setting…
  PUT       api/admin/v1/stores/{store}/tax/settings api.admin.v1.tax.setting…
  POST      api/admin/v1/stores/{store}/themes api.admin.v1.themes.store › Ap…
  POST      api/admin/v1/stores/{store}/themes/{theme}/publish api.admin.v1.t…
  PUT       api/admin/v1/stores/{store}/themes/{theme}/settings api.admin.v1.…
  GET|HEAD  api/apps/v1/stores/{store}/customers api.apps.v1.customers.index …
  GET|HEAD  api/apps/v1/stores/{store}/orders api.apps.v1.orders.index › Api\…
  GET|HEAD  api/apps/v1/stores/{store}/products api.apps.v1.products.index › …
  POST      api/storefront/v1/analytics/events api.storefront.v1.analytics.ev…
  POST      api/storefront/v1/carts api.storefront.v1.carts.store › Api\Store…
  GET|HEAD  api/storefront/v1/carts/{cart} api.storefront.v1.carts.show › Api…
  POST      api/storefront/v1/carts/{cart}/lines api.storefront.v1.carts.line…
  PUT       api/storefront/v1/carts/{cart}/lines/{cartLine} api.storefront.v1…
  DELETE    api/storefront/v1/carts/{cart}/lines/{cartLine} api.storefront.v1…
  POST      api/storefront/v1/checkouts api.storefront.v1.checkouts.store › A…
  GET|HEAD  api/storefront/v1/checkouts/{checkout} api.storefront.v1.checkout…
  PUT       api/storefront/v1/checkouts/{checkout}/address api.storefront.v1.…
  POST      api/storefront/v1/checkouts/{checkout}/apply-discount api.storefr…
  DELETE    api/storefront/v1/checkouts/{checkout}/discount api.storefront.v1…
  POST      api/storefront/v1/checkouts/{checkout}/pay api.storefront.v1.chec…
  PUT       api/storefront/v1/checkouts/{checkout}/payment-method api.storefr…
  PUT       api/storefront/v1/checkouts/{checkout}/shipping-method api.storef…
  GET|HEAD  api/storefront/v1/orders/{orderNumber} api.storefront.v1.orders.s…
  GET|HEAD  api/storefront/v1/search api.storefront.v1.search.index › Api\Sto…
  GET|HEAD  api/storefront/v1/search/suggest api.storefront.v1.search.suggest…
  ANY       dashboard .... dashboard › Illuminate\Routing › RedirectController
  GET|HEAD  oauth/authorize .. oauth.authorize › Api\OAuthController@authorize
  POST      oauth/token .............. oauth.token › Api\OAuthController@token
  ANY       settings ................. Illuminate\Routing › RedirectController

                                                           Showing [61] routes


cmd: find routes -maxdepth 1 -type f -print -exec sed -n '1,220p' {} \;


cmd: rg -n "forgot-password|reset-password|password.request|password.reset|Fortify|auth.php|require.*auth" routes app resources tests config


cmd: php artisan route:list --path=forgot-password --except-vendor && php artisan route:list --path=reset-password --except-vendor


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

web.php

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

aggregated_output:
<?php

use App\Http\Controllers\Api\OAuthController;
use App\Http\Middleware\EnsureUserEmailIsVerified;
use App\Livewire\Admin\Analytics\Index as AdminAnalyticsIndex;
use App\Livewire\Admin\Apps\Index as AdminAppsIndex;
use App\Livewire\Admin\Apps\Show as AdminAppShow;
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\Developers\Index as AdminDevelopersIndex;
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\Checkout as AdminSettingsCheckout;
use App\Livewire\Admin\Settings\Index as AdminSettingsIndex;
use App\Livewire\Admin\Settings\Notifications as AdminSettingsNotifications;
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\ForgotPassword as CustomerForgotPassword;
use App\Livewire\Storefront\Account\Auth\Login as CustomerLogin;
use App\Livewire\Storefront\Account\Auth\Register as CustomerRegister;
use App\Livewire\Storefront\Account\Auth\ResetPassword as CustomerResetPassword;
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::get('oauth/authorize', [OAuthController::class, 'authorize'])
    ->middleware('auth')
    ->name('oauth.authorize');

Route::post('oauth/token', [OAuthController::class, 'token'])
    ->name('oauth.token');

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('apps', AdminAppsIndex::class)->name('apps.index');
    Route::livewire('apps/{installation}', AdminAppShow::class)->name('apps.show');
    Route::livewire('developers', AdminDevelopersIndex::class)->name('developers.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('settings/checkout', AdminSettingsCheckout::class)->name('settings.checkout');
    Route::livewire('settings/notifications', AdminSettingsNotifications::class)->name('settings.notifications');
    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/forgot-password', CustomerForgotPassword::class)
        ->middleware('guest:customer')
        ->name('account.password.request');

    Route::livewire('account/reset-password/{token}', CustomerResetPassword::class)
        ->middleware('guest:customer')
        ->name('account.password.reset');

    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: find routes -maxdepth 1 -type f -print -exec sed -n '1,220p' {} \;

cmd: find routes -maxdepth 1 -type f -print -exec sed -n '1,220p' '{}' "\\;"

path: routes

aggregated_output:
routes/settings.php
<?php

use App\Livewire\Settings\Appearance;
use App\Livewire\Settings\Password;
use App\Livewire\Settings\Profile;
use App\Livewire\Settings\TwoFactor;
use Illuminate\Support\Facades\Route;
use Laravel\Fortify\Features;

Route::middleware(['auth'])->group(function () {
    Route::redirect('settings', 'settings/profile');

    Route::livewire('settings/profile', Profile::class)->name('profile.edit');
});

Route::middleware(['auth', 'verified'])->group(function () {
    Route::livewire('settings/password', Password::class)->name('user-password.edit');
    Route::livewire('settings/appearance', Appearance::class)->name('appearance.edit');

    Route::livewire('settings/two-factor', TwoFactor::class)
        ->middleware(
            when(
                Features::canManageTwoFactorAuthentication()
                && Features::optionEnabled(Features::twoFactorAuthentication(), 'confirmPassword'),
                ['password.confirm'],
                [],
            ),
        )
        ->name('two-factor.show');
});
routes/console.php
<?php

use App\Jobs\AggregateAnalytics;
use App\Jobs\CancelUnpaidBankTransferOrders;
use App\Jobs\CleanupAbandonedCarts;
use App\Jobs\ExpireAbandonedCheckouts;
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schedule;

Artisan::command('inspire', function () {
    $this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');

Schedule::job(new ExpireAbandonedCheckouts)->everyFifteenMinutes();
Schedule::job(new CleanupAbandonedCarts)->daily();
Schedule::job(new CancelUnpaidBankTransferOrders)->daily();
Schedule::job(new AggregateAnalytics)->dailyAt('01:00')->timezone('UTC');
routes/web.php
<?php

use App\Http\Controllers\Api\OAuthController;
use App\Http\Middleware\EnsureUserEmailIsVerified;
use App\Livewire\Admin\Analytics\Index as AdminAnalyticsIndex;
use App\Livewire\Admin\Apps\Index as AdminAppsIndex;
use App\Livewire\Admin\Apps\Show as AdminAppShow;
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\Developers\Index as AdminDevelopersIndex;
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\Checkout as AdminSettingsCheckout;
use App\Livewire\Admin\Settings\Index as AdminSettingsIndex;
use App\Livewire\Admin\Settings\Notifications as AdminSettingsNotifications;
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\ForgotPassword as CustomerForgotPassword;
use App\Livewire\Storefront\Account\Auth\Login as CustomerLogin;
use App\Livewire\Storefront\Account\Auth\Register as CustomerRegister;
use App\Livewire\Storefront\Account\Auth\ResetPassword as CustomerResetPassword;
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…10770 chars truncated…pi:write-collections')->group(function (): void {
            Route::post('collections', [AdminCollectionController::class, 'store'])->name('collections.store');
            Route::put('collections/{collection}', [AdminCollectionController::class, 'update'])->name('collections.update');
            Route::delete('collections/{collection}', [AdminCollectionController::class, 'destroy'])->name('collections.destroy');
        });

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

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

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

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

        Route::middleware('admin.api:read-settings')->group(function (): void {
            Route::get('search/status', [AdminSearchIndexController::class, 'status'])->name('search.status');
            Route::get('settings', [AdminStoreSettingsController::class, 'show'])->name('settings.show');
            Route::get('shipping/zones', [AdminShippingZoneController::class, 'index'])->name('shipping.zones.index');
            Route::get('tax/settings', [AdminTaxSettingsController::class, 'show'])->name('tax.settings.show');
        });

        Route::middleware('admin.api:write-settings')->group(function (): void {
            Route::post('search/reindex', [AdminSearchIndexController::class, 'reindex'])->name('search.reindex');
            Route::put('settings', [AdminStoreSettingsController::class, 'update'])->name('settings.update');
            Route::post('shipping/zones', [AdminShippingZoneController::class, 'store'])->name('shipping.zones.store');
            Route::put('shipping/zones/{shippingZone}', [AdminShippingZoneController::class, 'update'])->name('shipping.zones.update');
            Route::post('shipping/zones/{shippingZone}/rates', [AdminShippingRateController::class, 'store'])->name('shipping.zones.rates.store');
            Route::put('tax/settings', [AdminTaxSettingsController::class, 'update'])->name('tax.settings.update');
        });

        Route::middleware('admin.api:write-themes')->group(function (): void {
            Route::post('themes', [AdminThemeController::class, 'store'])->name('themes.store');
            Route::post('themes/{theme}/publish', [AdminThemeController::class, 'publish'])->name('themes.publish');
            Route::put('themes/{theme}/settings', [AdminThemeSettingsController::class, 'update'])->name('themes.settings.update');
        });

        Route::middleware('admin.api:read-analytics')->group(function (): void {
            Route::get('analytics/summary', [AdminAnalyticsSummaryController::class, 'show'])->name('analytics.summary');
        });

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

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

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