food review
This commit is contained in:
@@ -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 { Category } from './category.entity';
|
||||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||||
import { Restaurant } from '../../../modules/restaurants/entities/restaurant.entity';
|
import { Restaurant } from '../../../modules/restaurants/entities/restaurant.entity';
|
||||||
|
import { Review } from 'src/modules/review/entities/review.entity';
|
||||||
|
|
||||||
@Entity({ tableName: 'foods' })
|
@Entity({ tableName: 'foods' })
|
||||||
export class Food extends BaseEntity {
|
export class Food extends BaseEntity {
|
||||||
@@ -11,6 +12,9 @@ export class Food extends BaseEntity {
|
|||||||
@ManyToOne(() => Category)
|
@ManyToOne(() => Category)
|
||||||
category!: Category;
|
category!: Category;
|
||||||
|
|
||||||
|
@OneToMany(() => Review, review => review.food, { cascade: [Cascade.ALL], orphanRemoval: true })
|
||||||
|
reviews = new Collection<Review>(this);
|
||||||
|
|
||||||
@Property({ nullable: true })
|
@Property({ nullable: true })
|
||||||
title?: string;
|
title?: string;
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { Cron, CronExpression } from '@nestjs/schedule';
|
|||||||
import { OrderRepository } from './repositories/order.repository';
|
import { OrderRepository } from './repositories/order.repository';
|
||||||
import { FindOrdersDto } from './dto/find-orders.dto';
|
import { FindOrdersDto } from './dto/find-orders.dto';
|
||||||
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OrdersService {
|
export class OrdersService {
|
||||||
private readonly logger = new Logger(OrdersService.name);
|
private readonly logger = new Logger(OrdersService.name);
|
||||||
@@ -248,7 +248,7 @@ export class OrdersService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async findAll(restId: string, dto: FindOrdersDto): Promise<PaginatedResult<Order>> {
|
async findAll(restId: string, dto: FindOrdersDto): Promise<PaginatedResult<Order>> {
|
||||||
return this.orderRepository.findAllPaginated(restId, {
|
const result = await this.orderRepository.findAllPaginated(restId, {
|
||||||
page: dto.page,
|
page: dto.page,
|
||||||
limit: dto.limit,
|
limit: dto.limit,
|
||||||
status: dto.status,
|
status: dto.status,
|
||||||
@@ -259,6 +259,8 @@ export class OrdersService {
|
|||||||
orderBy: dto.orderBy,
|
orderBy: dto.orderBy,
|
||||||
order: dto.order,
|
order: dto.order,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOne(id: string, restId: string): Promise<Order> {
|
async findOne(id: string, restId: string): Promise<Order> {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Order } from '../entities/order.entity';
|
|||||||
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
||||||
import { OrderStatus } from '../interface/order-status';
|
import { OrderStatus } from '../interface/order-status';
|
||||||
import { PaymentStatusEnum } from '../../payments/interface/payment';
|
import { PaymentStatusEnum } from '../../payments/interface/payment';
|
||||||
|
import { Review } from '../../review/entities/review.entity';
|
||||||
|
|
||||||
type FindOrdersOpts = {
|
type FindOrdersOpts = {
|
||||||
page?: number;
|
page?: number;
|
||||||
@@ -84,13 +85,62 @@ export class OrderRepository extends EntityRepository<Order> {
|
|||||||
where.$or = searchConditions;
|
where.$or = searchConditions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// First, fetch orders without reviews
|
||||||
const [data, total] = await this.findAndCount(where, {
|
const [data, total] = await this.findAndCount(where, {
|
||||||
limit,
|
limit,
|
||||||
offset,
|
offset,
|
||||||
orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' },
|
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<string, string> = 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);
|
const totalPages = Math.ceil(total / limit);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -30,23 +30,22 @@ export class CreateReviewDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@IsEnum(PositivePoint, { each: true })
|
@IsEnum(PositivePoint, { each: true })
|
||||||
@ApiPropertyOptional({
|
@ApiPropertyOptional({
|
||||||
description: 'Positive points about the food',
|
description: 'Positive points about the food',
|
||||||
example: [PositivePoint.GREAT_TASTE, PositivePoint.FAST_DELIVERY],
|
example: [PositivePoint.GREAT_TASTE, PositivePoint.FAST_DELIVERY],
|
||||||
enum: PositivePoint,
|
enum: PositivePoint,
|
||||||
isArray: true
|
isArray: true,
|
||||||
})
|
})
|
||||||
positivePoints?: PositivePoint[];
|
positivePoints?: PositivePoint[];
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@IsEnum(NegativePoint, { each: true })
|
@IsEnum(NegativePoint, { each: true })
|
||||||
@ApiPropertyOptional({
|
@ApiPropertyOptional({
|
||||||
description: 'Negative points about the food',
|
description: 'Negative points about the food',
|
||||||
example: [NegativePoint.TOO_SPICY, NegativePoint.SMALL_PORTION],
|
example: [NegativePoint.SMALL_PORTION],
|
||||||
enum: NegativePoint,
|
enum: NegativePoint,
|
||||||
isArray: true
|
isArray: true,
|
||||||
})
|
})
|
||||||
negativePoints?: NegativePoint[];
|
negativePoints?: NegativePoint[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user