Files
homaroma/scripts/seed_car_diffuser_attributes.ts
T

296 lines
16 KiB
TypeScript

import * as fs from "fs";
import * as path from "path";
import prisma from "../apps/backend/src/lib/prisma";
const PROD_API = process.env.API_BASE || "https://api.homaroma.shop/api";
const ADMIN_USER = process.env.ADMIN_USER || "admin";
const ADMIN_PASS = process.env.ADMIN_PASS || "homaroma123";
interface ScentMapping {
productId: number;
scentName: string;
scentFamily: "floral" | "fresh_herbal" | "fruity" | "spa_relaxing" | "sweet_warm" | "aquatic_ozone";
moodBenefit: "relax_sleep" | "refresh_energize" | "focus_mind" | "odor_eliminator" | "romantic_cozy";
scentIntensity: "light" | "medium" | "strong";
roomPlacement: "car";
}
// 45 Car Diffuser products mapping
const CAR_DIFFUSER_MAPPINGS: ScentMapping[] = [
// Spa & Relaxing
{ productId: 5, scentName: "ตะไคร้", scentFamily: "spa_relaxing", moodBenefit: "odor_eliminator", scentIntensity: "strong", roomPlacement: "car" },
{ productId: 65, scentName: "ลีลาวดี", scentFamily: "spa_relaxing", moodBenefit: "relax_sleep", scentIntensity: "medium", roomPlacement: "car" },
{ productId: 140, scentName: "ดอกบัว", scentFamily: "spa_relaxing", moodBenefit: "focus_mind", scentIntensity: "light", roomPlacement: "car" },
{ productId: 150, scentName: "ไทยสปา", scentFamily: "spa_relaxing", moodBenefit: "relax_sleep", scentIntensity: "strong", roomPlacement: "car" },
// Fresh & Herbal (Ideal for car freshness, odor blocking & driver alertness)
{ productId: 10, scentName: "ลาเวนเดอร์", scentFamily: "fresh_herbal", moodBenefit: "relax_sleep", scentIntensity: "medium", roomPlacement: "car" },
{ productId: 30, scentName: "ยูคาลิปตัส", scentFamily: "fresh_herbal", moodBenefit: "refresh_energize", scentIntensity: "strong", roomPlacement: "car" },
{ productId: 35, scentName: "เฟรชลาเวนเดอร์", scentFamily: "fresh_herbal", moodBenefit: "relax_sleep", scentIntensity: "medium", roomPlacement: "car" },
{ productId: 40, scentName: "เปเปอร์มิ้น", scentFamily: "fresh_herbal", moodBenefit: "refresh_energize", scentIntensity: "strong", roomPlacement: "car" },
{ productId: 75, scentName: "มะนาว", scentFamily: "fresh_herbal", moodBenefit: "odor_eliminator", scentIntensity: "strong", roomPlacement: "car" },
{ productId: 100, scentName: "โรสเมรี่", scentFamily: "fresh_herbal", moodBenefit: "focus_mind", scentIntensity: "medium", roomPlacement: "car" },
{ productId: 125, scentName: "มะกรูด", scentFamily: "fresh_herbal", moodBenefit: "odor_eliminator", scentIntensity: "strong", roomPlacement: "car" },
// Floral
{ productId: 20, scentName: "ซากุระ", scentFamily: "floral", moodBenefit: "romantic_cozy", scentIntensity: "light", roomPlacement: "car" },
{ productId: 25, scentName: "มะลิ", scentFamily: "floral", moodBenefit: "relax_sleep", scentIntensity: "strong", roomPlacement: "car" },
{ productId: 55, scentName: "ดอกแก้ว", scentFamily: "floral", moodBenefit: "relax_sleep", scentIntensity: "strong", roomPlacement: "car" },
{ productId: 60, scentName: "ดอกโมก", scentFamily: "floral", moodBenefit: "relax_sleep", scentIntensity: "medium", roomPlacement: "car" },
{ productId: 95, scentName: "ดอกลิลลี่", scentFamily: "floral", moodBenefit: "romantic_cozy", scentIntensity: "medium", roomPlacement: "car" },
{ productId: 105, scentName: "อัญชัน", scentFamily: "floral", moodBenefit: "relax_sleep", scentIntensity: "light", roomPlacement: "car" },
{ productId: 110, scentName: "กุหลาบแดง", scentFamily: "floral", moodBenefit: "romantic_cozy", scentIntensity: "strong", roomPlacement: "car" },
{ productId: 135, scentName: "ดอกปีบ", scentFamily: "floral", moodBenefit: "relax_sleep", scentIntensity: "medium", roomPlacement: "car" },
{ productId: 165, scentName: "ดอกชมนาด", scentFamily: "floral", moodBenefit: "romantic_cozy", scentIntensity: "strong", roomPlacement: "car" },
{ productId: 175, scentName: "ดอกการะเวก", scentFamily: "floral", moodBenefit: "relax_sleep", scentIntensity: "strong", roomPlacement: "car" },
{ productId: 180, scentName: "ดอกจำปา", scentFamily: "floral", moodBenefit: "romantic_cozy", scentIntensity: "strong", roomPlacement: "car" },
{ productId: 185, scentName: "ดอกพิกุล", scentFamily: "floral", moodBenefit: "relax_sleep", scentIntensity: "medium", roomPlacement: "car" },
{ productId: 190, scentName: "ดอกพุด", scentFamily: "floral", moodBenefit: "relax_sleep", scentIntensity: "strong", roomPlacement: "car" },
{ productId: 195, scentName: "ดอกราตรี", scentFamily: "floral", moodBenefit: "romantic_cozy", scentIntensity: "strong", roomPlacement: "car" },
{ productId: 200, scentName: "ดอกลำดวน", scentFamily: "floral", moodBenefit: "relax_sleep", scentIntensity: "medium", roomPlacement: "car" },
{ productId: 215, scentName: "ดอกราชาวดี", scentFamily: "floral", moodBenefit: "relax_sleep", scentIntensity: "medium", roomPlacement: "car" },
// Fruity (Refreshing, energizing driving atmosphere)
{ productId: 70, scentName: "ส้ม", scentFamily: "fruity", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "car" },
{ productId: 80, scentName: "แคนตาลูป", scentFamily: "fruity", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "car" },
{ productId: 85, scentName: "แอปเปิ้ลแดง", scentFamily: "fruity", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "car" },
{ productId: 90, scentName: "แอปเปิ้ลเขียว", scentFamily: "fruity", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "car" },
{ productId: 120, scentName: "ผลไม้รวม", scentFamily: "fruity", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "car" },
{ productId: 155, scentName: "กล้วย", scentFamily: "fruity", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "car" },
{ productId: 205, scentName: "ทับทิม", scentFamily: "fruity", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "car" },
{ productId: 210, scentName: "มะละกอสุก", scentFamily: "fruity", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "car" },
{ productId: 220, scentName: "กลิ่นลูกพีช", scentFamily: "fruity", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "car" },
{ productId: 225, scentName: "กลิ่นองุ่น", scentFamily: "fruity", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "car" },
// Sweet & Warm
{ productId: 15, scentName: "วานิลลา", scentFamily: "sweet_warm", moodBenefit: "romantic_cozy", scentIntensity: "strong", roomPlacement: "car" },
{ productId: 115, scentName: "หมากฝรั่ง", scentFamily: "sweet_warm", moodBenefit: "refresh_energize", scentIntensity: "strong", roomPlacement: "car" },
{ productId: 130, scentName: "แซนด้าวูส", scentFamily: "sweet_warm", moodBenefit: "focus_mind", scentIntensity: "medium", roomPlacement: "car" },
{ productId: 160, scentName: "กาแฟ", scentFamily: "sweet_warm", moodBenefit: "focus_mind", scentIntensity: "strong", roomPlacement: "car" },
{ productId: 170, scentName: "กลิ่นน้ำนม", scentFamily: "sweet_warm", moodBenefit: "relax_sleep", scentIntensity: "medium", roomPlacement: "car" },
// Aquatic & Ozone (Clean car interior & ocean breeze)
{ productId: 45, scentName: "โอเชี่ยน", scentFamily: "aquatic_ozone", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "car" },
{ productId: 50, scentName: "เซเว่นซี", scentFamily: "aquatic_ozone", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "car" },
{ productId: 145, scentName: "โอโซน", scentFamily: "aquatic_ozone", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "car" },
];
// Standard Car Diffuser specifications common across all items
const CAR_DIFFUSER_SPECS = {
compatible_device: "คลิปหนีบช่องแอร์ (Car Vent Clip) / เชือกแขวนกระจกมองหลัง (Hanging Diffuser)",
coverage_area: "3 - 8",
scent_duration: "3 - 5 สัปดาห์ (ขึ้นอยู่กับอุณหภูมิห้องโดยสารและความแรงลมแอร์)",
formula_base: "Alcohol-Free DPG Carrier Base + Premium Fragrance Oil (สูตรปลอดภัย ไม่กัดกร่อนคอนโซลและช่องแอร์)",
usage_instructions: "หมุนเปิดฝาไม้ ดึงจุกพลาสติกด้านในออก แล้วหมุนปิดฝาไม้ให้แน่น คว่ำขวดลง 2-3 วินาทีให้น้ำหอมซึมเข้าเนื้อฝาไม้ นำไปแขวนที่กระจกมองหลังหรือติดคลิปช่องแอร์ คว่ำขวดซ้ำสัปดาห์ละ 1 ครั้งเมื่อกลิ่นเริ่มจาง",
};
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
async function main() {
console.log("=== Seeding Product Attributes for Car Diffuser Products ===");
console.log(`Targeting ${CAR_DIFFUSER_MAPPINGS.length} products on API: ${PROD_API}`);
// 1. Authenticate to get JWT token
console.log(`1. Logging in as '${ADMIN_USER}' to obtain JWT token...`);
const loginRes = await fetch(`${PROD_API}/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username: ADMIN_USER, password: ADMIN_PASS }),
});
if (!loginRes.ok) {
throw new Error(`Login failed (${loginRes.status}): ${await loginRes.text()}`);
}
const { token } = (await loginRes.json()) as { token: string };
console.log("✓ Authenticated successfully. JWT token acquired.\n");
// 2. Fetch active attribute definitions from API to map codes to IDs
console.log("2. Fetching active attribute definitions from API...");
const defsRes = await fetch(`${PROD_API}/product-attributes/definitions?lang=th`);
const definitions = (await defsRes.json()) as Array<{ id: number; code: string; type: string }>;
const codeToDef = new Map<string, { id: number; code: string; type: string }>();
for (const def of definitions) {
codeToDef.set(def.code, def);
}
const requiredCodes = [
"scent_family",
"mood_benefit",
"scent_intensity",
"room_placement",
"compatible_device",
"coverage_area",
"scent_duration",
"formula_base",
"usage_instructions",
];
for (const code of requiredCodes) {
if (!codeToDef.has(code)) {
throw new Error(`Attribute definition code '${code}' not found in API!`);
}
}
console.log("✓ All 9 attribute definitions verified on API.\n");
// Save the full matrix as JSON artifact
const exportPath = path.resolve(__dirname, "../data/car_diffuser_attributes_matrix.json");
fs.writeFileSync(
exportPath,
JSON.stringify(
CAR_DIFFUSER_MAPPINGS.map((m) => ({
...m,
specs: CAR_DIFFUSER_SPECS,
})),
null,
2
),
"utf-8"
);
console.log(`✓ Saved mapping matrix to ${exportPath}\n`);
// 3. Process each product via API
console.log(`3. Updating attributes for ${CAR_DIFFUSER_MAPPINGS.length} Car Diffuser products via API...`);
let successCount = 0;
let failCount = 0;
for (let i = 0; i < CAR_DIFFUSER_MAPPINGS.length; i++) {
const item = CAR_DIFFUSER_MAPPINGS[i];
const attributesPayload = [
{
attributeDefinitionId: codeToDef.get("scent_family")!.id,
valueText: item.scentFamily,
},
{
attributeDefinitionId: codeToDef.get("mood_benefit")!.id,
valueText: item.moodBenefit,
},
{
attributeDefinitionId: codeToDef.get("scent_intensity")!.id,
valueText: item.scentIntensity,
},
{
attributeDefinitionId: codeToDef.get("room_placement")!.id,
valueText: item.roomPlacement,
},
{
attributeDefinitionId: codeToDef.get("compatible_device")!.id,
valueText: CAR_DIFFUSER_SPECS.compatible_device,
},
{
attributeDefinitionId: codeToDef.get("coverage_area")!.id,
valueText: CAR_DIFFUSER_SPECS.coverage_area,
},
{
attributeDefinitionId: codeToDef.get("scent_duration")!.id,
valueText: CAR_DIFFUSER_SPECS.scent_duration,
},
{
attributeDefinitionId: codeToDef.get("formula_base")!.id,
valueText: CAR_DIFFUSER_SPECS.formula_base,
},
{
attributeDefinitionId: codeToDef.get("usage_instructions")!.id,
valueText: CAR_DIFFUSER_SPECS.usage_instructions,
},
];
try {
const updateRes = await fetch(`${PROD_API}/products/${item.productId}/attributes`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ attributes: attributesPayload }),
});
if (!updateRes.ok) {
const errText = await updateRes.text();
console.error(`❌ [${i + 1}/${CAR_DIFFUSER_MAPPINGS.length}] Product #${item.productId} (${item.scentName}) failed: ${updateRes.status} ${errText}`);
failCount++;
} else {
console.log(`✓ [${i + 1}/${CAR_DIFFUSER_MAPPINGS.length}] Product #${item.productId} (${item.scentName} Car Diffuser) linked with 9 attributes.`);
successCount++;
}
} catch (err: any) {
console.error(`❌ [${i + 1}/${CAR_DIFFUSER_MAPPINGS.length}] Product #${item.productId} error: ${err.message}`);
failCount++;
}
await sleep(80);
}
console.log(`\n=== API Seeding Completed: ${successCount} succeeded, ${failCount} failed ===\n`);
// 4. Also sync to Local Database if connected
try {
console.log("4. Syncing attributes to Local Database via Prisma...");
const localDefs = await prisma.attributeDefinition.findMany();
const localDefMap = new Map<string, number>();
for (const d of localDefs) {
localDefMap.set(d.code, d.id);
}
let localSuccess = 0;
for (const item of CAR_DIFFUSER_MAPPINGS) {
// Find local product by scent name in Car Diffuser category
const localProd = await prisma.product.findFirst({
where: {
name: { contains: item.scentName },
category: { name: { contains: "รถยนต์" } },
},
});
if (!localProd) continue;
const attrs = [
{ code: "scent_family", val: item.scentFamily },
{ code: "mood_benefit", val: item.moodBenefit },
{ code: "scent_intensity", val: item.scentIntensity },
{ code: "room_placement", val: item.roomPlacement },
{ code: "compatible_device", val: CAR_DIFFUSER_SPECS.compatible_device },
{ code: "coverage_area", val: CAR_DIFFUSER_SPECS.coverage_area },
{ code: "scent_duration", val: CAR_DIFFUSER_SPECS.scent_duration },
{ code: "formula_base", val: CAR_DIFFUSER_SPECS.formula_base },
{ code: "usage_instructions", val: CAR_DIFFUSER_SPECS.usage_instructions },
];
for (const attr of attrs) {
const defId = localDefMap.get(attr.code);
if (!defId) continue;
await prisma.productAttribute.upsert({
where: {
productId_attributeDefinitionId: {
productId: localProd.id,
attributeDefinitionId: defId,
},
},
create: {
productId: localProd.id,
attributeDefinitionId: defId,
valueText: attr.val,
},
update: {
valueText: attr.val,
},
});
}
localSuccess++;
}
console.log(`✓ Synced ${localSuccess} products into Local Database.\n`);
} catch (err: any) {
console.warn(`Local DB sync skipped or encountered an error: ${err.message}`);
}
}
main()
.then(() => {
console.log("✅ All Car Diffuser Product Attributes successfully linked!");
process.exit(0);
})
.catch((err) => {
console.error("❌ Process failed:", err);
process.exit(1);
});