diff --git a/src/modules/foods/entities/food.entity.ts b/src/modules/foods/entities/food.entity.ts index d3e95e9..2f19445 100644 --- a/src/modules/foods/entities/food.entity.ts +++ b/src/modules/foods/entities/food.entity.ts @@ -1,7 +1,8 @@ -import { Entity, ManyToOne, Property } from '@mikro-orm/core'; +import { Cascade, Collection, Entity, ManyToOne, OneToMany, Property } from '@mikro-orm/core'; import { Category } from './category.entity'; import { BaseEntity } from '../../../common/entities/base.entity'; import { Restaurant } from '../../../modules/restaurants/entities/restaurant.entity'; +import { Review } from 'src/modules/review/entities/review.entity'; @Entity({ tableName: 'foods' }) export class Food extends BaseEntity { @@ -11,6 +12,9 @@ export class Food extends BaseEntity { @ManyToOne(() => Category) category!: Category; + @OneToMany(() => Review, review => review.food, { cascade: [Cascade.ALL], orphanRemoval: true }) + reviews = new Collection(this); + @Property({ nullable: true }) title?: string; diff --git a/src/modules/orders/orders.service.ts b/src/modules/orders/orders.service.ts index bf028f1..419c325 100644 --- a/src/modules/orders/orders.service.ts +++ b/src/modules/orders/orders.service.ts @@ -19,7 +19,7 @@ import { Cron, CronExpression } from '@nestjs/schedule'; import { OrderRepository } from './repositories/order.repository'; import { FindOrdersDto } from './dto/find-orders.dto'; import { PaginatedResult } from 'src/common/interfaces/pagination.interface'; - + @Injectable() export class OrdersService { private readonly logger = new Logger(OrdersService.name); @@ -248,7 +248,7 @@ export class OrdersService { } async findAll(restId: string, dto: FindOrdersDto): Promise> { - return this.orderRepository.findAllPaginated(restId, { + const result = await this.orderRepository.findAllPaginated(restId, { page: dto.page, limit: dto.limit, status: dto.status, @@ -259,6 +259,8 @@ export class OrdersService { orderBy: dto.orderBy, order: dto.order, }); + + return result; } async findOne(id: string, restId: string): Promise { diff --git a/src/modules/orders/repositories/order.repository.ts b/src/modules/orders/repositories/order.repository.ts index a7938c9..2962e4f 100644 --- a/src/modules/orders/repositories/order.repository.ts +++ b/src/modules/orders/repositories/order.repository.ts @@ -5,6 +5,7 @@ import { Order } from '../entities/order.entity'; import { PaginatedResult } from 'src/common/interfaces/pagination.interface'; import { OrderStatus } from '../interface/order-status'; import { PaymentStatusEnum } from '../../payments/interface/payment'; +import { Review } from '../../review/entities/review.entity'; type FindOrdersOpts = { page?: number; @@ -84,13 +85,62 @@ export class OrderRepository extends EntityRepository { where.$or = searchConditions; } + // First, fetch orders without reviews const [data, total] = await this.findAndCount(where, { limit, offset, orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' }, - populate: ['user', 'restaurant', 'deliveryMethod', 'address', 'paymentMethod', 'items', 'items.food'], + populate: ['user', 'restaurant', 'deliveryMethod', 'address', 'paymentMethod', 'items', 'items.food'] as never, }); + // Collect all (orderId, foodId) pairs for efficient review lookup + const orderFoodPairs: Array<{ orderId: string; foodId: string }> = []; + for (const order of data) { + for (const item of order.items.getItems()) { + if (item.food?.id) { + orderFoodPairs.push({ orderId: order.id, foodId: item.food.id }); + } + } + } + + // Fetch all relevant reviews in a single query + const reviewsMap: Map = new Map(); + if (orderFoodPairs.length > 0) { + const orderIds = [...new Set(orderFoodPairs.map(p => p.orderId))]; + const foodIds = [...new Set(orderFoodPairs.map(p => p.foodId))]; + + const reviews = await this.em.find( + Review, + { + order: { id: { $in: orderIds } }, + food: { id: { $in: foodIds } }, + }, + { + fields: ['id', 'order', 'food'], + populate: ['order', 'food'], + }, + ); + + // Create a map: key = `${orderId}-${foodId}`, value = reviewId + for (const review of reviews) { + const key = `${review.order.id}-${review.food.id}`; + reviewsMap.set(key, review.id); + } + } + + // Map reviewIds to foods + for (const order of data) { + for (const item of order.items.getItems()) { + if (item.food) { + const key = `${order.id}-${item.food.id}`; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const food = item.food as any; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + food.reviewId = reviewsMap.get(key) || null; + } + } + } + const totalPages = Math.ceil(total / limit); return { diff --git a/src/modules/review/dto/create-review.dto.ts b/src/modules/review/dto/create-review.dto.ts index dfeab34..0304d0b 100644 --- a/src/modules/review/dto/create-review.dto.ts +++ b/src/modules/review/dto/create-review.dto.ts @@ -30,23 +30,22 @@ export class CreateReviewDto { @IsOptional() @IsArray() @IsEnum(PositivePoint, { each: true }) - @ApiPropertyOptional({ - description: 'Positive points about the food', + @ApiPropertyOptional({ + description: 'Positive points about the food', example: [PositivePoint.GREAT_TASTE, PositivePoint.FAST_DELIVERY], enum: PositivePoint, - isArray: true + isArray: true, }) positivePoints?: PositivePoint[]; @IsOptional() @IsArray() @IsEnum(NegativePoint, { each: true }) - @ApiPropertyOptional({ - description: 'Negative points about the food', - example: [NegativePoint.TOO_SPICY, NegativePoint.SMALL_PORTION], + @ApiPropertyOptional({ + description: 'Negative points about the food', + example: [NegativePoint.SMALL_PORTION], enum: NegativePoint, - isArray: true + isArray: true, }) negativePoints?: NegativePoint[]; } -