212 lines
7.6 KiB
TypeScript
212 lines
7.6 KiB
TypeScript
import { Types } from "mongoose";
|
|
|
|
import { cleanMongoData, extractDumpData, getCollectionData } from "./utils/extractDumpData";
|
|
import { Logger } from "../../core/logging/logger";
|
|
import { CategoryModel } from "../../modules/category/models/category.model";
|
|
import { ThemeModel } from "../../modules/category/models/theme.model";
|
|
import { ThemeValueModel } from "../../modules/category/models/themeValue.model";
|
|
|
|
/**
|
|
* Maps theme string from dump to Persian theme title
|
|
*/
|
|
const mapThemeStringToTitle = (themeString: string | null | undefined): string | null => {
|
|
if (!themeString || themeString === "noColor_noSize") return null;
|
|
|
|
const themeMap: { [key: string]: string } = {
|
|
Color: "رنگ",
|
|
Size: "سایز",
|
|
Meterage: "متراژ",
|
|
};
|
|
|
|
return themeMap[themeString] || null;
|
|
};
|
|
|
|
export const seedCategories = async (logger: Logger) => {
|
|
try {
|
|
logger.info("Categories seeding started");
|
|
|
|
// Extract data from dump
|
|
const dumpData = extractDumpData();
|
|
const categoriesData = getCollectionData(dumpData, "categories");
|
|
|
|
if (categoriesData.length === 0) {
|
|
logger.warn("No category data found in dump, skipping category seeding");
|
|
return;
|
|
}
|
|
|
|
// 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);
|
|
});
|
|
|
|
// 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);
|
|
});
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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 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));
|
|
}
|
|
|
|
if (remainingCategories.length > 0) {
|
|
logger.warn(`${remainingCategories.length} categories could not be created due to missing parents`);
|
|
}
|
|
|
|
logger.info(`Categories seeded successfully! Created ${categoryIdMap.size} categories`);
|
|
} catch (error) {
|
|
logger.error("Error seeding categories:", error);
|
|
throw error;
|
|
}
|
|
};
|