complete tag module and shop

This commit is contained in:
vahidrezvani 2025-08-26 09:20:49 +03:30
parent 58c823745e
commit 013311c9c6
9 changed files with 500 additions and 59 deletions

View File

@ -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<ApiResponse> {
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(),
});
@ -228,5 +226,63 @@ export class ShopCategoryDocumentService {
}
}
async addTagToCategory(
categoryId: string,
tagId: string,
lang: string
): Promise<ApiResponse> {
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,
};
}
}
}

View File

@ -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<ShopCategoryDoc>,
@InjectModel(ShopCategory.name)
private shopCategoryModel: Model<ShopCategoryDoc>,
private readonly languageService: LanguageService,
private readonly shopCategoryDocumentService: ShopCategoryDocumentService,
@Inject('I18NEXT') private readonly i18n: typeof i18next
) {}
async create(createShopCategoryDto: CreateShopCategoryDto, lang: string): Promise<ApiResponse> {
async create(
createShopCategoryDto: CreateShopCategoryDto,
lang: string
): Promise<ApiResponse> {
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<ApiResponse> {
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<ApiResponse> {
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<ApiResponse> {
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,
};
}
}
}

View File

@ -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;
}

View File

@ -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) {}

View File

@ -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<TagDocument>,
@Inject('I18NEXT') private readonly i18n: typeof i18next
) {}
async create(dto: CreateTagDto, lang: string): Promise<ApiResponse> {
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<ApiResponse> {
try {
const {
search,
type,
tagType,
status,
page = 1,
limit = 20,
sort = '-createdAt',
} = params || {};
const filter: FilterQuery<TagDocument> = {};
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<ApiResponse> {
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<ApiResponse> {
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<ApiResponse> {
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 };
}
}
}

View File

@ -1,14 +1,14 @@
{
"created": "Shop category created successfully",
"create_error": "Error occurred while creating shop category",
"create_error": "Error 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",
"retrieve_error": "Error retrieving shop category",
"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"
"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"
}

View File

@ -4,5 +4,8 @@
"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"
"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"
}

View File

@ -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"
}

View File

@ -1,7 +1,7 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument, Types } from 'mongoose';
export type TagDocument = HydratedDocument<Tag>;
@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()