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
This commit is contained in:
@@ -149,6 +149,18 @@ pub async fn create_product_with_sku(
|
||||
slug: &str,
|
||||
price_minor: i64,
|
||||
stock: i32,
|
||||
) -> (String, String) {
|
||||
create_product_with_sku_in_category(app, owner_token, slug, price_minor, stock, None).await
|
||||
}
|
||||
|
||||
/// Same, but placed in `category_id` so category filtering can be exercised.
|
||||
pub async fn create_product_with_sku_in_category(
|
||||
app: &TestApp,
|
||||
owner_token: &str,
|
||||
slug: &str,
|
||||
price_minor: i64,
|
||||
stock: i32,
|
||||
category_id: Option<&str>,
|
||||
) -> (String, String) {
|
||||
let slug = format!("{slug}-{}", &uuid::Uuid::new_v4().simple().to_string()[..8]);
|
||||
let res = client()
|
||||
@@ -158,6 +170,7 @@ pub async fn create_product_with_sku(
|
||||
"slug": slug,
|
||||
"name": {"en": format!("Product {slug}"), "zh": format!("商品 {slug}")},
|
||||
"description": {"en": "desc en", "zh": "描述"},
|
||||
"category_id": category_id,
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
@@ -183,6 +196,36 @@ pub async fn create_product_with_sku(
|
||||
(product_id, slug)
|
||||
}
|
||||
|
||||
/// Publish a draft product so it appears in the public catalog.
|
||||
pub async fn publish_product(app: &TestApp, owner_token: &str, product_id: &str) {
|
||||
let res = client()
|
||||
.post(app.url(&format!("/api/shop/products/{product_id}/publish")))
|
||||
.bearer_auth(owner_token)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), 200, "publish: {:?}", res.text().await);
|
||||
}
|
||||
|
||||
/// Look up a seeded reference category id by slug.
|
||||
pub async fn category_id_by_slug(app: &TestApp, slug: &str) -> String {
|
||||
let res = client()
|
||||
.get(app.url("/api/categories"))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), 200);
|
||||
let cats: serde_json::Value = res.json().await.unwrap();
|
||||
cats.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|c| c["slug"] == slug)
|
||||
.unwrap_or_else(|| panic!("seeded category {slug} missing"))["id"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Full sellable fixture: shop + owner + published product with one SKU.
|
||||
/// Returns (owner_token, shop_id, product_id, sku_id).
|
||||
pub async fn setup_sellable(
|
||||
|
||||
Reference in New Issue
Block a user