This commit is contained in:
2025-11-15 09:14:32 +03:30
parent 53ef6220c1
commit 84163f6c8c
7 changed files with 25 additions and 22 deletions
+1 -2
View File
@@ -3,10 +3,9 @@ import { ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
export class CreateCategoryDto { export class CreateCategoryDto {
@IsOptional()
@IsString() @IsString()
@ApiPropertyOptional({ example: 'Beverages' }) @ApiPropertyOptional({ example: 'Beverages' })
title?: string; title!: string;
@IsOptional() @IsOptional()
@IsBoolean() @IsBoolean()
@@ -1,14 +1,14 @@
import { Entity, Property, ManyToMany, Collection } from '@mikro-orm/core'; import { Entity, Property, ManyToMany, Collection } from '@mikro-orm/core';
import { Foods } from './food.entity'; import { Food } from './food.entity';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../common/entities/base.entity';
@Entity({ tableName: 'categories' }) @Entity({ tableName: 'categories' })
export class Category extends BaseEntity { export class Category extends BaseEntity {
@Property({ nullable: true }) @Property()
title?: string; title!: string;
@ManyToMany(() => Foods, food => food.categories) @ManyToMany(() => Food, food => food.categories)
foods = new Collection<Foods>(this); foods = new Collection<Food>(this);
@Property({ default: true }) @Property({ default: true })
isActive: boolean = true; isActive: boolean = true;
+1 -1
View File
@@ -3,7 +3,7 @@ import { Category } from './category.entity';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../common/entities/base.entity';
@Entity({ tableName: 'foods' }) @Entity({ tableName: 'foods' })
export class Foods extends BaseEntity { export class Food extends BaseEntity {
@ManyToMany(() => Category) @ManyToMany(() => Category)
categories = new Collection<Category>(this); categories = new Collection<Category>(this);
+4
View File
@@ -5,8 +5,12 @@ import { CategorysController } from './controllers/categorys.controller';
import { CategoryService } from './providers/category.service'; import { CategoryService } from './providers/category.service';
import { FoodRepository } from './repositories/food.repository'; import { FoodRepository } from './repositories/food.repository';
import { CategoryRepository } from './repositories/category.repository'; import { CategoryRepository } from './repositories/category.repository';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { Category } from './entities/category.entity';
import { Food } from './entities/food.entity';
@Module({ @Module({
imports: [MikroOrmModule.forFeature([Food, Category])],
controllers: [FoodController, CategorysController], controllers: [FoodController, CategorysController],
providers: [FoodService, CategoryService, FoodRepository, CategoryRepository], providers: [FoodService, CategoryService, FoodRepository, CategoryRepository],
exports: [FoodRepository, CategoryRepository], exports: [FoodRepository, CategoryRepository],
@@ -15,12 +15,12 @@ export class CategoryService {
) {} ) {}
async create(dto: CreateCategoryDto): Promise<Category> { async create(dto: CreateCategoryDto): Promise<Category> {
const data = { const data: RequiredEntityData<Category> = {
title: dto.title, title: dto.title,
isActive: dto.isActive ?? true, isActive: dto.isActive ?? true,
restId: dto.restId, restId: dto.restId,
avatarUrl: dto.avatarUrl, avatarUrl: dto.avatarUrl,
} as RequiredEntityData<Category>; };
const category = this.categoryRepository.create(data); const category = this.categoryRepository.create(data);
await this.em.persistAndFlush(category); await this.em.persistAndFlush(category);
+6 -6
View File
@@ -5,7 +5,7 @@ import { FoodRepository } from '../repositories/food.repository';
import { CategoryRepository } from '../repositories/category.repository'; import { CategoryRepository } from '../repositories/category.repository';
import { EntityManager } from '@mikro-orm/postgresql'; import { EntityManager } from '@mikro-orm/postgresql';
import { RequiredEntityData } from '@mikro-orm/core'; import { RequiredEntityData } from '@mikro-orm/core';
import { Foods } from '../entities/food.entity'; import { Food } from '../entities/food.entity';
import { FoodMessage } from 'src/common/enums/message.enum'; import { FoodMessage } from 'src/common/enums/message.enum';
@Injectable() @Injectable()
@@ -16,11 +16,11 @@ export class FoodService {
private readonly em: EntityManager, private readonly em: EntityManager,
) {} ) {}
async create(createFoodDto: CreateFoodDto): Promise<Foods> { async create(createFoodDto: CreateFoodDto): Promise<Food> {
const { categoryIds, ...rest } = createFoodDto; const { categoryIds, ...rest } = createFoodDto;
// prepare data with defaults for required fields so repository.create typing is satisfied // prepare data with defaults for required fields so repository.create typing is satisfied
const data: RequiredEntityData<Foods> = { const data: RequiredEntityData<Food> = {
// boolean/day flags // boolean/day flags
sat: rest.sat ?? false, sat: rest.sat ?? false,
sun: rest.sun ?? false, sun: rest.sun ?? false,
@@ -45,7 +45,7 @@ export class FoodService {
points: rest.points ?? 0, points: rest.points ?? 0,
prepareTime: rest.prepareTime ?? 0, prepareTime: rest.prepareTime ?? 0,
images: rest.images ?? undefined, images: rest.images ?? undefined,
} as RequiredEntityData<Foods>; } as RequiredEntityData<Food>;
const food = this.foodRepository.create(data); const food = this.foodRepository.create(data);
@@ -67,7 +67,7 @@ export class FoodService {
return this.foodRepository.findOne({ id }, { populate: ['categories'] }); return this.foodRepository.findOne({ id }, { populate: ['categories'] });
} }
async update(id: string, dto: Partial<CreateFoodDto>): Promise<Foods> { async update(id: string, dto: Partial<CreateFoodDto>): Promise<Food> {
const food = await this.foodRepository.findOne({ id }); const food = await this.foodRepository.findOne({ id });
if (!food) { if (!food) {
@@ -77,7 +77,7 @@ export class FoodService {
const { categoryIds, ...rest } = dto; const { categoryIds, ...rest } = dto;
// assign scalar fields // assign scalar fields
this.em.assign(food, rest as Partial<Foods>); this.em.assign(food, rest as Partial<Food>);
// attach new categories if provided (adds, does not attempt to preserve/remove existing ones) // attach new categories if provided (adds, does not attempt to preserve/remove existing ones)
if (Array.isArray(categoryIds) && categoryIds.length) { if (Array.isArray(categoryIds) && categoryIds.length) {
@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { EntityManager, EntityRepository } from '@mikro-orm/postgresql'; import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
import { FilterQuery } from '@mikro-orm/core'; import { FilterQuery } from '@mikro-orm/core';
import { Foods } from '../entities/food.entity'; import { Food } from '../entities/food.entity';
import { PaginatedResult } from 'src/common/interfaces/pagination.interface'; import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
type FindFoodsOpts = { type FindFoodsOpts = {
@@ -15,20 +15,20 @@ type FindFoodsOpts = {
}; };
@Injectable() @Injectable()
export class FoodRepository extends EntityRepository<Foods> { export class FoodRepository extends EntityRepository<Food> {
constructor(readonly em: EntityManager) { constructor(readonly em: EntityManager) {
super(em, Foods); super(em, Food);
} }
/** /**
* Find foods with pagination and optional filters. * Find foods with pagination and optional filters.
* Supports: search (title/content), categoryId, isActive, ordering. * Supports: search (title/content), categoryId, isActive, ordering.
*/ */
async findAllPaginated(opts: FindFoodsOpts = {}): Promise<PaginatedResult<Foods>> { async findAllPaginated(opts: FindFoodsOpts = {}): Promise<PaginatedResult<Food>> {
const { page = 1, limit = 10, search, orderBy = 'createdAt', order = 'desc', categoryId, isActive } = opts; const { page = 1, limit = 10, search, orderBy = 'createdAt', order = 'desc', categoryId, isActive } = opts;
const offset = (page - 1) * limit; const offset = (page - 1) * limit;
const where: FilterQuery<Foods> = {}; const where: FilterQuery<Food> = {};
if (typeof isActive === 'boolean') { if (typeof isActive === 'boolean') {
where.isActive = isActive; where.isActive = isActive;
@@ -41,7 +41,7 @@ export class FoodRepository extends EntityRepository<Foods> {
if (categoryId) { if (categoryId) {
// filter by related categories (typed via FilterQuery) // filter by related categories (typed via FilterQuery)
Object.assign(where, { categories: { id: categoryId } } as unknown as FilterQuery<Foods>); Object.assign(where, { categories: { id: categoryId } } as unknown as FilterQuery<Food>);
} }
const [data, total] = await this.findAndCount(where, { const [data, total] = await this.findAndCount(where, {