Wave 2 of replacing the fixed-data mock adapter: the auth domain joins the live list, so credentials, roles and tokens belong to the real user. - session: validate a restored token through /auth/me instead of trusting localStorage, clearing it on 401/403 but keeping it when the API is merely unreachable; the route guard now uses the validated session - login: report a 401 as invalid credentials rather than a generic failure, and drop the 6-character client rule so the API owns the password policy - register: raise the rule to the API's 8 characters, remove the verification-code field (its button only counted down and the value was never sent), and report a duplicate email (409) distinctly - TopBar and the user profile render their session-dependent branch client-only: validating the session before hydration made those localStorage-backed branches report hydration mismatches the previous code did not Verified against the running backend: wrong password rejected, real JWT issued, /user reachable, a short password refused with no network call, duplicate email reported, a tampered token cleared and bounced to sign-in, a stale token kept when the API is down, and the fixed-data rollback still signs in with the backend stopped. Also re-cuts docs/TBD-migrate-wave.md: auth is done, and cart, orders, shipments and invoices must move together, because the mock adapter keeps their state in one shared object and a partial flip fails at checkout. OpenSpec change: openspec/changes/replace-mock-api-wave-2
6.3 KiB
TBD — migrate the mall off the mock API (waves 3+)
Waves 1 and 2 are captured in openspec/changes/replace-mock-api-wave-1/ (catalog +
currency) and replace-mock-api-wave-2/ (auth). This file tracks what remains.
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). The per-domain
switch in apps/mall/plugins/api.ts means 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 Wave 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 (done)
Captured in openspec/changes/replace-mock-api-wave-2/; auth is live, with the session
validated through /auth/me rather than trusted from localStorage.
- Flip
authto live and verifylogin/register/meagainst:8080using the seededcustomer@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.tokenlocalStorage key, shared with shop-admin and admin, and that logout clears it.
Wave 3 — the transaction chain: cart + orders + shipments + invoices
These move together, not one at a time. The mock adapter keeps
state.cart -> state.orders -> state.shipments in a single shared state, so a partial flip
leaves the mock half reading state the live half never populates:
-
live cart + mock
checkout()fails withEMPTY_CART(apps/mall/mock/api.ts:194), -
mock
requestInvoice()404s on any live order id (mock/api.ts:285), -
mock
listMyShipments()returns shipments whoseorder_idmatches no live order, so the shipment block is silently empty (mock/api.ts:282,pages/user/orders/[id].vue:25). -
Flip
cart,orders,shipmentsandinvoicesin the same change, then verify one purchase end to end: add to cart, check out into per-shop orders, pay, ship, confirm delivery, request an invoice. -
Verify cancel restores stock and
payOrderonly acceptspending_payment(both enforced live; the mock only mimicked them). -
Verify a company invoice requires a tax number, and that one order can hold only one active invoice.
-
Remove cart's mock display coupling:
apps/mall/pages/cart.vue:4,34,41,56,60usesproductById/storeByIdfor shop name, image and stock. The liveCartViewalready returnsproduct_name,imageandunit_price_minor(apps/api/src/cart.rs:44-53) — but not the shop, so decide whether to add a shop field toCartItemin the shared contract or accept ungrouped lines until the public store read exists. -
Decide what caps cart quantity:
CartItemcarries nostock, somaxForfalls back to 999 (pages/cart.vue:60). The live cart does not enforce stock either — only checkout does (409). Either addstocktoCartItemor keep the cap at checkout and say so. -
Gate add-to-cart for anonymous shoppers: a live
addCartItemon the public product page returns 401, andpages/goods/[id].vueis not behind the auth middleware. -
Checkout keeps sourcing
shipping_addressfromMOCK_ADDRESSES(pages/checkout/index.vue:4,23,37,127) — intentional; see the out-of-scope note below. -
Fix contract debt so the TS types stop lying:
Shipment.itemsis required inpackages/shared/src/types.tsbut the live struct has noitemsfield (apps/api/src/models.rs:172-182), andInvoice.invoice_nois nullable live (models.rs:194) but non-nullstringin TS. -
Remove the remaining
storeByIdmock usage on the order pages (apps/mall/pages/user/orders/index.vue:4). -
Confirm the mall still renders when the live API is down, with every domain configured to fixed data.
Wave 4 — optional, mostly 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 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,stores/[id]and the cart's shop grouping leave mock. Small: products already carryshop_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
brandstable (products.brand_id+ i18n) plus sales and comments data, neither of which exists today. - Extend the
ORDER BYwhitelist if more sorts are wanted beyond thesort=priceadded in Wave 1. - (adjacent, not part of the migration) Move the session token to a cookie so SSR knows whether anyone is signed in. Today a full page load of a guarded route renders the page and then redirects on the client, which logs a hydration mismatch; it is pre-existing (verified identical before Wave 2) and harmless, but it is the real fix for the
ClientOnlyworkarounds incomponents/shell/TopBar.vueandpages/user.vue.
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:
Addressis embedded in the order and live checkout takes it in the request body, so no addresses table is needed.MOCK_ADDRESSEScan 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-adminandadminalready run live against the same backend. - Auth flips independently, but the transaction domains do not: the mock's shared cart/order/shipment state makes any partial flip fail loudly.