feat(catalog): add homaroma attribute definitions JSON schema and API import script

This commit is contained in:
Your Name committed 2026-10-19 09:21:29 +07:00
1 parent 1f7bc0e01c
commit e7fc0e2848
2 files changed
+399

No files matched your search

+324
View File
@@ -0,0 +1,324 @@
[
{
"code": "scent_family",
"label": "กลุ่มโทนกลิ่น",
"description": "การจัดกลุ่มโทนกลิ่นหลักของเครื่องหอม เพื่อให้ค้นหาตามสไตล์กลิ่นที่ชอบได้ง่าย",
"type": "SELECT",
"unit": null,
"options": [
{
"value": "floral",
"label": "Floral (ดอกไม้)",
"label_th": "กลิ่นแนวดอกไม้",
"label_en": "Floral"
},
{
"value": "fresh_herbal",
"label": "Fresh & Herbal (สดชื่น/สมุนไพร)",
"label_th": "กลิ่นสดชื่นและสมุนไพร",
"label_en": "Fresh & Herbal"
},
{
"value": "fruity",
"label": "Fruity (ผลไม้)",
"label_th": "กลิ่นแนวผลไม้",
"label_en": "Fruity"
},
{
"value": "spa_relaxing",
"label": "Spa & Relaxing (สปา/ผ่อนคลาย)",
"label_th": "กลิ่นแนวสปาและผ่อนคลาย",
"label_en": "Spa & Relaxing"
},
{
"value": "sweet_warm",
"label": "Sweet & Warm (หวานละมุน/อบอุ่น)",
"label_th": "กลิ่นแนวหวานละมุนและอบอุ่น",
"label_en": "Sweet & Warm"
},
{
"value": "aquatic_ozone",
"label": "Aquatic & Ozone (อากาศ/ทะเล)",
"label_th": "กลิ่นแนวปลอดโปร่งและทะเล",
"label_en": "Aquatic & Ozone"
}
],
"isRequired": false,
"isFilterable": true,
"sortOrder": 1,
"categoryId": null,
"translations": {
"th": {
"label": "กลุ่มโทนกลิ่น",
"description": "การจัดกลุ่มโทนกลิ่นหลักของเครื่องหอม"
},
"en": {
"label": "Fragrance Family",
"description": "Primary olfactory classification for fragrances"
}
}
},
{
"code": "mood_benefit",
"label": "อารมณ์และคุณประโยชน์",
"description": "ความรู้สึกหรือบรรยากาศที่ได้รับจากกลิ่นหอมนี้",
"type": "SELECT",
"unit": null,
"options": [
{
"value": "relax_sleep",
"label": "Relax & Sleep (ผ่อนคลาย หลับสบาย)",
"label_th": "ผ่อนคลาย หลับสบาย",
"label_en": "Relax & Sleep"
},
{
"value": "refresh_energize",
"label": "Refresh & Energize (สดชื่น เติมพลัง)",
"label_th": "สดชื่น เติมพลัง",
"label_en": "Refresh & Energize"
},
{
"value": "focus_mind",
"label": "Focus & Mind (สร้างสมาธิ กระตุ้นไอเดีย)",
"label_th": "สร้างสมาธิ กระตุ้นไอเดีย",
"label_en": "Focus & Mind"
},
{
"value": "odor_eliminator",
"label": "Odor Eliminator (ดับกลิ่นอับ ปรับอากาศ)",
"label_th": "ดับกลิ่นอับ ปรับอากาศ",
"label_en": "Odor Eliminator"
},
{
"value": "romantic_cozy",
"label": "Romantic & Cozy (อบอุ่น โรแมนติก)",
"label_th": "อบอุ่น โรแมนติก",
"label_en": "Romantic & Cozy"
}
],
"isRequired": false,
"isFilterable": true,
"sortOrder": 2,
"categoryId": null,
"translations": {
"th": {
"label": "อารมณ์และคุณประโยชน์",
"description": "บรรยากาศหรือความรู้สึกที่ได้จากกลิ่น"
},
"en": {
"label": "Mood & Benefit",
"description": "Atmosphere and emotional benefits"
}
}
},
{
"code": "scent_intensity",
"label": "ระดับความเข้มข้นของกลิ่น",
"description": "ระดับการกระจายตัวและความชัดของกลิ่นหอม",
"type": "SELECT",
"unit": null,
"options": [
{
"value": "light",
"label": "Light / Subtle (หอมละมุน บางเบา)",
"label_th": "หอมละมุน บางเบา",
"label_en": "Light / Subtle"
},
{
"value": "medium",
"label": "Medium (หอมปานกลาง กำลังพอดี)",
"label_th": "หอมปานกลาง กำลังพอดี",
"label_en": "Medium"
},
{
"value": "strong",
"label": "Strong / Rich (หอมชัดเจน เข้มข้น)",
"label_th": "หอมชัดเจน เข้มข้น",
"label_en": "Strong / Rich"
}
],
"isRequired": false,
"isFilterable": true,
"sortOrder": 3,
"categoryId": null,
"translations": {
"th": {
"label": "ระดับความเข้มข้นของกลิ่น",
"description": "ระดับการกระจายตัวและความชัดของกลิ่น"
},
"en": {
"label": "Scent Intensity",
"description": "Diffusion strength and fragrance throw"
}
}
},
{
"code": "room_placement",
"label": "พื้นที่ใช้งานที่แนะนำ",
"description": "ห้องหรือบริเวณที่เหมาะสมที่สุดสำหรับการกระจายกลิ่น",
"type": "SELECT",
"unit": null,
"options": [
{
"value": "bedroom",
"label": "ห้องนอน (Bedroom)",
"label_th": "ห้องนอน",
"label_en": "Bedroom"
},
{
"value": "living_room",
"label": "ห้องนั่งเล่น / โถงกลาง (Living Room)",
"label_th": "ห้องนั่งเล่น / โถงกลาง",
"label_en": "Living Room"
},
{
"value": "workspace",
"label": "ห้องทำงาน / โต๊ะทำงาน (Workspace)",
"label_th": "ห้องทำงาน / โต๊ะทำงาน",
"label_en": "Workspace"
},
{
"value": "bathroom",
"label": "ห้องน้ำ (Bathroom)",
"label_th": "ห้องน้ำ",
"label_en": "Bathroom"
},
{
"value": "car",
"label": "ห้องโดยสารรถยนต์ (Car Interior)",
"label_th": "ห้องโดยสารรถยนต์",
"label_en": "Car Interior"
},
{
"value": "commercial_spa",
"label": "ร้านสปา / โรงแรม / คาเฟ่ (Commercial / Spa)",
"label_th": "ร้านสปา / โรงแรม / คาเฟ่",
"label_en": "Commercial / Spa"
}
],
"isRequired": false,
"isFilterable": true,
"sortOrder": 4,
"categoryId": null,
"translations": {
"th": {
"label": "พื้นที่ใช้งานที่แนะนำ",
"description": "ห้องหรือบริเวณที่เหมาะสมสำหรับการกระจายกลิ่น"
},
"en": {
"label": "Recommended Space",
"description": "Ideal placement areas for optimal aroma diffusion"
}
}
},
{
"code": "compatible_device",
"label": "อุปกรณ์ที่รองรับ",
"description": "ประเภทเครื่องพ่นหรืออุปกรณ์กระจายกลิ่นที่สามารถใช้ร่วมกันได้",
"type": "TEXT",
"unit": null,
"options": null,
"isRequired": false,
"isFilterable": false,
"sortOrder": 5,
"categoryId": null,
"translations": {
"th": {
"label": "อุปกรณ์ที่รองรับ",
"description": "อุปกรณ์หรือเครื่องพ่นที่สามารถใช้ร่วมกันได้"
},
"en": {
"label": "Compatible Devices",
"description": "Supported diffusers and diffusion equipment"
}
}
},
{
"code": "coverage_area",
"label": "ขนาดพื้นที่ครอบคลุม",
"description": "ขนาดพื้นที่ห้องที่กลิ่นสามารถกระจายตัวได้อย่างมีประสิทธิภาพ",
"type": "TEXT",
"unit": "ตร.ม.",
"options": null,
"isRequired": false,
"isFilterable": false,
"sortOrder": 6,
"categoryId": null,
"translations": {
"th": {
"label": "ขนาดพื้นที่ครอบคลุม",
"description": "ขนาดพื้นที่ห้องที่กลิ่นกระจายตัวได้ดี"
},
"en": {
"label": "Coverage Area",
"description": "Effective room coverage area in square meters"
}
}
},
{
"code": "scent_duration",
"label": "ระยะเวลาการใช้งาน",
"description": "อายุการใช้งานหรือระยะเวลาที่กลิ่นหอมกระจายตัวต่อเนื่อง",
"type": "TEXT",
"unit": null,
"options": null,
"isRequired": false,
"isFilterable": false,
"sortOrder": 7,
"categoryId": null,
"translations": {
"th": {
"label": "ระยะเวลาการใช้งาน",
"description": "ระยะเวลาที่กลิ่นกระจายตัวต่อเนื่อง"
},
"en": {
"label": "Estimated Duration",
"description": "Approximate lifespan and continuous scent duration"
}
}
},
{
"code": "formula_base",
"label": "ประเภทเบสและส่วนผสม",
"description": "ชนิดของเบสน้ำมันหรือส่วนประกอบในการกระจายกลิ่น",
"type": "TEXT",
"unit": null,
"options": null,
"isRequired": false,
"isFilterable": false,
"sortOrder": 8,
"categoryId": null,
"translations": {
"th": {
"label": "ประเภทเบสและส่วนผสม",
"description": "ชนิดของเบสที่ใช้ในการผสมและกระจายกลิ่น"
},
"en": {
"label": "Formula Base",
"description": "Base formula and carrier type"
}
}
},
{
"code": "usage_instructions",
"label": "วิธีใช้งานและคำแนะนำ",
"description": "ขั้นตอนการใช้งานที่ถูกต้องเพื่อประสิทธิภาพสูงสุดในการกระจายกลิ่น",
"type": "TEXT",
"unit": null,
"options": null,
"isRequired": false,
"isFilterable": false,
"sortOrder": 9,
"categoryId": null,
"translations": {
"th": {
"label": "วิธีใช้งานและคำแนะนำ",
"description": "ขั้นตอนการใช้งานเพื่อประสิทธิภาพสูงสุด"
},
"en": {
"label": "Usage Instructions",
"description": "Recommended steps for best diffusion experience"
}
}
}
]
@@ -0,0 +1,75 @@
import * as fs from "fs";
import * as path from "path";
const API_BASE = 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";
async function main() {
console.log(`=== Importing Attribute Definitions via API (${API_BASE}) ===`);
// 1. Read JSON definitions
const jsonPath = path.resolve(__dirname, "../data/homaroma_attribute_definitions.json");
if (!fs.existsSync(jsonPath)) {
throw new Error(`File not found: ${jsonPath}`);
}
const definitions = JSON.parse(fs.readFileSync(jsonPath, "utf-8"));
console.log(`✓ Loaded ${definitions.length} definitions from data/homaroma_attribute_definitions.json`);
// 2. Login to get JWT Token
console.log(`2. Logging in as '${ADMIN_USER}' to obtain JWT token...`);
const loginRes = await fetch(`${API_BASE}/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username: ADMIN_USER, password: ADMIN_PASS }),
});
if (!loginRes.ok) {
const errorText = await loginRes.text();
throw new Error(`Login failed (${loginRes.status}): ${errorText}`);
}
const loginData = (await loginRes.json()) as { success: boolean; token: string };
if (!loginData.token) {
throw new Error("No token returned from login API");
}
const token = loginData.token;
console.log("✓ Successfully authenticated. JWT token acquired.");
// 3. Post bulk-import
console.log("3. Sending bulk-import payload to /product-attributes/definitions/bulk-import...");
const importRes = await fetch(`${API_BASE}/product-attributes/definitions/bulk-import`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(definitions),
});
if (!importRes.ok) {
const errorText = await importRes.text();
throw new Error(`Bulk import failed (${importRes.status}): ${errorText}`);
}
const importResult = await importRes.json();
console.log("✓ Bulk import response:", JSON.stringify(importResult, null, 2));
// 4. Fetch all definitions to verify
console.log("4. Verifying active attribute definitions from API...");
const listRes = await fetch(`${API_BASE}/product-attributes/definitions?lang=th`);
const listData = await listRes.json();
console.log(`✓ Total active attribute definitions on API: ${Array.isArray(listData) ? listData.length : 'N/A'}`);
return importResult;
}
main()
.then(() => {
console.log("\n✅ Completed successfully.");
process.exit(0);
})
.catch((err) => {
console.error("\n❌ Error:", err.message || err);
process.exit(1);
});