@@ -56,6 +56,8 @@ export class ProductController {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Get('admin/category/:id')
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiOperation({ summary: 'Get a category by id' })
|
||||
@@ -89,9 +91,23 @@ export class ProductController {
|
||||
@UseGuards(AuthGuard)
|
||||
@ApiOperation({ summary: 'Get list of categories' })
|
||||
async findAllCategoriesAsUser() {
|
||||
return this.categoryService.findAll({ limit: 1000, isActive: true });
|
||||
const result = await this.categoryService.findAll({ limit: 1000, isActive: true });
|
||||
return result.data;
|
||||
}
|
||||
|
||||
@Get('public/category/tree')
|
||||
@UseGuards(AuthGuard)
|
||||
@ApiOperation({ summary: 'Get a tree list of Categories' })
|
||||
@ApiQuery({ name: 'search', required: false, type: String })
|
||||
@ApiQuery({ name: 'orderBy', required: false, type: String })
|
||||
@ApiQuery({ name: 'order', required: false, enum: ['asc', 'desc'] })
|
||||
@ApiQuery({ name: 'isActive', required: false, type: Boolean })
|
||||
async findTreeCategories(@Query() dto: FindCategoriesDto) {
|
||||
const result = await this.categoryService.findTree(dto);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Get('public/products')
|
||||
@UseGuards(AuthGuard)
|
||||
@ApiOperation({ summary: 'Get list of products' })
|
||||
|
||||
@@ -2,7 +2,7 @@ import { BadGatewayException, Injectable, NotFoundException } from '@nestjs/comm
|
||||
import { EntityManager } from '@mikro-orm/postgresql';
|
||||
import { RequiredEntityData } from '@mikro-orm/core';
|
||||
import { CategoryMessage, productMessage } from 'src/common/enums/message.enum';
|
||||
import { CategoryRepository } from '../repositories/category.repository';
|
||||
import { CategoryRepository, CategoryTreeNode } from '../repositories/category.repository';
|
||||
import { CreateCategoryDto } from '../dto/create-category.dto';
|
||||
import { Category } from '../entities/category.entity';
|
||||
import { FindCategoriesDto } from '../dto/find-categories.dto';
|
||||
@@ -70,10 +70,14 @@ export class CategoryService {
|
||||
|
||||
}
|
||||
|
||||
findAll(dto: FindCategoriesDto) {
|
||||
findAll(dto: FindCategoriesDto): Promise<{ data: Category[]; meta: { total: number; page: number; limit: number; totalPages: number } }> {
|
||||
return this.categoryRepository.findAllPaginated(dto);
|
||||
}
|
||||
|
||||
findTree(dto: FindCategoriesDto): Promise<CategoryTreeNode[]> {
|
||||
return this.categoryRepository.findTree(dto);
|
||||
}
|
||||
|
||||
async findById(catId: string): Promise<Category> {
|
||||
const category = await this.categoryRepository.findOne({ id: catId },
|
||||
{ populate: ['children', 'parent'] });
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
|
||||
import { FilterQuery } from '@mikro-orm/core';
|
||||
import { Product } from '../entities/product.entity';
|
||||
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
||||
import { Category } from '../entities/category.entity';
|
||||
|
||||
@@ -14,31 +13,60 @@ type FindCategoriesOpts = {
|
||||
isActive?: boolean;
|
||||
};
|
||||
|
||||
export type CategoryTreeNode = {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
deletedAt?: Date;
|
||||
parent: { id: string } | null;
|
||||
title: string;
|
||||
isActive: boolean;
|
||||
avatarUrl?: string;
|
||||
order?: number;
|
||||
children: CategoryTreeNode[];
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class CategoryRepository extends EntityRepository<Category> {
|
||||
constructor(readonly em: EntityManager) {
|
||||
super(em, Category);
|
||||
}
|
||||
|
||||
private buildCategoryTree(categories: Category[]): Category[] {
|
||||
const roots: Category[] = [];
|
||||
private buildCategoryTree(categories: Category[]): CategoryTreeNode[] {
|
||||
const roots: CategoryTreeNode[] = [];
|
||||
const nodesById = new Map<string, CategoryTreeNode>();
|
||||
|
||||
for (const category of categories) {
|
||||
category.children.removeAll();
|
||||
const parentId = category.parent?.id ?? null;
|
||||
nodesById.set(category.id, {
|
||||
id: category.id,
|
||||
createdAt: category.createdAt,
|
||||
deletedAt: category.deletedAt,
|
||||
parent: parentId ? { id: parentId } : null,
|
||||
title: category.title,
|
||||
isActive: category.isActive,
|
||||
avatarUrl: category.avatarUrl,
|
||||
order: category.order,
|
||||
children: [],
|
||||
});
|
||||
}
|
||||
|
||||
for (const category of categories) {
|
||||
const parentId = category.parent?.id;
|
||||
const node = nodesById.get(category.id);
|
||||
if (!node) continue;
|
||||
|
||||
if (parentId) {
|
||||
const parent = categories.find((item) => item.id === parentId);
|
||||
if (parent) {
|
||||
parent.children.add(category);
|
||||
continue;
|
||||
}
|
||||
const parentId = category.parent?.id;
|
||||
if (!parentId) {
|
||||
roots.push(node);
|
||||
continue;
|
||||
}
|
||||
|
||||
roots.push(category);
|
||||
const parentNode = nodesById.get(parentId);
|
||||
if (parentNode) {
|
||||
parentNode.children.push(node);
|
||||
continue;
|
||||
}
|
||||
|
||||
roots.push(node);
|
||||
}
|
||||
|
||||
return roots;
|
||||
@@ -57,8 +85,8 @@ export class CategoryRepository extends EntityRepository<Category> {
|
||||
|
||||
if (search) {
|
||||
where.$or = [
|
||||
|
||||
]
|
||||
{ title: { $ilike: `%${search}%` } },
|
||||
];
|
||||
}
|
||||
|
||||
const allCategories = await this.find(where, {
|
||||
@@ -66,9 +94,8 @@ export class CategoryRepository extends EntityRepository<Category> {
|
||||
populate: ['parent'],
|
||||
});
|
||||
|
||||
const roots = this.buildCategoryTree(allCategories);
|
||||
const data = roots.slice(offset, offset + limit);
|
||||
const total = roots.length;
|
||||
const total = allCategories.length;
|
||||
const data = allCategories.slice(offset, offset + limit);
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
return {
|
||||
@@ -81,4 +108,26 @@ export class CategoryRepository extends EntityRepository<Category> {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async findTree(opts: FindCategoriesOpts = {}): Promise<CategoryTreeNode[]> {
|
||||
const { search, orderBy = 'createdAt', order = 'desc', isActive } = opts;
|
||||
const where: FilterQuery<Category> = {};
|
||||
|
||||
if (typeof isActive === 'boolean') {
|
||||
where.isActive = isActive;
|
||||
}
|
||||
|
||||
if (search) {
|
||||
where.$or = [
|
||||
{ title: { $ilike: `%${search}%` } },
|
||||
];
|
||||
}
|
||||
|
||||
const allCategories = await this.find(where, {
|
||||
orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' },
|
||||
populate: ['parent'],
|
||||
});
|
||||
|
||||
return this.buildCategoryTree(allCategories);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user