From 013311c9c633c1da87424bae73ce0424f418ab4b Mon Sep 17 00:00:00 2001 From: vahidrezvani Date: Tue, 26 Aug 2025 09:20:49 +0330 Subject: [PATCH] complete tag module and shop --- .../shop-category-document.service.ts | 64 +++++- .../shop-category/shop-category.service.ts | 197 +++++++++++++++--- .../src/app/tag/dto/crearTag.dto.ts | 40 ++++ .../src/app/tag/dto/updateTag.dto.ts | 5 + catalog-service/src/app/tag/tag.service.ts | 195 ++++++++++++++++- .../src/locales/en/shopCategory.json | 26 +-- .../src/locales/en/shopCategoryDocument.json | 17 +- catalog-service/src/locales/en/tag.json | 11 + catalog-service/src/schemas/tag.schema.ts | 4 +- 9 files changed, 500 insertions(+), 59 deletions(-) create mode 100644 catalog-service/src/app/tag/dto/crearTag.dto.ts create mode 100644 catalog-service/src/app/tag/dto/updateTag.dto.ts create mode 100644 catalog-service/src/locales/en/tag.json diff --git a/catalog-service/src/app/shop-category-document/shop-category-document.service.ts b/catalog-service/src/app/shop-category-document/shop-category-document.service.ts index c4259f6..8e0a8b5 100644 --- a/catalog-service/src/app/shop-category-document/shop-category-document.service.ts +++ b/catalog-service/src/app/shop-category-document/shop-category-document.service.ts @@ -7,12 +7,12 @@ import { } from '../../schemas/shopCategoryDocument'; import { Model } from 'mongoose'; import { v4 as uuidv4 } from 'uuid'; -import { I18nService } from 'nestjs-i18n'; import { CreateShopCategoryDocumentDto } from './dto/creat-shop-category-document.dto'; import { ApiResponse, LanguageService } from '../language/language.service'; import i18next from 'i18next'; import { UpdateShopCategoryDocumentDto } from './dto/update-shop-category-document.dto'; import { ShopCategoryService } from '../shop-category/shop-category.service'; +import { Types } from 'mongoose'; @Injectable() export class ShopCategoryDocumentService { @@ -30,7 +30,6 @@ export class ShopCategoryDocumentService { lang: string ): Promise { try { - // بررسی زبان const language = await this.languageService.findOneWithID( createShopCategoryDocumentDto.Language, lang @@ -122,7 +121,6 @@ export class ShopCategoryDocumentService { updateShopCategoryDocumentDto.Language = language.data._id; } - // آپدیت فیلدها Object.assign(existingDoc, updateShopCategoryDocumentDto, { updatedAt: new Date(), }); @@ -142,7 +140,7 @@ export class ShopCategoryDocumentService { return { success: false, message, errors: error.message }; } } - + async toggleStatus(id: string, lang: string): Promise { try { const existingDoc = await this.shopCategoryDocumentModel @@ -228,5 +226,63 @@ export class ShopCategoryDocumentService { } } + async addTagToCategory( + categoryId: string, + tagId: string, + lang: string + ): Promise { + try { + const category = await this.shopCategoryDocumentModel + .findOne({ ID: categoryId }) + .exec(); + if (!category) { + return { + success: false, + message: await this.i18n.t('not_found', { + lng: lang, + ns: 'shopCategoryDocument', + }), + data: null, + }; + } + + // بررسی می‌کنیم آیا تگ قبلاً اضافه شده یا نه + const tagObjectId = new Types.ObjectId(tagId); + if (category.Tags.some((t: Types.ObjectId) => t.equals(tagObjectId))) { + return { + success: false, + message: await this.i18n.t('tag_already_exists', { + lng: lang, + ns: 'shopCategoryDocument', + }), + data: null, + }; + } + + // اضافه کردن تگ + category.Tags.push(tagObjectId); + category.updatedAt = new Date(); + + const updatedCategory = await category.save(); + + return { + success: true, + message: await this.i18n.t('tag_added', { + lng: lang, + ns: 'shopCategoryDocument', + }), + data: updatedCategory, + }; + } catch (error) { + return { + success: false, + message: await this.i18n.t('add_tag_error', { + lng: lang, + ns: 'shopCategoryDocument', + }), + errors: error.message, + }; + } + } } diff --git a/catalog-service/src/app/shop-category/shop-category.service.ts b/catalog-service/src/app/shop-category/shop-category.service.ts index f89d509..974a065 100644 --- a/catalog-service/src/app/shop-category/shop-category.service.ts +++ b/catalog-service/src/app/shop-category/shop-category.service.ts @@ -1,34 +1,61 @@ -import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { v4 as uuidv4 } from 'uuid'; -import { ShopCategory, ShopCategoryDoc } from '../../schemas/shopCategory.schema'; +import { + ShopCategory, + ShopCategoryDoc, +} from '../../schemas/shopCategory.schema'; import { CreateShopCategoryDto } from './dto/create-shop-category.dto'; import { LanguageService, ApiResponse } from '../language/language.service'; import { ShopCategoryDocumentService } from '../shop-category-document/shop-category-document.service'; -import { I18nService } from 'nestjs-i18n'; import i18next from 'i18next'; +import { Types } from 'mongoose'; + @Injectable() export class ShopCategoryService { constructor( - @InjectModel(ShopCategory.name) private shopCategoryModel: Model, + @InjectModel(ShopCategory.name) + private shopCategoryModel: Model, private readonly languageService: LanguageService, - private readonly shopCategoryDocumentService: ShopCategoryDocumentService, - @Inject('I18NEXT') private readonly i18n: typeof i18next + private readonly shopCategoryDocumentService: ShopCategoryDocumentService, + @Inject('I18NEXT') private readonly i18n: typeof i18next ) {} - async create(createShopCategoryDto: CreateShopCategoryDto, lang: string): Promise { + async create( + createShopCategoryDto: CreateShopCategoryDto, + lang: string + ): Promise { try { - const language = await this.languageService.findOneWithID(createShopCategoryDto.Language, lang); + const language = await this.languageService.findOneWithID( + createShopCategoryDto.Language, + lang + ); if (!language.success) { - return { success: false, message: await this.i18n.t('not_found', { lng: lang, ns: 'shopCategory' }), data: null }; + return { + success: false, + message: await this.i18n.t('not_found', { + lng: lang, + ns: 'shopCategory', + }), + data: null, + }; } let parent = null; if (createShopCategoryDto.ParentCategory) { - parent = await this.shopCategoryModel.findOne({ ID: createShopCategoryDto.ParentCategory }).exec(); + parent = await this.shopCategoryModel + .findOne({ ID: createShopCategoryDto.ParentCategory }) + .exec(); if (!parent) { - return { success: false, message: await this.i18n.t('parent_not_found', { lng: lang, ns: 'shopCategory' }), data: null }; + return { + success: false, + message: await this.i18n.t('parent_not_found', { + lng: lang, + ns: 'shopCategory', + }), + data: null, + }; } } @@ -42,7 +69,7 @@ export class ShopCategoryService { Document: null, createdAt: new Date(), updatedAt: new Date(), - Tags: [] + Tags: [], }); const result = await newShopCategory.save(); @@ -52,13 +79,18 @@ export class ShopCategoryService { Name: createShopCategoryDto.Name, Description: createShopCategoryDto.Description, Language: createShopCategoryDto.Language, - ShopCategory: result.ID + ShopCategory: result.ID, }, lang ); if (!createdDocument.success) { - return { success: false, message: createdDocument.message, data: null, errors: createdDocument.errors }; + return { + success: false, + message: createdDocument.message, + data: null, + errors: createdDocument.errors, + }; } await this.shopCategoryModel.updateOne( @@ -66,45 +98,77 @@ export class ShopCategoryService { { $set: { Document: createdDocument.data._id } } ); - const message = await this.i18n.t('created', { lng: lang, ns: 'shopCategory' }); - return { success: true, message, data: { ...result.toObject(), Document: createdDocument.data } }; - + const message = await this.i18n.t('created', { + lng: lang, + ns: 'shopCategory', + }); + return { + success: true, + message, + data: { ...result.toObject(), Document: createdDocument.data }, + }; } catch (error) { - const message = await this.i18n.t('create_error', { lng: lang, ns: 'shopCategory' }); + const message = await this.i18n.t('create_error', { + lng: lang, + ns: 'shopCategory', + }); return { success: false, message, errors: error.message }; } } async findAll(lang: string): Promise { try { - const categories = await this.shopCategoryModel.find() + const categories = await this.shopCategoryModel + .find() .populate('Tags') .populate('ParentCategory') .populate('Document') .exec(); - const message = await this.i18n.t('retrieved_all', { lng: lang, ns: 'shopCategory' }); + const message = await this.i18n.t('retrieved_all', { + lng: lang, + ns: 'shopCategory', + }); return { success: true, message, data: categories }; } catch (error) { - const message = await this.i18n.t('retrieve_error', { lng: lang, ns: 'shopCategory' }); + const message = await this.i18n.t('retrieve_error', { + lng: lang, + ns: 'shopCategory', + }); return { success: false, message, errors: error.message }; } } async findOne(id: string, lang: string): Promise { try { - const category = await this.shopCategoryModel.findOne({ ID: id }) + const category = await this.shopCategoryModel + .findOne({ ID: id }) + .populate('Tags') + .populate('ParentCategory') + .populate('Document') .exec(); if (!category) { - const message = await this.i18n.t('not_found', { lng: lang, ns: 'shopCategory' }); - return { success: false, message, data: null }; + return { + success: false, + message: await this.i18n.t('not_found', { + lng: lang, + ns: 'shopCategory', + }), + data: null, + }; } - const message = await this.i18n.t('retrieved', { lng: lang, ns: 'shopCategory' }); + const message = await this.i18n.t('retrieved', { + lng: lang, + ns: 'shopCategory', + }); return { success: true, message, data: category }; } catch (error) { - const message = await this.i18n.t('retrieve_error', { lng: lang, ns: 'shopCategory' }); + const message = await this.i18n.t('retrieve_error', { + lng: lang, + ns: 'shopCategory', + }); return { success: false, message, errors: error.message }; } } @@ -113,14 +177,87 @@ export class ShopCategoryService { try { const result = await this.shopCategoryModel.deleteOne({ ID: id }).exec(); if (result.deletedCount === 0) { - const message = await this.i18n.t('not_found', { lng: lang, ns: 'shopCategory' }); - return { success: false, message, data: null }; + return { + success: false, + message: await this.i18n.t('not_found', { + lng: lang, + ns: 'shopCategory', + }), + data: null, + }; } - const message = await this.i18n.t('deleted', { lng: lang, ns: 'shopCategory' }); + + const message = await this.i18n.t('deleted', { + lng: lang, + ns: 'shopCategory', + }); return { success: true, message, data: null }; } catch (error) { - const message = await this.i18n.t('delete_error', { lng: lang, ns: 'shopCategory' }); + const message = await this.i18n.t('delete_error', { + lng: lang, + ns: 'shopCategory', + }); return { success: false, message, errors: error.message }; } } + + async addTagToCategory( + categoryId: string, + tagId: string, + lang: string + ): Promise { + try { + const category = await this.shopCategoryModel + .findOne({ ID: categoryId }) + .exec(); + + if (!category) { + return { + success: false, + message: await this.i18n.t('not_found', { + lng: lang, + ns: 'shopCategory', + }), + data: null, + }; + } + + // بررسی می‌کنیم آیا تگ قبلاً اضافه شده یا نه + const tagObjectId = new Types.ObjectId(tagId); + if (category.Tags.some((t: Types.ObjectId) => t.equals(tagObjectId))) { + return { + success: false, + message: await this.i18n.t('tag_already_exists', { + lng: lang, + ns: 'shopCategory', + }), + data: null, + }; + } + + // اضافه کردن تگ + category.Tags.push(tagObjectId); + category.updatedAt = new Date(); + + const updatedCategory = await category.save(); + + return { + success: true, + message: await this.i18n.t('tag_added', { + lng: lang, + ns: 'shopCategory', + }), + data: updatedCategory, + }; + } catch (error) { + return { + success: false, + message: await this.i18n.t('add_tag_error', { + lng: lang, + ns: 'shopCategory', + }), + errors: error.message, + }; + } + } } diff --git a/catalog-service/src/app/tag/dto/crearTag.dto.ts b/catalog-service/src/app/tag/dto/crearTag.dto.ts new file mode 100644 index 0000000..33c791c --- /dev/null +++ b/catalog-service/src/app/tag/dto/crearTag.dto.ts @@ -0,0 +1,40 @@ +// src/tags/dto/create-tag.dto.ts +import { IsBoolean, IsDateString, IsEnum, IsMongoId, IsOptional, IsString } from 'class-validator'; + +export class CreateTagDto { + @IsOptional() + @IsString() + Name?: string; + + @IsOptional() + @IsString() + Data?: string; + + @IsOptional() + @IsBoolean() + Status?: boolean; + + @IsOptional() + @IsEnum([ + 'shop', + 'product', + 'shopCategory', + 'shopCategoryDocument', + 'productCategory', + 'productCategoryDocument', + 'Discount', + ]) + TagType?: + | 'shop' + | 'product' + | 'shopCategory' + | 'shopCategoryDocument' + | 'productCategory' + | 'productCategoryDocument' + | 'Discount'; + + @IsOptional() + @IsDateString() + EndTime?: string; + +} diff --git a/catalog-service/src/app/tag/dto/updateTag.dto.ts b/catalog-service/src/app/tag/dto/updateTag.dto.ts new file mode 100644 index 0000000..380f14e --- /dev/null +++ b/catalog-service/src/app/tag/dto/updateTag.dto.ts @@ -0,0 +1,5 @@ +// src/tags/dto/update-tag.dto.ts +import { PartialType } from '@nestjs/mapped-types'; +import { CreateTagDto } from './crearTag.dto'; + +export class UpdateTagDto extends PartialType(CreateTagDto) {} diff --git a/catalog-service/src/app/tag/tag.service.ts b/catalog-service/src/app/tag/tag.service.ts index bb728fe..6bbd101 100644 --- a/catalog-service/src/app/tag/tag.service.ts +++ b/catalog-service/src/app/tag/tag.service.ts @@ -1,4 +1,193 @@ -import { Injectable } from '@nestjs/common'; - +import { Inject, Injectable, NotFoundException } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { FilterQuery, Model } from 'mongoose'; +import { Tag, TagDocument } from '../../schemas/tag.schema'; +import { CreateTagDto } from './dto/crearTag.dto'; +import { UpdateTagDto } from './dto/updateTag.dto'; +import { v4 as uuidv4 } from 'uuid'; +import { I18nService } from 'nestjs-i18n'; +import { ApiResponse } from '../../interface/responsePatern'; +import i18next from 'i18next'; @Injectable() -export class TagService {} +export class TagsService { + constructor( + @InjectModel(Tag.name) private readonly tagModel: Model, + @Inject('I18NEXT') private readonly i18n: typeof i18next + ) {} + + async create(dto: CreateTagDto, lang: string): Promise { + try { + const created = new this.tagModel({ + ID: uuidv4(), + Name: dto.Name, + Data: dto.Data, + Type: 'user', + User: null, + Status: dto.Status, + TagType: dto.TagType, + EndTime: dto.EndTime, + AdminStatus: false, + createdAt: new Date(), + updatedAt: new Date(), + }); + + const result = await created.save(); + + const message = await this.i18n.t('created', { + lng: lang, + ns: 'tag', + }); + + return { success: true, message, data: result }; + } catch (error) { + const message = await this.i18n.t('create_error', { + lng: lang, + ns: 'tag', + }); + return { success: false, message, errors: error.message }; + } + } + + async findAll(params: { + search?: string; + type?: string; + tagType?: string; + status?: string; + page?: number; + limit?: number; + sort?: string; + }, lang: string): Promise { + try { + const { + search, + type, + tagType, + status, + page = 1, + limit = 20, + sort = '-createdAt', + } = params || {}; + + const filter: FilterQuery = {}; + + if (search) { + filter.$or = [ + { Name: { $regex: search, $options: 'i' } }, + { Data: { $regex: search, $options: 'i' } }, + ]; + } + if (type) filter.Type = type; + if (tagType) filter.TagType = tagType; + if (status === 'true' || status === 'false') filter.Status = status === 'true'; + + const [data, total] = await Promise.all([ + this.tagModel + .find(filter) + .populate('User') + .sort(sort) + .skip((page - 1) * limit) + .limit(limit) + .exec(), + this.tagModel.countDocuments(filter), + ]); + + const message = await this.i18n.t('retrieved', { + lng: lang, + ns: 'tag', + }); + + return { success: true, message, data:{data, total, page, limit } }; + } catch (error) { + const message = await this.i18n.t('retrieve_error', { + lng: lang, + ns: 'tag', + }); + return { success: false, message, errors: error.message }; + } + } + + async findOne(id: string, lang: string): Promise { + try { + const doc = await this.tagModel.findById(id).populate('User').exec(); + if (!doc) { + const message = await this.i18n.t('not_found', { + lng: lang, + ns: 'tag', + }); + return { success: false, message, data: null }; + } + + const message = await this.i18n.t('retrieved', { + lng: lang, + ns: 'tag', + }); + + return { success: true, message, data: doc }; + } catch (error) { + const message = await this.i18n.t('retrieve_error', { + lng: lang, + ns: 'tag', + }); + return { success: false, message, errors: error.message }; + } + } + + async update(id: string, dto: UpdateTagDto, lang: string): Promise { + try { + if (dto.EndTime) { + (dto as any).EndTime = new Date(dto.EndTime as any); + } + + const updated = await this.tagModel + .findByIdAndUpdate(id, { $set: dto, updatedAt: new Date() }, { new: true }) + .exec(); + + if (!updated) { + const message = await this.i18n.t('not_found', { + lng: lang, + ns: 'tag', + }); + return { success: false, message, data: null }; + } + + const message = await this.i18n.t('updated', { + lng: lang, + ns: 'tag', + }); + + return { success: true, message, data: updated }; + } catch (error) { + const message = await this.i18n.t('update_error', { + lng: lang, + ns: 'tag', + }); + return { success: false, message, errors: error.message }; + } + } + + async remove(id: string, lang: string): Promise { + try { + const res = await this.tagModel.findByIdAndDelete(id).exec(); + if (!res) { + const message = await this.i18n.t('not_found', { + lng: lang, + ns: 'tag', + }); + return { success: false, message, data: null }; + } + + const message = await this.i18n.t('deleted', { + lng: lang, + ns: 'tag', + }); + + return { success: true, message, data: null }; + } catch (error) { + const message = await this.i18n.t('delete_error', { + lng: lang, + ns: 'tag', + }); + return { success: false, message, errors: error.message }; + } + } +} diff --git a/catalog-service/src/locales/en/shopCategory.json b/catalog-service/src/locales/en/shopCategory.json index 51cf4c8..5033d80 100644 --- a/catalog-service/src/locales/en/shopCategory.json +++ b/catalog-service/src/locales/en/shopCategory.json @@ -1,14 +1,14 @@ { - "created": "Shop category created successfully", - "create_error": "Error occurred while creating shop category", - "retrieved": "Shop category retrieved successfully", - "retrieved_all": "All shop categories retrieved successfully", - "retrieve_error": "Error occurred while retrieving shop category", - "not_found": "Shop category not found", - "parent_not_found": "Parent category not found", - "deleted": "Shop category deleted successfully", - "delete_error": "Error occurred while deleting shop category", - "updated": "Shop category updated successfully", - "update_error": "Error occurred while updating shop category", - "status_toggled": "Shop category status updated successfully" - } \ No newline at end of file + "created": "Shop category created successfully", + "create_error": "Error creating shop category", + "retrieved": "Shop category retrieved successfully", + "retrieved_all": "All shop categories retrieved successfully", + "retrieve_error": "Error retrieving shop category", + "deleted": "Shop category deleted successfully", + "delete_error": "Error deleting shop category", + "not_found": "Shop category not found", + "parent_not_found": "Parent shop category not found", + "tag_added": "Tag added to category successfully", + "tag_already_exists": "Tag already exists in this category", + "add_tag_error": "Error adding tag to category" +} diff --git a/catalog-service/src/locales/en/shopCategoryDocument.json b/catalog-service/src/locales/en/shopCategoryDocument.json index 52f448f..0136aa3 100644 --- a/catalog-service/src/locales/en/shopCategoryDocument.json +++ b/catalog-service/src/locales/en/shopCategoryDocument.json @@ -1,8 +1,11 @@ { - "created": "Shop category document created successfully", - "create_error": "Error occurred while creating shop category document", - "updated": "Shop category document updated successfully", - "update_error": "Error occurred while updating shop category document", - "status_toggled": "Shop category document status updated successfully", - "not_found": "Shop category document not found" - } \ No newline at end of file + "created": "Shop category document created successfully", + "create_error": "Error occurred while creating shop category document", + "updated": "Shop category document updated successfully", + "update_error": "Error occurred while updating shop category document", + "status_toggled": "Shop category document status updated successfully", + "not_found": "Shop category document not found", + "tag_added": "Tag added to category successfully", + "tag_already_exists": "Tag already exists in this category", + "add_tag_error": "Error adding tag to category" +} diff --git a/catalog-service/src/locales/en/tag.json b/catalog-service/src/locales/en/tag.json new file mode 100644 index 0000000..fd9e09a --- /dev/null +++ b/catalog-service/src/locales/en/tag.json @@ -0,0 +1,11 @@ +{ + "created": "Tag created successfully", + "create_error": "Error creating tag", + "retrieved": "Tags retrieved successfully", + "retrieve_error": "Error retrieving tags", + "updated": "Tag updated successfully", + "update_error": "Error updating tag", + "deleted": "Tag deleted successfully", + "delete_error": "Error deleting tag", + "not_found": "Tag not found" +} diff --git a/catalog-service/src/schemas/tag.schema.ts b/catalog-service/src/schemas/tag.schema.ts index 9114b7b..2a8fa42 100644 --- a/catalog-service/src/schemas/tag.schema.ts +++ b/catalog-service/src/schemas/tag.schema.ts @@ -1,7 +1,7 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { HydratedDocument, Types } from 'mongoose'; - +export type TagDocument = HydratedDocument; @Schema() export class Tag { @@ -23,7 +23,7 @@ export class Tag { @Prop() Status: boolean; - @Prop({ enum: ['shop', 'product','shopCategory','shopCategoryDocument','productCategory','productCategoryDocument'] }) + @Prop({ enum: ['shop', 'product','shopCategory','shopCategoryDocument','productCategory','productCategoryDocument','Discount'] }) TagType: string; @Prop()