#!/usr/bin/env node /** * Seed demo data into a running vmall-api (dev). * Usage: node scripts/seed-demo.mjs [baseUrl] * Idempotent: existing slugs/emails are reused, products are re-published. */ const base = process.argv[2] ?? "http://localhost:8080/api"; async function call(method, path, { token, body } = {}) { const res = await fetch(base + path, { method, headers: { "content-type": "application/json", ...(token ? { authorization: `Bearer ${token}` } : {}), }, body: body ? JSON.stringify(body) : undefined, }); const text = await res.text(); let data; try { data = JSON.parse(text); } catch { data = text; } return { status: res.status, data }; } const fail = (what, r) => { console.error(`FAILED ${what}: ${r.status}`, r.data); process.exit(1); }; // 1. admin login let r = await call("POST", "/auth/login", { body: { email: "admin@vmall.local", password: "admin1234" }, }); if (r.status !== 200) fail("admin login", r); const admin = r.data.token; // 2. demo shop let shopId; r = await call("POST", "/admin/shops", { token: admin, body: { name: { en: "Demo Store", zh: "演示店铺" }, slug: "demo-store", }, }); if (r.status === 201) shopId = r.data.id; else if (r.status === 409) { const shops = await call("GET", "/admin/shops", { token: admin }); shopId = shops.data.find((s) => s.slug === "demo-store").id; } else fail("create shop", r); // 3. demo shop owner (register may 409 if exists) const ownerEmail = "shop@vmall.local"; await call("POST", "/auth/register", { body: { email: ownerEmail, password: "shop12345", display_name: "Demo Owner" }, }); r = await call("POST", "/auth/login", { body: { email: ownerEmail, password: "shop12345" }, }); if (r.status !== 200) fail("owner login", r); let owner = r.data.token; if (r.data.user.role !== "shop_owner") { r = await call("PUT", `/admin/users/${r.data.user.id}/role`, { token: admin, body: { role: "shop_owner", shop_id: shopId }, }); if (r.status !== 200) fail("assign owner", r); r = await call("POST", "/auth/login", { body: { email: ownerEmail, password: "shop12345" }, }); owner = r.data.token; } // 4. demo customer await call("POST", "/auth/register", { body: { email: "customer@vmall.local", password: "customer123", display_name: "Demo Customer" }, }); // 5. categories const cats = (await call("GET", "/categories")).data; const catBy = (slug) => cats.find((c) => c.slug === slug)?.id ?? null; // 6. products const products = [ { slug: "wireless-headphones", name: { en: "Wireless Headphones", zh: "无线耳机" }, description: { en: "Noise-cancelling over-ear headphones with 40h battery.", zh: "降噪头戴式耳机,40 小时续航。", }, category: "electronics", price: 7999, stock: 25, img: "https://picsum.photos/seed/headphones/600/600", }, { slug: "mechanical-keyboard", name: { en: "Mechanical Keyboard", zh: "机械键盘" }, description: { en: "Hot-swappable 75% keyboard, gasket mount.", zh: "热插拔 75% 配列机械键盘,Gasket 结构。", }, category: "electronics", price: 12900, stock: 40, img: "https://picsum.photos/seed/keyboard/600/600", }, { slug: "linen-shirt", name: { en: "Linen Shirt", zh: "亚麻衬衫" }, description: { en: "Breathable 100% linen shirt.", zh: "透气纯亚麻衬衫。" }, category: "fashion", price: 4599, stock: 60, img: "https://picsum.photos/seed/shirt/600/600", }, { slug: "ceramic-mug", name: { en: "Ceramic Mug", zh: "陶瓷马克杯" }, description: { en: "Hand-glazed 350ml mug.", zh: "手工上釉 350ml 马克杯。" }, category: "home-living", price: 1999, stock: 100, img: "https://picsum.photos/seed/mug/600/600", }, ]; for (const p of products) { r = await call("POST", "/shop/products", { token: owner, body: { slug: p.slug, name: p.name, description: p.description, category_id: catBy(p.category), images: [p.img], }, }); let id; if (r.status === 201) id = r.data.id; else if (r.status === 409) { const list = await call("GET", "/shop/products?per_page=100", { token: owner }); id = list.data.items.find((x) => x.slug === p.slug).id; } else fail(`create product ${p.slug}`, r); r = await call("POST", `/shop/products/${id}/skus`, { token: owner, body: { sku_code: `${p.slug}-std`, price_minor: p.price, currency: "USD", stock: p.stock, }, }); if (r.status !== 200) fail(`sku ${p.slug}`, r); r = await call("POST", `/shop/products/${id}/publish`, { token: owner }); if (r.status !== 200) fail(`publish ${p.slug}`, r); console.log(`product ready: ${p.slug}`); } console.log("\nDemo data seeded."); console.log(" platform admin : admin@vmall.local / admin1234 (apps/admin :3002)"); console.log(" shop owner : shop@vmall.local / shop12345 (apps/shop-admin :3001)"); console.log(" customer : customer@vmall.local / customer123 (apps/mall :3000)");