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: "bedroom" | "living_room" | "workspace" | "bathroom" | "car" | "commercial_spa"; } // 45 Reed Diffuser products mapping const REED_DIFFUSER_MAPPINGS: ScentMapping[] = [ // Spa & Relaxing { productId: 3, scentName: "ตะไคร้", scentFamily: "spa_relaxing", moodBenefit: "odor_eliminator", scentIntensity: "strong", roomPlacement: "bathroom" }, { productId: 63, scentName: "ลีลาวดี", scentFamily: "spa_relaxing", moodBenefit: "relax_sleep", scentIntensity: "medium", roomPlacement: "commercial_spa" }, { productId: 138, scentName: "ดอกบัว", scentFamily: "spa_relaxing", moodBenefit: "focus_mind", scentIntensity: "light", roomPlacement: "commercial_spa" }, { productId: 148, scentName: "ไทยสปา", scentFamily: "spa_relaxing", moodBenefit: "relax_sleep", scentIntensity: "strong", roomPlacement: "commercial_spa" }, // Fresh & Herbal { productId: 8, scentName: "ลาเวนเดอร์", scentFamily: "fresh_herbal", moodBenefit: "relax_sleep", scentIntensity: "medium", roomPlacement: "bedroom" }, { productId: 28, scentName: "ยูคาลิปตัส", scentFamily: "fresh_herbal", moodBenefit: "refresh_energize", scentIntensity: "strong", roomPlacement: "workspace" }, { productId: 33, scentName: "เฟรชลาเวนเดอร์", scentFamily: "fresh_herbal", moodBenefit: "relax_sleep", scentIntensity: "medium", roomPlacement: "bedroom" }, { productId: 38, scentName: "เปเปอร์มิ้น", scentFamily: "fresh_herbal", moodBenefit: "refresh_energize", scentIntensity: "strong", roomPlacement: "workspace" }, { productId: 73, scentName: "มะนาว", scentFamily: "fresh_herbal", moodBenefit: "odor_eliminator", scentIntensity: "strong", roomPlacement: "bathroom" }, { productId: 98, scentName: "โรสเมรี่", scentFamily: "fresh_herbal", moodBenefit: "focus_mind", scentIntensity: "medium", roomPlacement: "workspace" }, { productId: 123, scentName: "มะกรูด", scentFamily: "fresh_herbal", moodBenefit: "odor_eliminator", scentIntensity: "strong", roomPlacement: "bathroom" }, // Floral { productId: 18, scentName: "ซากุระ", scentFamily: "floral", moodBenefit: "romantic_cozy", scentIntensity: "light", roomPlacement: "bedroom" }, { productId: 23, scentName: "มะลิ", scentFamily: "floral", moodBenefit: "relax_sleep", scentIntensity: "strong", roomPlacement: "living_room" }, { productId: 53, scentName: "ดอกแก้ว", scentFamily: "floral", moodBenefit: "relax_sleep", scentIntensity: "strong", roomPlacement: "bedroom" }, { productId: 58, scentName: "ดอกโมก", scentFamily: "floral", moodBenefit: "relax_sleep", scentIntensity: "medium", roomPlacement: "bedroom" }, { productId: 93, scentName: "ดอกลิลลี่", scentFamily: "floral", moodBenefit: "romantic_cozy", scentIntensity: "medium", roomPlacement: "bedroom" }, { productId: 103, scentName: "อัญชัน", scentFamily: "floral", moodBenefit: "relax_sleep", scentIntensity: "light", roomPlacement: "bedroom" }, { productId: 108, scentName: "กุหลาบแดง", scentFamily: "floral", moodBenefit: "romantic_cozy", scentIntensity: "strong", roomPlacement: "bedroom" }, { productId: 133, scentName: "ดอกปีบ", scentFamily: "floral", moodBenefit: "relax_sleep", scentIntensity: "medium", roomPlacement: "bedroom" }, { productId: 163, scentName: "ดอกชมนาด", scentFamily: "floral", moodBenefit: "romantic_cozy", scentIntensity: "strong", roomPlacement: "living_room" }, { productId: 173, scentName: "ดอกการะเวก", scentFamily: "floral", moodBenefit: "relax_sleep", scentIntensity: "strong", roomPlacement: "living_room" }, { productId: 178, scentName: "ดอกจำปา", scentFamily: "floral", moodBenefit: "romantic_cozy", scentIntensity: "strong", roomPlacement: "living_room" }, { productId: 183, scentName: "ดอกพิกุล", scentFamily: "floral", moodBenefit: "relax_sleep", scentIntensity: "medium", roomPlacement: "bedroom" }, { productId: 188, scentName: "ดอกพุด", scentFamily: "floral", moodBenefit: "relax_sleep", scentIntensity: "strong", roomPlacement: "bedroom" }, { productId: 193, scentName: "ดอกราตรี", scentFamily: "floral", moodBenefit: "romantic_cozy", scentIntensity: "strong", roomPlacement: "bedroom" }, { productId: 198, scentName: "ดอกลำดวน", scentFamily: "floral", moodBenefit: "relax_sleep", scentIntensity: "medium", roomPlacement: "bedroom" }, { productId: 213, scentName: "ดอกราชาวดี", scentFamily: "floral", moodBenefit: "relax_sleep", scentIntensity: "medium", roomPlacement: "bedroom" }, // Fruity { productId: 68, scentName: "ส้ม", scentFamily: "fruity", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "living_room" }, { productId: 78, scentName: "แคนตาลูป", scentFamily: "fruity", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "living_room" }, { productId: 83, scentName: "แอปเปิ้ลแดง", scentFamily: "fruity", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "living_room" }, { productId: 88, scentName: "แอปเปิ้ลเขียว", scentFamily: "fruity", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "workspace" }, { productId: 118, scentName: "ผลไม้รวม", scentFamily: "fruity", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "living_room" }, { productId: 153, scentName: "กล้วย", scentFamily: "fruity", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "living_room" }, { productId: 203, scentName: "ทับทิม", scentFamily: "fruity", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "living_room" }, { productId: 208, scentName: "มะละกอสุก", scentFamily: "fruity", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "living_room" }, { productId: 218, scentName: "กลิ่นลูกพีช", scentFamily: "fruity", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "living_room" }, { productId: 223, scentName: "กลิ่นองุ่น", scentFamily: "fruity", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "living_room" }, // Sweet & Warm { productId: 13, scentName: "วานิลลา", scentFamily: "sweet_warm", moodBenefit: "romantic_cozy", scentIntensity: "strong", roomPlacement: "living_room" }, { productId: 113, scentName: "หมากฝรั่ง", scentFamily: "sweet_warm", moodBenefit: "refresh_energize", scentIntensity: "strong", roomPlacement: "living_room" }, { productId: 128, scentName: "แซนด้าวูส", scentFamily: "sweet_warm", moodBenefit: "focus_mind", scentIntensity: "medium", roomPlacement: "workspace" }, { productId: 158, scentName: "กาแฟ", scentFamily: "sweet_warm", moodBenefit: "focus_mind", scentIntensity: "strong", roomPlacement: "workspace" }, { productId: 168, scentName: "กลิ่นน้ำนม", scentFamily: "sweet_warm", moodBenefit: "relax_sleep", scentIntensity: "medium", roomPlacement: "bedroom" }, // Aquatic & Ozone { productId: 43, scentName: "โอเชี่ยน", scentFamily: "aquatic_ozone", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "living_room" }, { productId: 48, scentName: "เซเว่นซี", scentFamily: "aquatic_ozone", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "living_room" }, { productId: 143, scentName: "โอโซน", scentFamily: "aquatic_ozone", moodBenefit: "refresh_energize", scentIntensity: "medium", roomPlacement: "living_room" }, ]; // Standard Reed Diffuser specifications common across all items const REED_DIFFUSER_SPECS = { compatible_device: "ก้านไม้ไฟเบอร์กระจายกลิ่น (Fiber Reeds) / ขวดแก้วตั้งโต๊ะ", coverage_area: "15 - 25", scent_duration: "3 - 4 สัปดาห์ (ขนาด 60ml), 6 - 8 สัปดาห์ (ขนาด 85-120ml)", formula_base: "Alcohol-Free Reed Diffuser Base + Premium Fragrance Oil", usage_instructions: "เปิดจุกขวดแก้ว ปักก้านไม้ 4-5 ก้านลงในขวด กลับด้านก้านไม้ทุก 3-5 วันเพื่อเพิ่มความฟุ้งกระจายของกลิ่น หลีกเลี่ยงการวางกลางแดดหรือลมพัดแรง", }; const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); async function main() { console.log("=== Seeding Product Attributes for Reed Diffuser Products ==="); console.log(`Targeting ${REED_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(); 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/reed_diffuser_attributes_matrix.json"); fs.writeFileSync( exportPath, JSON.stringify( REED_DIFFUSER_MAPPINGS.map((m) => ({ ...m, specs: REED_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 ${REED_DIFFUSER_MAPPINGS.length} Reed Diffuser products via API...`); let successCount = 0; let failCount = 0; for (let i = 0; i < REED_DIFFUSER_MAPPINGS.length; i++) { const item = REED_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: REED_DIFFUSER_SPECS.compatible_device, }, { attributeDefinitionId: codeToDef.get("coverage_area")!.id, valueText: REED_DIFFUSER_SPECS.coverage_area, }, { attributeDefinitionId: codeToDef.get("scent_duration")!.id, valueText: REED_DIFFUSER_SPECS.scent_duration, }, { attributeDefinitionId: codeToDef.get("formula_base")!.id, valueText: REED_DIFFUSER_SPECS.formula_base, }, { attributeDefinitionId: codeToDef.get("usage_instructions")!.id, valueText: REED_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}/${REED_DIFFUSER_MAPPINGS.length}] Product #${item.productId} (${item.scentName}) failed: ${updateRes.status} ${errText}`); failCount++; } else { console.log(`✓ [${i + 1}/${REED_DIFFUSER_MAPPINGS.length}] Product #${item.productId} (${item.scentName}) linked with 9 attributes.`); successCount++; } } catch (err: any) { console.error(`❌ [${i + 1}/${REED_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(); for (const d of localDefs) { localDefMap.set(d.code, d.id); } let localSuccess = 0; for (const item of REED_DIFFUSER_MAPPINGS) { // Find local product by name const expectedName = `${item.scentName} Reed Diffuser`; 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, isText: true }, { code: "mood_benefit", val: item.moodBenefit, isText: true }, { code: "scent_intensity", val: item.scentIntensity, isText: true }, { code: "room_placement", val: item.roomPlacement, isText: true }, { code: "compatible_device", val: REED_DIFFUSER_SPECS.compatible_device, isText: true }, { code: "coverage_area", val: REED_DIFFUSER_SPECS.coverage_area, isText: true }, { code: "scent_duration", val: REED_DIFFUSER_SPECS.scent_duration, isText: true }, { code: "formula_base", val: REED_DIFFUSER_SPECS.formula_base, isText: true }, { code: "usage_instructions", val: REED_DIFFUSER_SPECS.usage_instructions, isText: true }, ]; 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 Reed Diffuser Product Attributes successfully linked!"); process.exit(0); }) .catch((err) => { console.error("❌ Process failed:", err); process.exit(1); });