Files
vmall/openspec/changes/archive/2026-09-17-replace-mock-api-wave-5/tasks.md
T
james b44ba39e8c chore(openspec): archive replace-mock-api-wave-5
The merge creates the store-directory capability (two requirements, six
scenarios) and updates the mall's store-and-marketing requirement so the store
directory and store home read live shops and order surfaces name their shop.

openspec validate --all --strict stays green at 13 specs.
2026-09-17 17:13:49 +00:00

34 lines
3.9 KiB
Markdown

# Tasks
## 1. Schema
- [x] 1.1 Add a migration creating `shop_profiles` keyed 1:1 to `shops` (`shop_id` primary key, `logo`, `banner`, `company`, `region`, `address` JSONB, `notice` JSONB, `after_sale` JSONB, four score columns, `updated_at`); verified the table exists after `cargo run -p vmall-api`
## 2. Shared contract
- [x] 2.1 Add `ShopProfile` and `ShopProfileInput` to `packages/shared/src/types.ts`, with every profile field nullable; verified all three frontends build
- [x] 2.2 Add `listShops()` / `getShop(slug)`, `admin.setShopProfile(id, body)` and fixed-data implementations built from `MOCK_STORES`; also registered a `shops` domain in the per-domain switch. Unlike wave 4, where that registration was missed and only the browser caught it, here the domain was added alongside the methods
## 3. Public shop read
- [x] 3.1 Add unauthenticated `GET /api/shops` and `GET /api/shops/{slug}`, both tolerating a missing profile row; verified a suspended shop is absent from the list and that a suspended or unknown slug is a 404
- [x] 3.2 Add `PUT /api/admin/shops/{id}/profile` upserting the profile, refusing bilingual fields without non-empty `en`/`zh` and gated to `platform_admin`; verified a rejected payload leaves the stored profile untouched
## 4. Demo profiles
- [x] 4.1 Teach `scripts/seed-demo.mjs` to upsert a profile per demo shop; verified a re-run is idempotent and `GET /api/shops` returns all four with logos, companies and scores
## 5. Mall store surfaces
- [x] 5.1 `pages/stores/index.vue`: list from `GET /api/shops`, dropping the distance column, its sort and the whole two-option sort control that existed only to offer it; verified four shops render with their logos and no "km" text. A shop with no logo renders an initial-letter placeholder rather than an invented image
- [x] 5.2 `pages/stores/[id].vue`: load the shop by slug and its products from the catalog API by `shop_id`; verified the profile, its score rows and six products render. Two removals were needed beyond the plan, both following the wave-1 precedent that a UI must not claim what no model backs: the sort is now default + price only (sales and comments have no model), and the "best sellers" rail is gone because nothing ranks by sales
- [x] 5.3 `pages/goods/[id].vue`: read the store card from the shared shop read instead of `storeById`, guarding every field and dropping the card entirely when a shop has no profile
- [x] 5.4 `pages/checkout/pay.vue` and `pages/user/orders/index.vue`: resolve shop names from the same cached shop read; verified order cards name Terra Grocery, Aurora Digital and Demo Store instead of the placeholder
- [x] 5.5 Moved the pure `lowestSku` helper out of the fixed-data module into `apps/mall/utils/product.ts`, re-exported for the mock-era pages, so the live store and product pages no longer import `~/mock/data` for it. A first attempt used `export { lowestSku } from "~/utils/product"`, which creates no local binding and broke every internal caller at runtime
## 6. Verification
- [x] 6.1 `cargo test -p vmall-api` green at 28 tests (five new shop tests), and all three frontends build. **Caveat worth recording:** `nuxt build` does not typecheck — there is no `typescript.typeCheck` in `nuxt.config.ts` and no `vue-tsc` installed — so a build passing is not the type gate AGENTS.md describes. The `lowestSku is not defined` bug above passed the build and only failed in the browser, which is how it was found
- [x] 6.2 Verified in a browser: the directory lists the four demo shops with real logos and no distance text, a store home shows its profile and six products with default/price sorts, the product page's store card names Aurora Digital and links to its store, and order cards name their shop. Console showed only the header's signed-out cart 401, once per page load
- [x] 6.3 Verified the rollback: with every domain on fixed data and the backend stopped, the directory renders the four mock stores with their logos and a mock store home renders its profile and products