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:
@@ -0,0 +1,95 @@
|
||||
-- Reference taxonomy: extend the three top-level categories seeded in
|
||||
-- 0003_catalog.sql into the full 6 x 2 x 2 tree the storefront is designed
|
||||
-- around (six pinned sidebar rows, six home floors, two grandchildren per
|
||||
-- child link).
|
||||
--
|
||||
-- Categories are reference data, not demo data: `GET /categories` is the only
|
||||
-- category route (there is no write endpoint), so the seed script cannot create
|
||||
-- them, and shop-admin needs a real tree for its category picker.
|
||||
|
||||
-- New top-level categories (positions 4-6 continue 0003's 1-3).
|
||||
INSERT INTO categories (parent_id, name, slug, position)
|
||||
VALUES
|
||||
(NULL, '{"en": "Computers & Office", "zh": "电脑办公"}', 'computers-office', 4),
|
||||
(NULL, '{"en": "Beauty & Care", "zh": "美妆个护"}', 'beauty-care', 5),
|
||||
(NULL, '{"en": "Grocery & Fresh", "zh": "食品生鲜"}', 'grocery-fresh', 6);
|
||||
|
||||
-- Second level: two children under every top-level category.
|
||||
INSERT INTO categories (parent_id, name, slug, position)
|
||||
VALUES
|
||||
((SELECT id FROM categories WHERE slug = 'electronics'),
|
||||
'{"en": "Phones & Accessories", "zh": "手机与配件"}', 'electronics-phones', 1),
|
||||
((SELECT id FROM categories WHERE slug = 'electronics'),
|
||||
'{"en": "Audio", "zh": "影音娱乐"}', 'electronics-audio', 2),
|
||||
((SELECT id FROM categories WHERE slug = 'fashion'),
|
||||
'{"en": "Menswear", "zh": "男装"}', 'fashion-menswear', 1),
|
||||
((SELECT id FROM categories WHERE slug = 'fashion'),
|
||||
'{"en": "Bags & Luggage", "zh": "箱包"}', 'fashion-bags', 2),
|
||||
((SELECT id FROM categories WHERE slug = 'home-living'),
|
||||
'{"en": "Kitchen", "zh": "厨房电器"}', 'home-kitchen', 1),
|
||||
((SELECT id FROM categories WHERE slug = 'home-living'),
|
||||
'{"en": "Cleaning", "zh": "清洁电器"}', 'home-cleaning', 2),
|
||||
((SELECT id FROM categories WHERE slug = 'computers-office'),
|
||||
'{"en": "Laptops", "zh": "笔记本电脑"}', 'computers-laptops', 1),
|
||||
((SELECT id FROM categories WHERE slug = 'computers-office'),
|
||||
'{"en": "Peripherals", "zh": "外设产品"}', 'computers-peripherals', 2),
|
||||
((SELECT id FROM categories WHERE slug = 'beauty-care'),
|
||||
'{"en": "Skincare", "zh": "面部护肤"}', 'beauty-skincare', 1),
|
||||
((SELECT id FROM categories WHERE slug = 'beauty-care'),
|
||||
'{"en": "Grooming", "zh": "个人护理"}', 'beauty-grooming', 2),
|
||||
((SELECT id FROM categories WHERE slug = 'grocery-fresh'),
|
||||
'{"en": "Snacks", "zh": "休闲零食"}', 'grocery-snacks', 1),
|
||||
((SELECT id FROM categories WHERE slug = 'grocery-fresh'),
|
||||
'{"en": "Fresh Produce", "zh": "生鲜果蔬"}', 'grocery-produce', 2);
|
||||
|
||||
-- Third level: two grandchildren under every second-level category.
|
||||
INSERT INTO categories (parent_id, name, slug, position)
|
||||
VALUES
|
||||
((SELECT id FROM categories WHERE slug = 'electronics-phones'),
|
||||
'{"en": "Flagship Phones", "zh": "旗舰机型"}', 'electronics-flagship', 1),
|
||||
((SELECT id FROM categories WHERE slug = 'electronics-phones'),
|
||||
'{"en": "Budget Phones", "zh": "千元机"}', 'electronics-budget', 2),
|
||||
((SELECT id FROM categories WHERE slug = 'electronics-audio'),
|
||||
'{"en": "Earbuds", "zh": "真无线耳机"}', 'electronics-earbuds', 1),
|
||||
((SELECT id FROM categories WHERE slug = 'electronics-audio'),
|
||||
'{"en": "Speakers", "zh": "蓝牙音箱"}', 'electronics-speakers', 2),
|
||||
((SELECT id FROM categories WHERE slug = 'fashion-menswear'),
|
||||
'{"en": "Jackets", "zh": "夹克外套"}', 'fashion-jackets', 1),
|
||||
((SELECT id FROM categories WHERE slug = 'fashion-menswear'),
|
||||
'{"en": "Shirts", "zh": "衬衫"}', 'fashion-shirts', 2),
|
||||
((SELECT id FROM categories WHERE slug = 'fashion-bags'),
|
||||
'{"en": "Backpacks", "zh": "双肩包"}', 'fashion-backpacks', 1),
|
||||
((SELECT id FROM categories WHERE slug = 'fashion-bags'),
|
||||
'{"en": "Luggage", "zh": "旅行箱"}', 'fashion-luggage', 2),
|
||||
((SELECT id FROM categories WHERE slug = 'home-kitchen'),
|
||||
'{"en": "Cookers", "zh": "电饭煲"}', 'home-cookers', 1),
|
||||
((SELECT id FROM categories WHERE slug = 'home-kitchen'),
|
||||
'{"en": "Blenders", "zh": "破壁机"}', 'home-blenders', 2),
|
||||
((SELECT id FROM categories WHERE slug = 'home-cleaning'),
|
||||
'{"en": "Vacuums", "zh": "吸尘器"}', 'home-vacuums', 1),
|
||||
((SELECT id FROM categories WHERE slug = 'home-cleaning'),
|
||||
'{"en": "Air Purifiers", "zh": "空气净化器"}', 'home-purifiers', 2),
|
||||
((SELECT id FROM categories WHERE slug = 'computers-laptops'),
|
||||
'{"en": "Ultrabooks", "zh": "轻薄本"}', 'computers-ultrabooks', 1),
|
||||
((SELECT id FROM categories WHERE slug = 'computers-laptops'),
|
||||
'{"en": "Gaming Laptops", "zh": "游戏本"}', 'computers-gaming', 2),
|
||||
((SELECT id FROM categories WHERE slug = 'computers-peripherals'),
|
||||
'{"en": "Keyboards", "zh": "键盘"}', 'computers-keyboards', 1),
|
||||
((SELECT id FROM categories WHERE slug = 'computers-peripherals'),
|
||||
'{"en": "Monitors", "zh": "显示器"}', 'computers-monitors', 2),
|
||||
((SELECT id FROM categories WHERE slug = 'beauty-skincare'),
|
||||
'{"en": "Serums", "zh": "精华"}', 'beauty-serums', 1),
|
||||
((SELECT id FROM categories WHERE slug = 'beauty-skincare'),
|
||||
'{"en": "Creams", "zh": "面霜"}', 'beauty-creams', 2),
|
||||
((SELECT id FROM categories WHERE slug = 'beauty-grooming'),
|
||||
'{"en": "Shavers", "zh": "剃须刀"}', 'beauty-shavers', 1),
|
||||
((SELECT id FROM categories WHERE slug = 'beauty-grooming'),
|
||||
'{"en": "Hair Care", "zh": "洗发护发"}', 'beauty-haircare', 2),
|
||||
((SELECT id FROM categories WHERE slug = 'grocery-snacks'),
|
||||
'{"en": "Nuts", "zh": "坚果"}', 'grocery-nuts', 1),
|
||||
((SELECT id FROM categories WHERE slug = 'grocery-snacks'),
|
||||
'{"en": "Chocolate", "zh": "巧克力"}', 'grocery-chocolate', 2),
|
||||
((SELECT id FROM categories WHERE slug = 'grocery-produce'),
|
||||
'{"en": "Fruit", "zh": "水果"}', 'grocery-fruit', 1),
|
||||
((SELECT id FROM categories WHERE slug = 'grocery-produce'),
|
||||
'{"en": "Vegetables", "zh": "蔬菜"}', 'grocery-vegetables', 2);
|
||||
Reference in New Issue
Block a user