Files
vmall/scripts/seed-demo.mjs
T

168 lines
5.0 KiB
JavaScript
Executable File

#!/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)");