У меня есть эта конечная точка, это начальная конечная точка, когда клиент посещает интернет-магазин:
export const getAllProductsByCategory = async (req, res, next) => {
const pageSize = parseInt(req.query.pageSize);
const sort = parseInt(req.query.sort);
const skip = parseInt(req.query.skip);
const { order, filters } = req.query;
const { brands, tags, pricesRange } = JSON.parse(filters);
try {
const aggregate = Product.aggregate();
aggregate.lookup({
from: 'categories',
localField: 'categories',
foreignField: '_id',
as: 'categories'
});
aggregate.match({
productType: 'product',
available: true,
categories: {
$elemMatch: {
url: req.params
}
}
});
aggregate.lookup({
from: 'tags',
let: { tags: '$tags' },
pipeline: [
{
$match: {
$expr: { $in: ['$_id', '$$tags'] }
}
},
{
$project: {
_id: 1,
name: 1,
slug: 1
}
}
],
as: 'tags'
});
aggregate.lookup({
from: 'brands',
let: { brand: '$brand' },
pipeline: [
{
$match: {
$expr: { $eq: ['$_id', '$$brand'] }
}
},
{
$project: {
_id: 1,
name: 1,
slug: 1
}
}
],
as: 'brand'
});
if (brands.length > 0) {
const filterBrands = brands.map((_id) => utils.toObjectId(_id));
aggregate.match({
$and: [{ brand: { $elemMatch: { _id: { $in: filterBrands } } } }]
});
}
if (tags.length > 0) {
const filterTags = tags.map((_id) => utils.toObjectId(_id));
aggregate.match({ tags: { $elemMatch: { _id: { $in: filterTags } } } });
}
if (pricesRange.length > 0 && pricesRange !== 'all') {
const filterPriceRange = pricesRange.map((_id) => utils.toObjectId(_id));
aggregate.match({
_id: { $in: filterPriceRange }
});
}
aggregate.facet({
tags: [
{ $unwind: { path: '$tags' } },
{ $group: { _id: '$tags', tag: { $first: '$tags' }, total: { $sum: 1 } } },
{
$group: {
_id: '$tag._id',
name: { $addToSet: '$tag.name' },
total: { $addToSet: '$total' }
}
},
{
$project: {
name: { $arrayElemAt: ['$name', 0] },
total: { $arrayElemAt: ['$total', 0] },
_id: 1
}
},
{ $sort: { total: -1 } }
],
brands: [
{ $unwind: { path: '$brand' } },
{
$group: {
_id: '$brand._id',
name: { $first: '$brand.name' },
slug: { $first: '$brand.slug' },
total: {
$sum: 1
}
}
},
{ $sort: { name: 1 } }
],
pricesRange: [
{
$bucket: {
groupBy: {
$cond: {
if: { $ne: ['$onSale.value', true] },
then: '$price',
else: '$sale.salePrice'
}
},
boundaries: [0, 20.01, 50.01],
default: 'other',
output: {
count: { $sum: 1 },
products: { $push: '$_id' }
}
}
}
],
products: [
{ $skip: (skip - 1) * pageSize },
{ $limit: pageSize },
{
$project: {
_id: 1,
images: 1,
onSale: 1,
price: 1,
quantity: 1,
slug: 1,
sale: 1,
sku: 1,
status: 1,
title: 1,
brand: 1,
tags: 1,
description: 1
}
},
{ $sort: { [order]: sort } }
],
total: [
{
$group: {
_id: null,
count: { $sum: 1 }
}
},
{
$project: {
count: 1,
_id: 0
}
}
]
});
aggregate.addFields({
total: {
$arrayElemAt: ['$total', 0]
}
});
const [response] = await aggregate.exec();
if (!response.total) {
response.total = 0;
}
res.status(httpStatus.OK);
return res.json(response);
} catch (error) {
console.log(error);
return next(error);
}
};
Если фильтры не применяются, все продукты соответствуют запрошенной категории без проблем.
Моя проблема заключается в том, что когда покупатель выбирает марку или тег, фасет возвращает товары, но возвращает только одну марку / метку (как и должно быть, поскольку фильтруемые товары имеют только эту марку).
Что я долженсделать, чтобы сохранить все бренды / теги и позволить пользователю выбрать более одного бренда / тега? Если клиент выбирает марку, то теги должны соответствовать возвращаемым тегам продуктов и наоборот.
Есть ли лучший способ реализовать этап tags
в $facet
, поскольку теги - это массив, а желаемый результат -: [{_id: 123, name: {label: 'test', value: 123]}]
Запрос выглядит так: (1,2,3,4 представляет _id)
http://locahost:3000/get-products/?filters={brands: [1, 2], tags: [3,4], pricesRange:[]}
Обновление
Это products
схема с tags
и brands
:
brand: {
ref: 'Brand',
type: Schema.Types.ObjectId
},
tags: [
{
ref: 'Tags',
type: Schema.Types.ObjectId
}
]
tags
схема:
{
metaDescription: {
type: String
},
metaTitle: {
type: String
},
name: {
label: {
type: String,
index: true
},
value: {
type: Schema.Types.ObjectId
},
},
slug: {
type: String,
index: true
},
status: {
label: {
type: String
},
value: {
default: true,
type: Boolean
}
}
}
brands
схема:
description: {
default: '',
type: String
},
name: {
required: true,
type: String,
unique: true
},
slug: {
type: String,
index: true
},
status: {
label: {
default: 'Active',
type: String
},
value: {
default: true,
type: Boolean
}
}
Сценарий:
Пользователь посещает магазин, выбирает категорию, и все подходящие товары должны возвращаться с совпадающими brands
, tags
, priceRange
и разбиение на страницы.
Случай 1:
Пользователь нажимает brand
из флажка, затем запрос возвращает совпадение products
, tags
& priceRanges
и все бренды выбранной категории, не соответствующие продуктам
Случай 2:
Пользователь выбираетbrand
как в случае 1, но затем решает также проверить tag
, тогда запрос должен вернуть все brands
и tags
снова, но products
сопоставляется с ними.
Случай 3:
Пользователь не выбирает brand
, но выбирает только tag
, запрос должен вернуть все соответствующие products
, которые имеют этот тег / теги, и вернуть brands
, который соответствует возвращенным продуктам.
Случай 4:
То же, что и в случае 3, но пользователь выбирает brand
после выбора tag/tags
, запрос должен возвращать совпадения products
, brands
& tags
.
Во всех случаях нумерация страниц должна возвращать правильное итоговое значение, также priceRanges
должно соответствовать возвращаемым результатам.
Надеюсь, теперь все ясно, я думаю, что я не пропустил ни одного другого случая. Я мог бы, вероятно, выделить / отключить теги / бренды, которые не соответствуют ответу в интерфейсе, но я не знаю, насколько это удобно для пользователя.