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
+1 -1
View File
@@ -71,7 +71,7 @@ export const IOCTYPES = {
WarrantyRepository: Symbol.for("WarrantyRepository"),
ShipmentRepository: Symbol.for("ShipmentRepository"),
TokenRepository: Symbol.for("TokenRepository"),
ThemeRepository: Symbol.for("ThemeRepository"),
ThemeValueRepository: Symbol.for("ThemeValueRepository"),
QuestionRepository: Symbol.for("QuestionRepository"),
+8 -642
View File
@@ -1,13 +1,12 @@
import { faker } from "@faker-js/faker";
import { CreateProductStep, ProductSource, ProductStatus } from "../../common/enums/product.enum";
import { SellerType } from "../../common/enums/seller.enum";
import { OwnerRef } from "../../modules/shop/models/Abstraction/IShop";
faker.locale = "fa";
export const fakeImageGenerator = () => faker.image.imageUrl();
export const CreateFakeSellerData = () => ({
fullName: faker.name.fullName(),
fullName: "شمالی زاده",
shebaNumber: faker.finance.iban(),
dateOfBirth: "1360/08/25",
phoneNumber: faker.phone.number("0912#######"),
@@ -19,653 +18,20 @@ export const CreateFakeSellerData = () => ({
});
export const createFakeShopData = (shipmentMethod: number[], owner: string) => ({
shopName: `فروشگاه ${faker.commerce.department()}`,
shopDescription: "تست",
shopName: `فروشگاه فجر تابلو مرکزی`,
shopDescription: "فروشگاه فجر تابلو مرکزی",
telephoneNumber: faker.phone.number("0863#######"),
shopNationalId: faker.random.numeric(10),
shopPostalCode: faker.random.numeric(10),
logo: faker.image.imageUrl(),
rate: {
totalRate: faker.datatype.number({ min: 1, max: 5 }),
totalCount: faker.datatype.number({ min: 1, max: 1000 }),
onTimeShip: faker.datatype.number({ min: 1, max: 5 }),
returns: faker.datatype.number({ min: 1, max: 5 }),
cancel: faker.datatype.number({ min: 1, max: 5 }),
totalRate: 0,
totalCount: 0,
onTimeShip: 0,
returns: 0,
cancel: 0,
},
shipmentMethod,
owner,
ownerRef: OwnerRef.SELLER,
});
export const fakeProductsData = [
// {
// color: true,
// size: false,
// meterage: false,
// title_fa: "تلویزیون ال ای دی سامسونگ",
// title_en: "Samsung LED TV",
// model: "SAMSUNG123",
// source: ProductSource.LOCAL,
// description: "تلویزیون ال ای دی 55 اینچ سامسونگ با کیفیت تصویر 4K",
// metaDescription: "تلویزیون ال ای دی سامسونگ 4K",
// tags: ["ال ای دی", "سامسونگ", "4K"],
// advantages: ["کیفیت تصویر عالی", "قاب باریک"],
// disAdvantages: ["قیمت بالا"],
// isFake: false,
// imagesUrl: {
// cover: faker.image.imageUrl(),
// list: [faker.image.imageUrl(), faker.image.imageUrl()],
// },
// step: CreateProductStep.Image,
// status: ProductStatus.Approved,
// },
// {
// color: true,
// size: false,
// meterage: false,
// title_fa: "ماشین لباسشویی ال جی",
// title_en: "LG Washing Machine",
// model: "LGWASH456",
// source: ProductSource.IMPORT,
// description: "ماشین لباسشویی 8 کیلوگرمی ال جی با تکنولوژی بخار شوی",
// metaDescription: "ماشین لباسشویی ال جی با ظرفیت 8 کیلوگرم",
// tags: ["لباسشویی", "ال جی", "بخار شوی"],
// advantages: ["مصرف انرژی کم", "سرعت چرخش بالا"],
// disAdvantages: ["صدا در حالت خشک کن"],
// isFake: false,
// imagesUrl: {
// cover: faker.image.imageUrl(),
// list: [faker.image.imageUrl(), faker.image.imageUrl()],
// },
// step: CreateProductStep.Image,
// status: ProductStatus.Approved,
// },
// {
// color: true,
// size: false,
// meterage: false,
// title_fa: "یخچال ساید بای ساید بوش",
// title_en: "Bosch Side by Side Refrigerator",
// model: "BOSCHFRIDGE789",
// source: ProductSource.LOCAL,
// description: "یخچال فریزر ساید بای ساید بوش با ظرفیت 30 فوت",
// metaDescription: "یخچال فریزر ساید بای ساید بوش",
// tags: ["یخچال", "بوش", "ساید بای ساید"],
// advantages: ["ظرفیت بالا", "طراحی مدرن"],
// disAdvantages: ["قیمت بالا"],
// isFake: false,
// imagesUrl: {
// cover: faker.image.imageUrl(),
// list: [faker.image.imageUrl(), faker.image.imageUrl()],
// },
// step: CreateProductStep.Image,
// status: ProductStatus.Approved,
// },
{
color: true,
size: false,
meterage: false,
title_fa: "موبایل آیفون 13",
title_en: "iPhone 13",
model: "IPHONE13PRO",
source: ProductSource.IMPORT,
description: "گوشی آیفون 13 پرو با حافظه 256 گیگابایت و دوربین سه‌گانه",
metaDescription: "آیفون 13 پرو با حافظه 256 گیگ",
tags: ["آیفون", "اپل", "موبایل"],
advantages: ["دوربین عالی", "عملکرد سریع"],
disAdvantages: ["قیمت بسیار بالا"],
isFake: false,
imagesUrl: {
cover: faker.image.imageUrl(),
list: [faker.image.imageUrl(), faker.image.imageUrl()],
},
step: CreateProductStep.Image,
status: ProductStatus.Approved,
},
{
color: true,
size: false,
meterage: false,
title_fa: "لپ‌تاپ ایسوس",
title_en: "Asus Laptop",
model: "ASUSLAP234",
source: ProductSource.LOCAL,
description: "لپ‌تاپ ایسوس با پردازنده Core i7 و حافظه 16 گیگابایت",
metaDescription: "لپ‌تاپ ایسوس با حافظه 16 گیگ",
tags: ["لپ‌تاپ", "ایسوس", "Core i7"],
advantages: ["عملکرد قوی", "صفحه‌نمایش با کیفیت"],
disAdvantages: ["عمر باتری پایین"],
isFake: false,
imagesUrl: {
cover: faker.image.imageUrl(),
list: [faker.image.imageUrl(), faker.image.imageUrl()],
},
step: CreateProductStep.Image,
status: ProductStatus.Approved,
},
{
color: true,
size: false,
meterage: false,
title_fa: "ساعت هوشمند اپل واچ",
title_en: "Apple Watch",
model: "APPLEWATCH123",
source: ProductSource.IMPORT,
description: "ساعت هوشمند اپل با قابلیت اندازه‌گیری ضربان قلب",
metaDescription: "اپل واچ سری 6",
tags: ["ساعت هوشمند", "اپل", "واچ"],
advantages: ["اندازه‌گیری سلامت", "طراحی زیبا"],
disAdvantages: ["عمر باتری محدود"],
isFake: false,
imagesUrl: {
cover: faker.image.imageUrl(),
list: [faker.image.imageUrl(), faker.image.imageUrl()],
},
step: CreateProductStep.Image,
status: ProductStatus.Approved,
},
// {
// color: true,
// size: false,
// meterage: false,
// title_fa: "تبلت سامسونگ گلکسی تب",
// title_en: "Samsung Galaxy Tab",
// model: "SAMGALTAB567",
// source: ProductSource.LOCAL,
// description: "تبلت سامسونگ گلکسی تب با صفحه نمایش 10.5 اینچ",
// metaDescription: "تبلت سامسونگ گلکسی",
// tags: ["تبلت", "سامسونگ", "گلکسی"],
// advantages: ["صفحه نمایش بزرگ", "قابلیت چند وظیفگی"],
// disAdvantages: ["عمر باتری متوسط"],
// isFake: false,
// imagesUrl: {
// cover: faker.image.imageUrl(),
// list: [faker.image.imageUrl(), faker.image.imageUrl()],
// },
// step: CreateProductStep.Image,
// status: ProductStatus.Approved,
// },
// {
// color: true,
// size: false,
// meterage: false,
// title_fa: "دوربین عکاسی نیکون",
// title_en: "Nikon Camera",
// model: "NIKONCAM123",
// source: ProductSource.IMPORT,
// description: "دوربین دیجیتال نیکون با سنسور 24 مگاپیکسل",
// metaDescription: "دوربین نیکون دیجیتال",
// tags: ["دوربین", "نیکون", "دیجیتال"],
// advantages: ["کیفیت تصویر بالا", "سنسور قوی"],
// disAdvantages: ["بدنه سنگین"],
// isFake: false,
// imagesUrl: {
// cover: faker.image.imageUrl(),
// list: [faker.image.imageUrl(), faker.image.imageUrl()],
// },
// step: CreateProductStep.Image,
// status: ProductStatus.Approved,
// },
// {
// color: true,
// size: false,
// meterage: false,
// title_fa: "اسپیکر بلوتوثی جی بی ال",
// title_en: "JBL Bluetooth Speaker",
// model: "JBLSPK789",
// source: ProductSource.LOCAL,
// description: "اسپیکر بلوتوثی جی بی ال با کیفیت صدای عالی",
// metaDescription: "اسپیکر جی بی ال بلوتوث",
// tags: ["اسپیکر", "بلوتوث", "جی بی ال"],
// advantages: ["کیفیت صدای عالی", "قابلیت حمل آسان"],
// disAdvantages: ["قیمت نسبتاً بالا"],
// isFake: false,
// imagesUrl: {
// cover: faker.image.imageUrl(),
// list: [faker.image.imageUrl(), faker.image.imageUrl()],
// },
// step: CreateProductStep.Image,
// status: ProductStatus.Approved,
// },
// {
// color: true,
// size: false,
// meterage: false,
// title_fa: "هدفون بی سیم سونی",
// title_en: "Sony Wireless Headphones",
// model: "SONYWH123",
// source: ProductSource.IMPORT,
// description: "هدفون بی سیم سونی با قابلیت حذف نویز",
// metaDescription: "هدفون بی سیم سونی",
// tags: ["هدفون", "بی سیم", "سونی"],
// advantages: ["حذف نویز عالی", "طراحی ارگونومیک"],
// disAdvantages: ["قیمت بالا"],
// isFake: false,
// imagesUrl: {
// cover: faker.image.imageUrl(),
// list: [faker.image.imageUrl(), faker.image.imageUrl()],
// },
// step: CreateProductStep.Image,
// status: ProductStatus.Approved,
// },
// {
// color: true,
// size: false,
// meterage: false,
// title_fa: "دوربین فیلمبرداری سونی",
// title_en: "Sony Camcorder",
// model: "SONYCAM456",
// source: ProductSource.LOCAL,
// description: "دوربین فیلمبرداری سونی با قابلیت فیلم‌برداری 4K",
// metaDescription: "دوربین فیلمبرداری 4K سونی",
// tags: ["دوربین", "فیلمبرداری", "سونی"],
// advantages: ["فیلم‌برداری 4K", "باتری با عمر طولانی"],
// disAdvantages: ["وزن سنگین"],
// isFake: false,
// imagesUrl: {
// cover: faker.image.imageUrl(),
// list: [faker.image.imageUrl(), faker.image.imageUrl()],
// },
// step: CreateProductStep.Image,
// status: ProductStatus.Approved,
// },
// {
// color: true,
// size: false,
// meterage: false,
// title_fa: "گوشی شیائومی می 11",
// title_en: "Xiaomi Mi 11",
// model: "MI11PRO",
// source: ProductSource.IMPORT,
// description: "گوشی موبایل شیائومی می 11 پرو با حافظه 256 گیگابایت",
// metaDescription: "گوشی شیائومی می 11 پرو",
// tags: ["شیائومی", "موبایل", "می 11"],
// advantages: ["دوربین قوی", "پردازنده سریع"],
// disAdvantages: ["شارژر جداگانه فروخته می‌شود"],
// isFake: false,
// imagesUrl: {
// cover: faker.image.imageUrl(),
// list: [faker.image.imageUrl(), faker.image.imageUrl()],
// },
// step: CreateProductStep.Image,
// status: ProductStatus.Approved,
// },
// {
// color: true,
// size: false,
// meterage: false,
// title_fa: "تلویزیون ال جی OLED",
// title_en: "LG OLED TV",
// model: "LGOLED789",
// source: ProductSource.LOCAL,
// description: "تلویزیون 65 اینچ ال جی با کیفیت تصویر OLED",
// metaDescription: "تلویزیون OLED ال جی",
// tags: ["تلویزیون", "ال جی", "OLED"],
// advantages: ["کیفیت تصویر عالی", "طراحی باریک"],
// disAdvantages: ["قیمت بالا"],
// isFake: false,
// imagesUrl: {
// cover: faker.image.imageUrl(),
// list: [faker.image.imageUrl(), faker.image.imageUrl()],
// },
// step: CreateProductStep.Image,
// status: ProductStatus.Approved,
// },
// {
// color: true,
// size: false,
// meterage: false,
// title_fa: "پاوربانک انکر",
// title_en: "Anker Power Bank",
// model: "ANKERPB100",
// source: ProductSource.IMPORT,
// description: "پاوربانک انکر با ظرفیت 20000 میلی‌آمپر ساعت",
// metaDescription: "پاوربانک انکر با ظرفیت بالا",
// tags: ["پاوربانک", "انکر", "ظرفیت بالا"],
// advantages: ["ظرفیت بزرگ", "شارژ سریع"],
// disAdvantages: ["وزن بالا"],
// isFake: false,
// imagesUrl: {
// cover: faker.image.imageUrl(),
// list: [faker.image.imageUrl(), faker.image.imageUrl()],
// },
// step: CreateProductStep.Image,
// status: ProductStatus.Approved,
// },
// {
// color: false,
// size: false,
// meterage: false,
// title_fa: "کنسول بازی پلی استیشن 5",
// title_en: "PlayStation 5",
// model: "PS5DISC",
// source: ProductSource.IMPORT,
// description: "کنسول بازی پلی استیشن 5 با دیسک درایو",
// metaDescription: "کنسول بازی پلی استیشن 5",
// tags: ["کنسول", "پلی استیشن", "بازی"],
// advantages: ["بازی‌های انحصاری", "گرافیک فوق‌العاده"],
// disAdvantages: ["موجودی کم"],
// isFake: false,
// imagesUrl: {
// cover: faker.image.imageUrl(),
// list: [faker.image.imageUrl(), faker.image.imageUrl()],
// },
// step: CreateProductStep.Image,
// status: ProductStatus.Approved,
// },
// {
// color: true,
// size: false,
// meterage: false,
// title_fa: "هدفون بی سیم بوز",
// title_en: "Bose Wireless Headphones",
// model: "BOSEWH789",
// source: ProductSource.IMPORT,
// description: "هدفون بی سیم بوز با قابلیت حذف نویز فعال",
// metaDescription: "هدفون بی سیم بوز",
// tags: ["هدفون", "بی سیم", "بوز"],
// advantages: ["کیفیت صدای بالا", "حذف نویز"],
// disAdvantages: ["قیمت بالا"],
// isFake: false,
// imagesUrl: {
// cover: faker.image.imageUrl(),
// list: [faker.image.imageUrl(), faker.image.imageUrl()],
// },
// step: CreateProductStep.Image,
// status: ProductStatus.Approved,
// },
// {
// color: true,
// size: false,
// meterage: false,
// title_fa: "تبلت هواوی مدیاپد",
// title_en: "Huawei MediaPad",
// model: "HUAWEITAB1",
// source: ProductSource.LOCAL,
// description: "تبلت هواوی مدیاپد با صفحه نمایش 10.8 اینچی",
// metaDescription: "تبلت هواوی مدیاپد",
// tags: ["تبلت", "هواوی", "مدیاپد"],
// advantages: ["صفحه نمایش بزرگ", "طراحی سبک"],
// disAdvantages: ["عملکرد متوسط در بازی"],
// isFake: false,
// imagesUrl: {
// cover: faker.image.imageUrl(),
// list: [faker.image.imageUrl(), faker.image.imageUrl()],
// },
// step: CreateProductStep.Image,
// status: ProductStatus.Approved,
// },
// {
// color: true,
// size: false,
// meterage: false,
// title_fa: "اسپیکر بلوتوثی سونی",
// title_en: "Sony Bluetooth Speaker",
// model: "SONYSPK234",
// source: ProductSource.IMPORT,
// description: "اسپیکر بلوتوثی سونی با کیفیت صدای عالی",
// metaDescription: "اسپیکر بلوتوثی سونی",
// tags: ["اسپیکر", "بلوتوث", "سونی"],
// advantages: ["صدای شفاف", "طراحی مقاوم در برابر آب"],
// disAdvantages: ["عمر باتری محدود"],
// isFake: false,
// imagesUrl: {
// cover: faker.image.imageUrl(),
// list: [faker.image.imageUrl(), faker.image.imageUrl()],
// },
// step: CreateProductStep.Image,
// status: ProductStatus.Approved,
// },
// {
// color: true,
// size: false,
// meterage: false,
// title_fa: "موبایل نوکیا G20",
// title_en: "Nokia G20",
// model: "NOKIAG20",
// source: ProductSource.LOCAL,
// description: "گوشی موبایل نوکیا G20 با حافظه 128 گیگابایت",
// metaDescription: "گوشی نوکیا G20",
// tags: ["نوکیا", "موبایل", "G20"],
// advantages: ["باتری بادوام", "طراحی مقاوم"],
// disAdvantages: ["دوربین متوسط"],
// isFake: false,
// imagesUrl: {
// cover: faker.image.imageUrl(),
// list: [faker.image.imageUrl(), faker.image.imageUrl()],
// },
// step: CreateProductStep.Image,
// status: ProductStatus.Approved,
// },
// {
// color: true,
// size: false,
// meterage: false,
// title_fa: "مایکروویو پاناسونیک",
// title_en: "Panasonic Microwave",
// model: "PANAMW234",
// source: ProductSource.IMPORT,
// description: "مایکروویو پاناسونیک با قدرت 1200 وات",
// metaDescription: "مایکروویو پاناسونیک",
// tags: ["مایکروویو", "پاناسونیک", "پخت سریع"],
// advantages: ["پخت سریع", "قابلیت گریل"],
// disAdvantages: ["فضای داخلی کوچک"],
// isFake: false,
// imagesUrl: {
// cover: faker.image.imageUrl(),
// list: [faker.image.imageUrl(), faker.image.imageUrl()],
// },
// step: CreateProductStep.Image,
// status: ProductStatus.Approved,
// },
{
color: false,
size: true,
meterage: false,
title_fa: "تی شرت مردانه",
title_en: "mardane t-shirt",
model: "Nike",
source: ProductSource.IMPORT,
description: "تی شرت سبک و راحت مردانه نایک",
metaDescription: "تی شرت مردانه نایک برای ورزش و استفاده روزمره",
tags: ["تی شرت", "نایک", "لباس مردانه"],
advantages: ["پارچه سبک", "مناسب برای تابستان"],
disAdvantages: ["محدودیت رنگ"],
isFake: false,
imagesUrl: {
cover: faker.image.imageUrl(),
list: [faker.image.imageUrl(), faker.image.imageUrl()],
},
step: CreateProductStep.Image,
status: ProductStatus.Approved,
},
{
color: false,
size: false,
meterage: true,
title_fa: "فرش ماشینی",
title_en: "farsh mashini",
model: "Iranian Classic",
source: ProductSource.LOCAL,
description: "فرش ماشینی با طرح کلاسیک ایرانی",
metaDescription: "فرش ماشینی با کیفیت بالا و طرح سنتی ایرانی",
tags: ["فرش", "ماشینی", "کلاسیک"],
advantages: ["طرح زیبا", "قابلیت شستشو"],
disAdvantages: ["سنگین بودن"],
isFake: false,
imagesUrl: {
cover: faker.image.imageUrl(),
list: [faker.image.imageUrl(), faker.image.imageUrl()],
},
step: CreateProductStep.Image,
status: ProductStatus.Approved,
},
{
color: false,
size: true,
meterage: false,
title_fa: "شلوار جین زنانه",
title_en: "zanane jean pants",
model: "Levi's",
source: ProductSource.IMPORT,
description: "شلوار جین زنانه با استایل اسپرت",
metaDescription: "شلوار جین زنانه مناسب برای استفاده روزمره",
tags: ["شلوار جین", "زنانه", "لباس"],
advantages: ["کیفیت بالا", "راحتی"],
disAdvantages: ["قیمت بالا"],
isFake: false,
imagesUrl: {
cover: faker.image.imageUrl(),
list: [faker.image.imageUrl(), faker.image.imageUrl()],
},
step: CreateProductStep.Image,
status: ProductStatus.Approved,
},
{
color: false,
size: true,
meterage: false,
title_fa: "پیراهن مجلسی زنانه",
title_en: "zanane dress",
model: "Zara",
source: ProductSource.IMPORT,
description: "پیراهن مجلسی شیک زنانه برند زارا",
metaDescription: "پیراهن زنانه شیک برای مجالس و مهمانی‌ها",
tags: ["پیراهن", "مجلسی", "زنانه", "زارا"],
advantages: ["مدل زیبا", "جنس با کیفیت"],
disAdvantages: ["حساس به شستشو"],
isFake: false,
imagesUrl: {
cover: faker.image.imageUrl(),
list: [faker.image.imageUrl(), faker.image.imageUrl()],
},
step: CreateProductStep.Image,
status: ProductStatus.Approved,
},
{
color: false,
size: false,
meterage: true,
title_fa: "پرده اتاق خواب",
title_en: "bedroom curtain",
model: "LuxDecor",
source: ProductSource.LOCAL,
description: "پرده اتاق خواب با طرح مدرن و زیبا",
metaDescription: "پرده اتاق خواب شیک و جذاب با جنس مقاوم",
tags: ["پرده", "اتاق خواب", "دکوراسیون"],
advantages: ["نورگیر خوب", "نصب آسان"],
disAdvantages: ["قابلیت چین‌خوردگی"],
isFake: false,
imagesUrl: {
cover: faker.image.imageUrl(),
list: [faker.image.imageUrl(), faker.image.imageUrl()],
},
step: CreateProductStep.Image,
status: ProductStatus.Approved,
},
// {
// color: false,
// size: true,
// meterage: false,
// title_fa: "کفش ورزشی مردانه",
// title_en: "sport shoes",
// model: "Adidas",
// source: ProductSource.IMPORT,
// description: "کفش ورزشی مردانه مناسب دویدن",
// metaDescription: "کفش ورزشی سبک و راحت برای فعالیت‌های ورزشی",
// tags: ["کفش", "ورزشی", "مردانه", "آدیداس"],
// advantages: ["کفی نرم", "سبک"],
// disAdvantages: ["عدم مقاومت در برابر آب"],
// isFake: false,
// imagesUrl: {
// cover: faker.image.imageUrl(),
// list: [faker.image.imageUrl(), faker.image.imageUrl()],
// },
// step: CreateProductStep.Image,
// status: ProductStatus.Approved,
// },
{
color: false,
size: false,
meterage: true,
title_fa: "کفپوش چوبی",
title_en: "wooden flooring",
model: "WoodMaster",
source: ProductSource.LOCAL,
description: "کفپوش چوبی با کیفیت برای اتاق‌ها و سالن",
metaDescription: "کفپوش چوبی مقاوم با ظاهر زیبا",
tags: ["کفپوش", "چوبی", "دکوراسیون"],
advantages: ["زیبایی طبیعی", "مقاومت بالا"],
disAdvantages: ["نیاز به مراقبت ویژه"],
isFake: false,
imagesUrl: {
cover: faker.image.imageUrl(),
list: [faker.image.imageUrl(), faker.image.imageUrl()],
},
step: CreateProductStep.Image,
status: ProductStatus.Approved,
},
{
color: false,
size: true,
meterage: false,
title_fa: "کت چرمی مردانه",
title_en: "leather jacket",
model: "Tommy Hilfiger",
source: ProductSource.IMPORT,
description: "کت چرمی شیک مردانه برند تامی هیلفیگر",
metaDescription: "کت چرمی مردانه با کیفیت و استایل خاص",
tags: ["کت", "چرمی", "مردانه", "تامی"],
advantages: ["گرم و بادوام", "استایل خاص"],
disAdvantages: ["نیاز به مراقبت از چرم"],
isFake: false,
imagesUrl: {
cover: faker.image.imageUrl(),
list: [faker.image.imageUrl(), faker.image.imageUrl()],
},
step: CreateProductStep.Image,
status: ProductStatus.Approved,
},
// {
// color: false,
// size: true,
// meterage: false,
// title_fa: "هودی دخترانه",
// title_en: "hoodie",
// model: "Puma",
// source: ProductSource.IMPORT,
// description: "هودی گرم و راحت دخترانه برند پوما",
// metaDescription: "هودی دخترانه مناسب فصل پاییز و زمستان",
// tags: ["هودی", "دخترانه", "پوشاک", "پوما"],
// advantages: ["گرم و نرم", "کلاه قابل تنظیم"],
// disAdvantages: ["محدودیت رنگ"],
// isFake: false,
// imagesUrl: {
// cover: faker.image.imageUrl(),
// list: [faker.image.imageUrl(), faker.image.imageUrl()],
// },
// step: CreateProductStep.Image,
// status: ProductStatus.Approved,
// },
// {
// color: false,
// size: false,
// meterage: true,
// title_fa: "کاغذ دیواری مدرن",
// title_en: "modern wallpaper",
// model: "ArtWall",
// source: ProductSource.IMPORT,
// description: "کاغذ دیواری مدرن با طرح‌های جدید و زیبا",
// metaDescription: "کاغذ دیواری با طرح‌های مدرن برای دکوراسیون داخلی",
// tags: ["کاغذ دیواری", "دکوراسیون", "مدرن"],
// advantages: ["نصب آسان", "قابلیت شستشو"],
// disAdvantages: ["نیاز به دیوار صاف"],
// isFake: false,
// imagesUrl: {
// cover: faker.image.imageUrl(),
// list: [faker.image.imageUrl(), faker.image.imageUrl()],
// },
// step: CreateProductStep.Image,
// status: ProductStatus.Approved,
// },
];
+96 -30
View File
@@ -1,40 +1,106 @@
import { Types } from "mongoose";
import { Logger } from "../../core/logging/logger";
import { BlogCategoryModel } from "../../modules/blog/models/blogCategory.model";
// Blog categories data extracted from dump/tablofajr/blogcategories.bson
const blogCategoriesData = [
{
title_fa: "اخبار",
slug: "news",
description: "اخبار",
parentSlug: null,
leaf: false,
},
{
title_fa: "مقالات",
slug: "notifs",
description: "",
parentSlug: null,
leaf: false,
},
{
title_fa: "علمی",
slug: "Scientific",
description:
"Comparative Analysis of MPCB and Fuse in Industrial Electrical Systems\n\nIn industrial electrical systems, equipment protection is a crucial aspect of ensuring operational safety and reliability.\nA Motor Protection Circuit Breaker (MPCB) is an intelligent protective device designed to safeguard electric motors from overload, short circuit, and phase failure conditions.\nThe MPCB incorporates both thermal and magnetic trip mechanisms, allowing precise adjustment of the rated current according to the motor's specifications.\nWhen an overload occurs, the MPCB automatically interrupts the circuit, preventing motor damage, and can be reset manually after fault clearance — eliminating the need for replacement.\nConversely, a fuse provides protection only against excessive current; once it operates, it must be replaced, leading to maintenance delays and higher operational costs.\nMoreover, MPCBs offer enhanced coordination with motor starters, compact installation, and reusable functionality.\nThese advantages make MPCBs a more economical, reliable, and efficient solution compared to traditional fuse-based systems in modern industrial applications.\nRenowned manufacturers such as Hyundai, Schneider Electric, and Specter provide high-quality MPCBs optimized for diverse industrial requirements.\nOverall, the MPCB represents a significant technological advancement in motor protection, operational safety, and energy system efficiency.Scientific",
parentSlug: "notifs",
leaf: true,
},
{
title_fa: "Scientific",
slug: "Comparative-Analysis-of-MPCB-and-Fuse-in-Industrial-Electrical-Systems",
description: "مقاله علمی",
parentSlug: null,
leaf: true,
},
{
title_fa: "فنی ومهندسی",
slug: "ENGINERING",
description: "مطالب بروز شاخه مهندسی",
parentSlug: null,
leaf: true,
},
];
export const blogCategorySeeder = async (logger: Logger) => {
try {
const categories = [
{
title_fa: "الکترونیک",
slug: "electronics",
description: "آخرین اخبار و مقالات مربوط به محصولات الکترونیکی",
},
{
title_fa: "مد و پوشاک",
slug: "fashion",
description: "مقالات درباره مد، پوشاک و جدیدترین ترندهای فشن",
},
{
title_fa: "خانه و آشپزخانه",
slug: "home-kitchen",
description: "راهنمایی‌ها و مقالات مرتبط با محصولات خانه و آشپزخانه",
},
{
title_fa: "زیبایی و سلامت",
slug: "beauty-health",
description: "مقالات درباره زیبایی، مراقبت از پوست و سلامت",
},
{
title_fa: "بازی و سرگرمی",
slug: "gaming-entertainment",
description: "آخرین اخبار و مقالات مربوط به بازی‌ها و سرگرمی‌ها",
},
];
logger.info("start seeding Blog categories.");
await BlogCategoryModel.insertMany(categories);
logger.info("Blog categories seeded successfully");
// Create a map of slug to new ID for parent references
const slugToIdMap: { [key: string]: Types.ObjectId } = {};
// Separate root and child categories
const rootCategories = blogCategoriesData.filter((cat) => !cat.parentSlug);
const childCategories = blogCategoriesData.filter((cat) => cat.parentSlug);
// Insert root categories first
if (rootCategories.length > 0) {
const rootCategoryDocuments = rootCategories.map((category) => ({
title_fa: category.title_fa,
slug: category.slug,
description: category.description || "",
parent: null,
leaf: category.leaf,
}));
await BlogCategoryModel.insertMany(rootCategoryDocuments, { ordered: false });
// Query back inserted categories and create mapping by slug
const insertedRootCategories = await BlogCategoryModel.find({
slug: { $in: rootCategories.map((cat) => cat.slug) },
parent: null,
});
insertedRootCategories.forEach((inserted) => {
slugToIdMap[inserted.slug] = inserted._id;
});
}
// Insert child categories with mapped parent IDs
if (childCategories.length > 0) {
const childCategoryDocuments = childCategories
.map((category) => {
const parentId = category.parentSlug ? slugToIdMap[category.parentSlug] : null;
if (parentId) {
return {
title_fa: category.title_fa,
slug: category.slug,
description: category.description || "",
parent: parentId,
leaf: category.leaf,
};
}
return null; // Skip if parent not found
})
.filter((cat) => cat !== null);
if (childCategoryDocuments.length > 0) {
await BlogCategoryModel.insertMany(childCategoryDocuments, { ordered: false });
}
}
logger.info(`Blog categories seeded successfully (${blogCategoriesData.length} categories)`);
} catch (error) {
logger.error("Error seeding blog categories:", error);
}
+108 -218
View File
@@ -1,239 +1,129 @@
import { Types } from "mongoose";
import { Logger } from "../../core/logging/logger";
import { AdminModel } from "../../modules/admin/models/admin.model";
import { IBlog } from "../../modules/blog/models/Abstraction/IBlog";
import { BlogModel } from "../../modules/blog/models/blog.model";
import { BlogCategoryModel } from "../../modules/blog/models/blogCategory.model";
import { fakeImageGenerator } from "../data/product";
// Blog posts data extracted from dump/tablofajr/blogs.bson
// Mapping old category IDs to slugs:
// "69006695147da4f9c532e75c" -> "news"
// "690066f9147da4f9c532e75d" -> "notifs"
// "69008c5449df008eedfabd01" -> "Comparative-Analysis-of-MPCB-and-Fuse-in-Industrial-Electrical-Systems"
const blogsData = [
{
title_fa: "همه چیز درباره تابلو برق فشار قوی",
slug: "panel",
categorySlug: "news", // old category ID: 69006695147da4f9c532e75c
description:
"تابلو برق فشار قوی یکی از مهم‌ترین اجزای سیستم‌های توزیع و کنترل انرژی الکتریکی است. در صنایع بزرگ، نیروگاه‌ها و پست‌های برق، کنترل دقیق جریان و ولتاژ بدون استفاده از تابلوهای فشار قوی عملاً غیرممکن است. این تجهیزات علاوه بر ایمنی بالا، نقش حیاتی در پایداری و بهره‌وری شبکه‌های برق دارند.\nدر این مقاله به معرفی ساختار، کاربردها و نکات مهم در انتخاب و تولید تابلو برق فشار قوی می‌پردازیم.\n",
content:
"⚙️ تابلو برق فشار قوی چیست؟\n\nتابلو برق فشار قوی (High Voltage Switchgear) مجموعه‌ای از تجهیزات الکتریکی است که برای قطع و وصل، کنترل، حفاظت و اندازه‌گیری جریان‌های الکتریکی با ولتاژ بالاتر از ۱ کیلوولت استفاده می‌شود.\nاین تابلوها معمولاً شامل اجزایی مانند:\n\nکلید قدرت (Circuit Breaker)\n\nسکسیونر (Disconnector)\n\nترانس جریان و ولتاژ (CT / VT)\n\nرله‌های حفاظتی\n\nباس‌بار و کابل‌های انتقال\n\nمی‌باشند.\n\n🏭 کاربرد تابلو برق فشار قوی\n\nتابلوهای فشار قوی در محیط‌های زیر بیشترین کاربرد را دارند:\n\nپست‌های انتقال و توزیع برق\n\nنیروگاه‌های برق حرارتی، گازی و بادی\n\nصنایع فولاد، پتروشیمی، سیمان و معادن\n\nمجتمع‌های صنعتی بزرگ\n\nمراکز داده و زیرساخت‌های حیاتی\n\nدر این محیط‌ها، تابلو برق فشار قوی وظیفه دارد جریان‌های سنگین را با ایمنی کامل و حداقل تلفات انرژی مدیریت کند.\n\n🔐 اهمیت کیفیت در طراحی و تولید\n\nدر تولید تابلو برق فشار قوی، کوچک‌ترین خطا می‌تواند منجر به آسیب‌های جانی و مالی گسترده شود. به همین دلیل انتخاب متریال استاندارد، طراحی مهندسی دقیق و رعایت استانداردهای بین‌المللی (مانند IEC 62271) اهمیت ویژه‌ای دارد.\nشرکت‌هایی که در این حوزه فعالیت می‌کنند باید در مراحل زیر دقت بالایی داشته باشند:\n\nطراحی مدارات قدرت و کنترل\n\nعایق‌کاری و تهویه مناسب\n\nتست‌های فشار، عایق و ایمنی\n\nمونتاژ دقیق و کنترل کیفیت نهایی\n\n⚡ انواع تابلو برق فشار قوی\n\nتابلو فشار قوی هوایی (AIS) – مبتنی بر عایق هوا، رایج در پست‌های باز\n\nتابلو فشار قوی گازی (GIS) – عایق گازی SF6، مناسب فضاهای محدود\n\nتابلو فشار قوی ترکیبی (Hybrid) – ترکیبی از دو فناوری بالا\n\nتابلو فشار قوی قابل حمل (Portable Switchgear) برای پروژه‌های موقت و سایت‌های پیمانکاری\n\n🧰 ویژگی‌های یک تابلو برق با کیفیت\n\nطراحی مطابق با استانداردهای روز دنیا\n\nمقاومت بالا در برابر رطوبت، گرد و غبار و خوردگی\n\nسیستم تهویه و خنک‌سازی ایمن\n\nقابلیت مانیتورینگ و کنترل از راه دور\n\nماژولار بودن برای تعمیر یا ارتقاء آسان\n\n🏆 سخن پایانی\n\nتابلو برق فشار قوی، قلب تپنده هر سیستم صنعتی است. انتخاب و استفاده از تابلوهای باکیفیت، نه‌تنها از بروز خطاها و خاموشی‌های ناگهانی جلوگیری می‌کند، بلکه بهره‌وری انرژی و طول عمر تجهیزات را نیز افزایش می‌دهد.\nشرکت [نام شرکت شما] با بهره‌گیری از تیم مهندسی مجرب و تکنولوژی روز دنیا، در زمینه طراحی و تولید تابلو برق فشار قوی مطابق با استانداردهای بین‌المللی فعالیت می‌کند و آماده همکاری با صنایع مختلف کشور است.",
tags: ["تابلو برق"],
imageUrl: "https://shinan.storage.iran.liara.space/media-file/3cbe8e104541f365363128ad7f83b6aefbfba5827698da2c.jpg",
view: 12,
},
{
title_fa: "تفاوت کلید حرارتی (MPCB) با فیوز چیست؟",
slug: "tafavot-ha",
categorySlug: "notifs", // old category ID: 690066f9147da4f9c532e75d
description:
"تفاوت کلید حرارتی (MPCB) با فیوز چیست؟\n\nسیستم‌های برق صنعتی، حفاظت از تجهیزات و موتورهای الکتریکی اهمیت ویژه‌ای دارد.\nکلید حرارتی (MPCB) یک تجهیز هوشمند است که با دقت بالا از موتور در برابر اضافه‌بار، اتصال کوتاه و خطاهای حرارتی محافظت می‌کند.\n",
content:
"در سیستم‌های برق صنعتی، حفاظت از تجهیزات اهمیت زیادی دارد. کلید حرارتی (MPCB) ابزاری هوشمند برای حفاظت از موتور در برابر اضافه‌بار و اتصال کوتاه است. در مقابل، فیوز فقط در صورت بروز اضافه جریان شدید عمل می‌کند و پس از قطع، نیاز به تعویض دارد.\nکلید حرارتی با قابلیت تنظیم جریان و ریست مجدد، گزینه‌ای اقتصادی‌تر و مطمئن‌تر برای مدارهای موتوری محسوب می‌شود.\n\nکلید با قابلیت تنظیم جریان نامی، امکان هماهنگی دقیق با توان موتور را فراهم می‌کند.\nدر صورت بروز اضافه‌بار، کلید حرارتی به‌صورت خودکار مدار را قطع کرده و پس از رفع خطا، بدون نیاز به تعویض مجدد قابل استفاده است.\nدر مقابل، فیوز تنها در زمان عبور جریان بیش از حد مجاز عمل می‌کند و پس از عملکرد، باید تعویض شود.\nاز این رو، استفاده از فیوز در مدارهای موتوری باعث افزایش هزینه و زمان تعمیرات می‌شود.\nکلید حرارتی علاوه بر حفاظت مطمئن، قابلیت ریست سریع و نصب آسان در تابلوهای صنعتی را دارد.\nاین ویژگی‌ها باعث شده تا MPCB جایگزینی اقتصادی و ایمن برای فیوز در سیستم‌های صنعتی باشد.\nبرندهای معتبری مانند هیوندا، اشنایدر و اسپکتر نمونه‌های باکیفیت از این کلیدها را تولید می‌کنند.\nدر مجموع، کلید حرارتی راه‌حلی حرفه‌ای برای افزایش ایمنی، کاهش خرابی و بهینه‌سازی عملکرد موتورهای الکتریکی است.",
tags: [],
imageUrl: "https://shinan.storage.iran.liara.space/media-file/211b7a5c55dc6edd7d90b17a44361c104e3c621a90ae0283.png",
view: 15,
},
{
title_fa: "نقش تابلو برق در افزایش ایمنی و بهره‌وری صنایع",
slug: "panel2",
categorySlug: "news", // old category ID: 69006695147da4f9c532e75c
description:
"در هر واحد صنعتی، از کارخانه‌های بزرگ گرفته تا پست‌های انتقال برق، ایمنی و بهره‌وری انرژی دو عامل کلیدی برای تداوم تولید و کاهش هزینه‌ها هستند.\nیکی از تجهیزاتی که نقش مستقیم در تحقق این دو هدف دارد، تابلو برق صنعتی است.\nاین مجموعه تجهیزات، قلب کنترل و توزیع انرژی در یک مجموعه محسوب می‌شود و عملکرد صحیح آن، تضمین‌کننده پایداری کل سیستم است.\n\nشرکت تابلو فجر مرکزی به‌عنوان یکی از تولیدکنندگان پیشرو در زمینه طراحی و ساخت تابلو برق فشار قوی و متوسط، همواره در تلاش است تا محصولاتی مطابق با جدیدترین استانداردهای جهانی و نیازهای صنعتی کشور ارائه دهد.",
content:
"🔐 تابلو برق؛ محافظ هوشمند تجهیزات و کارکنان\n\nیکی از مهم‌ترین وظایف تابلو برق، حفاظت از تجهیزات و نیروی انسانی در برابر خطاهای الکتریکی است.\nدر زمان وقوع حوادثی مانند اتصال کوتاه، اضافه جریان یا نوسانات ولتاژ، تابلو برق با بهره‌گیری از رله‌های حفاظتی و کلیدهای قدرت، به‌صورت خودکار جریان را قطع کرده و از بروز خسارات سنگین جلوگیری می‌کند.\n\nدر محیط‌های صنعتی بزرگ، هر ثانیه قطعی برق یا آسیب به تجهیزات می‌تواند ضررهای میلیون‌تومانی ایجاد کند. به همین دلیل استفاده از تابلو برق‌های طراحی‌شده بر اساس نیاز واقعی هر پروژه، اهمیت زیادی دارد.\n\n🏭 تأثیر تابلو برق در افزایش بهره‌وری\n\nیک تابلو برق مدرن، فراتر از وظیفه‌ی حفاظتی خود عمل می‌کند.\nبه کمک سیستم‌های کنترل و مانیتورینگ، می‌توان وضعیت ولتاژ، جریان، دما و بار مصرفی را به‌صورت لحظه‌ای بررسی کرد.\nاین داده‌ها به مدیران فنی کمک می‌کند تا مصرف انرژی را بهینه و خرابی‌های احتمالی را پیش‌بینی کنند.\n\nشرکت تابلو فجر مرکزی با طراحی تابلوهایی که قابلیت اتصال به سیستم‌های اتوماسیون صنعتی (SCADA و PLC) را دارند، گامی مؤثر در جهت هوشمندسازی زیرساخت‌های برقی برداشته است.\n\n⚙️ استانداردها و کیفیت ساخت\n\nتولید تابلو برق بدون رعایت استانداردهای بین‌المللی، خطرات جدی به همراه دارد.\nتابلو فجر مرکزی در تمامی مراحل طراحی، تولید و تست نهایی، از استانداردهای زیر پیروی می‌کند:\n\nIEC 62271 برای تابلو فشار قوی\n\nIEC 61439 برای تابلو فشار ضعیف\n\nISIRI 3431 برای الزامات ایمنی و عایق‌بندی\n\nتمامی تابلوها در محیط‌های کنترل‌شده تست می‌شوند تا از نظر مقاومت در برابر حرارت، رطوبت و ارتعاش در بالاترین سطح عملکرد قرار گیرند.\n\n🧰 خدمات شرکت تابلو فجر مرکزی\n\nشرکت تابلو فجر مرکزی علاوه بر تولید تابلو برق فشار قوی و متوسط، خدمات زیر را نیز ارائه می‌دهد:\n\nطراحی سفارشی بر اساس نقشه‌های پروژه\n\nمشاوره مهندسی و انتخاب بهینه تجهیزات\n\nاجرای تست‌های عملکرد و ایمنی قبل از تحویل\n\nپشتیبانی فنی و خدمات پس از فروش\n\nبازسازی و بهینه‌سازی تابلوهای قدیمی\n\nاین مجموعه با رویکردی مهندسی و تجربه‌ای چند ده‌ساله، همواره هدف خود را رضایت مشتری و پایداری عملکرد در شرایط سخت صنعتی قرار داده است.\n\n🌟 نتیجه‌گیری\n\nتابلو برق فقط یک جعبه‌ی فلزی پر از تجهیزات نیست؛ بلکه مرکز فرمان سیستم‌های برقی است که امنیت، بازدهی و تداوم تولید را تضمین می‌کند.\nدر انتخاب و خرید تابلو برق، تجربه‌ی سازنده و کیفیت طراحی اهمیت حیاتی دارد.\n\nشرکت تابلو فجر مرکزی با تکیه بر دانش فنی مهندسان ایرانی، فناوری روز دنیا و تعهد به کیفیت، آماده است تا در مسیر توسعه صنعتی کشور نقش‌آفرینی کند و نیاز صنایع به تابلو برق‌های ایمن و پایدار را برطرف سازد.",
tags: [],
imageUrl: "https://shinan.storage.iran.liara.space/media-file/d626c894df92e31514b8f15abe13e814195be4ea52089707.webp",
view: 19,
},
{
title_fa: "علم ",
slug: "scientific",
categorySlug: "Comparative-Analysis-of-MPCB-and-Fuse-in-Industrial-Electrical-Systems", // old category ID: 69008c5449df008eedfabd01
description: "Comparative Analysis of MPCB and Fuse in Industrial Electrical Systems",
content:
"Comparative Analysis of MPCB and Fuse in Industrial Electrical Systems\n\nIn industrial electrical systems, equipment protection is a crucial aspect of ensuring operational safety and reliability.\nA Motor Protection Circuit Breaker (MPCB) is an intelligent protective device designed to safeguard electric motors from overload, short circuit, and phase failure conditions.\nThe MPCB incorporates both thermal and magnetic trip mechanisms, allowing precise adjustment of the rated current according to the motor's specifications.\nWhen an overload occurs, the MPCB automatically interrupts the circuit, preventing motor damage, and can be reset manually after fault clearance — eliminating the need for replacement.\nConversely, a fuse provides protection only against excessive current; once it operates, it must be replaced, leading to maintenance delays and higher operational costs.\nMoreover, MPCBs offer enhanced coordination with motor starters, compact installation, and reusable functionality.\nThese advantages make MPCBs a more economical, reliable, and efficient solution compared to traditional fuse-based systems in modern industrial applications.\nRenowned manufacturers such as Hyundai, Schneider Electric, and Specter provide high-quality MPCBs optimized for diverse industrial requirements.\nOverall, the MPCB represents a significant technological advancement in motor protection, operational safety, and energy system efficiency.",
tags: ["Comparative Analysis of MPCB and Fuse in Industrial Electrical Systems"],
imageUrl: "https://shinan.storage.iran.liara.space/media-file/71eb632eda4038fab0ae60f5954f7e0b1b0fa2e8f528ab0a.jpg",
view: 23,
},
];
export const blogSeeder = async (logger: Logger) => {
try {
const admin = await AdminModel.findOne({ userName: "defaultAdmin" });
const categories = await BlogCategoryModel.find();
logger.info("start seeding Blogs");
// Get default admin for author field
const admin = await AdminModel.findOne({ userName: "defaultAdmin" });
if (!admin) {
logger.error("Default admin not found. Please run the defaultAdmin seeder first.");
throw new Error("Default admin not found. Please run the defaultAdmin seeder first.");
}
// Get all categories from database
const categories = await BlogCategoryModel.find();
if (categories.length === 0) {
logger.error("No blog categories found. Please run the blogCategorySeeder first.");
throw new Error("No blog categories found. Please run the blogCategorySeeder first.");
}
const blogs: IBlog[] = [];
// Create category mapping by slug
const categoryMapBySlug: { [key: string]: Types.ObjectId } = {};
categories.forEach((category) => {
switch (category.slug) {
case "electronics":
blogs.push(
{
title_fa: "برترین گوشی‌های هوشمند سال ۲۰۲۴",
slug: "top-smartphones-2024",
category: category._id,
description: "مروری بر بهترین گوشی‌های هوشمند عرضه شده در سال ۲۰۲۴.",
content:
"در سال ۲۰۲۴، شرکت‌های بزرگ تکنولوژی گوشی‌های هوشمند جدیدی معرفی کردند که شامل ویژگی‌ها و قابلیت‌های پیشرفته‌ای هستند...",
author: admin!._id,
tags: ["گوشی", "هوشمند", "تکنولوژی"],
imageUrl: fakeImageGenerator(),
view: 0,
},
{
title_fa: "راهنمای خرید لپ‌تاپ مناسب برای دانشجویان",
slug: "laptop-buying-guide-for-students",
category: category._id,
description: "نکاتی که دانشجویان باید هنگام خرید لپ‌تاپ مد نظر داشته باشند.",
content:
"برای دانشجویان، انتخاب لپ‌تاپ مناسب اهمیت زیادی دارد. عواملی مانند وزن، عمر باتری، قدرت پردازش و قیمت باید در نظر گرفته شوند...",
author: admin!._id,
tags: ["لپ‌تاپ", "خرید", "راهنما"],
imageUrl: fakeImageGenerator(),
view: 0,
},
{
title_fa: "تکنولوژی‌های نوین در تلویزیون‌های هوشمند",
slug: "innovative-technologies-in-smart-tvs",
category: category._id,
description: "بررسی آخرین تکنولوژی‌های موجود در تلویزیون‌های هوشمند.",
content:
"تلویزیون‌های هوشمند امروزه با تکنولوژی‌های پیشرفته‌ای مانند 4K، OLED، HDR و سیستم‌های هوش مصنوعی مجهز شده‌اند که تجربه تماشای شما را بهبود می‌بخشند...",
author: admin!._id,
tags: ["تلویزیون", "هوشمند", "تکنولوژی"],
imageUrl: fakeImageGenerator(),
view: 0,
},
);
break;
case "fashion":
blogs.push(
{
title_fa: "جدیدترین ترندهای مد بهار ۲۰۲۴",
slug: "latest-spring-fashion-trends-2024",
category: category._id,
description: "مروری بر جدیدترین ترندهای مد فصل بهار ۲۰۲۴.",
content:
"فصل بهار ۲۰۲۴ با ورود رنگ‌های شاد، الگوهای گلدار و پارچه‌های سبک همراه است. این ساله تاکید بیشتری بر روی راحتی و استایل کلاسیک دیده می‌شود...",
author: admin!._id,
tags: ["مد", "بهار", "ترند"],
imageUrl: fakeImageGenerator(),
view: 0,
},
{
title_fa: "چگونه لباس مناسب را برای یک مراسم رسمی انتخاب کنیم",
slug: "choosing-suitable-clothes-for-formal-events",
category: category._id,
description: "نکاتی برای انتخاب لباس مناسب جهت شرکت در مراسم‌های رسمی.",
content:
"انتخاب لباس مناسب برای مراسم‌های رسمی می‌تواند تاثیر زیادی بر روی حضور شما داشته باشد. در این مقاله به بررسی نکاتی که باید هنگام انتخاب لباس رسمی در نظر بگیرید می‌پردازیم...",
author: admin!._id,
tags: ["لباس", "رسمی", "راهنما"],
imageUrl: fakeImageGenerator(),
view: 0,
},
{
title_fa: "استایل‌های مردانه محبوب در سال ۲۰۲۴",
slug: "popular-mens-fashion-styles-2024",
category: category._id,
description: "بررسی استایل‌های محبوب و جدید در مد مردانه سال ۲۰۲۴.",
content:
"مد مردانه در سال ۲۰۲۴ با ترکیب کلاسیک و مدرن، سبک‌های متنوعی را ارائه می‌دهد. از کت و شلوارهای شیک گرفته تا استایل‌های کژوال و خیابانی، انتخاب‌های زیادی وجود دارد...",
author: admin!._id,
tags: ["مد", "مردانه", "استایل"],
imageUrl: fakeImageGenerator(),
view: 0,
},
);
break;
case "home-kitchen":
blogs.push(
{
title_fa: "بهترین لوازم آشپزخانه برای هر خانه",
slug: "best-kitchen-appliances-for-every-home",
category: category._id,
description: "نکاتی برای انتخاب بهترین لوازم آشپزخانه مناسب برای خانه‌های مختلف.",
content:
"لوازم آشپزخانه نقش مهمی در راحتی و کارایی خانه دارند. از ماشین لباسشویی گرفته تا فرهای هوشمند، انتخاب صحیح می‌تواند تجربه آشپزی شما را بهبود بخشد...",
author: admin!._id,
tags: ["آشپزخانه", "لوازم", "راهنما"],
imageUrl: fakeImageGenerator(),
view: 0,
},
{
title_fa: "نکات طراحی داخلی خانه برای فضاهای کوچک",
slug: "interior-design-tips-for-small-spaces",
category: category._id,
description: "راهنمایی‌هایی برای طراحی داخلی مناسب فضاهای کوچک خانه.",
content:
"طراحی داخلی فضاهای کوچک نیازمند استفاده بهینه از فضا و انتخاب مبلمان چندکاره است. در این مقاله به بررسی نکاتی که می‌تواند به شما در طراحی منزل کوچک کمک کند می‌پردازیم...",
author: admin!._id,
tags: ["طراحی داخلی", "خانه کوچک", "نکات"],
imageUrl: fakeImageGenerator(),
view: 0,
},
{
title_fa: "روش‌های سازماندهی و بهینه‌سازی فضای آشپزخانه",
slug: "organizing-and-optimizing-kitchen-space",
category: category._id,
description: "نکاتی برای سازماندهی و بهینه‌سازی فضای آشپزخانه.",
content:
"یک آشپزخانه سازماندهی شده نه تنها زیبایی دارد بلکه کارایی بیشتری نیز دارد. از استفاده از کشوهای چندمنظوره تا نگهداری مناسب ابزارهای آشپزی، این مقاله به شما کمک می‌کند تا آشپزخانه‌تان را بهینه کنید...",
author: admin!._id,
tags: ["آشپزخانه", "سازماندهی", "بهینه‌سازی"],
imageUrl: fakeImageGenerator(),
view: 0,
},
);
break;
case "beauty-health":
blogs.push(
{
title_fa: "رازهای داشتن پوستی سالم و درخشان",
slug: "secrets-to-healthy-glowing-skin",
category: category._id,
description: "نکاتی برای داشتن پوستی سالم، درخشان و زیبا.",
content:
"پوستی سالم نیازمند مراقبت‌های منظم و استفاده از محصولات مناسب است. در این مقاله به بررسی رازهایی که می‌تواند به شما کمک کند پوستی درخشان و زیبا داشته باشید می‌پردازیم...",
author: admin!._id,
tags: ["زیبایی", "سلامت پوست", "راهنما"],
imageUrl: fakeImageGenerator(),
view: 0,
},
{
title_fa: "فواید ورزش منظم برای سلامت روانی",
slug: "benefits-of-regular-exercise-for-mental-health",
category: category._id,
description: "بررسی فواید ورزش منظم بر سلامت روانی انسان.",
content:
"ورزش نه تنها به بهبود سلامت جسمی کمک می‌کند بلکه تاثیرات مثبت زیادی بر سلامت روانی دارد. از کاهش استرس تا افزایش اعتماد به نفس، در این مقاله به بررسی این فواید می‌پردازیم...",
author: admin!._id,
tags: ["ورزش", "سلامت روان", "فواید"],
imageUrl: fakeImageGenerator(),
view: 0,
},
{
title_fa: "مراقبت از موهای خشک و آسیب‌دیده",
slug: "caring-for-dry-damaged-hair",
category: category._id,
description: "نکاتی برای مراقبت و ترمیم موهای خشک و آسیب‌دیده.",
content:
"موهای خشک و آسیب‌دیده نیازمند مراقبت ویژه‌ای هستند. از استفاده از شامپوهای مرطوب‌کننده تا ماسک‌های تغذیه‌کننده، در این مقاله به بررسی روش‌هایی که می‌تواند به ترمیم و تقویت موهای شما کمک کند می‌پردازیم...",
author: admin!._id,
tags: ["مو", "زیبایی", "مراقبت"],
imageUrl: fakeImageGenerator(),
view: 0,
},
);
break;
case "gaming-entertainment":
blogs.push(
{
title_fa: "پربازترین بازی‌های ویدیویی سال ۲۰۲۴",
slug: "most-played-video-games-2024",
category: category._id,
description: "مروری بر پربازترین و محبوب‌ترین بازی‌های ویدیویی عرضه شده در سال ۲۰۲۴.",
content:
"سال ۲۰۲۴ با عرضه بازی‌های ویدیویی هیجان‌انگیزی همراه بوده است. از بازی‌های اکشن گرفته تا RPGها، این مقاله به معرفی پربازترین و محبوب‌ترین بازی‌های سال می‌پردازد...",
author: admin!._id,
tags: ["بازی", "ویدیوئی", "مرور"],
imageUrl: fakeImageGenerator(),
view: 0,
},
{
title_fa: "نکاتی برای بهبود مهارت‌های بازی‌های رقابتی",
slug: "tips-to-improve-competitive-gaming-skills",
category: category._id,
description: "راهنمایی‌هایی برای ارتقاء مهارت‌ها و موفقیت در بازی‌های رقابتی.",
content:
"بازی‌های رقابتی نیازمند تمرین مداوم و استراتژی‌های هوشمندانه هستند. در این مقاله به بررسی نکاتی می‌پردازیم که می‌تواند به شما کمک کند مهارت‌های بازی رقابتی خود را بهبود ببخشید...",
author: admin!._id,
tags: ["بازی", "رقابتی", "مهارت"],
imageUrl: fakeImageGenerator(),
view: 0,
},
{
title_fa: "بررسی جدیدترین کنسول‌های بازی",
slug: "review-of-latest-gaming-consoles",
category: category._id,
description: "مروری بر جدیدترین کنسول‌های بازی و ویژگی‌های آن‌ها.",
content:
"کنسول‌های بازی جدید با تکنولوژی‌های پیشرفته‌ای عرضه شده‌اند که تجربه بازی را به سطح بالاتری می‌برند. در این مقاله به بررسی ویژگی‌ها و عملکرد جدیدترین کنسول‌های بازی می‌پردازیم...",
author: admin!._id,
tags: ["کنسول", "بازی", "مرور"],
imageUrl: fakeImageGenerator(),
view: 0,
},
);
break;
default:
console.warn(`No seeder defined for category slug: ${category.slug}`);
break;
}
categoryMapBySlug[category.slug] = category._id;
});
// Insert Blogs
await BlogModel.insertMany(blogs);
logger.info("Blogs seeded successfully");
// Prepare blog data from hardcoded array
const blogDocuments = blogsData
.map((blog) => {
// Skip if required fields are missing
if (!blog.title_fa || !blog.slug || !blog.categorySlug) {
logger.warn(`Skipping blog with missing required fields: ${blog.title_fa || blog.slug}`);
return null;
}
// Map category from slug to category ID
const categoryId = categoryMapBySlug[blog.categorySlug];
if (!categoryId) {
logger.warn(`Category not found for blog ${blog.title_fa || blog.slug} (category slug: ${blog.categorySlug}), skipping`);
return null;
}
return {
title_fa: blog.title_fa,
slug: blog.slug,
category: categoryId,
description: blog.description || "",
content: blog.content || blog.description || "",
author: admin._id, // Use default admin as author
tags: Array.isArray(blog.tags) ? blog.tags : [],
imageUrl: blog.imageUrl || fakeImageGenerator(),
view: blog.view || 0,
};
})
.filter((blog) => blog !== null);
// Bulk insert blogs
if (blogDocuments.length > 0) {
await BlogModel.insertMany(blogDocuments, { ordered: false });
logger.info(`Blogs seeded successfully (${blogDocuments.length} blogs)`);
} else {
logger.warn("No valid blogs to seed");
}
} catch (error) {
logger.error("Error seeding blogs:", error);
throw error;
}
};
+78 -105
View File
@@ -5,134 +5,107 @@ import { Logger } from "../../core/logging/logger";
import { BrandModel } from "../../modules/brand/models/brand.model";
import { CategoryModel } from "../../modules/category/models/category.model";
interface IBrandSeed {
export interface IBrandSeedData {
title_fa: string;
title_en: string;
// category: Types.ObjectId;
category: string | null;
status: StatusEnum;
description: string;
logoUrl: string;
images: string[];
website_link: string;
}
const brandSeedMap: { [key: string]: IBrandSeed[] } = {
"گوشی موبایل": [
{ title_fa: "سامسونگ", title_en: "Samsung" },
{ title_fa: "اپل", title_en: "Apple" },
{ title_fa: "شیائومی", title_en: "Xiaomi" },
{ title_fa: "هواوی", title_en: "Huawei" },
{ title_fa: "نوکیا", title_en: "Nokia" },
],
تبلت: [
{ title_fa: "سامسونگ", title_en: "Samsung" },
{ title_fa: "اپل", title_en: "Apple" },
{ title_fa: "مایکروسافت", title_en: "Microsoft" },
{ title_fa: "لنوو", title_en: "Lenovo" },
{ title_fa: "هواوی", title_en: "Huawei" },
],
"خودکار و خودنویس": [
{ title_fa: "پایلوت", title_en: "Pilot" },
{ title_fa: "شیفر", title_en: "Sheaffer" },
{ title_fa: "پارکر", title_en: "Parker" },
{ title_fa: "لامی", title_en: "Lamy" },
],
لامپ: [
{ title_fa: "فیلیپس", title_en: "Philips" },
{ title_fa: "شیائومی", title_en: "Xiaomi" },
{ title_fa: "اپتونیک", title_en: "Optonique" },
{ title_fa: "نورکالا", title_en: "NoorKala" },
],
اسپیکر: [
{ title_fa: "جی بی ال", title_en: "JBL" },
{ title_fa: "سونی", title_en: "Sony" },
],
تلویزیون: [
{ title_fa: "سامسونگ", title_en: "Samsung" },
{ title_fa: "ال جی", title_en: "LG" },
{ title_fa: "سونی", title_en: "Sony" },
{ title_fa: "پاناسونیک", title_en: "Panasonic" },
],
"دوربین عکاسی": [
{ title_fa: "سامسونگ", title_en: "Samsung" },
{ title_fa: "ال جی", title_en: "LG" },
{ title_fa: "سونی", title_en: "Sony" },
{ title_fa: "پاناسونیک", title_en: "Nikon" },
],
"ساعت هوشمند": [
{ title_fa: "شیائومی", title_en: "Xiaomi" },
{ title_fa: "سامسونگ", title_en: "Samsung" },
{ title_fa: "اپل", title_en: "Apple" },
{ title_fa: "هواوی", title_en: "Huawei" },
],
هدفون: [
{ title_fa: "سامسونگ", title_en: "Samsung" },
{ title_fa: "ال جی", title_en: "LG" },
{ title_fa: "سونی", title_en: "Sony" },
{ title_fa: "بوز", title_en: "Bose" },
],
"پاور بانک": [
{ title_fa: "سامسونگ", title_en: "Samsung" },
{ title_fa: "آنکر", title_en: "Anker" },
],
"لپ تاپ": [
{ title_fa: "ایسر", title_en: "Acer" },
{ title_fa: "ایسوس", title_en: "Asus" },
{ title_fa: "اپل", title_en: "Apple" },
{ title_fa: "لنوو", title_en: "Lenovo" },
],
"لباس زنانه": [
{ title_fa: "زارا", title_en: "Zara" },
{ title_fa: "H&M", title_en: "H&M" },
{ title_fa: "گوچی", title_en: "Gucci" },
{ title_fa: "دیور", title_en: "Dior" },
],
"لوازم آشپزخانه": [
{ title_fa: "بوش", title_en: "Bosch" },
{ title_fa: "فیلیپس", title_en: "Philips" },
{ title_fa: "سامسونگ", title_en: "Samsung" },
{ title_fa: "پاناسونیک", title_en: "Panasonic" },
{ title_fa: "ال جی", title_en: "LG" },
],
"پلی استیشن": [{ title_fa: "سونی", title_en: "Sony" }],
متفرقه: [{ title_fa: "متفرقه", title_en: "malicious" }],
};
// Brand seed data extracted from database
// To update this data, run: npx ts-node src/db/seeders/extractBrands.ts
const brandSeedData: IBrandSeedData[] = [
// Add your brand data here
// Example format:
// {
// title_fa: "سامسونگ",
// title_en: "Samsung",
// category: null, // Will be mapped to actual category ID during seeding
// status: StatusEnum.Approved,
// description: "",
// logoUrl: "",
// images: [],
// website_link: "",
// },
];
export const seedBrands = async (logger: Logger) => {
try {
logger.info("Brands seeding started");
const categoryMap: { [key: string]: Types.ObjectId } = {};
// Fetch all categories once
// Build category map by title and ID for mapping category references to IDs
const categories = await CategoryModel.find();
const categoryMapByTitle: { [key: string]: Types.ObjectId } = {};
const categoryMapById: { [key: string]: Types.ObjectId } = {};
categories.forEach((category) => {
categoryMap[category.title_fa] = category._id;
categoryMapByTitle[category.title_fa] = category._id;
categoryMapById[category._id.toString()] = category._id;
});
const brandDocuments = [];
// Prepare brand documents in bulk
for (const [categoryTitle, brands] of Object.entries(brandSeedMap)) {
const categoryId = categoryMap[categoryTitle];
// Use hardcoded seed data (extracted from database using extractBrands.ts)
if (brandSeedData.length > 0) {
logger.info(`Using ${brandSeedData.length} brands from seed data`);
if (categoryId) {
for (const brand of brands) {
const { title_fa, title_en } = brand;
brandDocuments.push({
title_fa,
title_en,
category: categoryId,
status: StatusEnum.Approved,
});
for (const brandSeed of brandSeedData) {
let categoryId: Types.ObjectId | null = null;
// Map category if provided (could be category title_fa or ObjectId string)
if (brandSeed.category) {
// Try to find by title first
if (categoryMapByTitle[brandSeed.category]) {
categoryId = categoryMapByTitle[brandSeed.category];
} else {
// Try to use as ObjectId
try {
const objectId = new Types.ObjectId(brandSeed.category);
if (categoryMapById[objectId.toString()]) {
categoryId = objectId;
} else {
logger.warn(`Category not found for brand ${brandSeed.title_fa} (category: ${brandSeed.category})`);
}
} catch (_e) {
logger.warn(`Invalid category reference for brand ${brandSeed.title_fa}: ${brandSeed.category}`);
}
}
}
} else {
logger.warn(`Category '${categoryTitle}' not found`);
brandDocuments.push({
title_fa: brandSeed.title_fa,
title_en: brandSeed.title_en,
category: categoryId,
status: brandSeed.status,
description: brandSeed.description || "",
logoUrl: brandSeed.logoUrl || "",
images: brandSeed.images || [],
website_link: brandSeed.website_link || "",
});
}
} else {
logger.warn("No brand seed data found. Run 'npx ts-node src/db/seeders/extractBrands.ts' to extract brands from database.");
}
// Bulk insert
// Check if brands already exist
const existingCount = await BrandModel.countDocuments({ deleted: { $ne: true } });
if (brandDocuments.length > 0) {
await BrandModel.insertMany(brandDocuments);
if (existingCount === 0) {
await BrandModel.insertMany(brandDocuments);
logger.info(`Brands seeded successfully (${brandDocuments.length} brands)`);
} else {
logger.info(`Brands already exist in database (${existingCount} brands). Skipping seed.`);
}
} else {
logger.warn("No brands to seed");
}
logger.info("Brands seeded successfully");
} catch (error) {
logger.error("Error seeding brands:", error);
throw error;
}
};
-111
View File
@@ -1,111 +0,0 @@
import { Logger } from "../../core/logging/logger";
import { InputTypeEnum, ThemeModel } from "../../modules/category/models/theme.model";
import { ThemeValueModel } from "../../modules/category/models/themeValue.model";
const colors = [
{ name: "قرمز", value: "#FF0000" },
{ name: "سبز", value: "#00FF00" },
{ name: "آبی", value: "#0000FF" },
{ name: "مشکی", value: "#000000" },
{ name: "سفید", value: "#FFFFFF" },
{ name: "زرد", value: "#FFFF00" },
{ name: "بنفش", value: "#800080" },
{ name: "نارنجی", value: "#FFA500" },
{ name: "صورتی", value: "#FFC0CB" },
{ name: "خاکستری", value: "#808080" },
];
const sizes = [
{ name: "XS", value: "XS" },
{ name: "S", value: "S" },
{ name: "M", value: "M" },
{ name: "L", value: "L" },
{ name: "XL", value: "XL" },
{ name: "XXL", value: "XXL" },
{ name: "3XL", value: "3XL" },
{ name: "4XL", value: "4XL" },
{ name: "5XL", value: "5XL" },
{ name: "6XL", value: "6XL" },
];
const meterages = [
{ name: "1", value: "1" },
{ name: "2", value: "2" },
{ name: "3", value: "3" },
{ name: "4", value: "4" },
{ name: "5", value: "5" },
{ name: "10", value: "10" },
{ name: "15", value: "15" },
{ name: "20", value: "20" },
{ name: "25", value: "25" },
{ name: "30", value: "30" },
];
const seedColors = async () => {
try {
console.log("Seeding colors...");
let colorTheme = await ThemeModel.findOne({ title: "رنگ" });
if (!colorTheme) {
colorTheme = await ThemeModel.create({ title: "رنگ", inputType: InputTypeEnum.Color });
}
await ThemeValueModel.deleteMany({ theme: colorTheme._id });
for (const color of colors) {
await ThemeValueModel.create({ theme: colorTheme._id, name: color.name, value: color.value });
}
console.log("Colors seeded successfully");
} catch (error) {
console.error("Error seeding colors:", error);
}
};
const seedSizes = async () => {
try {
console.log("Seeding sizes...");
let sizeTheme = await ThemeModel.findOne({ title: "سایز" });
if (!sizeTheme) {
sizeTheme = await ThemeModel.create({ title: "سایز", inputType: InputTypeEnum.Text });
}
await ThemeValueModel.deleteMany({ theme: sizeTheme._id });
for (const size of sizes) {
await ThemeValueModel.create({ theme: sizeTheme._id, name: size.name, value: size.value });
}
console.log("Sizes seeded successfully");
} catch (error) {
console.error("Error seeding sizes:", error);
}
};
const seedMeterages = async () => {
try {
console.log("Seeding meterages...");
let meterageTheme = await ThemeModel.findOne({ title: "متراژ" });
if (!meterageTheme) {
meterageTheme = await ThemeModel.create({ title: "متراژ", inputType: InputTypeEnum.Number });
}
await ThemeValueModel.deleteMany({ theme: meterageTheme._id });
for (const meterage of meterages) {
await ThemeValueModel.create({ theme: meterageTheme._id, name: meterage.name, value: meterage.value });
}
console.log("Meterages seeded successfully");
} catch (error) {
console.error("Error seeding meterages:", error);
}
};
// Seed function
export const seedCatVariant = async (logger: Logger) => {
try {
logger.info("Categories variant seeding started");
await seedColors();
await seedSizes();
await seedMeterages();
console.log("Seeding complete!");
} catch (error) {
logger.error("Seeding error:", error);
}
};
+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;
}
};
+2 -2
View File
@@ -56,8 +56,8 @@ const createDefaultAdmin = async (logger: Logger) => {
owner: admin._id.toString(),
ownerRef: OwnerRef.ADMIN,
shipmentMethod: shipments,
shopDescription: "فروشگاه شینان",
shopName: "شینان",
shopDescription: "فروشگاه فجر تابلو مرکزی",
shopName: "فجر تابلو مرکزی",
});
logger.info("Default admin user and native shop created", "SEED");
} else {
+101
View File
@@ -0,0 +1,101 @@
import "reflect-metadata";
import * as fs from "fs";
import * as path from "path";
import { config } from "dotenv";
import mongoose from "mongoose";
config();
import { StatusEnum } from "../../common/enums/status.enum";
import { BrandModel } from "../../modules/brand/models/brand.model";
import { connectMongo } from "../connection";
const extractBrands = async () => {
try {
await connectMongo();
console.log("Connected to MongoDB");
// Get all brands from database
const brands = await BrandModel.find({ deleted: { $ne: true } }).lean();
console.log(`Found ${brands.length} brands`);
// Format brands for seeder (TypeScript format)
const brandSeedData = brands.map((brand) => {
const categoryStr = brand.category ? `"${brand.category.toString()}"` : "null";
const statusValue = brand.status || StatusEnum.Pending;
const statusStr = `StatusEnum.${statusValue}`;
const imagesStr =
Array.isArray(brand.images) && brand.images.length > 0
? `[${brand.images.map((img) => `"${img.replace(/"/g, '\\"')}"`).join(", ")}]`
: "[]";
const descriptionStr = brand.description ? `"${brand.description.replace(/"/g, '\\"').replace(/\n/g, "\\n")}"` : '""';
const logoUrlStr = brand.logoUrl ? `"${brand.logoUrl.replace(/"/g, '\\"')}"` : '""';
const websiteLinkStr = brand.website_link ? `"${brand.website_link.replace(/"/g, '\\"')}"` : '""';
return ` {
title_fa: "${brand.title_fa.replace(/"/g, '\\"')}",
title_en: "${brand.title_en.replace(/"/g, '\\"')}",
category: ${categoryStr},
status: ${statusStr},
description: ${descriptionStr},
logoUrl: ${logoUrlStr},
images: ${imagesStr},
website_link: ${websiteLinkStr},
},`;
});
// Create TypeScript array format (just the array items, not the full const declaration)
const arrayItems = brandSeedData.join("\n");
// Create output file with full format
const fullOutput = `// Brand seed data extracted from database
// Generated by: npx ts-node src/db/seeders/extractBrands.ts
// Paste the content below into the brandSeedData array in brandSeeders.ts
const brandSeedData: IBrandSeedData[] = [
${arrayItems}
];`;
const outputPath = path.resolve(process.cwd(), "src/db/seeders/brandData.ts");
fs.writeFileSync(outputPath, fullOutput, "utf-8");
console.log(`\nBrand data exported to ${outputPath}`);
console.log(`Total brands: ${brands.length}`);
// Also output JSON format
const jsonData = brands.map((brand) => ({
title_fa: brand.title_fa,
title_en: brand.title_en,
category: brand.category ? brand.category.toString() : null,
status: brand.status,
description: brand.description || "",
logoUrl: brand.logoUrl || "",
images: brand.images || [],
website_link: brand.website_link || "",
}));
const jsonPath = path.resolve(process.cwd(), "src/db/seeders/brandData.json");
fs.writeFileSync(jsonPath, JSON.stringify(jsonData, null, 2), "utf-8");
console.log(`JSON data exported to ${jsonPath}`);
// Print formatted output for easy copy-paste
if (brands.length > 0) {
console.log("\n=== Brand Seed Data (copy the array items below to brandSeeders.ts) ===");
console.log(arrayItems);
console.log("\n=== Or copy the full array from brandData.ts ===");
} else {
console.log("\nNo brands found in database. The brandSeedData array is empty.");
console.log("Add brands to the database first, then run this script again.");
}
await mongoose.connection.close();
process.exit(0);
} catch (error) {
console.error("Error extracting brands:", error);
process.exit(1);
}
};
extractBrands();
+46 -24
View File
@@ -1,35 +1,57 @@
import { cleanMongoData, extractDumpData, getCollectionData } from "./utils/extractDumpData";
import { Logger } from "../../core/logging/logger";
import { FaqModel } from "../../modules/faq/models/faq.model";
export const seedFaqs = async (logger: Logger) => {
try {
logger.info("start seeding the FAQ");
const faqs = [
{
question: "چگونه میتوانم سفارش خود را پیگیری کنم؟",
answer: "برای پیگیری سفارش خود، می‌توانید به حساب کاربری خود وارد شوید و از قسمت سفارشات من، وضعیت سفارش خود را مشاهده کنید.",
},
{
question: "آیا امکان بازگشت کالا وجود دارد؟",
answer: "بله، در صورت داشتن شرایط بازگشت، می‌توانید کالا را تا ۷ روز پس از تحویل بازگردانید.",
},
{
question: "چگونه میتوانم حساب کاربری ایجاد کنم؟",
answer: "برای ایجاد حساب کاربری، کافیست در صفحه اصلی سایت به قسمت ثبت نام مراجعه کرده و اطلاعات خود را وارد کنید.",
},
{
question: "روش‌های پرداخت چگونه است؟",
answer: "پرداخت‌ها می‌تواند از طریق کارت‌های شتاب، درگاه‌های بانکی و پرداخت در محل انجام شود.",
},
{
question: "چگونه میتوانم از کد تخفیف استفاده کنم؟",
answer: "هنگام پرداخت، در قسمت کد تخفیف، کد خود را وارد کنید و سپس دکمه اعمال را بزنید.",
},
];
await FaqModel.insertMany(faqs);
// Extract data from dump
const dumpData = extractDumpData();
const faqsData = getCollectionData(dumpData, "faqs");
logger.info("FAQ data seeded successfully");
if (faqsData.length === 0) {
logger.warn("No FAQ data found in dump, using fallback data");
const fallbackFaqs = [
{
question: "چگونه میتوانم سفارش خود را پیگیری کنم؟",
answer: "برای پیگیری سفارش خود، می‌توانید به حساب کاربری خود وارد شوید و از قسمت سفارشات من، وضعیت سفارش خود را مشاهده کنید.",
},
{
question: "آیا امکان بازگشت کالا وجود دارد؟",
answer: "بله، در صورت داشتن شرایط بازگشت، می‌توانید کالا را تا ۷ روز پس از تحویل بازگردانید.",
},
{
question: "چگونه میتوانم حساب کاربری ایجاد کنم؟",
answer: "برای ایجاد حساب کاربری، کافیست در صفحه اصلی سایت به قسمت ثبت نام مراجعه کرده و اطلاعات خود را وارد کنید.",
},
{
question: "روش‌های پرداخت چگونه است؟",
answer: "پرداخت‌ها می‌تواند از طریق کارت‌های شتاب، درگاه‌های بانکی و پرداخت در محل انجام شود.",
},
{
question: "چگونه میتوانم از کد تخفیف استفاده کنم؟",
answer: "هنگام پرداخت، در قسمت کد تخفیف، کد خود را وارد کنید و سپس دکمه اعمال را بزنید.",
},
];
await FaqModel.insertMany(fallbackFaqs);
logger.info("FAQ data seeded successfully with fallback data");
return;
}
// Clean and prepare FAQ data
const cleanedFaqs = faqsData.map((faq) => {
const cleaned = cleanMongoData(faq);
return {
question: cleaned.question,
answer: cleaned.answer,
page: cleaned.page || "Faq",
};
});
await FaqModel.insertMany(cleanedFaqs);
logger.info(`FAQ data seeded successfully (${cleanedFaqs.length} FAQs)`);
} catch (error) {
logger.error("Error seeding FAQ data:", error);
}
+31 -21
View File
@@ -4,24 +4,28 @@ config();
import mongoose from "mongoose";
import { Logger } from "../../core/logging/logger";
import { connectMongo } from "../connection";
// import { backfillHierarchy } from "./backfillHierarchy";
// import { blogCategorySeeder } from "./blogCategorySeeder";
// import { blogSeeder } from "./blogSeeder";
import { contractSeeder } from "./contractSeeder";
// import { seedMedia } from "./mediaSeeder";
import { seedCityAndProvince } from "./iranCityseeder";
import { SeedPricing } from "./pricingSeeder";
import { clearDatabase } from "../clearDatabase";
import { connectMongo } from "../connection";
import { backfillHierarchy } from "./backfillHierarchy";
import { blogCategorySeeder } from "./blogCategorySeeder";
import { blogSeeder } from "./blogSeeder";
import { seedBrands } from "./brandSeeders";
import { seedCategories } from "./categorySeeder";
import { createDefaultAdmin } from "./defaultAdmin";
import { seedProduct } from "./productSeeders";
import { contractSeeder } from "./contractSeeder";
// import { seedCityAndProvince } from "./iranCityseeder";
// import { SeedPricing } from "./pricingSeeder";
// import { backfillHierarchy } from "./backfillHierarchy";
import { seedFaqs } from "./faqSeeder";
import { seedMedia } from "./mediaSeeder";
import { seedPaymentMethods } from "./pgSeeder";
import { ReportQuesSeeder } from "./reportQuestion";
import { createDefaultRoles } from "./roleSeeder";
// import { ReportQuesSeeder } from "./reportQuestion";
// import { createDefaultRoles } from "./roleSeeder";
import { SeedShipment } from "./shipmentSeeder";
import { seedTicketCategories } from "./ticketSeeder";
import { seedTheme } from "./themeSeeder";
import { seedThemeValue } from "./themeValueSeeder";
// import { seedTicketCategories } from "./ticketSeeder";
import { seedWarranty } from "./warrantySeeders";
const logger = new Logger("Seeder");
@@ -31,21 +35,27 @@ const seedDatabase = async () => {
await connectMongo();
logger.info("connected to mongodb");
await clearDatabase(logger);
await backfillHierarchy();
// await blogCategorySeeder(logger);
// await blogSeeder(logger);
await contractSeeder(logger);
await createDefaultAdmin(logger);
await seedTheme(logger);
await seedThemeValue(logger);
await seedCategories(logger);
await seedBrands(logger);
await seedWarranty(logger);
await seedProduct(logger);
await backfillHierarchy();
await blogCategorySeeder(logger);
await blogSeeder(logger);
await contractSeeder(logger);
await seedFaqs(logger);
await seedCityAndProvince(logger);
await SeedPricing(logger);
// await seedCityAndProvince(logger);
// await SeedPricing(logger);
await seedMedia(logger);
await seedPaymentMethods(logger);
await ReportQuesSeeder(logger);
await createDefaultRoles(logger);
// await ReportQuesSeeder(logger);
// await createDefaultRoles(logger);
await SeedShipment(logger);
await seedTicketCategories(logger);
await seedWarranty(logger);
// await seedTicketCategories(logger);
logger.info("Seeding completed");
process.exit(0);
} catch (error) {
+24 -11
View File
@@ -19,18 +19,31 @@ export const seedCityAndProvince = async (logger: Logger) => {
try {
logger.info("start seeding the city and province", "SEED");
for (const province of provinces) {
await ProvinceModel.create({
name: province.name,
});
}
// Prepare province documents
const provinceDocuments = provinces.map((province) => ({
name: province.name,
}));
for (const city of cities) {
await CityModel.create({
name: city.name,
province: city.ostan,
});
}
// Bulk insert provinces
const insertedProvinces = await ProvinceModel.insertMany(provinceDocuments, { ordered: false });
logger.info(`Inserted ${insertedProvinces.length} provinces`, "SEED");
// Create a mapping from JSON province id to database _id
// Since provinces are inserted in order, we can map by index
const provinceIdMap: { [key: number]: number } = {};
provinces.forEach((province, index) => {
provinceIdMap[province.id] = insertedProvinces[index]._id;
});
// Prepare city documents with mapped province IDs
const cityDocuments = cities.map((city) => ({
name: city.name,
province: provinceIdMap[city.ostan] || city.ostan, // Fallback to original if mapping fails
}));
// Bulk insert cities
await CityModel.insertMany(cityDocuments, { ordered: false });
logger.info(`Inserted ${cities.length} cities`, "SEED");
logger.info("city and province seeding completed", "SEED");
} catch (error) {
+117 -177
View File
@@ -1,192 +1,132 @@
import { cleanMongoData, extractDumpData, getCollectionData } from "./utils/extractDumpData";
import { DisplayLocations } from "../../common/enums/media.enum";
import { Logger } from "../../core/logging/logger";
import { BannerModel } from "../../modules/admin/models/media.model";
import { BannerModel, SliderModel } from "../../modules/admin/models/media.model";
export async function seedMedia(logger: Logger) {
try {
// await SliderModel.deleteMany({});
// await BannerModel.deleteMany({});
// logger.info("Cleared existing data");
logger.info("Media seeding started (Banners and Sliders)");
// // Seed Sliders
// const sliders = [
// {
// displayLocation: DisplayLocations.Mobile,
// imageUrl: "https://loremflickr.com/1920/1080",
// altText: "اسلایدر ۱",
// title: "خوش آمدید به اسلایدر ۱",
// linkUrl: "/slider1",
// isActive: true,
// },
// {
// displayLocation: DisplayLocations.Mobile,
// imageUrl: "https://loremflickr.com/1920/1080",
// altText: "اسلایدر ۲",
// title: "اسلایدر ۲ را کشف کنید",
// linkUrl: "/slider2",
// isActive: true,
// },
// {
// displayLocation: DisplayLocations.Desktop,
// imageUrl: "https://loremflickr.com/1920/1080",
// altText: "اسلایدر ۳",
// title: "بیشتر درباره اسلایدر ۳ بدانید",
// linkUrl: "/slider3",
// isActive: true,
// },
// {
// displayLocation: DisplayLocations.Desktop,
// imageUrl: "https://loremflickr.com/1920/1080",
// altText: "اسلایدر ۴",
// title: "اسلایدر جدید و جذاب",
// linkUrl: "/slider4",
// isActive: true,
// },
// ];
// Extract data from dump
const dumpData = extractDumpData();
const bannersData = getCollectionData(dumpData, "banners");
const slidersData = getCollectionData(dumpData, "sliders");
// for (const slider of sliders) {
// await SliderModel.create(slider);
// }
// Seed Sliders
await SliderModel.deleteMany({});
if (slidersData.length > 0) {
const cleanedSliders = slidersData.map((slider) => {
const cleaned = cleanMongoData(slider);
return {
displayLocation: (cleaned.displayLocation as DisplayLocations) || DisplayLocations.Desktop,
imageUrl: cleaned.imageUrl,
altText: cleaned.altText || "",
title: cleaned.title || "",
linkUrl: cleaned.linkUrl || "/",
isActive: cleaned.isActive !== undefined ? cleaned.isActive : true,
};
});
// logger.info(" Sliders Seeded");
await SliderModel.insertMany(cleanedSliders);
logger.info(`Sliders seeded successfully (${cleanedSliders.length} sliders)`);
} else {
logger.warn("No slider data found in dump, using fallback data");
const fallbackSliders = [
{
displayLocation: DisplayLocations.Desktop,
imageUrl: "https://shinan.storage.iran.liara.space/media-file/ec076066c5bc8f8319c71e5873be4e25f1179bbdf40697f0.jpg",
altText: "درباره ما",
title: "درباره ما",
linkUrl: "https://shop.dev.danakcorp.com/",
isActive: true,
},
{
displayLocation: DisplayLocations.Desktop,
imageUrl: "https://shinan.storage.iran.liara.space/media-file/24880014fc1a5978ef033f6f0161a475e4b76f42fb865508.jpg",
altText: "SLIDER",
title: "SLIDER2",
linkUrl: "https://shop.dev.danakcorp.com/",
isActive: true,
},
];
const existingBanners = await BannerModel.countDocuments({});
if (existingBanners && existingBanners == 16) return;
await SliderModel.insertMany(fallbackSliders);
logger.info("Sliders seeded successfully with fallback data");
}
// Seed Banners
await BannerModel.deleteMany({});
if (bannersData.length > 0) {
const cleanedBanners = bannersData.map((banner) => {
const cleaned = cleanMongoData(banner);
return {
displayLocation: (cleaned.displayLocation as DisplayLocations) || DisplayLocations.Desktop,
imageUrl: cleaned.imageUrl,
altText: cleaned.altText || "",
title: cleaned.title || "",
linkUrl: cleaned.linkUrl || "/",
isActive: cleaned.isActive !== undefined ? cleaned.isActive : true,
order: cleaned.order || 1,
};
});
const banners = [
{
imageUrl: "https://storage.shinan.ir/banner-placeholder.png",
altText: "بنر ۱",
title: "خوش آمدید به صفحه اصلی",
linkUrl: "/banner1",
isActive: true,
order: 1,
},
{
imageUrl: "https://storage.shinan.ir/banner-placeholder.png",
altText: "بنر ۲",
title: "درباره ما بیشتر بدانید",
linkUrl: "/banner2",
isActive: true,
order: 2,
},
{
imageUrl: "https://storage.shinan.ir/banner-placeholder.png",
altText: "بنر ۳",
title: "تماس با ما",
linkUrl: "/banner3",
isActive: true,
order: 3,
},
{
imageUrl: "https://storage.shinan.ir/banner-placeholder.png",
altText: "بنر ۴",
title: "خدمات ما را کشف کنید",
linkUrl: "/banner4",
isActive: true,
order: 4,
},
{
imageUrl: "https://storage.shinan.ir/banner-placeholder.png",
altText: "بنر ۵",
title: حصولات ما را مشاهده کنید",
linkUrl: "/banner5",
isActive: true,
order: 5,
},
{
imageUrl: "https://storage.shinan.ir/banner-placeholder.png",
altText: "بنر 6",
title: "محصولات ما را مشاهده کنید",
linkUrl: "/banner6",
isActive: true,
order: 6,
},
{
imageUrl: "https://storage.shinan.ir/banner-placeholder.png",
altText: "بنر 7",
title: "محصولات ما را مشاهده کنید",
linkUrl: "/banner7",
isActive: true,
order: 7,
},
{
imageUrl: "https://storage.shinan.ir/banner-placeholder.png",
altText: "بنر 8",
title: "محصولات ما را مشاهده کنید",
linkUrl: "/banner8",
isActive: true,
order: 8,
},
{
imageUrl: "https://storage.shinan.ir/banner-placeholder.png",
altText: "بنر 9",
title: "محصولات ما را مشاهده کنید",
linkUrl: "/banner9",
isActive: true,
order: 9,
},
{
imageUrl: "https://storage.shinan.ir/banner-placeholder.png",
altText: "بنر 10",
title: "محصولات ما را مشاهده کنید",
linkUrl: "/banner10",
isActive: true,
order: 10,
},
{
imageUrl: "https://storage.shinan.ir/banner-placeholder.png",
altText: "بنر 11",
title: "محصولات ما را مشاهده کنید",
linkUrl: "/banner11",
isActive: true,
order: 11,
},
{
imageUrl: "https://storage.shinan.ir/banner-placeholder.png",
altText: "بنر 12",
title: "محصولات ما را مشاهده کنید",
linkUrl: "/banner12",
isActive: true,
order: 12,
},
{
imageUrl: "https://storage.shinan.ir/banner-placeholder.png",
altText: "بنر 13",
title: "محصولات ما را مشاهده کنید",
linkUrl: "/banner13",
isActive: true,
order: 13,
},
{
imageUrl: "https://storage.shinan.ir/banner-placeholder.png",
altText: "بنر 14",
title: "محصولات ما را مشاهده کنید",
linkUrl: "/banner14",
isActive: true,
order: 14,
},
{
imageUrl: "https://storage.shinan.ir/banner-placeholder.png",
altText: "بنر 15",
title: "محصولات ما را مشاهده کنید",
linkUrl: "/banner15",
isActive: true,
order: 15,
},
{
imageUrl: "https://storage.shinan.ir/banner-placeholder.png",
altText: "بنر 16",
title: "محصولات ما را مشاهده کنید",
linkUrl: "/banner16",
isActive: true,
order: 16,
},
];
await BannerModel.insertMany(cleanedBanners);
logger.info(`Banners seeded successfully (${cleanedBanners.length} banners)`);
} else {
logger.warn("No banner data found in dump, using fallback data");
const fallbackBanners = [
{
displayLocation: DisplayLocations.Desktop,
imageUrl: "https://storage.shinan.ir/banner-placeholder.png",
altText: "بنر ۱",
title: "خوش آمدید به صفحه اصلی",
linkUrl: "/banner1",
isActive: true,
order: 1,
},
{
displayLocation: DisplayLocations.Desktop,
imageUrl: "https://storage.shinan.ir/banner-placeholder.png",
altText: "بنر ۲",
title: "درباره ما بیشتر بدانید",
linkUrl: "/banner2",
isActive: true,
order: 2,
},
{
displayLocation: DisplayLocations.Desktop,
imageUrl: "https://storage.shinan.ir/banner-placeholder.png",
altText: "بنر ۳",
title: "تماس با ما",
linkUrl: "/banner3",
isActive: true,
order: 3,
},
{
displayLocation: DisplayLocations.Desktop,
imageUrl: "https://storage.shinan.ir/banner-placeholder.png",
altText: "بنر ۴",
title: "خدمات ما را کشف کنید",
linkUrl: "/banner4",
isActive: true,
order: 4,
},
{
displayLocation: DisplayLocations.Desktop,
imageUrl: "https://storage.shinan.ir/banner-placeholder.png",
altText: "بنر ۵",
title: "محصولات ما را مشاهده کنید",
linkUrl: "/banner5",
isActive: true,
order: 5,
},
];
await BannerModel.insertMany(banners);
logger.info("Banners seeding completed!");
await BannerModel.insertMany(fallbackBanners);
logger.info("Banners seeded successfully with fallback data");
}
logger.info("Media seeding completed!");
} catch (error) {
logger.error("Error seeding the Medias: ", error);
}
+52 -12
View File
@@ -1,26 +1,66 @@
import { cleanMongoData, extractDumpData, getCollectionData } from "./utils/extractDumpData";
import { Logger } from "../../core/logging/logger";
import { IPricing, PricingTypeEnum } from "../../modules/pricing/models/Abstraction/IPricing";
import { PricingModel } from "../../modules/pricing/models/pricing.model";
export const SeedPricing = async (logger: Logger) => {
const pricing: IPricing[] = [
{ type: PricingTypeEnum.INSERT_FEE, price: 100_000 },
{ type: PricingTypeEnum.PHOTOGRAPHY_FEE, price: 150_000 },
{ type: PricingTypeEnum.UNBOXING_VIDEO_FEE, price: 200_000 },
{ type: PricingTypeEnum.EXPERT_REVIEWS_FEE, price: 200_000 },
];
try {
logger.info("Pricing seeding started");
const existPricing = await PricingModel.countDocuments({});
if (existPricing && existPricing == 4) return;
// Extract data from dump
const dumpData = extractDumpData();
const pricingsData = getCollectionData(dumpData, "pricings");
if (pricingsData.length === 0) {
logger.warn("No pricing data found in dump, using fallback data");
const fallbackPricing: IPricing[] = [
{ type: PricingTypeEnum.INSERT_FEE, price: 100_000 },
{ type: PricingTypeEnum.PHOTOGRAPHY_FEE, price: 150_000 },
{ type: PricingTypeEnum.UNBOXING_VIDEO_FEE, price: 200_000 },
{ type: PricingTypeEnum.EXPERT_REVIEWS_FEE, price: 200_000 },
];
const existPricing = await PricingModel.countDocuments({});
if (existPricing && existPricing == 4) return;
await PricingModel.deleteMany({});
await PricingModel.insertMany(fallbackPricing);
logger.info("Pricing seeded successfully with fallback data");
return;
}
// Clean and prepare pricing data
const cleanedPricings = pricingsData.map((pricing) => {
const cleaned = cleanMongoData(pricing);
// Map the type from dump to enum
let pricingType: PricingTypeEnum;
switch (cleaned.type) {
case "insertionFee":
pricingType = PricingTypeEnum.INSERT_FEE;
break;
case "photographyFee":
pricingType = PricingTypeEnum.PHOTOGRAPHY_FEE;
break;
case "unboxingVideoFee":
pricingType = PricingTypeEnum.UNBOXING_VIDEO_FEE;
break;
case "expertReviewsFee":
pricingType = PricingTypeEnum.EXPERT_REVIEWS_FEE;
break;
default:
pricingType = cleaned.type as PricingTypeEnum;
}
return {
type: pricingType,
price: cleaned.price,
};
});
await PricingModel.deleteMany({});
await PricingModel.insertMany(cleanedPricings);
await PricingModel.insertMany(pricing);
logger.info("Pricing seeded successfully");
logger.info(`Pricing seeded successfully (${cleanedPricings.length} pricing entries)`);
} catch (error) {
logger.error("Failed to Pricing:", error);
}
+431 -246
View File
@@ -1,217 +1,402 @@
import { faker } from "@faker-js/faker";
import mongoose from "mongoose";
faker.locale = "fa";
import { ProductMarketStatus } from "../../common/enums/product.enum";
import { CreateProductStep, ProductMarketStatus, ProductSource, ProductStatus } from "../../common/enums/product.enum";
import { StatusEnum } from "../../common/enums/status.enum";
import { Logger } from "../../core/logging/logger";
import { BrandModel } from "../../modules/brand/models/brand.model";
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";
import { ISpecifications } from "../../modules/product/models/Abstraction/IProduct";
import { ProductModel } from "../../modules/product/models/product.model";
import { ProductVariantModel } from "../../modules/product/models/productVariant.model";
import { SellerModel } from "../../modules/seller/models/seller.model";
import { ShipmentModel } from "../../modules/shipment/models/shipment.model";
import { ShopModel } from "../../modules/shop/models/shop.model";
import { WalletModel } from "../../modules/wallet/models/wallet.model";
import { OwnerRef } from "../../modules/shop/models/Abstraction/IShop";
import { WarrantyModel } from "../../modules/warranty/models/warranty.model";
import { TimeService } from "../../utils/time.service";
import { CreateFakeSellerData, createFakeShopData, fakeProductsData } from "../data/product";
type ProductCategory =
| "mobile"
| "stationery"
| "electric-electrical"
| "tools-equipment"
| "audio-visual"
| "computer"
| "game-console"
| "sports-travel"
| "fashion"
| "home-appliances"
| "mobile-phones"
| "tablets"
| "pens"
| "paper-notebooks"
| "bulbs"
| "cables-wires"
| "wrenches-screwdrivers"
| "drills"
| "televisions"
| "speakers"
| "laptops"
| "mouse-keyboards"
| "playstations"
| "xbox"
| "sports-equipment"
| "cameras"
| "headphones"
| "travel-equipment"
| "women-clothing"
| "men-clothing"
| "kitchen-appliances"
| "smart-watch"
| "power-banks"
| "cleaning-appliances"
| "malicious";
// Product data from dump/tablofajr
const productsFromDump = [
{
title_fa: "کلید مينياتوری سه فاز 10 آمپر 6KA اشنایدر موتوری AC ",
title_en: "Three-phase miniature circuit breaker 10 amp 6KA Schneider AC motor",
model: "کلید مينياتوری سه فاز 10 آمپر 6KA اشنایدر موتوری AC ",
description:
"•\tکد فنی24349 :\n•\tوزن0.34k : \n•\tابعاد81mm x 54mm x 65mm : \n•\tرنج جریان 10A : \n•\tقدرت قطع 6Ka : \n•\tتعداد پل 3P : \n•\tنوع کلید : تیپ C\n•\tنوع تغذیه AC/DC : \n•\tتعداد پل: 3پل\n•\tتعداد پل محافظ شده: 3عدد\n•\tنوع کنترل: عملکرد دستی\n",
metaDescription:
"•\tکد فنی24349 :\n•\tوزن0.34k : \n•\tابعاد81mm x 54mm x 65mm : \n•\tرنج جریان 10A : \n•\tقدرت قطع 6Ka : \n•\tتعداد پل 3P : \n•\tنوع کلید : تیپ C\n•\tنوع تغذیه AC/DC : \n•\tتعداد پل: 3پل\n•\tتعداد پل محافظ شده: 3عدد\n•\tنوع کنترل: عملکرد دستی\n",
tags: ["اشنایدر"],
advantages: ["عملکرد مطمئن و ایمن", "سه پل کامل و محافظت‌شده"],
disAdvantages: [],
source: ProductSource.LOCAL,
imagesUrl: {
cover: "https://shinan.storage.iran.liara.space/media-file/8b08ae7cff7f80d48dbd5a60e7ce481cbb4b8fe1b23464a3.jpg",
list: ["https://shinan.storage.iran.liara.space/media-file/8b08ae7cff7f80d48dbd5a60e7ce481cbb4b8fe1b23464a3.jpg"],
},
categoryId: "68ffc3c577b494038ef96bc5",
brandId: "68ffc70b77b494038ef96c85",
color: false,
size: false,
meterage: false,
},
{
title_fa: "کلید مینیاتوری تک فاز 10 آمپر اشنایدر",
title_en: "Schneider single-phase miniature circuit breaker 10 amps",
model: "کلید مینیاتوری تک فاز 10 آمپر اشنایدر",
description:
"•\tکد فنی24401:\n•\tوزن0.12kg :\n•\tابعاد80mm x 18mm x 65m : \n•\tرنج جریان 10A :\n•\tقدرت قطع 6Ka :\n•\tتعداد پل 1P:\n•\tنوع کلید: تیپ C \n•\tنوع تغذیه:AC \n",
metaDescription:
"•\tکد فنی24401:\n•\tوزن0.12kg :\n•\tابعاد80mm x 18mm x 65m : \n•\tرنج جریان 10A :\n•\tقدرت قطع 6Ka :\n•\tتعداد پل 1P:\n•\tنوع کلید: تیپ C \n•\tنوع تغذیه:AC \n",
tags: [],
advantages: ["ابعاد و وزن بهینه"],
disAdvantages: [],
source: ProductSource.LOCAL,
imagesUrl: {
cover: "https://shinan.storage.iran.liara.space/media-file/5eb2bf2fa9475b66805df0bc1c5c4c02a05cb7a8b2f1eb36.jpg",
list: ["https://shinan.storage.iran.liara.space/media-file/5eb2bf2fa9475b66805df0bc1c5c4c02a05cb7a8b2f1eb36.jpg"],
},
categoryId: "68ffc4a577b494038ef96c40",
brandId: "68ffc70b77b494038ef96c85",
color: false,
size: false,
meterage: false,
},
{
title_fa: "کلید نشتی جریان چهار پل 63 آمپر اسپکتر",
title_en: "Specter 63 Amp Four-Pole Residual Current Circuit Breaker",
model: "کلید نشتی جریان چهار پل 63 آمپر اسپکتر",
description:
"•\tبرند: اسپکتر\n•\tکد: M9R11463-S\n•\tنوع محصول: کلید قطع جریان پسماند\n•\tجریان نامی: 63A\n•\tنوع کنترل: با دسته\n•\tتعداد گام 9 میلیمتری:4\n•\tنام محصول :M9 ID\n•\tتعداد پل ها: P4\n•\tمحل وردی نول: چپ\n•\tنوع شبکه: AC\n•\tمیزان حساسیت به جریان نشتی: 30 MA\n•\tتاخیر زمانی حفاظت در برابر نشتی : آنی\n•\tکلاس حفاظتی جریان نشتی: AC\n",
metaDescription:
"•\tبرند: اسپکتر\n•\tکد: M9R11463-S\n•\tنوع محصول: کلید قطع جریان پسماند\n•\tجریان نامی: 63A\n•\tنوع کنترل: با دسته\n•\tتعداد گام 9 میلیمتری:4\n•\tنام محصول :M9 ID\n•\tتعداد پل ها: P4\n•\tمحل وردی نول: چپ\n•\tنوع شبکه: AC\n•\tمیزان حساسیت به جریان نشتی: 30 MA\n•\tتاخیر زمانی حفاظت در برابر نشتی : آنی\n•\tکلاس حفاظتی جریان نشتی: AC\n",
tags: ["کلید نشتی"],
advantages: ["حفاظت ایمن در برابر نشتی جریان: جلوگیری از برق‌گرفتگی و آسیب به تجهیزات"],
disAdvantages: [],
source: ProductSource.LOCAL,
imagesUrl: {
cover: "https://shinan.storage.iran.liara.space/media-file/494314865784813df4cee356541cf72cb777bd463a4e3fe2.jpg",
list: ["https://shinan.storage.iran.liara.space/media-file/494314865784813df4cee356541cf72cb777bd463a4e3fe2.jpg"],
},
categoryId: "68ffc4a577b494038ef96c40",
brandId: "68ffc7dd77b494038ef96ca0",
color: false,
size: false,
meterage: false,
},
{
title_fa: "ترمینال پیچی سایز 2.5",
title_en: "Screw terminal size 2.5",
model: "ترمینال پیچی سایز 2.5",
description:
"•\tقابل نصب بر روی ریل TH35 \n•\t ساخته شده از مواد عایقی پلی آمید در بدنه ترمینال به جهت دارا بودن خواص ارتقا یافته الکتریکی\n•\tسهولت در بستن پیچ های ترمینال توسط پیچ گوشتی\n•\tبسته بودن دهانه ورود کابل به جهت جلوگیری از چند رشته شدن آن\n•\tاتصال مناسب بدون نیاز به آماده سازی خاصی جهت کابل\n•\t مناسب برای کابل های تک رشته یا چند رشته با سطح مقطع های مختلف درپوش ها و پارتیشن های مشترک جهت انواع مختلف\n•\tامکان نصب سریع ، آسان و مطمئن جمپر و قابلیت حذف اتصال آن در مقاطع دلخواه\n•\tقابل استفاده با ترمینال های هادی حفاظتی برای سایزهای مختلف\n•\tاستفاده از شماره های ترمینال در سایزهای مختلف و امکان نصب همزمان آنها به صورت ۱۰ تایی\n•\t دارای گواهی از موسسات گواهی کننده معتبر\n",
metaDescription:
"•\tقابل نصب بر روی ریل TH35 \n•\t ساخته شده از مواد عایقی پلی آمید در بدنه ترمینال به جهت دارا بودن خواص ارتقا یافته الکتریکی\n•\tسهولت در بستن پیچ های ترمینال توسط پیچ گوشتی\n•\tبسته بودن دهانه ورود کابل به جهت جلوگیری از چند رشته شدن آن\n•\tاتصال مناسب بدون نیاز به آماده سازی خاصی جهت کابل\n•\t مناسب برای کابل های تک رشته یا چند رشته با سطح مقطع های مختلف درپوش ها و پارتیشن های مشترک جهت انواع مختلف\n•\tامکان نصب سریع ، آسان و مطمئن جمپر و قابلیت حذف اتصال آن در مقاطع دلخواه\n•\tقابل استفاده با ترمینال های هادی حفاظتی برای سایزهای مختلف\n•\tاستفاده از شماره های ترمینال در سایزهای مختلف و امکان نصب همزمان آنها به صورت ۱۰ تایی\n•\t دارای گواهی از موسسات گواهی کننده معتبر\n",
tags: ["گواهی دار"],
advantages: ["کیفیت ساخت بالا"],
disAdvantages: [],
source: ProductSource.LOCAL,
imagesUrl: {
cover: "https://shinan.storage.iran.liara.space/media-file/9d7410e03dae0396c393a5377764bd8d01ab00e2198d533c.jpg",
list: [
"https://shinan.storage.iran.liara.space/media-file/9d7410e03dae0396c393a5377764bd8d01ab00e2198d533c.jpg",
"https://shinan.storage.iran.liara.space/media-file/6484d385f9af15c93c731997d7eda2501eed5264d20afe83.jpg",
],
},
categoryId: "68ffc3c577b494038ef96bc5",
brandId: "68ffc5ba77b494038ef96c60",
color: false,
size: false,
meterage: false,
},
{
title_fa: "ترمینال ریلی پیچی سایز 2.5H",
title_en: "Screw rail terminal size 2.5H",
model: "ترمینال ریلی پیچی سایز 2.5H",
description:
"قابل نصب بر روی ریل TH35 \n•\t ساخته شده از مواد عایقی پلی آمید در بدنه ترمینال به جهت دارا بودن خواص ارتقا یافته الکتریکی\n•\t سهولت در بستن پیچ های ترمینال توسط پیچ گوشتی\n•\t بسته بودن دهانه ورود کابل به جهت جلوگیری از چند رشته شدن\n•\t اتصال مناسب بدون نیاز به آماده سازی خاصی جهت کابل\n•\t مناسب برای کابل های تک رشته یا چند رشته با سطح مقطع های مختلف\n•\t درپوش ها و پارتیشن های مشترک جهت انواع مختلف\n•\t امکان نصب سریع ، آسان و مطمئن جمپر و قابلیت حذف اتصال آن در مقاطع دلخواه\n•\t قابل استفاده با ترمینالهای هادی حفاظتی برای سایزهای مختلف\n•\t استفاده از شماره های ترمینال در سایزهای مختلف و امکان نصب همزمان آنها به صورت ۱۰تایی\n•\t دارای گواهی از موسسات گواهی کننده معتبر\n",
metaDescription:
"قابل نصب بر روی ریل TH35 \n•\t ساخته شده از مواد عایقی پلی آمید در بدنه ترمینال به جهت دارا بودن خواص ارتقا یافته الکتریکی\n•\t سهولت در بستن پیچ های ترمینال توسط پیچ گوشتی\n•\t بسته بودن دهانه ورود کابل به جهت جلوگیری از چند رشته شدن\n•\t اتصال مناسب بدون نیاز به آماده سازی خاصی جهت کابل\n•\t مناسب برای کابل های تک رشته یا چند رشته با سطح مقطع های مختلف\n•\t درپوش ها و پارتیشن های مشترک جهت انواع مختلف\n•\t امکان نصب سریع ، آسان و مطمئن جمپر و قابلیت حذف اتصال آن در مقاطع دلخواه\n•\t قابل استفاده با ترمینالهای هادی حفاظتی برای سایزهای مختلف\n•\t استفاده از شماره های ترمینال در سایزهای مختلف و امکان نصب همزمان آنها به صورت ۱۰تایی\n•\t دارای گواهی از موسسات گواهی کننده معتبر\n",
tags: ["نصب اسان"],
advantages: ["دارای گواهی از موسسات گواهی کننده معتبر"],
disAdvantages: [],
source: ProductSource.LOCAL,
imagesUrl: {
cover: "https://shinan.storage.iran.liara.space/media-file/96c98510d45bb39dc29995158fc41073d26d5b0f67120d43.jpg",
list: [
"https://shinan.storage.iran.liara.space/media-file/96c98510d45bb39dc29995158fc41073d26d5b0f67120d43.jpg",
"https://shinan.storage.iran.liara.space/media-file/5ceb27b9a311f178d05dce3a40206525dc28251e588f6538.jpg",
"https://shinan.storage.iran.liara.space/media-file/bd076811b08bd0dc26a795fb30e727c159c4e640af4f454f.jpg",
"https://shinan.storage.iran.liara.space/media-file/6f6a42232482ba4059d7e1276373abd93c20ce494758fa12.jpg",
"https://shinan.storage.iran.liara.space/media-file/b1a89f21b8931e2d9b3b5238756bce55221491e4053afa5f.jpg",
"https://shinan.storage.iran.liara.space/media-file/98a67076cd84879ebd61415976ca07a93e7139c4435c6b6d.jpg",
"https://shinan.storage.iran.liara.space/media-file/396317e5d5518ec7dbe7070f83917daeddeb211ca84d0b8c.jpg",
],
},
categoryId: "68ffc3c577b494038ef96bc5",
brandId: "68ffc5ba77b494038ef96c60",
color: false,
size: false,
meterage: false,
},
{
title_fa: "ترمینال توزیع 4*11 125A",
title_en: "Distribution terminal 4*11 125A",
model: "ترمینال توزیع 4*11 125A",
description:
"طراحی شده بر اساس الزامات استاندارد IEC 60947-7-1 \nجنس بدنه : پلی آمید با گرید 6.6\nجنس قاب : پلی کربنات\nجنس شینه : برنج\nقابل نصب بر روی ریل TH35-7.5, 15 و سطح تخت\nافزایش نظم و زیبایی محیط تابلو\nصرفه جویی در فضای سیم بندی تابلو\n",
metaDescription:
"طراحی شده بر اساس الزامات استاندارد IEC 60947-7-1 \nجنس بدنه : پلی آمید با گرید 6.6\nجنس قاب : پلی کربنات\nجنس شینه : برنج\nقابل نصب بر روی ریل TH35-7.5, 15 و سطح تخت\nافزایش نظم و زیبایی محیط تابلو\nصرفه جویی در فضای سیم بندی تابلو\n",
tags: ["صرفه جویی در فضای سیم بندی تابلو"],
advantages: ["صرفه جویی در فضای سیم بندی تابلو"],
disAdvantages: [],
source: ProductSource.LOCAL,
imagesUrl: {
cover: "https://shinan.storage.iran.liara.space/media-file/9efa024ebe677f73a08adb67726f31ea2d666e350e6d6559.jpg",
list: [
"https://shinan.storage.iran.liara.space/media-file/9efa024ebe677f73a08adb67726f31ea2d666e350e6d6559.jpg",
"https://shinan.storage.iran.liara.space/media-file/041f6fd5f77cd948a8c46b03cb05380437cf39bbff18bc66.jpg",
],
},
categoryId: "68ffc3c577b494038ef96bc5",
brandId: "68ffc5ba77b494038ef96c60",
color: false,
size: false,
meterage: false,
},
{
title_fa: "چراغ سیگنال 24 ولت با لامپ ثابت",
title_en: "24V signal light with fixed bulb",
model: "چراغ سیگنال 24 ولت با لامپ ثابت",
description:
"قابلیت استفاده از لامپ های میخی BA9S\n دارای LED ثابت\n از جنس پلی آمید غیر قابل اشتعال\nچراغ سیگنال با لامپ ثابت\nتنوع رنگ: سبز، زرد، قرمز\n",
metaDescription:
"قابلیت استفاده از لامپ های میخی BA9S\n دارای LED ثابت\n از جنس پلی آمید غیر قابل اشتعال\nچراغ سیگنال با لامپ ثابت\nتنوع رنگ: سبز، زرد، قرمز\n",
tags: ["جنس پلی آمید"],
advantages: ["جنس پلی آمید غیر قابل اشتعال"],
disAdvantages: [],
source: ProductSource.LOCAL,
imagesUrl: {
cover: "https://shinan.storage.iran.liara.space/media-file/fdfb924a5fb09c8e0bfa181958601f2ddfdfd47eacf31f7c.jpg",
list: ["https://shinan.storage.iran.liara.space/media-file/fdfb924a5fb09c8e0bfa181958601f2ddfdfd47eacf31f7c.jpg"],
},
categoryId: "68ffc3c577b494038ef96bc5",
brandId: "68ffc5ba77b494038ef96c60",
color: true,
size: false,
meterage: false,
},
{
title_fa: "مولتی تایمر دیجیتالMTB_15M ",
title_en: "Digital Multimeter MTB 15M",
model: "مولتی تایمر دیجیتالMTB_15M ",
description:
"این دستگاه، تایمری با کارآیی فوق العاده و با دقتِ زمانی صدم ثانیه می باشد. این دستگاه با دو رله مجزا قابلیت کاربری های متفاوت در چهار گروه زیر را دارد\n تایمر تأخیر در قطع و یا تأخیر در وصل : A\nکنترل موتورهای چپگرد و راستگرد: B\n عملکرد فلاشری در زمان های مختلف: C\nعملکرد ترکیبی فلاشری : D\nویژگی های مولتی تایمر دیجیتال شیوا امواج\nسیستم میکروپروسسوری با دقت بالا (۰.۱ ثانیه)\n دارای نمایشگر\n زمان\n حالت کاری: Mode\n Aوصل رله : Rel A\n B وصل رله :Rel B \n انتخاب نوع استارت دستگاه به صورت نرم افزاری\n وصل برق به ترمینال Start\n قطع برق از ترمینال Start\n شروع به کار مجدد Reset\n قطع و وصل برق ترمینال Start\n فشار دادن هم زمان دو کلید و \nقابلیت\n تعیین وضعیت رله (وصل یا قطع ) بعد از استارت\n",
metaDescription:
"این دستگاه، تایمری با کارآیی فوق العاده و با دقتِ زمانی صدم ثانیه می باشد. این دستگاه با دو رله مجزا قابلیت کاربری های متفاوت در چهار گروه زیر را دارد\n تایمر تأخیر در قطع و یا تأخیر در وصل : A\nکنترل موتورهای چپگرد و راستگرد: B\n عملکرد فلاشری در زمان های مختلف: C\nعملکرد ترکیبی فلاشری : D\nویژگی های مولتی تایمر دیجیتال شیوا امواج\nسیستم میکروپروسسوری با دقت بالا (۰.۱ ثانیه)\n دارای نمایشگر\n زمان\n حالت کاری: Mode\n Aوصل رله : Rel A\n B وصل رله :Rel B \n انتخاب نوع استارت دستگاه به صورت نرم افزاری\n وصل برق به ترمینال Start\n قطع برق از ترمینال Start\n شروع به کار مجدد Reset\n قطع و وصل برق ترمینال Start\n فشار دادن هم زمان دو کلید و \nقابلیت\n تعیین وضعیت رله (وصل یا قطع ) بعد از استارت\n",
tags: ["شیوا امواج"],
advantages: [],
disAdvantages: [],
source: ProductSource.LOCAL,
imagesUrl: {
cover: "https://shinan.storage.iran.liara.space/media-file/7338c3f8a28f60e30b1ae8e381a8d22057cd9520dd145dd5.jpg",
list: ["https://shinan.storage.iran.liara.space/media-file/7338c3f8a28f60e30b1ae8e381a8d22057cd9520dd145dd5.jpg"],
},
categoryId: "68ffc43e77b494038ef96bf5",
brandId: "68ffc53477b494038ef96c51",
color: false,
size: false,
meterage: false,
},
{
title_fa: "تایمر چپ گرد راست گردLRB-SLE",
title_en: "Left-hand, right-hand, round timer LRB-SLE",
model: "تایمر چپ گرد راست گردLRB-SLE",
description:
"•\t3 عدد نشانگر برای اعلام \n\tL: وصل رله چپ گرد\n\tOFF : استراحت\n\tR : وصل رله راست گرد \n•\t3 عدد نشانگر معرف محدوده زمانی انتخاب شده\n\t S-S-S=A \n\tB = M-M-M\n\tC = M-S-M\n\tA,B,C= H-S-H\n•\tقابلیت تنظیم زمان های چپ گرد، استراحت، راست گرد\n•\tقابلیت انتخاب ۴ محدوده زمانی\n\tSSS: ثانیه،ثانیه،ثانیه\n\tMMM: قیقه،دقیقه،دقیقه\n\tMSM: دقیقه،ثانیه،دقیقه\n\tHSH: ساعت،ثانیه،ساعت\n",
metaDescription:
"•\t3 عدد نشانگر برای اعلام \n\tL: وصل رله چپ گرد\n\tOFF : استراحت\n\tR : وصل رله راست گرد \n•\t3 عدد نشانگر معرف محدوده زمانی انتخاب شده\n\t S-S-S=A \n\tB = M-M-M\n\tC = M-S-M\n\tA,B,C= H-S-H\n•\tقابلیت تنظیم زمان های چپ گرد، استراحت، راست گرد\n•\tقابلیت انتخاب ۴ محدوده زمانی\n\tSSS: ثانیه،ثانیه،ثانیه\n\tMMM: قیقه،دقیقه،دقیقه\n\tMSM: دقیقه،ثانیه،دقیقه\n\tHSH: ساعت،ثانیه،ساعت\n",
tags: ["مولتی تایمر"],
advantages: ["•\t عدم تغییر زمان تنظیم شده در اثز ضربات کنتاکتور تابلو", "نگهداری زمان سپری شده در حالت قطع برق در صورت نیاز"],
disAdvantages: [],
source: ProductSource.LOCAL,
imagesUrl: {
cover: "https://shinan.storage.iran.liara.space/media-file/7b883425fcbe18a18fe960651405d64499cf717690a7bc10.jpg",
list: ["https://shinan.storage.iran.liara.space/media-file/7b883425fcbe18a18fe960651405d64499cf717690a7bc10.jpg"],
},
categoryId: "68ffc43e77b494038ef96bf5",
brandId: "68ffc53477b494038ef96c51",
color: false,
size: false,
meterage: false,
},
{
title_fa: "سوپر تایمر دیجیتالDTB-8M",
title_en: "Super Digital Timer DTB-8M",
model: "سوپر تایمر دیجیتالDTB-8M",
description:
"سوپر تایمر دیجیتال شیوا امواج تایمری با دقت و کارآیی بسیار بالا و قابل استفاده در رنج وسیعی از محدوده زمانی (از ۰.۱ ثانیه تا ۹۹۹ ساعت) می باشد . همچنین این تایمر می تواند در ۴حالت دهم ثانیه ، ثانیه ، دقیقه و ساعت به صورت فلاشر کار کند . سوپر تایمر شیوا امواج را می توانید در ۲حالت تایمر تأخیر در قطع و تایمر تأخیر در وصل مورد استفاده قرار دهید . تغذیه این تایمر از ترمینال های START مجزا است و در ۲ مدل با تغذیه می شود.ACو DC عرضه می شود\nویژگی های سوپرتایمر دیجیتال شیوا امواج\nسیستم میکروپروسسوری با دقت بالا (۰.۱ ثانیه)\nدارای نمایشگر\n \nزمان :Time Value \nحالت کاری: MODE\nوصل رله: Rel\nدارای ۸ حالت مختلف کاری:\nتایمر: 1. دهم ثانیه ۲. ثانیه ۳. دقیقه ۴. ساعت\nفلاشر: ۵. دهم ثانیه ۶ .ثانیه ۷. دقیقه ۸. ساعت\nتعیین وضعیت رله (وصل یا قطع) در شروع زمان گیر\n",
metaDescription:
"سوپر تایمر دیجیتال شیوا امواج تایمری با دقت و کارآیی بسیار بالا و قابل استفاده در رنج وسیعی از محدوده زمانی (از ۰.۱ ثانیه تا ۹۹۹ ساعت) می باشد . همچنین این تایمر می تواند در ۴حالت دهم ثانیه ، ثانیه ، دقیقه و ساعت به صورت فلاشر کار کند . سوپر تایمر شیوا امواج را می توانید در ۲حالت تایمر تأخیر در قطع و تایمر تأخیر در وصل مورد استفاده قرار دهید . تغذیه این تایمر از ترمینال های START مجزا است و در ۲ مدل با تغذیه می شود.ACو DC عرضه می شود\nویژگی های سوپرتایمر دیجیتال شیوا امواج\nسیستم میکروپروسسوری با دقت بالا (۰.۱ ثانیه)\nدارای نمایشگر\n \nزمان :Time Value \nحالت کاری: MODE\nوصل رله: Rel\nدارای ۸ حالت مختلف کاری:\nتایمر: 1. دهم ثانیه ۲. ثانیه ۳. دقیقه ۴. ساعت\nفلاشر: ۵. دهم ثانیه ۶ .ثانیه ۷. دقیقه ۸. ساعت\nتعیین وضعیت رله (وصل یا قطع) در شروع زمان گیر\n",
tags: ["تنظیم و برنامه‌ریزی"],
advantages: ["تنظیم و برنامه‌ریزی ساده: امکان تنظیم سریع و راحت پارامترها بدون نیاز به ابزار تخصصی"],
disAdvantages: [],
source: ProductSource.LOCAL,
imagesUrl: {
cover: "https://shinan.storage.iran.liara.space/media-file/3636f8a3eafb761740410e7b5238ef48f77faa16bbb46ea2.jpg",
list: ["https://shinan.storage.iran.liara.space/media-file/3636f8a3eafb761740410e7b5238ef48f77faa16bbb46ea2.jpg"],
},
categoryId: "68ffc43e77b494038ef96bf5",
brandId: "68ffc53477b494038ef96c51",
color: false,
size: false,
meterage: false,
},
{
title_fa: "کلید حرارتی (MPCB) 1.6-2.5 هیوندا",
title_en: "Thermal switch (MPCB) 1.6-2.5 Hyundai",
model: "کلید حرارتی (MPCB) 1.6-2.5 هیوندا",
description:
"•\tبرند: هیوندا\n•\tکد: MMS32K-02P5\n•\tرنج تنظیم جریان کلید حرارتی: 1.6-2.5A\n•\tقدرت قطع در ولتاژ کاری شبکه 400V: 100KA\n•\tولتاژ نامی: 690 VAC\n•\tگارانتی: 24 ماه شرکت آریان نیرو رای\n",
metaDescription:
"•\tبرند: هیوندا\n•\tکد: MMS32K-02P5\n•\tرنج تنظیم جریان کلید حرارتی: 1.6-2.5A\n•\tقدرت قطع در ولتاژ کاری شبکه 400V: 100KA\n•\tولتاژ نامی: 690 VAC\n•\tگارانتی: 24 ماه شرکت آریان نیرو رای\n",
tags: ["گارانتی: 24 ماه شرکت آریان نیرو رای"],
advantages: ["گارانتی: 24 ماه شرکت آریان نیرو رای"],
disAdvantages: [],
source: ProductSource.LOCAL,
imagesUrl: {
cover: "https://shinan.storage.iran.liara.space/media-file/abe295b4f516083e76ed228ba991fc4b024aa53de3102061.jpg",
list: ["https://shinan.storage.iran.liara.space/media-file/abe295b4f516083e76ed228ba991fc4b024aa53de3102061.jpg"],
},
categoryId: "68ffc43e77b494038ef96bf5",
brandId: "68ffc66477b494038ef96c6f",
color: false,
size: false,
meterage: false,
},
{
title_fa: "کلید اتوماتیک کمپکت قابل تنظیم حرارتی – غیرقابل تنظیم مغناطیسی 63A هیوندا",
title_en: "Hyundai 63A Compact Thermally Adjustable Automatic Switch - Non-Adjustable Magnetic",
model: "کلید اتوماتیک کمپکت قابل تنظیم حرارتی – غیرقابل تنظیم مغناطیسی 63A هیوندا",
description:
"•\tکد: HGM125L-F 3PT5S0000C-F63\n•\tبرند: هیوندا\n•\tتعداد پل: 3P\n•\tنوع کلید: قابل تنظیم حرارتی – غیرقابل تنظیم مغناطیسی\n•\tجریان نامی: 63A\n•\tفریم آمپر: HGM125\n•\tقدرت قطع در ولتاژ کاری شبکه: 55KA\n•\tبالاترین ولتاژکاری خط: 690V\n•\tگارانتی: 24 ماه شرکت آریان نیرو رای\n",
metaDescription:
"•\tکد: HGM125L-F 3PT5S0000C-F63\n•\tبرند: هیوندا\n•\tتعداد پل: 3P\n•\tنوع کلید: قابل تنظیم حرارتی – غیرقابل تنظیم مغناطیسی\n•\tجریان نامی: 63A\n•\tفریم آمپر: HGM125\n•\tقدرت قطع در ولتاژ کاری شبکه: 55KA\n•\tبالاترین ولتاژکاری خط: 690V\n•\tگارانتی: 24 ماه شرکت آریان نیرو رای\n",
tags: ["گارانتی: 24 ماه شرکت آریان نیرو رای"],
advantages: ["گارانتی: 24 ماه شرکت آریان نیرو رای"],
disAdvantages: [],
source: ProductSource.LOCAL,
imagesUrl: {
cover: "https://shinan.storage.iran.liara.space/media-file/760b8d67ff3954d41daa33ea94b47295c552bb9355c59304.jpg",
list: ["https://shinan.storage.iran.liara.space/media-file/760b8d67ff3954d41daa33ea94b47295c552bb9355c59304.jpg"],
},
categoryId: "68ffc43e77b494038ef96bf5",
brandId: "68ffc66477b494038ef96c6f",
color: false,
size: false,
meterage: false,
},
{
title_fa: "کنتاکتور 12 آمپر هیوندای بوبین 220VAC",
title_en: "Contactor 12 Amp Hyundai Coil 220VAC",
model: "کنتاکتور 12 آمپر هیوندای بوبین 220VAC",
description:
"•\tبرند: هیوندا\n•\tکد: HGC12B 10NS,X220G\n•\tتعداد پل: 3P\n•\tجریان نامی: 9A\n•\tتوان نامی: 5.5KW\n•\tبالاترین ولتاژکاری خط: 690VAC\n•\tولتاژ بوبین: 220VAC\n•\tکنتاکت های کمکی: 1NO\n•\tگارانتی: 24 ماه شرکت آریان نیرو رای\n",
metaDescription:
"•\tبرند: هیوندا\n•\tکد: HGC12B 10NS,X220G\n•\tتعداد پل: 3P\n•\tجریان نامی: 9A\n•\tتوان نامی: 5.5KW\n•\tبالاترین ولتاژکاری خط: 690VAC\n•\tولتاژ بوبین: 220VAC\n•\tکنتاکت های کمکی: 1NO\n•\tگارانتی: 24 ماه شرکت آریان نیرو رای\n",
tags: ["کیفیت ساخت"],
advantages: ["کیفیت ساخت برند هیوندا: دوام طولانی، مقاومت در برابر حرارت و کارکرد پایدار."],
disAdvantages: [],
source: ProductSource.LOCAL,
imagesUrl: {
cover: "https://shinan.storage.iran.liara.space/media-file/0e40dc73b5c2b51347aa97159d298ae71b0a41dfacd1bbf6.jpg",
list: [
"https://shinan.storage.iran.liara.space/media-file/0e40dc73b5c2b51347aa97159d298ae71b0a41dfacd1bbf6.jpg",
"https://shinan.storage.iran.liara.space/media-file/c0fdeabaf32050e3af38133cd07bf6d72d865714e12023d8.jpg",
],
},
categoryId: "68ffc4a577b494038ef96c40",
brandId: "68ffc66477b494038ef96c6f",
color: false,
size: false,
meterage: false,
},
];
type ProductBrand =
| "Samsung"
| "LG"
| "Bosch"
| "Apple"
| "Asus"
| "Xiaomi"
| "Sony"
| "JBL"
| "Bose"
| "Huawei"
| "Nokia"
| "Nikon"
| "Panasonic"
| "Anker"
| "Microsoft"
| "Lenovo"
| "Pilot"
| "Sheaffer"
| "Parker"
| "Lamy"
| "Parker"
| "Philips"
| "Optonique"
| "NoorKala"
| "Acer"
| "Zara"
| "H&M"
| "Gucci"
| "Dior"
| "Gucci"
| "Gucci"
| "malicious";
// Mapping from dump category IDs to category title_en in database
const categoryIdToTitleEn: Record<string, string> = {
"68ffc3c577b494038ef96bc5": "Panels-and-peripheral-equipment",
"68ffc4a577b494038ef96c40": "Industrial-electrical-equipment",
"68ffc43e77b494038ef96bf5": "Controller-and-display",
};
const productMapping = (productModel: string) => {
let category: ProductCategory;
let brand: ProductBrand;
// Mapping from dump brand IDs to brand title_en in database
const brandIdToTitleEn: Record<string, string> = {
"68ffc53477b494038ef96c51": "shiva-amvaj",
"68ffc5ba77b494038ef96c60": "raad",
"68ffc66477b494038ef96c6f": "Hyundai",
"68ffc70b77b494038ef96c85": "Schneider-Electric",
"68ffc7dd77b494038ef96ca0": "Specter",
};
switch (productModel) {
case "SAMSUNG123":
case "SAMGALTAB567":
brand = "Samsung";
category = productModel === "SAMSUNG123" ? "televisions" : "tablets";
break;
case "LGWASH456":
brand = "LG";
category = "kitchen-appliances";
break;
case "LGOLED789":
brand = "LG";
category = "televisions";
break;
case "BOSCHFRIDGE789":
brand = "Bosch";
category = "kitchen-appliances";
break;
case "IPHONE13PRO":
case "APPLEWATCH123":
brand = "Apple";
category = productModel === "IPHONE13PRO" ? "mobile-phones" : "smart-watch";
break;
case "ASUSLAP234":
brand = "Asus";
category = "laptops";
break;
case "MI11PRO":
brand = "Xiaomi";
category = "mobile-phones";
break;
case "SONYWH123":
case "SONYCAM456":
case "SONYSPK234":
brand = "Sony";
category = productModel === "SONYWH123" ? "headphones" : productModel === "SONYCAM456" ? "cameras" : "speakers";
break;
case "JBLSPK789":
brand = "JBL";
category = "speakers";
break;
case "NIKONCAM123":
brand = "Nikon";
category = "cameras";
break;
case "BOSEWH789":
brand = "Bose";
category = "headphones";
break;
case "HUAWEITAB1":
brand = "Huawei";
category = "tablets";
break;
case "NOKIAG20":
brand = "Nokia";
category = "mobile-phones";
break;
case "PANAMW234":
brand = "Panasonic";
category = "kitchen-appliances";
break;
case "ANKERPB100":
brand = "Anker";
category = "power-banks";
break;
case "PS5DISC":
brand = "Sony";
category = "playstations";
break;
default:
brand = "malicious";
category = "malicious";
const getBrandAndCategoryForProduct = async (product: (typeof productsFromDump)[0]) => {
// Get category title_en from the mapping
const categoryTitleEn = categoryIdToTitleEn[product.categoryId];
if (!categoryTitleEn) {
throw new Error(`Category mapping not found for product: ${product.title_fa}`);
}
return { brand, category };
};
const getBrandAndCategoryForProduct = async (product: (typeof fakeProductsData)[0]) => {
const { brand: brandName, category: categoryName } = productMapping(product.model) || {};
const category = await CategoryModel.findOne({ title_en: categoryName });
// Find category by title_en
const category = await CategoryModel.findOne({ title_en: categoryTitleEn });
if (!category) {
throw new Error(`Category not found for product: ${product.title_fa}`);
throw new Error(`Category not found for product: ${product.title_fa} (looking for: ${categoryTitleEn})`);
}
const brand = await BrandModel.findOne({ title_en: brandName, category: category._id.toString() });
// Get brand title_en from the mapping
const brandTitleEn = brandIdToTitleEn[product.brandId];
if (!brandTitleEn) {
throw new Error(`Brand mapping not found for product: ${product.title_fa}`);
}
// Try to find brand by title_en and specific category first
let brand = await BrandModel.findOne({ title_en: brandTitleEn, category: category._id.toString() });
// If not found, try to find as global brand (category: null)
if (!brand) {
throw new Error(`Brand not found for product: ${product.title_fa}`);
brand = await BrandModel.findOne({ title_en: brandTitleEn, category: null });
}
// If still not found, try to find any brand with this title_en (in case category is stored differently)
if (!brand) {
brand = await BrandModel.findOne({ title_en: brandTitleEn, deleted: { $ne: true } });
}
// If brand still not found, create it as a global brand
if (!brand) {
brand = await BrandModel.create({
title_fa: brandTitleEn, // Using title_en as fallback for title_fa
title_en: brandTitleEn,
category: null, // Create as global brand
status: StatusEnum.Approved,
description: "",
logoUrl: "",
images: [],
website_link: "",
});
}
return { brand, category };
};
const createFakeProductVariant = (productId: number, shopId: string, warrantyId: number, themeValueId?: string) => {
const createFakeProductVariant = (productId: number, shopId: string, warrantyId: number, themeValueId: string) => {
return new ProductVariantModel({
product: productId,
rate: faker.datatype.float({ min: 1, max: 5 }),
rate: 0,
warranty: warrantyId,
shop: shopId,
themeValue: themeValueId,
sellerSpecialCode: crypto.randomUUID().slice(0, 6).toUpperCase(),
sellerSpecialCode: "SellerSpecialCode",
market_status: faker.helpers.arrayElement([ProductMarketStatus.Marketable]),
postingTime: faker.datatype.number({ min: 1, max: 10 }),
price: {
@@ -225,13 +410,9 @@ const createFakeProductVariant = (productId: number, shopId: string, warrantyId:
},
saleFormat: {
retail: true,
wholeSale: [
{ from: 10, to: 30, price: faker.datatype.number({ min: 10000, max: 100000000 }) },
{ from: 30, to: 50, price: faker.datatype.number({ min: 10000, max: 100000000 }) },
{ from: 50, to: 100, price: faker.datatype.number({ min: 10000, max: 100000000 }) },
],
wholeSale: [],
},
stock: faker.datatype.number({ min: 20, max: 100 }),
stock: 50,
dimensions: {
package_weight: faker.datatype.number({ min: 1, max: 5 }),
package_height: faker.datatype.number({ min: 10, max: 100 }),
@@ -245,80 +426,84 @@ export const seedProduct = async (logger: Logger) => {
try {
logger.info("start seeding products");
// Fetch warranties and shipment methods
const warranties = await WarrantyModel.find().limit(10);
const shipments = (await ShipmentModel.find()).map((shipper) => shipper._id);
// Fetch warranties
const allWarranties = await WarrantyModel.find().limit(10);
// Filter out warranties without valid _id (auto-increment might not have set it yet)
const warranties = allWarranties.filter((w) => w._id !== undefined && w._id !== null);
const colorTheme = await ThemeModel.findOne({ title: "رنگ" });
const sizeTheme = await ThemeModel.findOne({ title: "سایز" });
const meterageTheme = await ThemeModel.findOne({ title: "متراژ" });
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) : [];
// Get all theme values randomly (required field)
const allThemeValues = await ThemeValueModel.find().limit(100);
const allThemeValueIds = allThemeValues.map((tv) => tv._id);
if (!warranties.length) {
throw new Error("No warranties found in the database. Please seed them first.");
throw new Error(
`No valid warranties found in the database. Found ${allWarranties.length} warranties but none have valid _id. Please seed warranties first.`,
);
}
const remainingProducts = [...fakeProductsData];
if (allThemeValueIds.length === 0) {
throw new Error("No theme values found in the database. Please seed them first.");
}
for (let i = 0; i < 10; i++) {
// Create fake seller
const seller = await SellerModel.create(CreateFakeSellerData());
await WalletModel.create({ seller: seller._id.toString() });
const shop = await ShopModel.create(createFakeShopData(faker.helpers.arrayElements(shipments, 3), seller._id.toString()));
// Find the admin shop (created in defaultAdmin seeder)
// Query using native MongoDB collection to bypass Mongoose pre-hook that requires Address model
const db = mongoose.connection.db;
if (!db) {
throw new Error("Database connection not available");
}
const shopsCollection = db.collection("shops");
const shop = await shopsCollection.findOne({ ownerRef: OwnerRef.ADMIN }, { projection: { _id: 1 } });
if (!shop || !shop._id) {
throw new Error("Admin shop not found. Please ensure defaultAdmin seeder runs before product seeder.");
}
const sellerProducts = remainingProducts.splice(0, 5);
// Process each product - all products belong to the admin shop
for (const productData of productsFromDump) {
// Get the correct brand and category for the product
const { brand, category } = await getBrandAndCategoryForProduct(productData);
const catAttributes = await CategoryAttributeModel.find({ category: category._id.toString() });
const attributeValues = await AttributeValueModel.find({ attribute: { $in: catAttributes.map((att) => att._id) } });
for (const productData of sellerProducts) {
// Get the correct brand and category for the product
const { brand, category } = await getBrandAndCategoryForProduct(productData);
const catAttributes = await CategoryAttributeModel.find({ category: category._id.toString() });
const attributeValues = await AttributeValueModel.find({ attribute: { $in: catAttributes.map((att) => att._id) } });
const specifications: ISpecifications[] = catAttributes.map((catAttr) => {
const valuesForAttribute = attributeValues
.filter((attValue) => attValue.attribute.toString() === catAttr._id.toString())
.map((attValue) => attValue.text);
const specifications: ISpecifications[] = catAttributes.map((catAttr) => {
const valuesForAttribute = attributeValues
.filter((attValue) => attValue.attribute.toString() === catAttr._id.toString())
.map((attValue) => attValue.text);
return {
title: catAttr.title,
values: valuesForAttribute,
};
});
return {
title: catAttr.title,
values: valuesForAttribute,
};
});
const product = await new ProductModel({
...productData,
shop: shop._id,
specifications,
brand: brand._id.toString(),
category: category._id.toString(),
step: CreateProductStep.Image,
status: ProductStatus.Approved,
isFake: false,
}).save();
const product = await new ProductModel({
...productData,
shop: shop._id,
specifications,
brand: brand._id.toString(),
category: category._id.toString(),
}).save();
for (let k = 0; k < 3; k++) {
// Select a theme value randomly
const themeValueId = faker.helpers.arrayElement(allThemeValueIds).toString();
for (let k = 0; k < 3; k++) {
// Select a theme value based on product data
let themeValueId: string | undefined;
if (productData.color && colors.length > 0) {
themeValueId = faker.helpers.arrayElement(colors).toString();
} else if (productData.size && sizes.length > 0) {
themeValueId = faker.helpers.arrayElement(sizes).toString();
} else if (productData.meterage && meterages.length > 0) {
themeValueId = faker.helpers.arrayElement(meterages).toString();
}
const variant = await createFakeProductVariant(
product._id,
shop._id.toString(),
faker.helpers.arrayElement(warranties)._id,
themeValueId,
).save();
product.variants.push(variant._id);
await product.save();
const selectedWarranty = faker.helpers.arrayElement(warranties);
if (!selectedWarranty) {
throw new Error(`No warranty available. Warranties array length: ${warranties.length}`);
}
}
if (remainingProducts.length < 5) break;
const warrantyId = selectedWarranty._id;
if (warrantyId === undefined || warrantyId === null || (typeof warrantyId !== "number" && typeof warrantyId !== "string")) {
throw new Error(`Invalid warranty ID: ${warrantyId} (type: ${typeof warrantyId})`);
}
const variant = await createFakeProductVariant(product._id, shop._id.toString(), warrantyId, themeValueId).save();
product.variants.push(variant._id);
await product.save();
}
}
logger.info("Seeding product completed!");
} catch (error) {
+25 -15
View File
@@ -1,7 +1,8 @@
import { cleanMongoData, extractDumpData, getCollectionData } from "./utils/extractDumpData";
import { Logger } from "../../core/logging/logger";
import { ReportQuestionModel } from "../../modules/product/models/reportQuestion.model";
const Questions = [
const fallbackQuestions = [
{
title: "نام کالا نادرست است",
type: "checkbox",
@@ -29,7 +30,6 @@ const Questions = [
{
title: "کالا تکراری است",
type: "checkbox",
// related_questions: [relatedQues._id],
},
];
@@ -37,24 +37,34 @@ export const ReportQuesSeeder = async (logger: Logger) => {
try {
logger.info("ReportQuestion seeding started");
// Extract data from dump
const dumpData = extractDumpData();
const reportQuestionsData = getCollectionData(dumpData, "reportquestions");
await ReportQuestionModel.deleteMany();
for (const question of Questions) {
await ReportQuestionModel.create(question);
if (reportQuestionsData.length === 0) {
logger.warn("No report question data found in dump, using fallback data");
for (const question of fallbackQuestions) {
await ReportQuestionModel.create(question);
}
logger.info("report questions seeded successfully with fallback data");
return;
}
// const relatedQues = await ReportQuestionModel.create({
// title: "کد کالای مشابه ",
// type: "number",
// placeholder: "https://shinan.ir/product/SHP-313420/",
// });
// Clean and prepare report question data
const cleanedQuestions = reportQuestionsData.map((question) => {
const cleaned = cleanMongoData(question);
return {
title: cleaned.title,
type: cleaned.type || "checkbox",
placeholder: cleaned.placeholder || null,
};
});
// await ReportQuestionModel.create({
// title: "کالا تکراری است",
// type: "checkbox",
// related_questions: [relatedQues._id],
// });
await ReportQuestionModel.insertMany(cleanedQuestions);
logger.info("report questions seeded successfully");
logger.info(`report questions seeded successfully (${cleanedQuestions.length} questions)`);
} catch (error) {
logger.error("error seeding the report questions:", error);
}
+46 -16
View File
@@ -1,3 +1,4 @@
import { cleanMongoData, extractDumpData, getCollectionData } from "./utils/extractDumpData";
import { Logger } from "../../core/logging/logger";
import { RoleEnum } from "../../modules/admin/models/Abstraction/IRole";
import { RoleModel } from "../../modules/admin/models/role.model";
@@ -5,29 +6,58 @@ import { RoleModel } from "../../modules/admin/models/role.model";
const createDefaultRoles = async (logger: Logger) => {
logger.info("create roles started", "SEED");
// const roles = Object.values(RoleEnum);
const rolesWithPermissions = [
{
role: RoleEnum.SUPERADMIN,
},
{
role: RoleEnum.MODERATOR,
},
{
role: RoleEnum.EDITOR,
},
];
// Extract data from dump
const dumpData = extractDumpData();
const rolesData = getCollectionData(dumpData, "roles");
if (rolesData.length === 0) {
logger.warn("No role data found in dump, using fallback data");
const rolesWithPermissions = [
{
role: RoleEnum.SUPERADMIN,
},
{
role: RoleEnum.MODERATOR,
},
{
role: RoleEnum.EDITOR,
},
];
const createdRoles = await Promise.all(
rolesWithPermissions.map(async ({ role }) => {
const existingRole = await RoleModel.findOne({ name: role });
if (!existingRole) {
return RoleModel.create({ name: role });
}
return existingRole;
}),
);
if (createdRoles) {
logger.info("Default roles created", "SEED");
} else {
logger.warn("Default roles already exists", "SEED");
}
return;
}
// Clean and prepare role data
const cleanedRoles = rolesData.map((role) => {
const cleaned = cleanMongoData(role);
return cleaned.name as RoleEnum;
});
const createdRoles = await Promise.all(
rolesWithPermissions.map(async ({ role }) => {
const existingRole = await RoleModel.findOne({ name: role });
cleanedRoles.map(async (roleName) => {
const existingRole = await RoleModel.findOne({ name: roleName });
if (!existingRole) {
return RoleModel.create({ name: role });
return RoleModel.create({ name: roleName });
}
return existingRole;
}),
);
if (createdRoles) {
logger.info("Default roles created", "SEED");
logger.info(`Default roles created (${createdRoles.length} roles)`, "SEED");
} else {
logger.warn("Default roles already exists", "SEED");
}
+93 -61
View File
@@ -1,74 +1,106 @@
import { cleanMongoData, extractDumpData, getCollectionData } from "./utils/extractDumpData";
import { DeliveryType } from "../../common/enums/shipment.enum";
import { Logger } from "../../core/logging/logger";
import { ShipmentModel } from "../../modules/shipment/models/shipment.model";
export const SeedShipment = async (logger: Logger) => {
const providers = [
{
name: "پست",
description: "ارسال توسط پست",
costs: [
{ weightRange: { min: 0, max: 1 }, cost_first: 40000, cost_rest: 2000 },
{ weightRange: { min: 1, max: 5 }, cost_first: 80000, cost_rest: 2000 },
{ weightRange: { min: 5, max: 10 }, cost_first: 100000, cost_rest: 2000 },
],
deliveryTime: 5, // Standard (3-5 days)
deliveryType: DeliveryType.Standard,
},
{
name: "چاپار",
description: "ارائه بهترین راه حل های جهانی لجستیک چاپار",
costs: [
{ weightRange: { min: 0, max: 2 }, cost_first: 40000, cost_rest: 2000 },
{ weightRange: { min: 2, max: 6 }, cost_first: 80000, cost_rest: 2000 },
{ weightRange: { min: 6, max: 12 }, cost_first: 100000, cost_rest: 2000 },
],
deliveryTime: 6, // Standard (4-6 days)
deliveryType: DeliveryType.Standard,
},
{
name: "پیک",
description: "ارسال توسط پیک",
costs: [
{ weightRange: { min: 0, max: 1 }, cost_first: 40000, cost_rest: 2000 },
{ weightRange: { min: 1, max: 3 }, cost_first: 80000, cost_rest: 2000 },
{ weightRange: { min: 3, max: 6 }, cost_first: 100000, cost_rest: 2000 },
],
deliveryTime: 0, // Same Day
deliveryType: DeliveryType.SameDay,
},
{
name: "تیپاکس",
description: "ارسال بسته های پستی در سریعترین زمان، از درب منزل با امکان پرداخت هزینه بعد از ارسال و پوشش تمام نقاط ایران",
costs: [
{ weightRange: { min: 0, max: 1 }, cost_first: 40000, cost_rest: 2000 },
{ weightRange: { min: 1, max: 5 }, cost_first: 80000, cost_rest: 2000 },
{ weightRange: { min: 5, max: 10 }, cost_first: 100000, cost_rest: 2000 },
],
deliveryTime: 2, // Express (1-2 days)
deliveryType: DeliveryType.Express,
},
{
name: "باربری",
description: "ارسال توسط باربری",
costs: [
{ weightRange: { min: 0, max: 10 }, cost_first: 40000, cost_rest: 2000 },
{ weightRange: { min: 10, max: 20 }, cost_first: 80000, cost_rest: 2000 },
{ weightRange: { min: 20, max: 50 }, cost_first: 100000, cost_rest: 2000 },
],
deliveryTime: 7, // Freight (5-7 days)
deliveryType: DeliveryType.Standard,
},
];
try {
logger.info("ShipmentMethod seeding started");
for (const provider of providers) {
await ShipmentModel.create(provider);
// Extract data from dump
const dumpData = extractDumpData();
const shipmentsData = getCollectionData(dumpData, "shipments");
if (shipmentsData.length === 0) {
logger.warn("No shipment data found in dump, using fallback data");
const fallbackProviders = [
{
name: "پست",
description: "ارسال توسط پست",
costs: [
{ weightRange: { min: 0, max: 1 }, cost_first: 40000, cost_rest: 2000 },
{ weightRange: { min: 1, max: 5 }, cost_first: 80000, cost_rest: 2000 },
{ weightRange: { min: 5, max: 10 }, cost_first: 100000, cost_rest: 2000 },
],
deliveryTime: 5,
deliveryType: DeliveryType.Standard,
},
{
name: "چاپار",
description: "ارائه بهترین راه حل های جهانی لجستیک چاپار",
costs: [
{ weightRange: { min: 0, max: 2 }, cost_first: 40000, cost_rest: 2000 },
{ weightRange: { min: 2, max: 6 }, cost_first: 80000, cost_rest: 2000 },
{ weightRange: { min: 6, max: 12 }, cost_first: 100000, cost_rest: 2000 },
],
deliveryTime: 6,
deliveryType: DeliveryType.Standard,
},
{
name: "پیک",
description: "ارسال توسط پیک",
costs: [
{ weightRange: { min: 0, max: 1 }, cost_first: 40000, cost_rest: 2000 },
{ weightRange: { min: 1, max: 3 }, cost_first: 80000, cost_rest: 2000 },
{ weightRange: { min: 3, max: 6 }, cost_first: 100000, cost_rest: 2000 },
],
deliveryTime: 0,
deliveryType: DeliveryType.SameDay,
},
{
name: "تیپاکس",
description: "ارسال بسته های پستی در سریعترین زمان، از درب منزل با امکان پرداخت هزینه بعد از ارسال و پوشش تمام نقاط ایران",
costs: [
{ weightRange: { min: 0, max: 1 }, cost_first: 40000, cost_rest: 2000 },
{ weightRange: { min: 1, max: 5 }, cost_first: 80000, cost_rest: 2000 },
{ weightRange: { min: 5, max: 10 }, cost_first: 100000, cost_rest: 2000 },
],
deliveryTime: 2,
deliveryType: DeliveryType.Express,
},
{
name: "باربری",
description: "ارسال توسط باربری",
costs: [
{ weightRange: { min: 0, max: 10 }, cost_first: 40000, cost_rest: 2000 },
{ weightRange: { min: 10, max: 20 }, cost_first: 80000, cost_rest: 2000 },
{ weightRange: { min: 20, max: 50 }, cost_first: 100000, cost_rest: 2000 },
],
deliveryTime: 7,
deliveryType: DeliveryType.Standard,
},
];
for (const provider of fallbackProviders) {
await ShipmentModel.create(provider);
}
logger.info("Shipment Providers seeded successfully with fallback data");
return;
}
logger.info("Shipment Providers seeded successfully");
// Clean and prepare shipment data (only non-deleted)
const cleanedShipments = shipmentsData
.map((shipment) => {
const cleaned = cleanMongoData(shipment);
if (cleaned.deleted) {
return null;
}
return {
name: cleaned.name,
description: cleaned.description,
costs: cleaned.costs,
deliveryTime: cleaned.deliveryTime,
deliveryType: cleaned.deliveryType as DeliveryType,
};
})
.filter((s) => s !== null);
if (cleanedShipments.length > 0) {
await ShipmentModel.insertMany(cleanedShipments);
logger.info(`Shipment Providers seeded successfully (${cleanedShipments.length} providers)`);
} else {
logger.warn("No valid shipments found after filtering");
}
} catch (error) {
logger.error("Error seeding Shipment providers:", error);
}
+63
View File
@@ -0,0 +1,63 @@
import { Logger } from "../../core/logging/logger";
import { InputTypeEnum, ThemeModel } from "../../modules/category/models/theme.model";
export const seedTheme = async (logger: Logger) => {
try {
logger.info("Theme seeding started");
const themes = [
{
title: "جریان نام",
inputType: InputTypeEnum.Number,
},
{
title: "ولتاژ کاری",
inputType: InputTypeEnum.Number,
},
{
title: "تعداد پل",
inputType: InputTypeEnum.Number,
},
{
title: "فاز",
inputType: InputTypeEnum.Text,
},
{
title: "رنگ",
inputType: InputTypeEnum.Color,
},
{
title: "سایز",
inputType: InputTypeEnum.Text,
},
{
title: "قدرت قطع",
inputType: InputTypeEnum.Number,
},
{
title: "نوع تغذیه",
inputType: InputTypeEnum.Text,
},
{
title: "تیپ",
inputType: InputTypeEnum.Text,
},
];
// Check if themes already exist
const existingThemes = await ThemeModel.find({
title: { $in: themes.map((t) => t.title) },
});
if (existingThemes.length > 0) {
logger.info(`Themes already exist (${existingThemes.length} found), skipping creation`);
return;
}
await ThemeModel.insertMany(themes);
logger.info(`Theme seeding complete! (${themes.length} themes)`);
} catch (error) {
logger.error("Error seeding themes:", error);
throw error;
}
};
+213
View File
@@ -0,0 +1,213 @@
import { Logger } from "../../core/logging/logger";
import { ThemeModel } from "../../modules/category/models/theme.model";
import { ThemeValueModel } from "../../modules/category/models/themeValue.model";
export const seedThemeValue = async (logger: Logger) => {
try {
logger.info("ThemeValue seeding started");
// Get all themes
const themes = await ThemeModel.find();
if (themes.length === 0) {
logger.warn("No themes found. Please seed themes first.");
return;
}
const themeMap = new Map(themes.map((theme) => [theme.title, theme._id]));
const themeValues: Array<{ theme: any; name?: string; value: string | number }> = [];
// جریان نام (Rated Current) - in Amperes
const ratedCurrentTheme = themeMap.get("جریان نام");
if (ratedCurrentTheme) {
const ratedCurrentValues = [6, 10, 16, 20, 25, 32, 40, 50, 63, 80, 100, 125, 160, 200, 250, 320, 400, 500, 630, 800, 1000];
ratedCurrentValues.forEach((value) => {
themeValues.push({
theme: ratedCurrentTheme,
name: `${value}A`,
value: value,
});
});
}
// ولتاژ کاری (Working Voltage) - in Volts
const workingVoltageTheme = themeMap.get("ولتاژ کاری");
if (workingVoltageTheme) {
const voltageValues = [110, 220, 230, 240, 380, 400, 415, 440, 500, 600, 690, 1000];
voltageValues.forEach((value) => {
themeValues.push({
theme: workingVoltageTheme,
name: `${value}V`,
value: value,
});
});
}
// تعداد پل (Number of Poles)
const polesTheme = themeMap.get("تعداد پل");
if (polesTheme) {
const polesValues = [1, 2, 3, 4];
polesValues.forEach((value) => {
themeValues.push({
theme: polesTheme,
name: `${value} پل`,
value: value,
});
});
}
// فاز (Phase)
const phaseTheme = themeMap.get("فاز");
if (phaseTheme) {
const phaseValues = ["تک فاز", "سه فاز", "یک فاز", "دو فاز"];
phaseValues.forEach((value) => {
themeValues.push({
theme: phaseTheme,
name: value,
value: value,
});
});
}
// رنگ (Color)
const colorTheme = themeMap.get("رنگ");
if (colorTheme) {
const colorValues = [
{ name: "سفید", value: "#FFFFFF" },
{ name: "سیاه", value: "#000000" },
{ name: "قرمز", value: "#FF0000" },
{ name: "آبی", value: "#0000FF" },
{ name: "سبز", value: "#00FF00" },
{ name: "زرد", value: "#FFFF00" },
{ name: "نارنجی", value: "#FFA500" },
{ name: "بنفش", value: "#800080" },
{ name: "قهوه‌ای", value: "#A52A2A" },
{ name: "خاکستری", value: "#808080" },
{ name: "نقره‌ای", value: "#C0C0C0" },
{ name: "طلایی", value: "#FFD700" },
];
colorValues.forEach((color) => {
themeValues.push({
theme: colorTheme,
name: color.name,
value: color.value,
});
});
}
// سایز (Size)
const sizeTheme = themeMap.get("سایز");
if (sizeTheme) {
const sizeValues = [
"XS",
"S",
"M",
"L",
"XL",
"XXL",
"XXXL",
"کوچک",
"متوسط",
"بزرگ",
"خیلی بزرگ",
"1/2",
"3/4",
"1",
"1.5",
"2.5",
"4",
"6",
"10",
"16",
"25",
"35",
"50",
"70",
"95",
"120",
"150",
"185",
"240",
];
sizeValues.forEach((value) => {
themeValues.push({
theme: sizeTheme,
name: value,
value: value,
});
});
}
// قدرت قطع (Breaking Capacity) - in kA
const breakingCapacityTheme = themeMap.get("قدرت قطع");
if (breakingCapacityTheme) {
const breakingCapacityValues = [1, 1.5, 2, 3, 4.5, 6, 10, 15, 18, 20, 25, 30, 35, 42, 50, 65, 70, 85, 100, 120, 150];
breakingCapacityValues.forEach((value) => {
themeValues.push({
theme: breakingCapacityTheme,
name: `${value}kA`,
value: value,
});
});
}
// نوع تغذیه (Power Supply Type)
const powerSupplyTheme = themeMap.get("نوع تغذیه");
if (powerSupplyTheme) {
const powerSupplyValues = ["AC", "DC", "AC/DC", "برق شهری", "باتری", "خورشیدی", "گاز", "بنزین", "دیزل", "هیبریدی", "الکتریکی"];
powerSupplyValues.forEach((value) => {
themeValues.push({
theme: powerSupplyTheme,
name: value,
value: value,
});
});
}
// تیپ (Type)
const typeTheme = themeMap.get("تیپ");
if (typeTheme) {
const typeValues = [
"مکانیکی",
"الکترونیکی",
"هیدرولیکی",
"پنوماتیکی",
"دیجیتال",
"آنالوگ",
"هوشمند",
"ساده",
"پیشرفته",
"استاندارد",
"ویژه",
"صنعتی",
"خانگی",
"تجاری",
];
typeValues.forEach((value) => {
themeValues.push({
theme: typeTheme,
name: value,
value: value,
});
});
}
if (themeValues.length === 0) {
logger.warn("No theme values to create");
return;
}
// Check if theme values already exist
const existingCount = await ThemeValueModel.countDocuments();
if (existingCount > 0) {
logger.info(`Theme values already exist (${existingCount} found), skipping creation`);
return;
}
await ThemeValueModel.insertMany(themeValues);
logger.info(`ThemeValue seeding complete! (${themeValues.length} theme values)`);
} catch (error) {
logger.error("Error seeding theme values:", error);
throw error;
}
};
+45 -26
View File
@@ -1,37 +1,56 @@
import { cleanMongoData, extractDumpData, getCollectionData } from "./utils/extractDumpData";
import { Logger } from "../../core/logging/logger";
import { TicketCategoryModel } from "../../modules/ticket/models/ticketCategory.model";
// Define ticket categories to seed
const ticketCategories = [
{
title_fa: "مشکل فنی",
description: "مشکلات مربوط به سیستم، سایت یا فروشگاه.",
},
{
title_fa: "مشکلات مالی",
description: "مشکلات مربوط به پرداخت‌ها و مسائل مالی.",
},
{
title_fa: "پشتیبانی محصول",
description: "سوالات و پشتیبانی‌های مربوط به محصولات.",
},
{
title_fa: "مشکلات ارسال",
description: "مشکلات مربوط به ارسال سفارشات و حمل و نقل.",
},
{
title_fa: "درخواست راهنمایی",
description: "درخواست‌ها برای راهنمایی و کمک در مورد مسائل مختلف.",
},
];
export const seedTicketCategories = async (logger: Logger) => {
try {
logger.info("start seeding the ticket category");
await TicketCategoryModel.insertMany(ticketCategories);
// Extract data from dump
const dumpData = extractDumpData();
const ticketCategoriesData = getCollectionData(dumpData, "ticketcategories");
logger.info("Ticket categories seeded successfully!");
if (ticketCategoriesData.length === 0) {
logger.warn("No ticket category data found in dump, using fallback data");
const fallbackCategories = [
{
title_fa: "مشکل فنی",
description: "مشکلات مربوط به سیستم، سایت یا فروشگاه.",
},
{
title_fa: "مشکلات مالی",
description: "مشکلات مربوط به پرداخت‌ها و مسائل مالی.",
},
{
title_fa: "پشتیبانی محصول",
description: "سوالات و پشتیبانی‌های مربوط به محصولات.",
},
{
title_fa: "مشکلات ارسال",
description: "مشکلات مربوط به ارسال سفارشات و حمل و نقل.",
},
{
title_fa: "درخواست راهنمایی",
description: "درخواست‌ها برای راهنمایی و کمک در مورد مسائل مختلف.",
},
];
await TicketCategoryModel.insertMany(fallbackCategories);
logger.info("Ticket categories seeded successfully with fallback data!");
return;
}
// Clean and prepare ticket category data
const cleanedCategories = ticketCategoriesData.map((category) => {
const cleaned = cleanMongoData(category);
return {
title_fa: cleaned.title_fa,
description: cleaned.description,
};
});
await TicketCategoryModel.insertMany(cleanedCategories);
logger.info(`Ticket categories seeded successfully! (${cleanedCategories.length} categories)`);
} catch (error) {
logger.error("Failed to seed ticket categories:", error);
}
+112
View File
@@ -0,0 +1,112 @@
import { execSync } from "child_process";
import * as fs from "fs";
import * as path from "path";
export interface DumpData {
[collectionName: string]: any[];
}
/**
* Extracts data from BSON dump files and converts them to JSON
*/
export const extractDumpData = (dumpDir: string = "dump/tablofajr"): DumpData => {
const dumpPath = path.resolve(process.cwd(), dumpDir);
const data: DumpData = {};
if (!fs.existsSync(dumpPath)) {
throw new Error(`Dump directory not found: ${dumpPath}`);
}
const files = fs.readdirSync(dumpPath);
const bsonFiles = files.filter((file) => file.endsWith(".bson"));
for (const bsonFile of bsonFiles) {
const collectionName = bsonFile.replace(".bson", "");
const bsonPath = path.join(dumpPath, bsonFile);
try {
// Use bsondump to convert BSON to JSON
const output = execSync(`bsondump "${bsonPath}"`, { encoding: "utf-8" });
const lines = output
.trim()
.split("\n")
.filter((line) => line.trim());
// Parse each JSON line (bsondump outputs one JSON object per line)
const documents = lines
.map((line) => {
try {
return JSON.parse(line);
} catch (e) {
// Skip lines that are not JSON (like the summary line at the end)
return null;
}
})
.filter((doc) => doc !== null);
if (documents.length > 0) {
data[collectionName] = documents;
}
} catch (error) {
console.warn(`Failed to extract data from ${bsonFile}:`, error);
}
}
return data;
};
/**
* Helper to get data for a specific collection
*/
export const getCollectionData = (dumpData: DumpData, collectionName: string): any[] => {
return dumpData[collectionName] || [];
};
/**
* Clean up MongoDB extended JSON format (convert $oid, $date, etc. to regular values)
*/
export const cleanMongoData = (data: any): any => {
if (Array.isArray(data)) {
return data.map(cleanMongoData);
}
if (data && typeof data === "object") {
// Handle $oid
if (data.$oid) {
return data.$oid;
}
// Handle $date
if (data.$date) {
return new Date(data.$date.$numberLong || data.$date);
}
// Handle $numberInt
if (data.$numberInt !== undefined) {
return parseInt(data.$numberInt, 10);
}
// Handle $numberLong
if (data.$numberLong !== undefined) {
return parseInt(data.$numberLong, 10);
}
// Handle $numberDouble
if (data.$numberDouble !== undefined) {
return parseFloat(data.$numberDouble);
}
// Recursively clean nested objects
const cleaned: any = {};
for (const [key, value] of Object.entries(data)) {
// Skip MongoDB internal fields that we don't need in seeders
if (key === "__v" || key === "createdAt" || key === "updatedAt") {
continue;
}
cleaned[key] = cleanMongoData(value);
}
return cleaned;
}
return data;
};
+58 -30
View File
@@ -1,46 +1,74 @@
import { faker } from "@faker-js/faker"; // Use faker for generating random data
import { cleanMongoData, extractDumpData, getCollectionData } from "./utils/extractDumpData";
import { StatusEnum } from "../../common/enums/status.enum";
import { Logger } from "../../core/logging/logger";
import { WarrantyModel } from "../../modules/warranty/models/warranty.model";
const warrantyNames = [
"Iran Warranty Co.",
// "Pars Guarantee",
// "Tehran Warranty Services",
// "Safir Warranty",
// "Farda Warranty Solutions",
// "Aryan Protection",
// "Mehr Warranty",
// "Saman Warranty Group",
// "Persian Warranty Solutions",
// "Shahab After Sales",
// "Payam Warranty",
// "Kavir Protection",
// "Tajrish Warranty",
// "Sina Warranty",
// "Atlas Warranty Services",
// "Sepahan Warranty",
// "Kish Warranty Group",
// "Navid Warranty Solutions",
// "Alborz Protection Services",
// "Raha Warranty Solutions",
];
export const seedWarranty = async (logger: Logger) => {
try {
logger.info("Warranty seeding started");
for (const name of warrantyNames) {
// Extract data from dump
const dumpData = extractDumpData();
const warrantiesData = getCollectionData(dumpData, "warranties");
if (warrantiesData.length === 0) {
logger.warn("No warranty data found in dump, using fallback data");
await WarrantyModel.create({
name,
name: "Iran Warranty Co.",
status: StatusEnum.Approved,
duration: faker.datatype.number({ min: 1, max: 36 }) + " months",
logoUrl: faker.image.business(),
duration: "12 months",
logoUrl: "",
});
logger.info("Warranty seeding complete with fallback data!");
return;
}
logger.info("Warranty seeding complete!");
// Clean and prepare warranty data
const cleanedWarranties = warrantiesData
.map((warranty) => {
const cleaned = cleanMongoData(warranty);
// Only include non-deleted warranties
if (cleaned.deleted) {
return null;
}
return {
name: cleaned.name,
status: cleaned.status || StatusEnum.Pending,
duration: cleaned.duration || "0",
logoUrl: cleaned.logoUrl || "",
};
})
.filter((w) => w !== null);
if (cleanedWarranties.length > 0) {
// Check for existing warranties and only create new ones to avoid duplicate key errors
let createdCount = 0;
let skippedCount = 0;
for (const warranty of cleanedWarranties) {
const existing = await WarrantyModel.findOne({ name: warranty.name });
if (!existing) {
try {
await WarrantyModel.create(warranty);
createdCount++;
} catch (error: any) {
// Handle duplicate key errors gracefully
if (error.code === 11000) {
logger.warn(`Skipping duplicate warranty: ${warranty.name}`);
skippedCount++;
} else {
throw error;
}
}
} else {
skippedCount++;
}
}
logger.info(`Warranty seeding complete! (${createdCount} created, ${skippedCount} skipped)`);
} else {
logger.warn("No valid warranties found after filtering");
}
} catch (error) {
logger.error("Error seeding warranties:", error);
}
@@ -26,4 +26,3 @@ export interface IChatMessage {
createdAt: Date;
updatedAt: Date;
}
@@ -16,4 +16,3 @@ export interface IChatSession {
createdAt: Date;
updatedAt: Date;
}
-1
View File
@@ -25,4 +25,3 @@ export function getOrCreateChatbotUlid(req: Request, res: Response): string {
return ulidValue;
}
+3 -3
View File
@@ -13,7 +13,7 @@ const contractHtml = `
<span style="font-size: 12pt">
<span style="font-size: 11pt">
<span style="color: #515151">
شرکت شینان در راستای رسالت خود در جهت حمایت از کسب و کارهای کوچک، توسعه کارآفرینی و بهبود فضای فروش اینترنتی و
شرکت فجر تابلو مرکزی در راستای رسالت خود در جهت حمایت از کسب و کارهای کوچک، توسعه کارآفرینی و بهبود فضای فروش اینترنتی و
رقابتی، در تاریخ
</span>
</span>
@@ -35,7 +35,7 @@ const contractHtml = `
<p style="text-align: justify">
<span style="font-size: 12pt">
<strong
><span style="font-size: 11pt"><span style="color: #515151">شینان</span></span></strong
><span style="font-size: 11pt"><span style="color: #515151">فجر تابلو مرکزی</span></span></strong
>
<span style="font-size: 11pt">
<span style="color: #515151"> به شماره ثبت 0000 و شناسه ملی 00000000 و نشانی اینترنتی </span>
@@ -106,7 +106,7 @@ const contractHtml = `
</span>
</p>
<p style="text-align: justify">
<span style="font-size: 12pt; font-weight: bold; color: #000000">مهر و امضا شینان :</span>
<span style="font-size: 12pt; font-weight: bold; color: #000000">مهر و امضا فجر تابلو مرکزی :</span>
</p>
</div>
`;