first
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { Expose } from "class-transformer";
|
||||
import { IsNotEmpty, IsOptional } from "class-validator";
|
||||
|
||||
import { ApiProperty } from "../../../common/decorator/swggerDocs";
|
||||
import { IsValidId } from "../../../common/decorator/validation.decorator";
|
||||
import { CategoryModel } from "../models/category.model";
|
||||
|
||||
export class CreateCategoryDTO {
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ type: "string", description: "the title_fa of category", example: "موبایل" })
|
||||
title_fa: string;
|
||||
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ type: "string", description: "the title_en of category", example: "mobile" })
|
||||
title_en: string;
|
||||
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ type: "string", description: "the name of icon", example: "mobile" })
|
||||
icon: string;
|
||||
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ type: "string", description: "the image url of category ", example: "https://cdn.com" })
|
||||
imageUrl: string;
|
||||
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ type: "string", description: "the description of category", example: " موبایل و لوازم جانبی" })
|
||||
description: string;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
@IsValidId(CategoryModel)
|
||||
@ApiProperty({ type: "string", description: "the id of parent category", example: "6103e3a9f9b1b5b27cd7c8e9" })
|
||||
parent?: string;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Expose } from "class-transformer";
|
||||
import { IsEnum, IsNotEmpty, IsOptional, IsString, Length } from "class-validator";
|
||||
|
||||
import { ApiProperty } from "../../../common/decorator/swggerDocs";
|
||||
import { IsValidId } from "../../../common/decorator/validation.decorator";
|
||||
import { CategoryThemeEnum } from "../../../common/enums/category.enum";
|
||||
import { CategoryModel } from "../models/category.model";
|
||||
|
||||
export class UpdateCategoryDTO {
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@Length(24, 24)
|
||||
@IsValidId(CategoryModel)
|
||||
@ApiProperty({ type: "string", description: "cat id", example: "66f3bcaee566db722a044c62" })
|
||||
catId: string;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ type: "string", description: "the title_fa of category", example: "موبایل" })
|
||||
title_fa: string;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ type: "string", description: "the title_en of category", example: "mobile" })
|
||||
title_en: string;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ type: "string", description: "the name of icon", example: "mobile" })
|
||||
icon: string;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ type: "string", description: "the description of category", example: " موبایل و لوازم جانبی" })
|
||||
description: string;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ type: "string", description: "the image url of category ", example: "https://cdn.com" })
|
||||
imageUrl: string;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
@IsValidId(CategoryModel)
|
||||
@ApiProperty({ type: "string", description: "the id of parent category", example: "6103e3a9f9b1b5b27cd7c8e9" })
|
||||
parent?: string;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
@IsEnum(CategoryThemeEnum)
|
||||
@ApiProperty({
|
||||
type: "string",
|
||||
description: "the variation theme of a category ==> should be one of Color/Size/Meterage or noColor_noSize",
|
||||
example: "Color",
|
||||
})
|
||||
theme: CategoryThemeEnum;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { Expose } from "class-transformer";
|
||||
import { IsBoolean, IsInt, IsOptional, IsString, Max, Min } from "class-validator";
|
||||
|
||||
import { PaginationDTO } from "../../../common/dto/pagination.dto";
|
||||
|
||||
export enum categoryFetchType {
|
||||
Panel = "panel",
|
||||
Front = "front",
|
||||
}
|
||||
export class CategorySearchQueryDTO {
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Max(50)
|
||||
limit?: number;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
page?: number;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
stock?: boolean;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
maxPrice?: number;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
minPrice?: number;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
brand?: string;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
wholeSale?: boolean;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(7)
|
||||
sort?: number;
|
||||
}
|
||||
|
||||
export class SellerCategorySearchDTO {
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
// @MinLength(1)
|
||||
q: string;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
parentId: string;
|
||||
}
|
||||
|
||||
export class CategoryQueryDto extends PaginationDTO {
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
// @MinLength(1)
|
||||
q: string;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
type: categoryFetchType;
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
import { Expose, Transform, Type, plainToInstance } from "class-transformer";
|
||||
|
||||
import { ICategory } from "../models/Abstraction/ICategory";
|
||||
import { ICategoryAttribute } from "../models/Abstraction/ICategoryAttributes";
|
||||
export class Size_MeterageDTO {
|
||||
@Expose()
|
||||
_id: number;
|
||||
|
||||
@Expose()
|
||||
value: string;
|
||||
}
|
||||
export class ColorDTO {
|
||||
@Expose()
|
||||
_id: number;
|
||||
|
||||
@Expose()
|
||||
name: string;
|
||||
|
||||
@Expose()
|
||||
hexColor: string;
|
||||
}
|
||||
export class VariantDTO {
|
||||
@Expose()
|
||||
_id: string;
|
||||
|
||||
@Expose()
|
||||
@Type(() => ColorDTO)
|
||||
@Transform(({ value }) => (value && value.length > 0 ? value : undefined) /*, { toClassOnly: true }*/)
|
||||
colors: ColorDTO[];
|
||||
|
||||
@Expose()
|
||||
@Type(() => Size_MeterageDTO)
|
||||
@Transform(({ value }) => (value && value.length > 0 ? value : undefined) /*, { toClassOnly: true }*/)
|
||||
sizes: Size_MeterageDTO[];
|
||||
|
||||
@Expose()
|
||||
@Type(() => Size_MeterageDTO)
|
||||
@Transform(({ value }) => (value && value.length > 0 ? value : undefined) /*, { toClassOnly: true }*/)
|
||||
meterages: Size_MeterageDTO[];
|
||||
}
|
||||
|
||||
export class CategoryVariantDTO {
|
||||
@Expose()
|
||||
_id: string;
|
||||
|
||||
@Expose()
|
||||
title_fa: string;
|
||||
|
||||
@Expose()
|
||||
title_en: string;
|
||||
|
||||
@Expose()
|
||||
theme: string;
|
||||
|
||||
@Expose()
|
||||
icon: string;
|
||||
|
||||
@Expose()
|
||||
imageUrl: string;
|
||||
|
||||
@Expose()
|
||||
description: string;
|
||||
|
||||
@Expose()
|
||||
leaf: boolean;
|
||||
|
||||
@Expose()
|
||||
@Type(() => VariantDTO)
|
||||
categoryVariants: VariantDTO;
|
||||
|
||||
public static transformCategory(data: ICategory): CategoryVariantDTO {
|
||||
const CategoryDTO = plainToInstance(CategoryVariantDTO, data, {
|
||||
excludeExtraneousValues: true,
|
||||
enableImplicitConversion: true, //this is for transform the value to the exact type that defined for property
|
||||
});
|
||||
|
||||
return CategoryDTO;
|
||||
}
|
||||
}
|
||||
|
||||
export class CategoryPath {
|
||||
@Expose()
|
||||
_id: string;
|
||||
|
||||
@Expose()
|
||||
title_fa: string;
|
||||
|
||||
@Expose()
|
||||
title_en: string;
|
||||
}
|
||||
|
||||
export class CategoryTreeDTO {
|
||||
@Expose()
|
||||
// @Transform(({ obj }) => obj._id.toString())
|
||||
_id: string;
|
||||
|
||||
@Expose()
|
||||
title_fa: string;
|
||||
|
||||
@Expose()
|
||||
title_en: string;
|
||||
|
||||
@Expose()
|
||||
icon: string;
|
||||
|
||||
// @Expose()
|
||||
// @Type(() => CategoryPath)
|
||||
// hierarchy: CategoryPath[];
|
||||
|
||||
@Expose()
|
||||
imageUrl: string;
|
||||
|
||||
@Expose()
|
||||
theme: string;
|
||||
|
||||
@Expose()
|
||||
description: string;
|
||||
|
||||
@Expose()
|
||||
leaf: boolean;
|
||||
|
||||
@Expose()
|
||||
@Type(() => CategoryPath)
|
||||
path: CategoryPath[];
|
||||
|
||||
@Expose()
|
||||
parent: string; // parent can be null too but class transformer convert it to null automatically
|
||||
|
||||
public static transformCategory(data: ICategory): CategoryTreeDTO {
|
||||
const transformed = plainToInstance(CategoryTreeDTO, data, {
|
||||
excludeExtraneousValues: true,
|
||||
enableImplicitConversion: true,
|
||||
});
|
||||
|
||||
// transformed._id = data._id instanceof ObjectId ? data._id.toString() : data._id;
|
||||
// transformed.parent = data.parent instanceof ObjectId ? data.parent.toString() : data.parent;
|
||||
|
||||
return transformed;
|
||||
}
|
||||
}
|
||||
|
||||
class AttributeValuesDTO {
|
||||
@Expose()
|
||||
_id: number;
|
||||
@Expose()
|
||||
text: string;
|
||||
}
|
||||
class AttributeDTO {
|
||||
@Expose()
|
||||
_id: number;
|
||||
|
||||
@Expose()
|
||||
title: string;
|
||||
|
||||
@Expose()
|
||||
type: string;
|
||||
|
||||
@Expose()
|
||||
multiple: boolean;
|
||||
|
||||
@Expose()
|
||||
hint: string;
|
||||
|
||||
@Expose()
|
||||
required: boolean;
|
||||
|
||||
@Expose()
|
||||
@Type(() => AttributeValuesDTO)
|
||||
values: AttributeValuesDTO[];
|
||||
}
|
||||
|
||||
export class CategoryAttributeDTO {
|
||||
@Expose()
|
||||
_id: number;
|
||||
|
||||
@Expose()
|
||||
category_title_fa: string;
|
||||
|
||||
@Expose()
|
||||
@Type(() => AttributeDTO)
|
||||
attributes: AttributeDTO[];
|
||||
|
||||
public static transformAttribute(data: ICategoryAttribute): CategoryAttributeDTO {
|
||||
const transformed = plainToInstance(CategoryAttributeDTO, data, {
|
||||
excludeExtraneousValues: true,
|
||||
enableImplicitConversion: true,
|
||||
});
|
||||
|
||||
// transformed._id = data._id instanceof ObjectId ? data._id.toString() : data._id;
|
||||
// transformed.parent = data.parent instanceof ObjectId ? data.parent.toString() : data.parent;
|
||||
|
||||
return transformed;
|
||||
}
|
||||
}
|
||||
|
||||
// @Transform((value) => {
|
||||
// console.log(value);
|
||||
|
||||
// if (!value.obj.parent) return value.obj.parent;
|
||||
// return value.obj.parent.toString();
|
||||
// })
|
||||
|
||||
// CategoryDTO._id = data._id instanceof ObjectId ? data._id.toString() : data._id;
|
||||
// CategoryDTO.attributes._id = data.attributes._id instanceof ObjectId ? data.attributes._id.toString() : data.attributes._id;
|
||||
|
||||
// @Transform(({ obj }) => obj._id.toString())
|
||||
// _id: string;
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Expose, Type } from "class-transformer";
|
||||
import { ArrayMinSize, IsArray, IsBoolean, IsEnum, IsNotEmpty, IsOptional, IsString, ValidateNested } from "class-validator";
|
||||
|
||||
import { ApiProperty } from "../../../common/decorator/swggerDocs";
|
||||
import { categoryAttType } from "../../../common/enums/category.enum";
|
||||
class AttributeValue {
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@ApiProperty({ type: "string", description: "Text of the attribute value", example: "ابریشم" })
|
||||
text: string;
|
||||
}
|
||||
|
||||
export class CreateCategoryAttDTO {
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ type: "string", description: "Title of the attribute", example: "جنس" })
|
||||
title: string;
|
||||
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@IsEnum(categoryAttType)
|
||||
@ApiProperty({ type: "string", description: "Type of the attribute", example: "checkbox" })
|
||||
type: categoryAttType;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
@IsBoolean()
|
||||
@ApiProperty({ type: "boolean", description: "if this be true then attribute can be selected many times", example: false })
|
||||
multiple: boolean;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@ApiProperty({ type: "string", description: "hint of the attribute", example: "نوع پارچه" })
|
||||
hint: string;
|
||||
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@IsBoolean()
|
||||
@ApiProperty({ type: "boolean", description: "Specifies if the attribute is required", example: true })
|
||||
required: boolean;
|
||||
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@IsArray()
|
||||
@ArrayMinSize(1)
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => AttributeValue)
|
||||
@ApiProperty({
|
||||
type: "Array",
|
||||
description: "If the attribute allows multiple values (e.g., checkbox or select), provide the values here",
|
||||
example: [{ text: "ابریشم" }, { text: "اسفنجی" }],
|
||||
})
|
||||
values: AttributeValue[];
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { PickType } from "@nestjs/mapped-types";
|
||||
import { Expose, Type } from "class-transformer";
|
||||
import { ArrayMinSize, IsEnum, IsHexColor, IsNotEmpty, IsOptional, ValidateNested } from "class-validator";
|
||||
|
||||
import { ApiProperty } from "../../../common/decorator/swggerDocs";
|
||||
import { CategoryThemeEnum } from "../../../common/enums/category.enum";
|
||||
|
||||
export class CreateColorDTO {
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
name: string;
|
||||
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@IsHexColor()
|
||||
hexColor: string;
|
||||
}
|
||||
|
||||
export class CreateSizeDTO {
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class CreateMeterageDTO {
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class CreateCategoryThemeDTO {
|
||||
@Expose()
|
||||
@IsNotEmpty()
|
||||
@IsEnum(CategoryThemeEnum)
|
||||
@ApiProperty({
|
||||
type: "string",
|
||||
description: "the variation theme of a category ==> should be one of Color/Size/Meterage or noColor_noSize",
|
||||
example: "Color",
|
||||
})
|
||||
theme: CategoryThemeEnum;
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
@ArrayMinSize(1)
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreateColorDTO)
|
||||
@ApiProperty({
|
||||
type: "Array",
|
||||
required: true,
|
||||
description: "the variation colors of a category theme",
|
||||
example: [{ name: "مشکی", hexColor: "#000000" }],
|
||||
})
|
||||
colors?: CreateColorDTO[];
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
@ArrayMinSize(1)
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreateSizeDTO)
|
||||
@ApiProperty({
|
||||
type: "Array",
|
||||
description: "the variation sizes of a category theme",
|
||||
example: [{ value: "M" }, { value: "S" }, { value: "L" }],
|
||||
})
|
||||
sizes?: CreateSizeDTO[];
|
||||
|
||||
@Expose()
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
@ArrayMinSize(1)
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreateSizeDTO)
|
||||
@ApiProperty({
|
||||
type: "Array",
|
||||
description: "the variation meterage of a category theme",
|
||||
example: [{ value: "10" }, { value: "20" }, { value: "50" }],
|
||||
})
|
||||
meterages?: CreateMeterageDTO[];
|
||||
}
|
||||
|
||||
export class UpdateCategoryVariantDTO extends PickType(CreateCategoryThemeDTO, ["colors", "sizes", "meterages"] as const) {}
|
||||
@@ -0,0 +1,127 @@
|
||||
import { Request } from "express";
|
||||
import { inject } from "inversify";
|
||||
import { controller, httpGet, httpPost, queryParam, request, requestParam } from "inversify-express-utils";
|
||||
|
||||
import { CategoryService } from "./category.service";
|
||||
import { HttpStatus } from "../../common";
|
||||
import { CategoryQueryDto, CategorySearchQueryDTO, SellerCategorySearchDTO, categoryFetchType } from "./DTO/category-sarch.dto";
|
||||
import { BaseController } from "../../common/base/controller";
|
||||
import { ApiAuth, ApiFile, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from "../../common/decorator/swggerDocs";
|
||||
import { Guard } from "../../core/middlewares/guard.middleware";
|
||||
import { ValidationMiddleware } from "../../core/middlewares/validator.middleware";
|
||||
import { IOCTYPES } from "../../IOC/ioc.types";
|
||||
import { UploadService } from "../../utils/upload.service";
|
||||
import { PermissionEnum } from "../admin/models/Abstraction/IPermission";
|
||||
import { IUser } from "../user/models/Abstraction/IUser";
|
||||
|
||||
@controller("/category")
|
||||
@ApiTags("Category")
|
||||
class CategoryController extends BaseController {
|
||||
@inject(IOCTYPES.CategoryService) categoryService: CategoryService;
|
||||
|
||||
//#######################################################
|
||||
//#######################################################
|
||||
@ApiOperation("Get all categories")
|
||||
@ApiResponse("get all categories", HttpStatus.Ok)
|
||||
@ApiQuery("limit", "the limit of return data")
|
||||
@ApiQuery("page", "the page want to get")
|
||||
@ApiQuery("q", "search based on category name")
|
||||
@ApiQuery("type", "specify to add pagination for category if its panel or front ==> panel | front")
|
||||
@httpGet("")
|
||||
public async getAll(@queryParam() queryDto: CategoryQueryDto) {
|
||||
if (queryDto.type === categoryFetchType.Panel) {
|
||||
const { count, category } = await this.categoryService.getCategoryForPanel(queryDto);
|
||||
const { pager } = this.paginate(count);
|
||||
return this.response({ category, pager });
|
||||
}
|
||||
const data = await this.categoryService.getCategories();
|
||||
return this.response({ data });
|
||||
}
|
||||
|
||||
@ApiOperation("Get all categories (tree like)")
|
||||
@ApiResponse("successful", HttpStatus.Ok)
|
||||
@ApiQuery("parentId", "parent id of a category")
|
||||
@httpGet("/tree", ValidationMiddleware.validateQuery(SellerCategorySearchDTO))
|
||||
public async getAllCategoryTree(@queryParam() queryDto: SellerCategorySearchDTO) {
|
||||
const data = await this.categoryService.getCategoriesTree(queryDto);
|
||||
return this.response({ data });
|
||||
}
|
||||
|
||||
@ApiOperation("Get a category with id")
|
||||
@ApiResponse("get a category", HttpStatus.Ok)
|
||||
@ApiParam("id", "id of the category", true)
|
||||
@httpGet("/:id")
|
||||
public async getById(@requestParam("id") id: string) {
|
||||
const data = await this.categoryService.getCategoryById(id);
|
||||
return this.response({ data });
|
||||
}
|
||||
|
||||
@ApiOperation("get all variant of a category with its id ")
|
||||
@ApiResponse("successful", HttpStatus.Ok)
|
||||
@ApiParam("id", "id of category", true)
|
||||
@httpGet("/:id/variants")
|
||||
public async getCategoryVariants(@requestParam("id") catId: string) {
|
||||
const data = await this.categoryService.getCategoryVariantsS(catId);
|
||||
return this.response({ data });
|
||||
}
|
||||
|
||||
@ApiOperation("get all attribute of a category with its id ")
|
||||
@ApiResponse("successful", HttpStatus.Ok)
|
||||
@ApiParam("id", "id of category", true)
|
||||
@httpGet("/:id/attributes")
|
||||
public async getCategoryAttribute(@requestParam("id") catId: string) {
|
||||
const data = await this.categoryService.getCategoryAttributeS(catId);
|
||||
return this.response({ data }, HttpStatus.Ok);
|
||||
}
|
||||
|
||||
@ApiOperation("get products of a category")
|
||||
@ApiResponse("successful", HttpStatus.Ok)
|
||||
@ApiParam("name", "name of category", true)
|
||||
@ApiQuery("limit", "the limit of return data")
|
||||
@ApiQuery("page", "the page want to get")
|
||||
@ApiQuery("stock", "search based on product stock")
|
||||
@ApiQuery("maxPrice", "search based on product priceRange")
|
||||
@ApiQuery("minPrice", "search based on product priceRange")
|
||||
@ApiQuery("brand", "search based on product brand id")
|
||||
@ApiQuery("wholeSale", "search based on product wholeSale if its enable")
|
||||
@ApiQuery("sort", "sort option should be int from 1 to 7")
|
||||
@httpGet("/:name/search", Guard.authOptional(), ValidationMiddleware.validateQuery(CategorySearchQueryDTO))
|
||||
@ApiAuth()
|
||||
public async getProductsOfCategory(
|
||||
@requestParam("name") name: string,
|
||||
@queryParam() searchQueryDto: CategorySearchQueryDTO,
|
||||
@request() req: Request,
|
||||
) {
|
||||
const user = req.user as IUser;
|
||||
const { count, ...data } = await this.categoryService.getProductsOfCategory(name, searchQueryDto, user?._id?.toString());
|
||||
const { pager } = this.paginate(count);
|
||||
return this.response({ ...data, pager });
|
||||
}
|
||||
|
||||
//#########################################uploader
|
||||
@ApiOperation("Upload a category image ==> need to login as admin")
|
||||
@ApiResponse("File uploaded successfully", HttpStatus.Accepted)
|
||||
@ApiFile("image")
|
||||
@ApiAuth()
|
||||
@httpPost(
|
||||
"/image/upload",
|
||||
Guard.authAdmin(),
|
||||
Guard.checkAdminPermissions([PermissionEnum.ADMIN]),
|
||||
UploadService.single("image", "category-images"),
|
||||
)
|
||||
public async uploadImage(@request() req: Request) {
|
||||
// eslint-disable-next-line no-undef
|
||||
const file = req.file as Express.MulterS3.File;
|
||||
const data = {
|
||||
message: "file uploaded!",
|
||||
url: {
|
||||
size: file?.size,
|
||||
url: file?.location,
|
||||
type: file?.mimetype,
|
||||
},
|
||||
};
|
||||
return this.response({ data }, HttpStatus.Accepted);
|
||||
}
|
||||
}
|
||||
|
||||
export { CategoryController };
|
||||
@@ -0,0 +1,541 @@
|
||||
import { Types } from "mongoose";
|
||||
|
||||
import { SellerCategorySearchDTO } from "./DTO/category-sarch.dto";
|
||||
import { IAttributeValue } from "./models/Abstraction/IAttributeValue";
|
||||
import { ICategory } from "./models/Abstraction/ICategory";
|
||||
import { ICategoryAttribute } from "./models/Abstraction/ICategoryAttributes";
|
||||
import { IColor } from "./models/Abstraction/IColor";
|
||||
import { IMeterage } from "./models/Abstraction/IMeterage";
|
||||
import { ISize } from "./models/Abstraction/ISize";
|
||||
import { AttributeValueModel } from "./models/attributeValue.model";
|
||||
import { CategoryModel } from "./models/category.model";
|
||||
import { CategoryAttributeModel } from "./models/CategoryAttribute.model";
|
||||
import { ColorModel } from "./models/color.model";
|
||||
import { MeterageModel } from "./models/meterage.model";
|
||||
import { SizeModel } from "./models/size.model";
|
||||
import { BaseRepository } from "../../common/base/repository";
|
||||
import { BadRequestError } from "../../core/app/app.errors";
|
||||
const { ObjectId } = Types;
|
||||
|
||||
class CategoryRepository extends BaseRepository<ICategory> {
|
||||
constructor() {
|
||||
super(CategoryModel);
|
||||
}
|
||||
async findByTitle(title_en: string, id?: string) {
|
||||
const query: Record<string, any> = { title_en };
|
||||
|
||||
if (id) {
|
||||
query._id = { $ne: id };
|
||||
}
|
||||
return this.model.findOne(query);
|
||||
}
|
||||
|
||||
// async getCategoryTree(parentId: string) {
|
||||
// const categories = await this.model.aggregate([
|
||||
// {
|
||||
// $match: {
|
||||
// parent: parentId ? new ObjectId(parentId) : null,
|
||||
// deleted: false,
|
||||
// },
|
||||
// },
|
||||
// ]);
|
||||
|
||||
// for (const category of categories) {
|
||||
// category.path = await this.getCategoryPath(category._id);
|
||||
// }
|
||||
// return categories;
|
||||
// }
|
||||
|
||||
async getCategoryTree(queryDto: SellerCategorySearchDTO) {
|
||||
const matchConditions: Record<string, any> = {
|
||||
deleted: false,
|
||||
};
|
||||
|
||||
if (queryDto.q) {
|
||||
matchConditions.$or = [{ title_en: { $regex: queryDto.q, $options: "i" } }, { title_fa: { $regex: queryDto.q, $options: "i" } }];
|
||||
} else {
|
||||
matchConditions.parent = queryDto.parentId ? new ObjectId(queryDto.parentId) : null;
|
||||
}
|
||||
|
||||
const categories = await this.model.aggregate([
|
||||
{
|
||||
$match: matchConditions,
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: "categories",
|
||||
foreignField: "_id",
|
||||
localField: "hierarchy",
|
||||
as: "path",
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
path: {
|
||||
icon: 0,
|
||||
imageUrl: 0,
|
||||
hierarchy: 0,
|
||||
description: 0,
|
||||
theme: 0,
|
||||
variants: 0,
|
||||
leaf: 0,
|
||||
parent: 0,
|
||||
url: 0,
|
||||
children: 0,
|
||||
deleted: 0,
|
||||
__v: 0,
|
||||
},
|
||||
children: 0,
|
||||
variants: 0,
|
||||
__v: 0,
|
||||
},
|
||||
},
|
||||
]);
|
||||
console.dir(categories, { depth: null });
|
||||
|
||||
// for (const category of categories) {
|
||||
// category.path = await this.getCategoryPath(category._id);
|
||||
// }
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
||||
//#######################################################
|
||||
async getCategoryTree2(): Promise<ICategory[]> {
|
||||
return this.model.aggregate([
|
||||
{
|
||||
$match: { parent: null },
|
||||
},
|
||||
{
|
||||
$graphLookup: {
|
||||
from: "categories",
|
||||
startWith: "$_id",
|
||||
connectFromField: "_id",
|
||||
connectToField: "parent",
|
||||
as: "children",
|
||||
depthField: "level",
|
||||
},
|
||||
},
|
||||
{
|
||||
$addFields: {
|
||||
// url: "/category/$title_en",
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
__v: 0,
|
||||
updatedAt: 0,
|
||||
"children.__v": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
$sort: {
|
||||
level: 1,
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
/** get category variant */
|
||||
|
||||
async getCategoryVariants(categoryIds: string[]) {
|
||||
// Step 1: Fetch all categories by IDs
|
||||
const categories = await this.model
|
||||
.find({ _id: { $in: categoryIds.map((id) => new Types.ObjectId(id)) } }, { _id: 1, theme: 1, variants: 1, title_en: 1 })
|
||||
.lean();
|
||||
|
||||
// Create a map to store themes and their corresponding unique variant IDs
|
||||
const themeVariantsMap = new Map<string, Set<string>>();
|
||||
|
||||
// Step 2: Populate the map with themes and variant IDs
|
||||
for (const category of categories) {
|
||||
if (!category?.theme || !category?.variants.length) continue;
|
||||
|
||||
const theme = `${category?.theme}s`.toLowerCase(); // e.g., 'colors', 'sizes', 'meterages'
|
||||
|
||||
if (!themeVariantsMap.has(theme)) {
|
||||
themeVariantsMap.set(theme, new Set());
|
||||
}
|
||||
|
||||
// Add variant IDs to the set for the specific theme
|
||||
category.variants.forEach((variantId) => {
|
||||
themeVariantsMap.get(theme)?.add(variantId.toString());
|
||||
});
|
||||
}
|
||||
|
||||
// Step 3: Prepare a result object to store fetched variant data
|
||||
const result: Record<string, any[]> = {};
|
||||
|
||||
// Step 4: Perform a single lookup per theme
|
||||
for (const [theme, variantIdsSet] of themeVariantsMap.entries()) {
|
||||
const variantIds = Array.from(variantIdsSet).map((id) => +id);
|
||||
|
||||
if (variantIds.length > 0) {
|
||||
let variantsData;
|
||||
// Fetch the variants from the corresponding theme collection
|
||||
if (theme === "colors") variantsData = await ColorModel.find({ _id: { $in: variantIds } }, { __v: 0 }).lean();
|
||||
if (theme === "sizes") variantsData = await SizeModel.find({ _id: { $in: variantIds } }, { __v: 0 }).lean();
|
||||
if (theme === "meterages") variantsData = await MeterageModel.find({ _id: { $in: variantIds } }, { __v: 0 }).lean();
|
||||
// throw new Error("theme not valid");
|
||||
|
||||
// Store the result for this theme
|
||||
result[theme] = variantsData as any;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async getSingleCategoryVariant(catId: string) {
|
||||
// const category = await this.model.findById(catId).populate("variants").lean();
|
||||
// if (!category) {
|
||||
// throw new Error("Category not found");
|
||||
// }
|
||||
// return category;
|
||||
|
||||
const category = await this.model.findById(catId).lean();
|
||||
if (!category || !category.theme) throw new BadRequestError("category has no theme");
|
||||
|
||||
const theme = `${category?.theme}s`.toLowerCase();
|
||||
|
||||
// Ensure the theme is valid
|
||||
const allowedThemes = ["colors", "sizes", "meterages"];
|
||||
if (!allowedThemes.includes(theme)) {
|
||||
throw new BadRequestError(`category has ${theme}`);
|
||||
}
|
||||
|
||||
const result = await this.model.aggregate([
|
||||
{
|
||||
$match: {
|
||||
_id: new ObjectId(catId),
|
||||
},
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: theme,
|
||||
localField: "variants",
|
||||
foreignField: "_id",
|
||||
as: "variantData",
|
||||
},
|
||||
},
|
||||
{
|
||||
$addFields: {
|
||||
categoryVariants: {
|
||||
[theme]: "$variantData",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
variantData: 0,
|
||||
variants: 0,
|
||||
categoryVariants: {
|
||||
[theme]: {
|
||||
__v: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
return result[0];
|
||||
}
|
||||
|
||||
/** get category attributes */
|
||||
async getCategoryAttributes(categoryIds: string[]): Promise<any | null> {
|
||||
const attributes = await this.model.aggregate([
|
||||
{
|
||||
$match: {
|
||||
_id: { $in: categoryIds.map((id) => new Types.ObjectId(id)) },
|
||||
},
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: "categoryattributes",
|
||||
localField: "_id",
|
||||
foreignField: "category",
|
||||
as: "attributes",
|
||||
},
|
||||
},
|
||||
{
|
||||
$unwind: "$attributes",
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: "attributevalues",
|
||||
localField: "attributes._id",
|
||||
foreignField: "attribute",
|
||||
as: "attributes.values",
|
||||
},
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: null, // Use `null` to group all categories together
|
||||
category_title_fa: { $addToSet: "$title_fa" },
|
||||
attributes: {
|
||||
$push: {
|
||||
_id: "$attributes._id",
|
||||
title: "$attributes.title",
|
||||
hint: "$attributes.hint",
|
||||
type: "$attributes.type",
|
||||
multiple: "$attributes.multiple",
|
||||
required: "$attributes.required",
|
||||
values: "$attributes.values",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 0, // Remove the grouping _id
|
||||
category_title_fa: 1,
|
||||
attributes: 1,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
return attributes.length ? attributes[0] : null;
|
||||
}
|
||||
|
||||
/** find with name */
|
||||
async findWithName(name: string) {
|
||||
return this.model.findOne(
|
||||
{ title_en: name },
|
||||
{ _id: 1, title_fa: 1, title_en: 1, description: 1, icon: 1, theme: 1, children: 1, url: 1 },
|
||||
);
|
||||
}
|
||||
|
||||
async getCategoryPath(catId: string) {
|
||||
const category = await this.model.findById(catId).lean();
|
||||
if (!category) return null;
|
||||
|
||||
const path = [{ id: category._id, title_fa: category.title_fa, title_en: category.title_en }]; // Add the current category to the path
|
||||
let parent = category.parent;
|
||||
|
||||
while (parent) {
|
||||
const parentCategory = await this.model.findById(parent).lean();
|
||||
if (!parentCategory) break;
|
||||
path.unshift({ id: parentCategory._id, title_fa: parentCategory.title_fa, title_en: parentCategory.title_en });
|
||||
parent = parentCategory.parent;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
function createCategoryRepo(): CategoryRepository {
|
||||
return new CategoryRepository();
|
||||
}
|
||||
//=====================>category att repo
|
||||
class CategoryAttributeRepo extends BaseRepository<ICategoryAttribute> {
|
||||
constructor() {
|
||||
super(CategoryAttributeModel);
|
||||
}
|
||||
|
||||
async findByCategoryId(catId: string) {
|
||||
return this.model.findOne({ category: catId });
|
||||
}
|
||||
}
|
||||
function createCategoryAttributeRepo(): CategoryAttributeRepo {
|
||||
return new CategoryAttributeRepo();
|
||||
}
|
||||
|
||||
//=============================> attribute value repo
|
||||
class AttributeValueRepo extends BaseRepository<IAttributeValue> {
|
||||
constructor() {
|
||||
super(AttributeValueModel);
|
||||
}
|
||||
|
||||
async findByText(text: string) {
|
||||
return this.model.findOne({ text });
|
||||
}
|
||||
}
|
||||
|
||||
function createAttributeValueRepo(): AttributeValueRepo {
|
||||
return new AttributeValueRepo();
|
||||
}
|
||||
|
||||
//=====================>size model repository
|
||||
class SizeRepository extends BaseRepository<ISize> {
|
||||
constructor() {
|
||||
super(SizeModel);
|
||||
}
|
||||
}
|
||||
function createSizeRepo(): SizeRepository {
|
||||
return new SizeRepository();
|
||||
}
|
||||
|
||||
//=====================>color model repo
|
||||
class ColorRepository extends BaseRepository<IColor> {
|
||||
constructor() {
|
||||
super(ColorModel);
|
||||
}
|
||||
}
|
||||
function createColorRepo(): ColorRepository {
|
||||
return new ColorRepository();
|
||||
}
|
||||
|
||||
//=====================>meterage model repo
|
||||
class MeterageRepository extends BaseRepository<IMeterage> {
|
||||
constructor() {
|
||||
super(MeterageModel);
|
||||
}
|
||||
}
|
||||
function createMeterageRepo(): MeterageRepository {
|
||||
return new MeterageRepository();
|
||||
}
|
||||
|
||||
export {
|
||||
createCategoryRepo,
|
||||
createCategoryAttributeRepo,
|
||||
createSizeRepo,
|
||||
createMeterageRepo,
|
||||
createColorRepo,
|
||||
createAttributeValueRepo,
|
||||
AttributeValueRepo,
|
||||
CategoryRepository,
|
||||
CategoryAttributeRepo,
|
||||
SizeRepository,
|
||||
ColorRepository,
|
||||
MeterageRepository,
|
||||
};
|
||||
|
||||
// async getCategoryVariant(catId: string) {
|
||||
|
||||
// return this.model.aggregate([
|
||||
// {
|
||||
// $match: {
|
||||
// _id: new ObjectId(catId),
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// $lookup: {
|
||||
// from: "colors",
|
||||
// localField: "variants",
|
||||
// foreignField: "_id",
|
||||
// as: "colors",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// $lookup: {
|
||||
// from: "sizes",
|
||||
// localField: "variants",
|
||||
// foreignField: "_id",
|
||||
// as: "sizes",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// $lookup: {
|
||||
// from: "meterages",
|
||||
// localField: "variants",
|
||||
// foreignField: "_id",
|
||||
// as: "meterages",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// $addFields: {
|
||||
// variants: {
|
||||
// $cond: {
|
||||
// if: { $gt: [{ $size: "$colors" }, 0] },
|
||||
// then: { colors: "$colors" },
|
||||
// else: {},
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// $addFields: {
|
||||
// "variants.sizes": {
|
||||
// $cond: {
|
||||
// if: { $gt: [{ $size: "$sizes" }, 0] },
|
||||
// then: "$sizes",
|
||||
// else: "$$REMOVE",
|
||||
// },
|
||||
// },
|
||||
// "variants.meterages": {
|
||||
// $cond: {
|
||||
// if: { $gt: [{ $size: "$meterages" }, 0] },
|
||||
// then: "$meterages",
|
||||
// else: "$$REMOVE",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// $project: {
|
||||
// colors: 0,
|
||||
// sizes: 0,
|
||||
// meterages: 0,
|
||||
// },
|
||||
// },
|
||||
// ]);
|
||||
// return this.model.aggregate([
|
||||
// {
|
||||
// $match: {
|
||||
// _id: new ObjectId(catId),
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// $lookup: {
|
||||
// from: "colors",
|
||||
// localField: "variants",
|
||||
// foreignField: "_id",
|
||||
// as: "colors",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// $lookup: {
|
||||
// from: "sizes",
|
||||
// localField: "variants",
|
||||
// foreignField: "_id",
|
||||
// as: "sizes",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// $lookup: {
|
||||
// from: "meterages",
|
||||
// localField: "variants",
|
||||
// foreignField: "_id",
|
||||
// as: "meterages",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// $addFields: {
|
||||
// variants: {},
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// $addFields: {
|
||||
// "variants.colors": "$colors",
|
||||
// "variants.sizes": "$sizes",
|
||||
// "variants.meterages": "$meterages",
|
||||
// },
|
||||
// },
|
||||
// // {
|
||||
// // $unwind: "$variants",
|
||||
// // },
|
||||
// {
|
||||
// $project: {
|
||||
// colors: 0,
|
||||
// sizes: 0,
|
||||
// meterages: 0,
|
||||
// // "variants.colors": { $cond: { if: { $ne: ["$variants.colors", null] }, then: "$variants.colors", else: "$$REMOVE" } },
|
||||
// // "variants.sizes": { $cond: { if: { $ne: ["$variants.sizes", null] }, then: "$variants.sizes", else: "$$REMOVE" } },
|
||||
// // "variants.meterages": { $cond: { if: { $ne: ["$variants.meterages", null] }, then: "$variants.meterages", else: "$$REMOVE" } },
|
||||
// },
|
||||
// },
|
||||
// // {
|
||||
// // $addFields: {
|
||||
// // "variants.colors": {
|
||||
// // $cond: { if: { $eq: [{ $size: "$variants.colors" }, 0] }, then: null, else: "$variants.colors" },
|
||||
// // },
|
||||
// // "variants.sizes": {
|
||||
// // $cond: { if: { $eq: [{ $size: "$variants.sizes" }, 0] }, then: null, else: "$variants.sizes" },
|
||||
// // },
|
||||
// // "variants.meterages": {
|
||||
// // $cond: { if: { $eq: [{ $size: "$variants.meterages" }, 0] }, then: null, else: "$variants.meterages" },
|
||||
// // },
|
||||
// // },
|
||||
// // },
|
||||
// ]);
|
||||
// }
|
||||
@@ -0,0 +1,481 @@
|
||||
import { inject, injectable } from "inversify";
|
||||
import { ClientSession, FilterQuery, isValidObjectId, startSession } from "mongoose";
|
||||
import slugify from "slugify";
|
||||
|
||||
import {
|
||||
AttributeValueRepo,
|
||||
CategoryAttributeRepo,
|
||||
CategoryRepository,
|
||||
ColorRepository,
|
||||
MeterageRepository,
|
||||
SizeRepository,
|
||||
} from "./category.repository";
|
||||
import { BrandRepository } from "../brand/brand.repository";
|
||||
import { CategoryQueryDto, CategorySearchQueryDTO, SellerCategorySearchDTO } from "./DTO/category-sarch.dto";
|
||||
import { CategoryAttributeDTO, CategoryTreeDTO, CategoryVariantDTO } from "./DTO/category.dto";
|
||||
import { CreateCategoryDTO } from "./DTO/CreateCategory.dto";
|
||||
import { CreateCategoryAttDTO } from "./DTO/createCategoryAtt.dto";
|
||||
import {
|
||||
CreateCategoryThemeDTO,
|
||||
CreateColorDTO,
|
||||
CreateMeterageDTO,
|
||||
CreateSizeDTO,
|
||||
UpdateCategoryVariantDTO,
|
||||
} from "./DTO/createCategoryTheme.dto";
|
||||
import { UpdateCategoryDTO } from "./DTO/UpdateCategory.dto";
|
||||
import { IColor } from "./models/Abstraction/IColor";
|
||||
import { IMeterage } from "./models/Abstraction/IMeterage";
|
||||
import { ISize } from "./models/Abstraction/ISize";
|
||||
import { BaseRepository } from "../../common/base/repository";
|
||||
import { CategoryThemeEnum } from "../../common/enums/category.enum";
|
||||
import { CategoryMessage, CommonMessage } from "../../common/enums/message.enum";
|
||||
import { BadRequestError, ForbiddenError } from "../../core/app/app.errors";
|
||||
import { IOCTYPES } from "../../IOC/ioc.types";
|
||||
import { ProductDTO } from "../product/DTO/product.dto";
|
||||
import { IProduct } from "../product/models/Abstraction/IProduct";
|
||||
import { ProductRepository } from "../product/Repository/product";
|
||||
import { ICategory } from "./models/Abstraction/ICategory";
|
||||
import { paginationUtils } from "../../utils/pagination.utils";
|
||||
|
||||
@injectable()
|
||||
class CategoryService {
|
||||
@inject(IOCTYPES.CategoryRepository) categoryRepository: CategoryRepository;
|
||||
@inject(IOCTYPES.ProductRepository) productRepo: ProductRepository;
|
||||
@inject(IOCTYPES.BrandRepository) brandRepo: BrandRepository;
|
||||
@inject(IOCTYPES.CategoryAttributeRepository) categoryAttRepo: CategoryAttributeRepo;
|
||||
@inject(IOCTYPES.AttributeValueRepository) attributeValueRepo: AttributeValueRepo;
|
||||
@inject(IOCTYPES.ColorRepository) colorRepo: ColorRepository;
|
||||
@inject(IOCTYPES.SizeRepository) sizeRepo: SizeRepository;
|
||||
@inject(IOCTYPES.MeterageRepository) meterageRepo: MeterageRepository;
|
||||
|
||||
async getCategories() {
|
||||
return this.categoryRepository.model.find({ parent: null });
|
||||
}
|
||||
//################################
|
||||
async getCategoryForPanel(queryDto: CategoryQueryDto) {
|
||||
const { limit, skip } = paginationUtils(queryDto);
|
||||
|
||||
const searchQuery: FilterQuery<ICategory> = {
|
||||
parent: null,
|
||||
deleted: false,
|
||||
};
|
||||
|
||||
if (queryDto.q) {
|
||||
searchQuery.$or = [{ title_en: { $regex: queryDto.q, $options: "i" } }, { title_fa: { $regex: queryDto.q, $options: "i" } }];
|
||||
}
|
||||
const count = await this.categoryRepository.model.countDocuments(searchQuery);
|
||||
const category = await this.categoryRepository.model.find(searchQuery).skip(skip).limit(limit);
|
||||
return { count, category };
|
||||
}
|
||||
//################################
|
||||
async getCategoriesTree(queryDto: SellerCategorySearchDTO) {
|
||||
return this.categoryRepository.getCategoryTree(queryDto);
|
||||
// const categories = docs.map((doc) => CategoryTreeDTO.transformCategory(doc));
|
||||
|
||||
// return categories;
|
||||
}
|
||||
|
||||
//################################
|
||||
|
||||
// async searchCategory(queryDto: SellerCategorySearchDTO) {
|
||||
// const docs = await this.categoryRepository.searchCategory(queryDto.q);
|
||||
// const categories = docs.map((doc) => CategoryTreeDTO.transformCategory(doc));
|
||||
// return {
|
||||
// categories,
|
||||
// };
|
||||
// }
|
||||
|
||||
async getCategoryById(id: string) {
|
||||
if (!isValidObjectId(id)) throw new BadRequestError(CommonMessage.NotValidId);
|
||||
return this.categoryRepository.model.findOne({ _id: id, deleted: false });
|
||||
}
|
||||
//################################
|
||||
|
||||
async updateCategory(updateDto: UpdateCategoryDTO) {
|
||||
const category = await this.checkCategoryId(updateDto.catId);
|
||||
if (updateDto.title_en) {
|
||||
updateDto.title_en = slugify(updateDto.title_en);
|
||||
}
|
||||
|
||||
const existTitle = await this.categoryRepository.findByTitle(updateDto.title_en, category._id.toString());
|
||||
|
||||
if (existTitle) throw new BadRequestError(CategoryMessage.DuplicateTitle);
|
||||
//TODO: check if parent is valid for update theme
|
||||
if (updateDto.theme) {
|
||||
await this.categoryRepository.model.findOneAndUpdate({ _id: category._id.toString() }, { theme: updateDto.theme, variants: [] });
|
||||
}
|
||||
if (updateDto.parent) {
|
||||
const updatedCategory = await this.categoryRepository.model.findByIdAndUpdate(
|
||||
category._id.toString(),
|
||||
{ ...updateDto, leaf: false },
|
||||
{ new: true },
|
||||
);
|
||||
return {
|
||||
message: CategoryMessage.Updated,
|
||||
updatedCategory,
|
||||
};
|
||||
}
|
||||
const updatedCategory = await this.categoryRepository.model.findByIdAndUpdate(category._id.toString(), updateDto, { new: true });
|
||||
return {
|
||||
message: CategoryMessage.Updated,
|
||||
updatedCategory,
|
||||
};
|
||||
}
|
||||
//################################
|
||||
|
||||
async createCategory(createDto: CreateCategoryDTO) {
|
||||
createDto.title_en = slugify(createDto.title_en);
|
||||
|
||||
const existTitle = await this.categoryRepository.findByTitle(createDto.title_en);
|
||||
|
||||
if (existTitle) throw new BadRequestError(CategoryMessage.DuplicateTitle);
|
||||
//if parent exist first set parent leaf false
|
||||
if (createDto.parent) {
|
||||
await this.categoryRepository.model.findByIdAndUpdate(createDto.parent, { leaf: false });
|
||||
}
|
||||
//we set the leaf to true
|
||||
const category = await this.categoryRepository.model.create(createDto);
|
||||
|
||||
return {
|
||||
message: CategoryMessage.Created,
|
||||
category,
|
||||
};
|
||||
}
|
||||
|
||||
//################################
|
||||
async deleteCategory(categoryId: string) {
|
||||
const category = await this.checkCategoryId(categoryId);
|
||||
|
||||
const existChild = await this.categoryRepository.model.exists({ parent: category._id.toString() });
|
||||
if (existChild) throw new BadRequestError(CategoryMessage.CategoryIsParent);
|
||||
|
||||
const productsWithCategory = await this.productRepo.model.find(
|
||||
{
|
||||
category: category._id.toString(),
|
||||
},
|
||||
{ _id: 1, title_fa: 1 },
|
||||
);
|
||||
if (productsWithCategory.length > 0) {
|
||||
const productsName = productsWithCategory.map((product) => product.title_en);
|
||||
throw new BadRequestError([`${CategoryMessage.CategoryHasProduct} ${productsName.join(" ")}`]);
|
||||
}
|
||||
|
||||
await this.categoryRepository.model.findByIdAndUpdate(category._id.toString(), {
|
||||
deleted: true,
|
||||
});
|
||||
return {
|
||||
message: CategoryMessage.Deleted,
|
||||
};
|
||||
}
|
||||
|
||||
//################################
|
||||
|
||||
async updateCategoryVariantS(updateDto: UpdateCategoryVariantDTO, id: string) {
|
||||
const session = await startSession();
|
||||
session.startTransaction();
|
||||
try {
|
||||
const { colors, sizes, meterages } = updateDto;
|
||||
|
||||
const category = await this.checkCategoryId(id);
|
||||
|
||||
// Ensure the theme matches the provided attributes and create variants
|
||||
const variantRefs = await this.createVariantsBasedOnTheme(session, category.theme, colors, sizes, meterages);
|
||||
|
||||
// Update the category with the theme and variants
|
||||
category.variants = variantRefs;
|
||||
await category.save({ session });
|
||||
|
||||
await session.commitTransaction();
|
||||
|
||||
return {
|
||||
message: CommonMessage.Updated,
|
||||
category,
|
||||
};
|
||||
} catch (error) {
|
||||
await session.abortTransaction();
|
||||
throw error;
|
||||
} finally {
|
||||
await session.endSession();
|
||||
}
|
||||
}
|
||||
//################################
|
||||
async createCategoryThemeS(createCategoryThemeDto: CreateCategoryThemeDTO, catId: string) {
|
||||
const session = await startSession();
|
||||
session.startTransaction();
|
||||
try {
|
||||
const { theme, colors, sizes, meterages } = createCategoryThemeDto;
|
||||
|
||||
// Validate category ID
|
||||
const category = await this.checkCategoryId(catId);
|
||||
|
||||
// Check if the theme already exists
|
||||
if (category.theme) throw new BadRequestError(CategoryMessage.AlreadyExist);
|
||||
|
||||
// Ensure the theme matches the provided attributes and create variants
|
||||
const variantRefs = await this.createVariantsBasedOnTheme(session, theme, colors, sizes, meterages);
|
||||
|
||||
// Update the category with the theme and variants
|
||||
category.theme = theme;
|
||||
category.variants = variantRefs;
|
||||
await category.save({ session });
|
||||
|
||||
// Commit the transaction
|
||||
await session.commitTransaction();
|
||||
session.endSession();
|
||||
|
||||
return {
|
||||
message: CategoryMessage.VariantCreated,
|
||||
variant: category,
|
||||
};
|
||||
} catch (error) {
|
||||
// Rollback any changes made in the transaction
|
||||
await session.abortTransaction();
|
||||
session.endSession();
|
||||
throw error; // Re-throw the error to be handled by the calling errorHandler
|
||||
}
|
||||
}
|
||||
|
||||
async createCategoryAttributeS(createAttributeDto: CreateCategoryAttDTO, categoryId: string) {
|
||||
const session = await startSession();
|
||||
session.startTransaction();
|
||||
try {
|
||||
const { title, type, multiple, required, values, hint } = createAttributeDto;
|
||||
|
||||
// Validate the existence of the category
|
||||
await this.checkCategoryId(categoryId);
|
||||
|
||||
// Check if attribute already exists for the category
|
||||
const existAttribute = await this.categoryAttRepo.model.exists({ category: categoryId, title });
|
||||
if (existAttribute) {
|
||||
throw new BadRequestError(CategoryMessage.AttAlreadyExist);
|
||||
}
|
||||
|
||||
// Create the category attribute
|
||||
const categoryAttribute = await this.categoryAttRepo.model.create(
|
||||
[
|
||||
{
|
||||
category: categoryId,
|
||||
title,
|
||||
type,
|
||||
multiple,
|
||||
hint,
|
||||
required,
|
||||
},
|
||||
],
|
||||
{ session },
|
||||
);
|
||||
|
||||
for (const value of values) {
|
||||
await this.attributeValueRepo.model.create([{ text: value.text, attribute: categoryAttribute[0]._id }], { session });
|
||||
}
|
||||
|
||||
// Commit the transaction
|
||||
await session.commitTransaction();
|
||||
session.endSession();
|
||||
|
||||
return {
|
||||
message: CategoryMessage.AttributeCreated,
|
||||
attribute: categoryAttribute[0],
|
||||
};
|
||||
} catch (error) {
|
||||
// Rollback any changes made in the transaction
|
||||
await session.abortTransaction();
|
||||
session.endSession();
|
||||
throw error; // Re-throw the error to be handled by the calling errorHandler
|
||||
}
|
||||
}
|
||||
|
||||
async getCategoryAttributeS(catId: string) {
|
||||
await this.checkCategoryId(catId);
|
||||
|
||||
const docs = await this.categoryRepository.getCategoryAttributes([catId]);
|
||||
if (!docs) throw new ForbiddenError(CategoryMessage.CategoryNotValidToChose);
|
||||
|
||||
const attribute = CategoryAttributeDTO.transformAttribute(docs);
|
||||
|
||||
return {
|
||||
category: attribute,
|
||||
};
|
||||
}
|
||||
|
||||
async deleteCategoryAttributeS(attId: string) {
|
||||
const existAttribute = await this.categoryAttRepo.model.findById(attId);
|
||||
if (!existAttribute) throw new BadRequestError(CategoryMessage.NotValidId);
|
||||
|
||||
await this.categoryAttRepo.model.findByIdAndDelete(attId);
|
||||
|
||||
return {
|
||||
message: CategoryMessage.AttributeDeleted,
|
||||
};
|
||||
}
|
||||
|
||||
async deleteCategoryVariantS(catId: string, varId: string) {
|
||||
const category = await this.checkCategoryId(catId);
|
||||
category.variants = category.variants.filter((variant) => variant.toString() !== varId);
|
||||
await category.save();
|
||||
|
||||
return {
|
||||
message: CategoryMessage.AttributeDeleted,
|
||||
};
|
||||
}
|
||||
|
||||
async getCategoryVariantsS(catId: string) {
|
||||
await this.checkCategoryId(catId);
|
||||
const docs = await this.categoryRepository.getSingleCategoryVariant(catId);
|
||||
// const category = await this.categoryRepository.model.findById(catId).populate({
|
||||
// path: "attributes",
|
||||
// populate: {
|
||||
// path: "variants",
|
||||
// },
|
||||
// });
|
||||
if (!docs) throw new ForbiddenError(CategoryMessage.CategoryNotValidToChose);
|
||||
const category = CategoryVariantDTO.transformCategory(docs);
|
||||
return {
|
||||
category,
|
||||
};
|
||||
}
|
||||
|
||||
async getProductsOfCategory(name: string, queries: CategorySearchQueryDTO, userId?: string) {
|
||||
const category = await this.categoryRepository.findWithName(name);
|
||||
if (!category) throw new BadRequestError(CategoryMessage.NotFound);
|
||||
|
||||
const categoryIds = await this.getAllCategoryIds(category._id.toString());
|
||||
// get related attributes, price range, and brands
|
||||
const catAttributes = await this.categoryRepository.getCategoryAttributes(categoryIds);
|
||||
const priceRange = await this.productRepo.getPriceRange(categoryIds);
|
||||
const brands = await this.brandRepo.model.find({ category: { $in: categoryIds } });
|
||||
|
||||
// get category variants
|
||||
const categoryVariants = await this.categoryRepository.getCategoryVariants(categoryIds);
|
||||
|
||||
const colors = categoryVariants?.colors;
|
||||
const sizes = categoryVariants?.sizes;
|
||||
const meterages = categoryVariants?.meterages;
|
||||
|
||||
// fetch products from the category and its children
|
||||
const { docs, count } = await this.productRepo.getProductsWithCategory(categoryIds, queries, userId);
|
||||
const products = docs.map((doc: IProduct) => ProductDTO.transformProduct(doc));
|
||||
const filters = { attributes: catAttributes?.attributes, priceRange, colors, sizes, meterages };
|
||||
|
||||
// fetch category breadcrumb
|
||||
const breadcrumb = await this.getCategoryBreadcrumb(category._id.toString());
|
||||
|
||||
return { category, brands, products, filters, count, breadcrumb };
|
||||
}
|
||||
|
||||
//=========================================>helper method
|
||||
|
||||
// Helper method to get category breadcrumb
|
||||
private async getCategoryBreadcrumb(categoryId: string): Promise<CategoryTreeDTO[]> {
|
||||
const breadcrumb: CategoryTreeDTO[] = [];
|
||||
let currentCategory = await this.categoryRepository.findById(categoryId);
|
||||
|
||||
while (currentCategory) {
|
||||
breadcrumb.unshift(CategoryTreeDTO.transformCategory(currentCategory));
|
||||
if (currentCategory.parent) {
|
||||
currentCategory = await this.categoryRepository.findById(currentCategory.parent.toString());
|
||||
} else {
|
||||
currentCategory = null;
|
||||
}
|
||||
}
|
||||
|
||||
return breadcrumb;
|
||||
}
|
||||
|
||||
// Helper function to get all category IDs, including children
|
||||
private async getAllCategoryIds(rootCategoryId: string): Promise<string[]> {
|
||||
const categoriesToProcess = [rootCategoryId];
|
||||
const allCategoryIds = new Set<string>([rootCategoryId]);
|
||||
|
||||
while (categoriesToProcess.length > 0) {
|
||||
const currentCategoryId = categoriesToProcess.pop();
|
||||
const currentCategory = await this.categoryRepository.findById(currentCategoryId as string);
|
||||
|
||||
if (currentCategory && currentCategory.children && currentCategory.children.length > 0) {
|
||||
for (const child of currentCategory.children) {
|
||||
if (!allCategoryIds.has(child._id.toString())) {
|
||||
allCategoryIds.add(child._id.toString());
|
||||
categoriesToProcess.push(child._id.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(allCategoryIds);
|
||||
}
|
||||
// Helper method to check the category ID validity
|
||||
private async checkCategoryId(catId: string) {
|
||||
//check if id is valid
|
||||
if (!isValidObjectId(catId)) throw new BadRequestError(CommonMessage.NotValidId);
|
||||
//check if category exist in database
|
||||
const existCategory = await this.categoryRepository.findById(catId);
|
||||
if (!existCategory) throw new BadRequestError(CategoryMessage.NotValidId);
|
||||
|
||||
return existCategory;
|
||||
}
|
||||
|
||||
//Helper method to Create variants based on the provided theme
|
||||
public async createVariantsBasedOnTheme(
|
||||
session: ClientSession,
|
||||
theme: CategoryThemeEnum,
|
||||
colors?: CreateColorDTO[],
|
||||
sizes?: CreateSizeDTO[],
|
||||
meterages?: CreateMeterageDTO[],
|
||||
): Promise<number[]> {
|
||||
const variantRefs: number[] = [];
|
||||
|
||||
if (theme === CategoryThemeEnum.Colored) {
|
||||
if (colors && colors.length > 0) {
|
||||
await this.processVariants(this.colorRepo, "name", colors, session, variantRefs);
|
||||
} else {
|
||||
throw new BadRequestError(CategoryMessage.MissingColors);
|
||||
}
|
||||
} else if (theme === CategoryThemeEnum.Sized) {
|
||||
if (sizes && sizes.length > 0) {
|
||||
await this.processVariants(this.sizeRepo, "value", sizes, session, variantRefs);
|
||||
} else {
|
||||
throw new BadRequestError(CategoryMessage.MissingSizes);
|
||||
}
|
||||
} else if (theme === CategoryThemeEnum.Meterage) {
|
||||
if (meterages && meterages.length > 0) {
|
||||
await this.processVariants(this.meterageRepo, "value", meterages, session, variantRefs);
|
||||
} else {
|
||||
throw new BadRequestError(CategoryMessage.MissingMeterages);
|
||||
}
|
||||
} else if (theme === CategoryThemeEnum.No_color_No_sized) {
|
||||
return [];
|
||||
} else {
|
||||
throw new BadRequestError(CategoryMessage.InvalidTheme);
|
||||
}
|
||||
|
||||
return variantRefs;
|
||||
}
|
||||
|
||||
// Generic function to process create variants
|
||||
private async processVariants<V extends IColor | ISize | IMeterage, T>(
|
||||
repo: BaseRepository<V>,
|
||||
field: keyof T,
|
||||
items: T[],
|
||||
session: ClientSession,
|
||||
variantRefs: number[],
|
||||
) {
|
||||
for (const item of items) {
|
||||
const query = { [field]: item[field] } as FilterQuery<V>;
|
||||
console.log("query", query);
|
||||
const all = await repo.model.find();
|
||||
const existingItem = await repo.model.exists({ ...query });
|
||||
console.log(existingItem);
|
||||
console.log("all", all);
|
||||
if (existingItem) {
|
||||
variantRefs.push(existingItem._id);
|
||||
} else {
|
||||
console.log(item);
|
||||
const newItem = await repo.model.create([{ ...item }], { session });
|
||||
variantRefs.push(newItem[0]._id);
|
||||
await session.commitTransaction();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { CategoryService };
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface IAttributeValue {
|
||||
_id: number;
|
||||
attribute: number;
|
||||
text: string;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Types } from "mongoose";
|
||||
|
||||
import { CategoryThemeEnum } from "../../../../common/enums/category.enum";
|
||||
|
||||
export interface ICategory {
|
||||
_id: Types.ObjectId;
|
||||
title_fa: string;
|
||||
title_en: string;
|
||||
icon: string;
|
||||
imageUrl: string;
|
||||
description: string;
|
||||
theme: CategoryThemeEnum;
|
||||
variants: number[];
|
||||
leaf: boolean;
|
||||
parent: Types.ObjectId;
|
||||
url: string;
|
||||
children: ICategory[];
|
||||
deleted: boolean;
|
||||
hierarchy: Types.ObjectId[];
|
||||
// attributes: Types.ObjectId;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Types } from "mongoose";
|
||||
|
||||
import { categoryAttType } from "../../../../common/enums/category.enum";
|
||||
|
||||
export interface ICategoryAttribute {
|
||||
_id: number;
|
||||
category: Types.ObjectId;
|
||||
title: string;
|
||||
hint: string;
|
||||
type: categoryAttType;
|
||||
multiple: boolean;
|
||||
required: boolean;
|
||||
// value: number;
|
||||
// values: number[];
|
||||
// divisions: string[];
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface IColor {
|
||||
_id: number;
|
||||
name: string;
|
||||
hexColor: string;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface IMeterage {
|
||||
_id: number;
|
||||
value: string;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface ISize {
|
||||
_id: number;
|
||||
value: string;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import mongoose, { Schema, model } from "mongoose";
|
||||
|
||||
import { ICategoryAttribute } from "./Abstraction/ICategoryAttributes";
|
||||
import { AttributeValueModel } from "./attributeValue.model";
|
||||
import { categoryAttType } from "../../../common/enums/category.enum";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
|
||||
const AutoIncrement = require("mongoose-sequence")(mongoose);
|
||||
|
||||
const CategoryAttributesSchema = new Schema<ICategoryAttribute>(
|
||||
{
|
||||
_id: Number,
|
||||
category: { type: Schema.Types.ObjectId, ref: "Category", required: true },
|
||||
title: { type: String, required: true },
|
||||
hint: { type: String, default: null },
|
||||
type: { type: String, enum: categoryAttType, required: true },
|
||||
multiple: { type: Boolean, default: false },
|
||||
required: { type: Boolean, required: true },
|
||||
// value: { type: Number, ref: "AttributeValue", default: null }, // Single value
|
||||
// values: [{ type: Number, ref: "AttributeValue" }], // Multiple values
|
||||
},
|
||||
{ timestamps: true, toJSON: { virtuals: true, versionKey: false }, id: false },
|
||||
);
|
||||
CategoryAttributesSchema.plugin(AutoIncrement, { id: "catAtt_id", inc_field: "_id" });
|
||||
|
||||
CategoryAttributesSchema.post(["deleteOne", "findOneAndDelete"], async function (doc, next) {
|
||||
await AttributeValueModel.deleteMany({ attribute: doc._id });
|
||||
next();
|
||||
});
|
||||
|
||||
const CategoryAttributeModel = model<ICategoryAttribute>("CategoryAttribute", CategoryAttributesSchema);
|
||||
|
||||
export { CategoryAttributeModel };
|
||||
@@ -0,0 +1,19 @@
|
||||
import mongoose, { Schema, model } from "mongoose";
|
||||
|
||||
import { IAttributeValue } from "./Abstraction/IAttributeValue";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
|
||||
const AutoIncrement = require("mongoose-sequence")(mongoose);
|
||||
|
||||
const AttributeValueSchema = new Schema<IAttributeValue>(
|
||||
{
|
||||
_id: Number,
|
||||
attribute: { type: Number, ref: "CategoryAttribute", required: true },
|
||||
text: { type: String, required: true },
|
||||
},
|
||||
{ timestamps: true },
|
||||
);
|
||||
AttributeValueSchema.plugin(AutoIncrement, { id: "attValue_id", inc_field: "_id" });
|
||||
const AttributeValueModel = model<IAttributeValue>("AttributeValue", AttributeValueSchema);
|
||||
|
||||
export { AttributeValueModel };
|
||||
@@ -0,0 +1,64 @@
|
||||
import { CallbackError, Schema, model } from "mongoose";
|
||||
|
||||
import { ICategory } from "./Abstraction/ICategory";
|
||||
import { CategoryThemeEnum } from "../../../common/enums/category.enum";
|
||||
|
||||
const categorySchema = new Schema<ICategory>(
|
||||
{
|
||||
title_fa: { type: String, required: true },
|
||||
title_en: { type: String, unique: true, required: true },
|
||||
icon: { type: String, default: "/images/icons/default.png" },
|
||||
imageUrl: { type: String, required: true },
|
||||
description: { type: String, required: true },
|
||||
theme: { type: String, enum: CategoryThemeEnum },
|
||||
variants: [{ type: Number, refPath: "theme" }],
|
||||
hierarchy: [{ type: Schema.Types.ObjectId, ref: "Category" }],
|
||||
leaf: { type: Boolean, default: true },
|
||||
parent: { type: Schema.Types.ObjectId, ref: "Category", default: null },
|
||||
deleted: { type: Boolean, default: false },
|
||||
// variants: { type: [Number], refPath: "theme" },
|
||||
// attributes: { type: Schema.Types.ObjectId, ref: "CategoryAttribute" },
|
||||
},
|
||||
{ timestamps: true, toJSON: { virtuals: true, versionKey: false }, id: false },
|
||||
);
|
||||
|
||||
categorySchema.virtual("url").get(function () {
|
||||
return `/category/${this.title_en}/search`;
|
||||
});
|
||||
|
||||
categorySchema.virtual("children", {
|
||||
ref: "Category",
|
||||
localField: "_id",
|
||||
foreignField: "parent",
|
||||
justOne: false,
|
||||
});
|
||||
|
||||
categorySchema.pre("find", function (next) {
|
||||
this.populate({ path: "children" }).where({ deleted: false });
|
||||
next();
|
||||
});
|
||||
categorySchema.pre("findOne", function (next) {
|
||||
this.populate({ path: "children" }).where({ deleted: false });
|
||||
next();
|
||||
});
|
||||
|
||||
categorySchema.pre("save", async function (next) {
|
||||
try {
|
||||
if (this.isNew || this.isModified("parent")) {
|
||||
if (this.parent) {
|
||||
const parentCategory = await CategoryModel.findById(this.parent).exec();
|
||||
if (!parentCategory) return next(new Error("Parent category not found."));
|
||||
this.hierarchy = parentCategory.hierarchy.concat(this._id);
|
||||
} else {
|
||||
this.hierarchy = [this._id];
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (err) {
|
||||
next(err as CallbackError);
|
||||
}
|
||||
});
|
||||
|
||||
const CategoryModel = model<ICategory>("Category", categorySchema);
|
||||
export { CategoryModel };
|
||||
@@ -0,0 +1,20 @@
|
||||
import mongoose, { Schema, model } from "mongoose";
|
||||
|
||||
import { IColor } from "./Abstraction/IColor";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
|
||||
const AutoIncrement = require("mongoose-sequence")(mongoose);
|
||||
|
||||
const ColorSchema = new Schema<IColor>(
|
||||
{
|
||||
_id: Number,
|
||||
name: { type: String, unique: true, required: true },
|
||||
hexColor: { type: String, unique: true, required: true },
|
||||
},
|
||||
{ timestamps: false, _id: false, toJSON: { versionKey: false } },
|
||||
);
|
||||
|
||||
ColorSchema.plugin(AutoIncrement, { id: "color_id", inc_field: "_id" });
|
||||
const ColorModel = model<IColor>("Color", ColorSchema);
|
||||
|
||||
export { ColorModel };
|
||||
@@ -0,0 +1,19 @@
|
||||
import mongoose, { Schema, model } from "mongoose";
|
||||
|
||||
import { IMeterage } from "./Abstraction/IMeterage";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
||||
const AutoIncrement = require("mongoose-sequence")(mongoose);
|
||||
|
||||
const MeterageSchema = new Schema<IMeterage>(
|
||||
{
|
||||
_id: Number,
|
||||
value: { type: String, unique: true, required: true },
|
||||
},
|
||||
{ timestamps: false, _id: false, toJSON: { versionKey: false } },
|
||||
);
|
||||
|
||||
MeterageSchema.plugin(AutoIncrement, { id: "meterage_id", inc_field: "_id" });
|
||||
const MeterageModel = model<IMeterage>("Meterage", MeterageSchema);
|
||||
|
||||
export { MeterageModel };
|
||||
@@ -0,0 +1,19 @@
|
||||
import mongoose, { Schema, model } from "mongoose";
|
||||
|
||||
import { ISize } from "./Abstraction/ISize";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
|
||||
const AutoIncrement = require("mongoose-sequence")(mongoose);
|
||||
|
||||
const SizeSchema = new Schema<ISize>(
|
||||
{
|
||||
_id: Number,
|
||||
value: { type: String, unique: true, required: true },
|
||||
},
|
||||
{ timestamps: false, _id: false, toJSON: { versionKey: false } },
|
||||
);
|
||||
|
||||
SizeSchema.plugin(AutoIncrement, { id: "size_id", inc_field: "_id" });
|
||||
const SizeModel = model<ISize>("Size", SizeSchema);
|
||||
|
||||
export { SizeModel };
|
||||
Reference in New Issue
Block a user