db: seeder

This commit is contained in:
morteza-mortezai
2025-11-30 21:45:24 +03:30
parent 316a5f096f
commit 1345f81b3a
27 changed files with 1931 additions and 2285 deletions
+178 -531
View File
@@ -1,564 +1,211 @@
import { faker } from "@faker-js/faker";
import { Types } from "mongoose";
faker.locale = "fa";
import { cleanMongoData, extractDumpData, getCollectionData } from "./utils/extractDumpData";
import { Logger } from "../../core/logging/logger";
import { AttributeValueModel } from "../../modules/category/models/attributeValue.model";
import { CategoryModel } from "../../modules/category/models/category.model";
import { CategoryAttributeModel } from "../../modules/category/models/CategoryAttribute.model";
import { ThemeModel } from "../../modules/category/models/theme.model";
import { ThemeValueModel } from "../../modules/category/models/themeValue.model";
interface ICategorySeed {
title_fa: string;
title_en: string;
icon: string;
imageUrl: string;
description: string;
leaf: boolean;
themeType?: "color" | "size" | "meterage" | null;
parent?: Types.ObjectId;
}
/**
* Maps theme string from dump to Persian theme title
*/
const mapThemeStringToTitle = (themeString: string | null | undefined): string | null => {
if (!themeString || themeString === "noColor_noSize") return null;
interface ICategoryAttributeSeed {
title: string;
hint?: string;
type: string;
multiple: boolean;
required: boolean;
}
const themeMap: { [key: string]: string } = {
Color: "رنگ",
Size: "سایز",
Meterage: "متراژ",
};
const rootCategories: ICategorySeed[] = [
{
title_fa: "موبایل و تبلت",
icon: "mobile",
imageUrl: faker.image.imageUrl(),
description: "لوازم جانبی",
title_en: "mobile",
leaf: false,
},
{
title_fa: "لوازم تحریر و اداری",
icon: "stationery",
description: "انواع لوازم تحریر و اداری",
title_en: "stationery-office",
leaf: false,
imageUrl: faker.image.imageUrl(),
},
{
title_fa: "برق و الکتریکی",
icon: "electric",
description: "لوازم برق و الکتریکی",
title_en: "electric-electrical",
leaf: false,
imageUrl: faker.image.imageUrl(),
},
{
title_fa: "لوازم و ابزارآلات",
icon: "tools",
description: "انواع لوازم و ابزارآلات",
title_en: "tools-equipment",
leaf: false,
imageUrl: faker.image.imageUrl(),
},
{
title_fa: "صوت و تصویر",
icon: "audio-visual",
description: "انواع لوازم صوتی و تصویری",
title_en: "audio-visual",
leaf: false,
imageUrl: faker.image.imageUrl(),
},
{
title_fa: "کامپیوتر",
icon: "computer",
description: "انواع کامپیوتر و لوازم جانبی",
title_en: "computer",
leaf: false,
imageUrl: faker.image.imageUrl(),
},
{
title_fa: "کنسول بازی",
icon: "console",
description: "انواع کنسول بازی و لوازم جانبی",
title_en: "game-console",
leaf: false,
imageUrl: faker.image.imageUrl(),
},
{
title_fa: "ورزش و سفر",
icon: "sports",
description: "لوازم ورزشی و سفر",
title_en: "sports-travel",
leaf: false,
imageUrl: faker.image.imageUrl(),
},
{ title_fa: "پوشاک و مد", icon: "fashion", description: "لباس و مد", title_en: "fashion", leaf: false, imageUrl: faker.image.imageUrl() },
{
title_fa: "لوازم خانگی",
icon: "home-appliances",
description: "لوازم خانه",
title_en: "home-appliances",
leaf: false,
imageUrl: faker.image.imageUrl(),
},
{
title_fa: "متفرقه",
icon: "malicious",
description: "متفرقه",
title_en: "malicious",
leaf: true,
imageUrl: faker.image.imageUrl(),
},
];
const subcategories: { [key: string]: ICategorySeed[] } = {
"موبایل و تبلت": [
{
title_fa: "گوشی موبایل",
icon: "phone",
description: "انواع گوشی های موبایل",
title_en: "mobile-phones",
leaf: true,
imageUrl: faker.image.imageUrl(),
themeType: "color",
},
{
title_fa: "تبلت",
icon: "tablet",
description: "انواع تبلت ها",
title_en: "tablets",
leaf: true,
themeType: "color",
imageUrl: faker.image.imageUrl(),
},
{
title_fa: "ساعت هوشمند",
icon: "watch",
description: "انواع ساعت هوشمند",
title_en: "smart-watch",
leaf: true,
themeType: "color",
imageUrl: faker.image.imageUrl(),
},
{
title_fa: "پاور بانک",
icon: "watch",
description: "انواع پاور بانک",
title_en: "power-banks",
leaf: true,
themeType: null,
imageUrl: faker.image.imageUrl(),
},
],
"لوازم تحریر و اداری": [
{
title_fa: "خودکار و خودنویس",
icon: "pen",
description: "انواع خودکار و خودنویس",
title_en: "pens",
leaf: true,
themeType: "color",
imageUrl: faker.image.imageUrl(),
},
{
title_fa: "دفتر و کاغذ",
icon: "paper",
description: "انواع دفتر و کاغذ",
title_en: "paper-notebooks",
leaf: true,
themeType: "color",
imageUrl: faker.image.imageUrl(),
},
],
"برق و الکتریکی": [
{
title_fa: "لامپ",
icon: "bulb",
description: "انواع لامپ ها",
title_en: "bulbs",
leaf: true,
themeType: null,
imageUrl: faker.image.imageUrl(),
},
{
title_fa: "کابل و سیم",
icon: "cable",
description: "انواع کابل و سیم",
title_en: "cables-wires",
leaf: true,
themeType: null,
imageUrl: faker.image.imageUrl(),
},
],
"لوازم و ابزارآلات": [
{
title_fa: "آچار و پیچ گوشتی",
icon: "wrench",
description: "انواع آچار و پیچ گوشتی",
title_en: "wrenches-screwdrivers",
leaf: true,
themeType: null,
imageUrl: faker.image.imageUrl(),
},
{
title_fa: "دریل",
icon: "drill",
description: "انواع دریل ها",
title_en: "drills",
leaf: true,
themeType: null,
imageUrl: faker.image.imageUrl(),
},
],
"صوت و تصویر": [
{
title_fa: "تلویزیون",
icon: "tv",
description: "انواع تلویزیون",
title_en: "televisions",
leaf: true,
themeType: null,
imageUrl: faker.image.imageUrl(),
},
{
title_fa: "اسپیکر",
icon: "speaker",
description: "انواع اسپیکر",
title_en: "speakers",
leaf: true,
themeType: null,
imageUrl: faker.image.imageUrl(),
},
{
title_fa: "هدفون",
icon: "headphone",
description: "انواع هدفون",
title_en: "headphones",
leaf: true,
themeType: "color",
imageUrl: faker.image.imageUrl(),
},
{
title_fa: "دوربین عکاسی",
icon: "camera",
description: "انواع دوربین عکاسی",
title_en: "cameras",
leaf: true,
themeType: null,
imageUrl: faker.image.imageUrl(),
},
],
کامپیوتر: [
{
title_fa: "لپ تاپ",
icon: "laptop",
description: "انواع لپ تاپ",
title_en: "laptops",
leaf: true,
themeType: null,
imageUrl: faker.image.imageUrl(),
},
{
title_fa: "موس و کیبورد",
icon: "mouse-keyboard",
description: "انواع موس و کیبورد",
title_en: "mouse-keyboards",
leaf: true,
themeType: null,
imageUrl: faker.image.imageUrl(),
},
],
"کنسول بازی": [
{
title_fa: "پلی استیشن",
icon: "playstation",
description: "انواع پلی استیشن",
title_en: "playstations",
leaf: true,
themeType: null,
imageUrl: faker.image.imageUrl(),
},
{
title_fa: "ایکس باکس",
icon: "xbox",
description: "انواع ایکس باکس",
title_en: "xbox",
leaf: true,
themeType: null,
imageUrl: faker.image.imageUrl(),
},
],
"ورزش و سفر": [
{
title_fa: "تجهیزات ورزشی",
icon: "sports-equipment",
description: "انواع تجهیزات ورزشی",
title_en: "sports-equipment",
leaf: true,
themeType: null,
imageUrl: faker.image.imageUrl(),
},
{
title_fa: "لوازم سفر",
icon: "travel",
description: "انواع لوازم سفر",
title_en: "travel-equipment",
leaf: true,
themeType: null,
imageUrl: faker.image.imageUrl(),
},
],
"پوشاک و مد": [
{
title_fa: "لباس زنانه",
icon: "women-clothing",
description: "انواع لباس زنانه",
title_en: "women-clothing",
leaf: true,
themeType: "size",
imageUrl: faker.image.imageUrl(),
},
{
title_fa: "لباس مردانه",
icon: "men-clothing",
description: "انواع لباس مردانه",
title_en: "men-clothing",
leaf: true,
themeType: "size",
imageUrl: faker.image.imageUrl(),
},
],
"لوازم خانگی": [
{
title_fa: "لوازم آشپزخانه",
icon: "kitchen-appliances",
description: "انواع لوازم آشپزخانه",
title_en: "kitchen-appliances",
leaf: true,
themeType: null,
imageUrl: faker.image.imageUrl(),
},
{
title_fa: "لوازم نظافت",
icon: "cleaning-appliances",
description: "انواع لوازم نظافت",
title_en: "cleaning-appliances",
leaf: true,
themeType: null,
imageUrl: faker.image.imageUrl(),
},
],
};
const categoryAttributesMap: { [key: string]: ICategoryAttributeSeed[] } = {
"گوشی موبایل": [
{ title: "باتری", hint: "ظرفیت باتری", type: "select", multiple: false, required: true },
{ title: "دوربین", hint: "کیفیت دوربین", type: "select", multiple: false, required: true },
{ title: "سیستم عامل", hint: "نوع سیستم عامل", type: "select", multiple: false, required: true },
{ title: "حافظه داخلی موبایل", hint: "ظرفیت حافظه", type: "select", multiple: false, required: true },
{ title: "پشتیبانی از 5G", hint: "آیا پشتیبانی می‌کند؟", type: "checkbox", multiple: false, required: false },
],
تبلت: [
{ title: "اندازه صفحه تبلت", hint: "اینچ", type: "select", multiple: false, required: true },
{ title: "پردازنده تبلت", hint: "نوع پردازنده", type: "select", multiple: false, required: true },
{ title: "دوربین جلو", hint: "کیفیت دوربین جلو", type: "select", multiple: false, required: false },
{ title: "ظرفیت باتری", hint: "میلی‌آمپر ساعت", type: "select", multiple: false, required: true },
{ title: "اتصال به شبکه", hint: "نوع شبکه", type: "select", multiple: false, required: false },
],
"خودکار و خودنویس": [
{ title: "نوع جوهر", hint: "جوهر مورد استفاده", type: "select", multiple: false, required: true },
{ title: "ضخامت نوشتاری", hint: "ضخامت نوک", type: "select", multiple: false, required: true },
{ title: "قابلیت شارژ مجدد", hint: "آیا قابل شارژ است؟", type: "checkbox", multiple: false, required: false },
{ title: "جنس بدنه خودکار", hint: "جنس بدنه خودکار", type: "select", multiple: false, required: true },
{ title: "رنگ جوهر", hint: "رنگ جوهر خودکار", type: "select", multiple: false, required: true },
],
لامپ: [
{ title: "نوع لامپ", hint: "نوع لامپ (LED، هالوژن و غیره)", type: "select", multiple: false, required: true },
{ title: "توان مصرفی لامپ", hint: "وات", type: "select", multiple: false, required: true },
{ title: "رنگ نور", hint: "رنگ نور", type: "select", multiple: false, required: true },
{ title: "طول عمر", hint: "ساعت", type: "select", multiple: false, required: true },
{ title: "قابلیت تنظیم شدت نور", hint: "آیا قابل تنظیم است؟", type: "checkbox", multiple: false, required: false },
],
تلویزیون: [
{ title: "اندازه صفحه تلویزیون", hint: "اینچ", type: "select", multiple: false, required: true },
{ title: "رزولوشن", hint: "کیفیت تصویر", type: "select", multiple: false, required: true },
{ title: "نوع صفحه", hint: "LED, OLED, QLED", type: "select", multiple: false, required: true },
{ title: "هوشمند", hint: "آیا هوشمند است؟", type: "checkbox", multiple: false, required: false },
{ title: "درگاه‌های ارتباطی", hint: "نوع و تعداد درگاه‌ها", type: "select", multiple: true, required: true },
],
"لپ تاپ": [
{ title: "پردازنده لپ تاپ", hint: "نوع پردازنده", type: "select", multiple: false, required: true },
{ title: "حافظه رم", hint: "ظرفیت حافظه رم", type: "select", multiple: false, required: true },
{ title: "حافظه داخلی", hint: "ظرفیت حافظه", type: "select", multiple: false, required: true },
{ title: "اندازه صفحه", hint: "اینچ", type: "select", multiple: false, required: true },
{ title: "کارت گرافیک", hint: "نوع کارت گرافیک", type: "select", multiple: false, required: true },
],
"لباس زنانه": [
{ title: "جنس پارچه", hint: "نوع پارچه", type: "select", multiple: false, required: true },
{ title: "نوع یقه", hint: "یقه لباس", type: "select", multiple: false, required: true },
{ title: "نوع آستین", hint: "آستین لباس", type: "select", multiple: false, required: true },
{ title: "مدل لباس", hint: "نوع و مدل لباس", type: "select", multiple: false, required: true },
{ title: "نوع شستشو", hint: "نحوه شستشو", type: "select", multiple: false, required: false },
],
"لوازم آشپزخانه": [
{ title: "توان مصرفی", hint: "توان لوازم برقی", type: "select", multiple: false, required: true },
{ title: "جنس بدنه", hint: "جنس بدنه دستگاه", type: "select", multiple: false, required: true },
{ title: "ظرفیت", hint: "ظرفیت دستگاه", type: "select", multiple: false, required: true },
{ title: "قابلیت تنظیم", hint: "آیا قابلیت تنظیم دارد؟", type: "checkbox", multiple: false, required: false },
{ title: "نوع کاربری", hint: "کاربری دستگاه", type: "select", multiple: false, required: true },
],
// Add more categories as needed
};
const attributeValuesMap: { [key: string]: string[] } = {
باتری: ["3000mAh", "4000mAh", "5000mAh", "6000mAh", "7000mAh"],
دوربین: ["12MP", "16MP", "20MP", "48MP", "108MP"],
"سیستم عامل": ["اندروید", "iOS", "ویندوز", "لینوکس", "سایر"],
"حافظه داخلی موبایل": ["32GB", "64GB", "128GB", "256GB", "512GB"],
"پشتیبانی از 5G": ["بله", "خیر"],
"اندازه صفحه تبلت": ["7 اینچ", "10 اینچ", "13 اینچ", "15 اینچ", "17 اینچ"],
"پردازنده تبلت": ["Intel", "AMD", "Apple M1", "Qualcomm", "MediaTek"],
"دوربین جلو": ["5MP", "8MP", "12MP", "16MP", "20MP"],
"ظرفیت باتری": ["2000mAh", "3000mAh", "4000mAh", "5000mAh", "6000mAh"],
"اتصال به شبکه": ["WiFi", "4G", "5G", "Ethernet", "بلوتوث"],
"نوع جوهر": ["ژل", "روغنی", "آبی", "خودکار", "پلاستیکی"],
"ضخامت نوشتاری": ["0.5mm", "0.7mm", "1.0mm", "1.2mm", "1.5mm"],
"قابلیت شارژ مجدد": ["بله", "خیر"],
"جنس بدنه خودکار": ["فلز", "پلاستیک", "چوب", "سرامیک", "کربن"],
"رنگ جوهر": ["مشکی", "آبی", "قرمز", "سبز", "بنفش"],
"نوع لامپ": ["LED", "هالوژن", "فلورسنت", "رشته‌ای", "پلاسما"],
"توان مصرفی لامپ": ["5W", "10W", "15W", "20W", "25W"],
"رنگ نور": ["سفید", "زرد", "آبی", "سبز", "قرمز"],
"طول عمر": ["1000 ساعت", "5000 ساعت", "10000 ساعت", "20000 ساعت", "50000 ساعت"],
"قابلیت تنظیم شدت نور": ["بله", "خیر"],
"اندازه صفحه تلویزیون": ["20 اینچ", "32 اینچ", "40 اینچ", "60 اینچ", "70 اینچ"],
رزولوشن: ["720p", "1080p", "4K", "8K", "HDR"],
"نوع صفحه": ["LED", "OLED", "QLED", "Plasma", "LCD"],
هوشمند: ["بله", "خیر"],
"درگاه‌های ارتباطی": ["HDMI", "USB", "Bluetooth", "WiFi", "Ethernet"],
"پردازنده لپ تاپ": ["Intel", "AMD", "Apple M1", "Qualcomm", "Apple M2"],
"حافظه رم": ["4GB", "8GB", "16GB", "32GB", "64GB"],
"حافظه داخلی": ["128GB", "256GB", "512GB", "1TB"],
"اندازه صفحه": ["14 اینچ", "12 اینچ", "16 اینچ"],
"کارت گرافیک": ["NVIDIA", "AMD", "Intel", "Apple M1", "ARM Mali"],
"جنس پارچه": ["پنبه", "پلی‌استر", "نایلون", "ریون", "کتان"],
"نوع یقه": ["گرد", "هفت", "ایستاده", "دکمه‌ای", "یقه اسکی"],
"نوع آستین": ["کوتاه", "بلند", "سه‌ربع", "بی‌آستین", "کشباف"],
"مدل لباس": ["تی‌شرت", "پیراهن", "ژاکت", "هودی", "پلیور"],
"نوع شستشو": ["ماشینی", "دستی", "خشک‌شویی", "آب سرد", "آب گرم"],
"توان مصرفی": ["150W", "200W", "400W"],
"جنس بدنه": ["فلز", "پلاستیک", "چوب", "سرامیک", "کربن"],
ظرفیت: ["1 لیتر", "2 لیتر", "3 لیتر", "4 لیتر", "5 لیتر"],
"قابلیت تنظیم": ["بله", "خیر"],
"نوع کاربری": ["آبمیوه‌گیری", "مخلوط‌کن", "آسیاب", "کباب‌پز", "زودپز"],
// Add more attributes and their values as needed
return themeMap[themeString] || null;
};
export const seedCategories = async (logger: Logger) => {
try {
logger.info("Categories seeding started");
logger.info("Categories seeding started");
const colorTheme = await ThemeModel.findOne({ title: "رنگ" });
const sizeTheme = await ThemeModel.findOne({ title: "سایز" });
const meterageTheme = await ThemeModel.findOne({ title: "متراژ" });
// Extract data from dump
const dumpData = extractDumpData();
const categoriesData = getCollectionData(dumpData, "categories");
const colors = colorTheme ? (await ThemeValueModel.find({ theme: colorTheme._id })).map((color) => color._id) : [];
const sizes = sizeTheme ? (await ThemeValueModel.find({ theme: sizeTheme._id })).map((size) => size._id) : [];
const meterages = meterageTheme ? (await ThemeValueModel.find({ theme: meterageTheme._id })).map((meterage) => meterage._id) : [];
const createdCategories: { [key: string]: Types.ObjectId } = {};
const createdSubCategories: { [key: string]: Types.ObjectId } = {};
// Create root categories
for (const category of rootCategories) {
const { title_fa, icon, description, title_en, leaf, imageUrl } = category;
const rootCategory = new CategoryModel({ title_fa, icon, title_en, description, leaf, imageUrl });
await rootCategory.save();
createdCategories[title_fa] = rootCategory._id;
if (categoriesData.length === 0) {
logger.warn("No category data found in dump, skipping category seeding");
return;
}
// Create subcategories
for (const [rootName, subs] of Object.entries(subcategories)) {
const parentId = createdCategories[rootName];
if (parentId) {
for (const subcategory of subs) {
const { title_fa, icon, description, title_en, leaf, themeType, imageUrl } = subcategory;
// Get all themes and create a map
const themes = await ThemeModel.find();
const themeMap = new Map<string, Types.ObjectId>();
themes.forEach((theme) => {
themeMap.set(theme.title, theme._id);
});
// Map themeType to theme ID and variants
let themeId: Types.ObjectId | undefined;
let catVariants: Types.ObjectId[] = [];
// Get all theme values and create maps by theme
const themeValues = await ThemeValueModel.find();
const themeValueMap = new Map<Types.ObjectId, Types.ObjectId[]>();
themes.forEach((theme) => {
const values = themeValues.filter((tv) => tv.theme.toString() === theme._id.toString()).map((tv) => tv._id);
themeValueMap.set(theme._id, values);
});
if (themeType === "color" && colorTheme) {
themeId = colorTheme._id;
catVariants = [...colors];
} else if (themeType === "size" && sizeTheme) {
themeId = sizeTheme._id;
catVariants = [...sizes];
} else if (themeType === "meterage" && meterageTheme) {
themeId = meterageTheme._id;
catVariants = [...meterages];
// Clean and prepare category data
const cleanedCategories = categoriesData.map((cat) => cleanMongoData(cat));
// Filter out deleted categories
const activeCategories = cleanedCategories.filter((cat) => !cat.deleted);
// Create a map to store created categories by their old _id
const categoryIdMap = new Map<string, Types.ObjectId>();
// First pass: Create all categories without parent relationships
interface CategoryToCreate {
oldId: string;
data: {
title_fa: string;
title_en: string;
icon: string;
imageUrl: string;
description: string;
leaf: boolean;
theme?: Types.ObjectId;
variants: Types.ObjectId[];
deleted: boolean;
};
parentOldId?: string;
}
const categoriesToCreate: CategoryToCreate[] = [];
for (const cat of activeCategories) {
const oldId = cat._id?.toString() || "";
const parentOldId = cat.parent?.toString();
// Map theme string to theme ObjectId
let themeId: Types.ObjectId | undefined;
let variants: Types.ObjectId[] = [];
const themeTitle = mapThemeStringToTitle(cat.theme);
if (themeTitle) {
themeId = themeMap.get(themeTitle);
if (themeId) {
// Get all theme values for this theme
variants = themeValueMap.get(themeId) || [];
}
}
// Map variants from dump if they exist (they might be old IDs, so we'll use all theme values)
// If the category has specific variants in the dump, we could map them here
// For now, we'll use all theme values for the theme
const categoryData = {
title_fa: cat.title_fa,
title_en: cat.title_en,
icon: cat.icon || "/images/icons/default.png",
imageUrl: cat.imageUrl || "",
description: cat.description || "",
leaf: cat.leaf !== undefined ? cat.leaf : true,
theme: themeId,
variants: variants,
deleted: false,
};
categoriesToCreate.push({
oldId,
data: categoryData,
parentOldId,
});
}
// Sort categories: root categories first (no parent), then children
const rootCategories = categoriesToCreate.filter((cat) => !cat.parentOldId);
const childCategories = categoriesToCreate.filter((cat) => cat.parentOldId);
// Create root categories first
for (const { oldId, data } of rootCategories) {
try {
const category = new CategoryModel(data);
await category.save();
categoryIdMap.set(oldId, category._id);
logger.info(`Created root category: ${data.title_fa}`);
} catch (error: unknown) {
// Handle duplicate key error (title_en must be unique)
if (error && typeof error === "object" && "code" in error && error.code === 11000) {
const existing = await CategoryModel.findOne({ title_en: data.title_en });
if (existing) {
categoryIdMap.set(oldId, existing._id);
logger.warn(`Category with title_en '${data.title_en}' already exists, using existing`);
}
} else {
logger.error(`Error creating category ${data.title_fa}:`, error);
}
}
}
// Create child categories with parent relationships
// We need to process them in levels (children of roots, then grandchildren, etc.)
let remainingCategories = [...childCategories];
const maxIterations = 10; // Prevent infinite loops
let iteration = 0;
while (remainingCategories.length > 0 && iteration < maxIterations) {
iteration++;
const categoriesToProcess = remainingCategories.filter((cat) => {
// Check if parent exists in our map
return cat.parentOldId && categoryIdMap.has(cat.parentOldId);
});
if (categoriesToProcess.length === 0) {
// No more categories can be processed (orphaned categories)
logger.warn(`Stopped processing: ${remainingCategories.length} categories have missing parents`);
break;
}
for (const { oldId, data, parentOldId } of categoriesToProcess) {
try {
const parentId = parentOldId ? categoryIdMap.get(parentOldId) : undefined;
if (!parentId) {
logger.warn(`Parent not found for category ${data.title_fa}, skipping`);
continue;
}
const subCategoryDoc = new CategoryModel({
title_fa,
icon,
imageUrl,
title_en,
description,
leaf,
theme: themeId,
variants: catVariants,
// Build hierarchy from parent
const parentCategory = await CategoryModel.findById(parentId);
const hierarchy = parentCategory?.hierarchy ? [...parentCategory.hierarchy, parentId] : [parentId];
const category = new CategoryModel({
...data,
parent: parentId,
hierarchy: hierarchy,
});
await subCategoryDoc.save();
// Save the subcategory ID for later use
createdSubCategories[title_fa] = subCategoryDoc._id;
await category.save();
categoryIdMap.set(oldId, category._id);
logger.info(`Created category: ${data.title_fa} (parent: ${parentCategory?.title_fa})`);
} catch (error: unknown) {
// Handle duplicate key error
if (error && typeof error === "object" && "code" in error && error.code === 11000) {
const existing = await CategoryModel.findOne({ title_en: data.title_en });
if (existing) {
categoryIdMap.set(oldId, existing._id);
logger.warn(`Category with title_en '${data.title_en}' already exists, using existing`);
}
} else {
logger.error(`Error creating category ${data.title_fa}:`, error);
}
}
}
// Remove processed categories
remainingCategories = remainingCategories.filter((cat) => !categoriesToProcess.includes(cat));
}
const createdAttributes: { [key: string]: number } = {};
// Generate attributes for each subcategory
for (const [subcategoryTitle, attributes] of Object.entries(categoryAttributesMap)) {
const subcategoryId = createdSubCategories[subcategoryTitle];
if (subcategoryId) {
for (const attr of attributes) {
const attributeDoc = new CategoryAttributeModel({
category: subcategoryId,
title: attr.title,
hint: attr.hint,
type: attr.type,
multiple: attr.multiple,
required: attr.required,
});
await attributeDoc.save();
createdAttributes[attr.title] = attributeDoc._id;
}
} else {
logger.warn(`Subcategory '${subcategoryTitle}' not found.`);
}
}
// Generate values for each attribute
for (const [attributeTitle, attributeId] of Object.entries(createdAttributes)) {
const values = attributeValuesMap[attributeTitle] || [];
for (const valueText of values) {
await AttributeValueModel.create({
attribute: attributeId,
text: valueText,
});
// await valueDoc.save();
}
if (remainingCategories.length > 0) {
logger.warn(`${remainingCategories.length} categories could not be created due to missing parents`);
}
logger.info("Categories, subcategories, attributes, and values seeded successfully");
logger.info(`Categories seeded successfully! Created ${categoryIdMap.size} categories`);
} catch (error) {
logger.error("Error seeding categories:", error);
throw error;
}
};