Files
vmall/docs/TBD-migrate-wave.md
T
james e0e833d0e5 feat(mall): serve catalog and currency from the live API
Wave 1 of replacing the fixed-data mock adapter. The mall now selects its API
adapter per domain, with catalog and currency served live while auth, cart,
orders, shipments and invoices stay on fixed data.

Backend:
- seed the 6 x 2 x 2 category tree as reference data (migration 0005). The API
  exposes no category write route, so this cannot come from the seed script
- filter public product listing by the category subtree with a recursive CTE,
  matching the mock's existing behaviour instead of exact-match
- add sort=price with order=asc|desc, validated by hand so an unsupported value
  returns the project's ApiError 400 shape rather than axum's own rejection

Mall:
- replace the all-or-nothing mockApi boolean with a liveDomains list composed
  through a typed per-domain pick map
- source home floors, the category menu, search and product detail from the
  catalog API; banners, promos, quick links, store card and comment/coupon
  content stay local display-only content
- drop the brand facet and the sales/comments sorts: no backend model backs them
- fix salesOf/commentCountOf, which parsed digits out of the product id and so
  rendered "NaN sold" for live UUID ids; they now hash the id

Seed: 24 products across 4 shops, idempotent on re-run.

Note: the mall defaults to a live catalog, so pnpm dev:mall now expects the API
to be running; set NUXT_PUBLIC_LIVE_DOMAINS to an empty array for all-mock work.

OpenSpec change: openspec/changes/replace-mock-api-wave-1
2026-09-17 15:15:25 +00:00

4.8 KiB

TBD — migrate the mall off the mock API (waves 2+)

Wave 1 is planned in the OpenSpec change openspec/changes/replace-mock-api-wave-1/ (catalog + currency). This file tracks everything after it.

How to use: check a box only once the behaviour is implemented and verified against the live backend (cargo run -p vmall-api, node scripts/seed-demo.mjs). Wave 1 leaves a per-domain switch in apps/mall/plugins/api.ts, so most of these are adding a domain name to the live list — each still needs its own verification below.

Delete this file once every box in Waves 2 and 3 is checked. Wave 4 is optional: if you decide against it, delete this file anyway and record that decision wherever you like.


Wave 2 — auth + cart

  • Flip auth to live and verify login / register / me against :8080 using the seeded customer@vmall.local / customer123.
  • Confirm bad credentials now produce a real 401 — the mock accepted any input (apps/mall/mock/api.ts:125), so this is a deliberate UX change.
  • Confirm the JWT round-trips through the vmall.token localStorage key, shared with shop-admin and admin, and that logout clears it.
  • Flip cart to live. Cart requires a token (AuthUser on all four handlers in apps/api/src/routes/cart.rs) and real SKU ids from Wave 1 — both are prerequisites, not optional.
  • Remove cart's mock display coupling: apps/mall/pages/cart.vue:4,34,41,56,60 uses productById / storeById for shop name, image and stock. The live CartView already returns product_name, image and unit_price_minor (apps/api/src/cart.rs:44-53).
  • Verify: add / qty update / remove / empty cart for a logged-out user, and the out-of-stock 409 path.

Wave 3 — orders + shipments + invoices

  • Flip orders to live and verify checkout splits one cart into per-shop orders with a price snapshot and stock decrement.
  • Checkout still sources shipping_address from MOCK_ADDRESSES (apps/mall/pages/checkout/index.vue:4,23,37,127) — that is intentional; see the out-of-scope note below.
  • Verify cancel restores stock and payOrder only accepts pending_payment (both are enforced live; the mock only mimicked them).
  • Flip shipments to live and verify confirmDelivered moves the order to completed.
  • Flip invoices to live and verify a company invoice requires a tax number and that one order can hold only one active invoice.
  • Fix contract debt so the TS types stop lying: Shipment.items is required in packages/shared/src/types.ts but the live struct has no items field (apps/api/src/models.rs:172-182), and Invoice.invoice_no is nullable live (models.rs:194) but non-null string in TS.
  • Remove the remaining storeById mock usage on the order pages (apps/mall/pages/user/orders/index.vue:4).
  • Confirm the mall still renders when the live API is down (mock mode remains the fallback for local UI work).

Wave 4 — optional new backend capabilities

Only if you want more of the storefront backed by real data. Each is a new capability, not a flip.

  • Storefront content — banners, promos, quick links, home floors and floor advert art. Needs real tables, admin CRUD and i18n JSONB. Do this first if you want the home page fully live; it is the most visible remaining mock surface.
  • Public store read — a buyer-facing shop endpoint so stores/index and stores/[id] leave mock. Small: products already carry shop_id, and the public catalog already joins shops for the active check.
  • Brand model + sales/comments sorts — restores the brand facet and the sorts removed in Wave 1. Needs a brands table (products.brand_id + i18n) plus sales and comments data, neither of which exists today.
  • Extend the ORDER BY whitelist if more sorts are wanted beyond the sort=price added in Wave 1.

Deliberately out of scope — not tracked here, and they do not block deleting this file

These have no API contract and no backend model. Leaving them on ~/mock/data is a decision, not a backlog item.

  • Addresses — never a blocker: Address is embedded in the order and live checkout takes it in the request body, so no addresses table is needed. MOCK_ADDRESSES can stay behind checkout indefinitely.
  • Favorites, coupons, account stats — pure presentation, no transactional impact.
  • seckill / collective / integral marketing pages — display-only mock content.

Decisions already made (do not relitigate)

  • Category filtering is by subtree; the backend exact-match filter was the bug (fixed in Wave 1).
  • A migration wave must not change what the UI claims: facets without a backing model are removed rather than left matching nothing.
  • The mall is the last mock holdout; shop-admin and admin already run live against the same backend.