Related
I have the following objects
Person {
name: string
birthday: Date
lifeEvents: LifeEvent[]
}
LifeEvent {
eventId: number
message: string
comments: string
}
As the data comes in chunks, I will have an array of Person with one object that has name and birthday with values but lifeEvents is empty (Think of this one like a parent object.)
All other objects won't have birthday and name populated and will have only one LifeEvent with either eventId and message with values or eventId and comments
Within that array, I need to get the parent object, which has name and birthday with values, then get all lifeEvents from the remaining objects, merge all items that contains same eventId into a LifeEvent then push it to lifeEvents of the parent.
I have tried array.reduce, array.map but can't figure out a way of combining those objects into one.
My output should be only one Person with all lifeEvents merged by eventId
Sample data:
let results = [
{
name: 'Test1',
birthday: '2022-06-14',
lifeEvents: null
},
{
name: null,
birthday: null,
lifeEvents: [
{
eventId: 1,
message: 'First event',
comments: null
}
]
},
{
name: null,
birthday: null,
lifeEvents: [
{
eventId: 2,
message: 'Second event',
comments: null
}
]
},
{
name: null
birthday: null
lifeEvents: [
{
eventId: 1,
message: null,
comments: 'First event comment'
}
]
},
{
name: null
birthday: null
lifeEvents: [
{
eventId: 2,
message: null,
comments: 'Second event comment'
}
]
},
]
Appreciate any help.
Premise: The data structure you are using is wrong, you should try to use arrays with homogenous models.
That said I used a reduce method, with a condition to treat the first element in a different way from the other ones.
Last thing, you said merge lifeEvents, I assume you meant to overwrite the nullish values for events with same ids, if you want to overwrite all values then you can omit the merge utility function I wrote.
let results = [{
name: 'Test1',
birthday: '2022-06-14',
lifeEvents: null,
},
{
name: null,
birthday: null,
lifeEvents: [{
eventId: 1,
message: 'First event',
comments: null,
}, ],
},
{
name: null,
birthday: null,
lifeEvents: [{
eventId: 2,
message: 'Second event',
comments: null,
}, ],
},
{
name: null,
birthday: null,
lifeEvents: [{
eventId: 1,
message: null,
comments: 'First event comment',
}, ],
},
{
name: null,
birthday: null,
lifeEvents: [{
eventId: 2,
message: null,
comments: 'Second event comment',
}, ],
},
];
const merge = (o1, o2) => {
const r = {...o1}
Object.keys(o1).forEach(k => r[k] = o2[k] || o1[k])
return r
}
const r = results.reduce((o, curr, i) => {
if (i === 0) {
return { ...o,
lifeEvents: []
};
} else {
const currentEvent = curr.lifeEvents[0]
const idx = o.lifeEvents.findIndex((_o) => _o.eventId === currentEvent.eventId);
if (idx !== -1) return { ...o,
lifeEvents: o.lifeEvents.map((_o, i) => i === idx ? merge(_o, currentEvent) : _o)
}
else return { ...o,
lifeEvents: [...o.lifeEvents, currentEvent]
}
}
}, results[0]);
console.log("RESULT:", r);
The following produces the requested result based on examples provided:
let results = [
{
name: 'Test1',
birthday: '2022-06-14',
lifeEvents: null
},
{
name: null,
birthday: null,
lifeEvents: [
{
eventId: 1,
message: 'First event',
comments: null
}
]
},
{
name: null,
birthday: null,
lifeEvents: [
{
eventId: 2,
message: 'Second event',
comments: null
}
]
},
{
name: null,
birthday: null,
lifeEvents: [
{
eventId: 1,
message: null,
comments: 'First event comment'
}
]
},
{
name: null,
birthday: null,
lifeEvents: [
{
eventId: 2,
message: null,
comments: 'Second event comment'
}
]
},
];
// extract parent
const parentResult = results.find((result) => result.name);
// generate unique events from sibling entities
const uniqueEvents = new Map();
results.forEach((result) => result.lifeEvents?.forEach(
(lifeEvent) => {
if (uniqueEvents.has(lifeEvent.eventId)) {
updateEvent(lifeEvent);
} else {
uniqueEvents.set(lifeEvent.eventId, { eventId: lifeEvent.eventId, message: lifeEvent.message, comments: lifeEvent.comments});
}
})
);
// function to update event that is already stored in uniqueEvents
function updateEvent(lifeEvent) {
const existingLifeEvent = uniqueEvents.get(lifeEvent.eventId);
if (lifeEvent.message) existingLifeEvent.message = lifeEvent.message;
if (lifeEvent.comments) {
if (existingLifeEvent.comments) {
existingLifeEvent.comments.concat(lifeEvent.comments)
} else {
existingLifeEvent.comments = lifeEvent.comments;
}
}
}
// populate lifeEvents inside the parentResult
parentResult.lifeEvents = [];
uniqueEvents.forEach((uniqueId) => {
parentResult.lifeEvents.push(uniqueId);
});
console.log(parentResult);
I am designing a system and I have some bottlenecks.
I have user array such like that:
const users = [
{
name: "Jack",
workspaces: [
{
_id: "61216512315615645jbk",
permissions: ["CAN_DELETE_WORKSPACE", "CAN_EDIT_PROJECT"],
},
{
_id: "41ss16512315615645bk",
permissions: ["CAN_DELETE_WORKSPACE", "CAN_EDIT_PROJECT"],
},
],
},
{
name: "Joe",
workspaces: [
{
_id: "71216512315615645jbk",
permissions: ["CAN_DELETE_WORKSPACE"],
},
],
},
];
And I have activeWorkspace object such like that:
const activeWorkspace = {
name: "W1",
_id: "61216512315615645jbk",
};
I need to filter the objects in the users array whose workspace _id is equal to activeWorkspace _id.
Output must be like that:
{
name: "Jack",
workspaces: [
{
_id: "61216512315615645jbk",
permissions: ["CAN_DELETE_WORKSPACE", "CAN_EDIT_PROJECT"],
},
{
_id: "41ss16512315615645bk",
permissions: ["CAN_DELETE_WORKSPACE", "CAN_EDIT_PROJECT"],
},
],
}
How can I do that?
In addition:
If we want to return an array, not an object, how should we do it? Like that:
[{
name: "Jack",
workspaces: [
{
_id: "61216512315615645jbk",
permissions: ["CAN_DELETE_WORKSPACE", "CAN_EDIT_PROJECT"],
},
{
_id: "41ss16512315615645bk",
permissions: ["CAN_DELETE_WORKSPACE", "CAN_EDIT_PROJECT"],
},
],
}]
Thanks
If there is only one match. You need to use find(). Inside of the find method, you want to use some() to look for an _id match.
const users = [
{
name: "Jack",
workspaces: [
{
_id: "61216512315615645jbk",
permissions: ["CAN_DELETE_WORKSPACE", "CAN_EDIT_PROJECT"],
},
{
_id: "41ss16512315615645bk",
permissions: ["CAN_DELETE_WORKSPACE", "CAN_EDIT_PROJECT"],
},
],
},
{
name: "Joe",
workspaces: [
{
_id: "CHANGED_ID",
permissions: ["CAN_DELETE_WORKSPACE"],
},
],
},
];
const activeWorkspace = {
name: "W1",
_id: "61216512315615645jbk",
};
const active = users.find(function (user) {
return user.workspaces.some( function (workspace) {
return workspace._id === activeWorkspace._id;
});
});
console.log(active);
// Same thing as above, just done with a modern approach
const active2 = users.find(({workspaces}) => workspaces.some(({_id}) => _id === activeWorkspace._id));
console.log(active2);
Now if there could be more than one match (your orginal code before the typo, you would use filter() and some() to find all users that have the workspace in their array.
const users = [
{
name: "Jack",
workspaces: [
{
_id: "61216512315615645jbk",
permissions: ["CAN_DELETE_WORKSPACE", "CAN_EDIT_PROJECT"],
},
{
_id: "41ss16512315615645bk",
permissions: ["CAN_DELETE_WORKSPACE", "CAN_EDIT_PROJECT"],
},
],
},
{
name: "Joe",
workspaces: [
{
_id: "61216512315615645jbk",
permissions: ["CAN_DELETE_WORKSPACE"],
},
],
},
];
const activeWorkspace = {
name: "W1",
_id: "61216512315615645jbk",
};
const active = users.filter(function (user) {
return user.workspaces.some( function (workspace) {
return workspace._id === activeWorkspace._id;
});
});
console.log(active);
// Same thing as above, just done with a modern approach
const active2 = users.filter(({workspaces}) => workspaces.some(({_id}) => _id === activeWorkspace._id));
console.log(active2);
I adjusted the provided data from Joe so he doesn't have permissions
const users = [{
name: "Jack",
workspaces: [{
_id: "61216512315615645jbk",
permissions: ["CAN_DELETE_WORKSPACE", "CAN_EDIT_PROJECT"],
},
{
_id: "41ss16512315615645bk",
permissions: ["CAN_DELETE_WORKSPACE", "CAN_EDIT_PROJECT"],
},
],
},
{
name: "Joe",
workspaces: [{
_id: "61216512315615645bk",
permissions: ["CAN_DELETE_WORKSPACE"],
}, ],
},
];
const activeWorkspace = {
name: "W1",
_id: "61216512315615645jbk",
};
function findPermittedUser() {
return users.filter(user => {
let hasPermission = false
user.workspaces.forEach(workspace => {
if (activeWorkspace._id == workspace._id) {
hasPermission = true
}
})
return hasPermission
})
}
console.log(findPermittedUser())
You can use map and filter to "filter" out the unwanted ids from the users object. Something like :
const users = [
{
"name": "Jack",
"workspaces": [
{
"_id": "61216512315615645jbk",
"permissions": [
"CAN_DELETE_WORKSPACE",
"CAN_EDIT_PROJECT"
]
},
{
"_id": "41ss16512315615645bk",
"permissions": [
"CAN_DELETE_WORKSPACE",
"CAN_EDIT_PROJECT"
]
}
]
},
{
"name": "Joe",
"workspaces": [
{
"_id": "61216512315615645jbk",
"permissions": [
"CAN_DELETE_WORKSPACE"
]
}
]
}
]
const activeWorkspace = {
name: "W1",
_id: "61216512315615645jbk",
};
const filteredUsers = users.map(item => ({
name : item.name,
workspaces: item.workspaces.filter(user => user._id === activeWorkspace._id)}
));
console.log(filteredUsers);
This should work (tested):
const filteredUsers = users.filter(
user => user.workspaces.reduce(
(acc, workspace) => acc || workspace._id === activeWorkspace._id, false)
)
)
Explanation:
We are using filter and reduce as evident from the code. What the code is doing is pretty simple, first, we want to apply filter on the user array. Now in the filter, we need to define the logic, which should return true whenever our condition happens to be true.
Since we have an array of workspaces, we need to iterate over all of them to check if our activeWorkspace._id exists in any of them. For this, you can use a for loop and return true when you find it, else return false if not. But the functional way of doing it would be to use reduce and initialize the accumulator with false. Every time you access a workspace, you return acc || <our condition>. Notice how if even once our condition returns true, the accumulator becomes true for the rest of the execution of reduce. This is slightly poor in performance since you are not exiting as soon as you have found your workspace._id as you would have done in case of a for loop.
users.map(u => u.workspaces).flat().filter(w => w._id === activeWorkspaceId);
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat
I have the following code, where I have a set of obj b is where the way to group them are defined in a by elements.
I'm having some doubts about how to do it.
For example, is it correct to scroll a or b?
Can you give me a hand?
const a = [{
_id: 0,
elements: ['aasa', 'cccx', 'zzzx', 'sd']
},
{
_id: 1,
elements: ['bb', 'xx']
}
];
const b = [{
_id: 'aasa',
info: "sssas"
},
{
_id: 'bb'
},
{
_id: 'zzzx',
info: "ssss"
},
{
_id: 'cccx',
info: "sss"
},
{
_id: 'xx'
}
];
// result
[
[{
_id: 'aasa',
info: "sssas"
},
{
_id: 'zzzx',
info: "ssss"
},
{
_id: 'cccx',
info: "sss"
}
],
[{
_id: 'bb'
},
{
_id: 'xx'
}
]
];
c = a.map(el => el.elements)
const p = b
//.map(el => el.elements)
.reduce(function(prev, curr) {
//if
prev.push(curr);
return prev;
}, []);
//console.log("End",p)
You can first change b to be a lookup object (here I have used a Map), where each id is a key that points to the object itself:
// Transform `b` into a Map of the form:
Map {
'aasa' => {
_id: 'aasa',
info: "sssas"
},
'bb' => {
_id: 'bb'
}
// ... etc ...
}
Then, for each object in a, you can map over the elements array, and use the id from that object as a key in the lookup object to obtain the associated object with that id. Before you perform a map, you can first filter any keys (ie: ids) that don't exist in the lookup table by using .has on the Map:
const a = [{ _id: 0, elements: ['aasa', 'cccx', 'zzzx', 'sd'] }, { _id: 1, elements: ['bb', 'xx'] } ];
const b = [{ _id: 'aasa', info: "sssas" }, { _id: 'bb' }, { _id: 'zzzx', info: "ssss" }, { _id: 'cccx', info: "sss" }, { _id: 'xx' } ];
const lut = new Map(b.map(obj => [obj._id, obj]));
const res = a.map(({elements}) => elements.filter(key => lut.has(key)).map(key => lut.get(key)));
console.log(res);
This should work
const c = a.map(el1 => {
return b.filter(el2 => {
return el1.elements.includes(el2._id)
})
});
I have an object like this
[
{
_id: '5ef34e92858bff53bcf69e11',
factors: [ {factor_id:'factor_id1',calmode:'calmode1',desc:'desc1',webserv:'webserv1',approach:'approach1'},
{factor_id:'factor_id2',calmode:'calmode2',desc:'desc2',webserv:'webserv2',approach:'approach2'},
{factor_id:'factor_id3',calmode:'calmode3',desc:'desc3',webserv:'webserv3',approach:'approach3'}
],
clientId: 'Company1',
module: 'Mod1',
__v: 0
},
{
_id: '5ef350c9c1acd61e58ef9d08',
factors: [ {factor_id:'factor_id4',calmode:'calmode4',desc:'desc4',webserv:'webserv4',approach:'approach4'},
{factor_id:'factor_id5',calmode:'calmode5',desc:'desc5',webserv:'webserv5',approach:'approach5'}
],
clientId: 'Company1',
module: 'Mod2',
__v: 0
}
]
I want to create a final list like below
_id, ClientId,module,factor_id,calmode,desc,webserv,approach
I am trying to use map operator within another map operator but its not coming out properly. Any help would be appreciated.
const tmpFacLst = FacExists.map((module) => {
const Factor = {
module_id: module._id,
module: module.module,
};
return Factor;
/*const Fac = module.factors.map((factor)=>{
const FactorDtl = {
factor_id:factor._id,
factor_desc: factor.desc
}
return FactorDtl;
})*/
});
Update: I am able to achieve using loop
const result = [];
FacExists.forEach((item) => {
const Factors = item.factors;
Factors.forEach((Subitem) => {
const Facobj = {
_id: item._id,
ClientId: item.clientId,
module: item._id,
factor_id: Subitem._id,
calmode: Subitem.calmode,
desc: Subitem.desc,
webserv: Subitem.webserv,
};
result.push(Facobj);
});
});
I want to know is there any better way of doing this without looping.
An approach like this should work:
const items = [
{
_id: "5ef34e92858bff53bcf69e11",
factors: [
{
factor_id: 2,
calmode: "cal",
desc: "something",
webserv: "10.0.0.0",
approach: "forwards",
},
],
clientId: "Company1",
module: "Mod1",
__v: 0,
},
{
_id: "5ef350c9c1acd61e58ef9d08",
factors: [
{
factor_id: 3,
calmode: "cal",
desc: "something",
webserv: "10.0.0.1",
approach: "forwards",
},
],
clientId: "Company1",
module: "Mod2",
__v: 0,
},
];
const result = [];
items.forEach((item) => {
const { factors, __v, ...rest } = item;
result.push(...factors.map((factor) => ({ ...factor, ...rest })));
});
console.log(result);
First, you need to clean your question up a bit because you have the Object keyword / class listed as elements of your factors array, which you call an "object". You should include those objects in your snippets.
let notAnObj = [
{
_id: '5ef34e92858bff53bcf69e11',
factors: [ {_id: 1234, desc: 'bob loblaw'}],
clientId: 'Company1',
module: 'Mod1',
__v: 0
},
{
_id: '5ef350c9c1acd61e58ef9d08',
factors: [],
clientId: 'Company1',
module: 'Mod2',
__v: 0
}
]
console.log(notAnObject)
let arr= [
{
_id: '5ef34e92858bff53bcf69e11',
factors: [ {_id: 1234, desc: 'bob loblaw'}],
clientId: 'Company1',
module: 'Mod1',
__v: 0
},
{
_id: '5ef350c9c1acd61e58ef9d08',
factors: [],
clientId: 'Company1',
module: 'Mod2',
__v: 0
}
]
const tmpFacLst = arr.map((obj) => {
return {
_id: obj._id,
ClientId: obj.clientId,
module: obj.module,
factors: obj.factors.map(factor => {
return {
_id: factor._id,
desc: factor.desc,
}
}),
calmode: undefined,
webserv: undefined,
approach: undefined
};
});
console.log(tmpFacLst)
you can do it like this
const finalResult = FacExists.reduce((aggregator,fact) => {
let result = fact.factors.map(fac=>{
return {
_id: fact._id,
clientId: fact.clientId,
module: fact.module,
...fac
}})
aggregator = [...aggregator,...result];
return aggregator
},[]);
you will get the desired result in the "finalResult" variable.
I have this endpoint, it's the initial endpoint when a customer is visiting the eshop:
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);
}
};
If no filters are applied all products matches the category requested with no problem.
My issue is when a customer selects a brand or tag, then the facet returns the products, but returns only one brand/tag (as it should be since the products filtered have only this brand).
What I must do in order to retain all brands/tags and let the user select more than one brand/tag? If customer selects a brand, then the tags should match the returned products tags and vice versa.
Is there a better way to implement tags stage in $facet since tags is an array and the desired output is: [{_id: 123, name: {label: 'test', value: 123]}]
The request is like:(1,2,3,4 represents _id)
http://locahost:3000/get-products/?filters={brands: [1, 2], tags: [3,4], pricesRange:[]}
Update
This is the products schema with tags and brands:
brand: {
ref: 'Brand',
type: Schema.Types.ObjectId
},
tags: [
{
ref: 'Tags',
type: Schema.Types.ObjectId
}
]
tags schema:
{
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 schema:
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
}
}
Scenario:
User visits store, selects a category and all matching products should return with matched brands, tags, priceRange & pagination.
Case 1:
User clicks a brand from checkbox, then the request returns matching products,tags & priceRanges and all brands of the selected category, not of matched products
Case 2:
User selects a brand like Case 1, but then decides to check a tag too, then the request should return all brands and tags again, but products matched against them.
Case 3:
User do not select brand but selects a tag only, the request should return all matching products that have that tag/tags and return the brands that matched the products returned.
Case 4:
Same as case 3, but user selects a brand after selecting a tag/tags, the request should return matching products, brands & tags.
In all cases pagination should return proper total, also priceRanges should match the returned results.
I hope it's clear now, I think I've not missed any other case. I could probably grey out/disable the tags/brands that do not match the response in the front end but I don't know if this is user friendly.
This is what I ended up with:
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: `/${JSON.stringify(req.params['0']).replace(/"/g, '')}`
}
}
});
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'
});
const filterBrands = brands.map((_id) => utils.toObjectId(_id));
const filterTags = tags.map((_id) => utils.toObjectId(_id));
const priceRanges = pricesRange ? pricesRange.match(/\d+/g).map(Number) : '';
aggregate.facet({
tags: [
{ $unwind: { path: '$brand' } },
{ $unwind: { path: '$tags' } },
{
$match: {
$expr: {
$and: [
filterBrands.length ? { $in: ['$brand._id', filterBrands] } : true
]
}
}
},
{ $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: { name: 1 } }
],
brands: [
{ $unwind: { path: '$brand' } },
{ $unwind: { path: '$tags' } },
{
$match: {
$expr: {
$and: [
filterTags.length ? { $in: ['$tags._id', filterTags] } : true
]
}
}
},
{
$group: {
_id: '$brand._id',
name: { $first: '$brand.name' },
slug: { $first: '$brand.slug' },
total: {
$sum: 1
}
}
},
{ $sort: { name: 1 } }
],
products: [
{ $unwind: { path: '$brand', preserveNullAndEmptyArrays: true } },
{ $unwind: { path: '$tags', preserveNullAndEmptyArrays: true } },
{
$match: {
$expr: {
$and: [
filterBrands.length ? { $in: ['$brand._id', filterBrands] } : true,
filterTags.length ? { $in: ['$tags._id', filterTags] } : true,
pricesRange.length
? {
$and: [
{
$gte: [
{
$cond: {
if: { $ne: ['$onSale.value', true] },
then: '$price',
else: '$sale.salePrice'
}
},
priceRanges[0]
]
},
{
$lte: [
{
$cond: {
if: { $ne: ['$onSale.value', true] },
then: '$price',
else: '$sale.salePrice'
}
},
priceRanges[1]
]
}
]
}
: true
]
}
}
},
{ $skip: (skip - 1) * pageSize },
{ $limit: pageSize },
{
$project: {
_id: 1,
brand: 1,
description: 1,
images: 1,
onSale: 1,
price: 1,
quantity: 1,
sale: 1,
shipping: 1,
sku: 1,
skuThreshold: 1,
slug: 1,
status: 1,
stock: 1,
tags: 1,
title: 1
}
},
{ $sort: { [order]: sort } }
],
pricesRange: [
{ $unwind: { path: '$brand', preserveNullAndEmptyArrays: true } },
{ $unwind: { path: '$tags', preserveNullAndEmptyArrays: true } },
{
$match: {
$expr: {
$and: [
filterBrands.length ? { $in: ['$brand._id', filterBrands] } : true,
filterTags.length ? { $in: ['$tags._id', filterTags] } : true
]
}
}
},
{
$project: {
price: 1,
onSale: 1,
sale: 1,
range: {
$cond: [
{
$and: [
{
$gte: [
{
$cond: {
if: { $ne: ['$onSale.value', true] },
then: '$price',
else: '$sale.salePrice'
}
},
0
]
},
{
$lte: [
{
$cond: {
if: { $ne: ['$onSale.value', true] },
then: '$price',
else: '$sale.salePrice'
}
},
20
]
}
]
},
'0-20',
{
$cond: [
{
$and: [
{
$gte: [
{
$cond: {
if: { $ne: ['$onSale.value', true] },
then: '$price',
else: '$sale.salePrice'
}
},
20
]
},
{
$lte: [
{
$cond: {
if: { $ne: ['$onSale.value', true] },
then: '$price',
else: '$sale.salePrice'
}
},
50
]
}
]
},
'20-50',
'50+'
]
}
]
}
}
},
{
$group: {
_id: '$range',
count: { $sum: 1 }
}
},
{
$project: {
_id: 0,
range: '$_id',
count: 1
}
},
{ $unwind: { path: '$range', preserveNullAndEmptyArrays: true } },
{
$sort: {
range: 1
}
}
],
total: [
{ $unwind: { path: '$brand', preserveNullAndEmptyArrays: true } },
{ $unwind: { path: '$tags', preserveNullAndEmptyArrays: true } },
{
$match: {
$expr: {
$and: [
filterBrands.length ? { $in: ['$brand._id', filterBrands] } : true,
filterTags.length ? { $in: ['$tags._id', filterTags] } : true,
pricesRange.length
? {
$and: [
{
$gte: [
{
$cond: {
if: { $ne: ['$onSale.value', true] },
then: '$price',
else: '$sale.salePrice'
}
},
priceRanges[0]
]
},
{
$lte: [
{
$cond: {
if: { $ne: ['$onSale.value', true] },
then: '$price',
else: '$sale.salePrice'
}
},
priceRanges[1]
]
}
]
}
: true
]
}
}
},
{
$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);
}
};