Exclude field from a document based on another field value mongodb mongoose - database

I have a users schema in which there is a companies field which I only want to select if role field value is admin else it should not be returned in find query.
user Schema:
const userInfoSchema = new mongoose.Schema({
...,
companies: [
{
type: mongoose.Schema.ObjectId,
ref: 'companyinfos',
},
],
role: {
type: String,
enum: ['user', 'admin', 'employee'],
default: 'user',
},
});
I have tried to solve this by using pre find hook but was unable to exclude the companies field.
userInfoSchema.post(/^find/, function (doc, next) {
if (doc.role !== 'admin') {
this.find({}).select('-companies');
}
next();
});
Or is there any way to conditionally set select in the companies field in the userInfoSchema based on the role value?
Please help.

use aggregate
db.collection.aggregate([
{
"$project": {
companies: {
"$cond": {
"if": {
$eq: [
"$role",
"admin"
]
},
"then": "$companies",
"else": 0
}
}
}
},
{
$match: {
companies: {
$ne: 0
}
}
}
])
https://mongoplayground.net/p/I8HGvz6h7MP

Related

Mongoose how to sort by date which is in array of objects of a field

i want to grab data sorted by its date but the date is not in a field's value, its in the field's value's object's array(if i said it correctly).
here is an example of the data i have:
{
role: "User",
fullName: "Verna Pagac",
username: "dwightkoss95",
email: "shawn.ryan#yahoo.com",
orders: [{
buyerUsername: 'admin',
boughtAt: 2022-09-20T20:14:59.304Z
},
{
buyerUsername: 'admin',
boughtAt: 2022-10-30T22:35:35.546Z
}]
}
after i extracted the orders by the following command, i want to sort them by the boughtAt but how?
const usersWithOrders = await users
.find({
orders: { $exists: true, $ne: [] }
})
const orders = []
usersWithOrders.map((user) => {
for (i=0 ; i<user.orders.length ; i++) {
orders.push(user.orders[i])
}
})
i want the newer order to be top.
console.log(orders)
// now it shows the following out put:
/*
[{
buyerUsername: 'admin',
boughtAt: 2022-09-20T20:14:59.304Z
},
{
buyerUsername: 'admin',
boughtAt: 2022-10-30T22:35:35.546Z
}]
*/
If you want you can sort directly from db using the aggregation framework
db.users.aggregate([
{ $unwind: '$orders' },
{ $sort: { 'orders.boughtAt': -1 } },
{
$group: {
_id: '$_id',
user: { $first: '$$ROOT' },
orders: { $push: '$orders' },
},
},
])
or if you prefer you can sort in JS
orders.sort((a, b) => new Date(b.boughtAt) - new Date(a.boughtAt))

Looping through array to count in mongodb/mongoose

I have a user schema that contains a value called amputationInfo:
amputationInfo: [
{
type: String,
},
],
Here is an example of what that might look like in the database:
amputationInfo: [
"Double Symes/Boyd",
"Single Above-Elbow"
]
I have a review Schema that allows a user to leave a review, it contains a reference to the user who left it:
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
require: [true, 'Each review must have an associated user!'],
},
When a user leaves a review, I want to create an aggregate function that looks up the user on the review, finds their amputationInfo, loops through the array and adds up the total amount of users that contain "Double Symes/Boyd", "Single Above-Elbow"
So if we have 3 users and their amputationInfo is as follows:
amputationInfo: [
"Double Symes/Boyd",
"Single Above-Elbow"
]
amputationInfo: [
"Single Above-Elbow"
]
amputationInfo: []
The return from the aggregate function will count each term and add one to the corresponding value and look something like this:
[
{
doubleSymesBoyd: 1,
singleAboveElbow: 2
}
]
Here is what I have tried, but I just don't know enough about mongoDB to solve the issue:
[
{
'$match': {
'prosthetistID': new ObjectId('6126ca6148f34c00189f86f5')
}
}, {
'$lookup': {
'from': 'users',
'localField': 'user',
'foreignField': '_id',
'as': 'userInfo'
}
}, {
'$unwind': {
'path': '$userInfo'
}
}
]
After the $unwind, the resulting object has a userInfo key, that contains an amputationInfo array nested:
You can have following stages
$unwind to deconstruct the array
first $group to get the sum of each category
second $group to push into one document and make it as key value pair
$arrayToObject to get the desired output
$replaceRoot to make the data output into root
Here is the code
db.collection.aggregate([
{ "$unwind": "$userInfo.amputationInfo" },
{
"$group": {
"_id": "$userInfo.amputationInfo",
"count": { "$sum": 1 }
}
},
{
$group: {
_id: null,
data: { $push: {
k: "$_id",
v: "$count"
}
}
}
},
{ $project: { data: { "$arrayToObject": "$data" } } },
{ "$replaceRoot": { "newRoot": "$data" } }
])
Working Mongo playground

How to get object in deeply nested array in mongoose using nodes

In my collection of users I have the following
{
_id: ObjectId('whatever user id'),
movies: [
{
_id: ObjectId('whatever id of this movie'),
name: 'name of this movie',
actors: [
{
_id: ObjectId('whatever id of this actor'),
name: 'name of this actor'
}
]
}
]
}
So in my users collection I want to be able to query for a actor by the user.id, pet.id, and the actor.id
I want to return the actor somewhat like this...
actor: {
fields...
}
I tried the following...
const actor = await User.findById(req.user.id, {
movies: {
$elemMatch: {
_id: req.params.movie_id,
actors: {
$elemMatch: {
_id: req.params.actor_id,
},
},
},
},
});
I have tried other things but can't seem to get it to work. I saw that you can maybe use aggregate but I am not sure how to query that while using the ids I have at my disposal.
I was able to figure it out by using aggregate. I was using this before but it seems that I needed to cast my ids with mongoose.Types.ObjectId so a simple req.user.id would not work.
In order to get my answer I did...
const user = await User.aggregate([
{ $match: { _id: mongoose.Types.ObjectId(req.user.id) } },
{ $unwind: '$movies' },
{ $match: { 'movies._id': mongoose.Types.ObjectId(req.params.movie_id) } },
{ $unwind: '$movies.actors' },
{
$match: {
'movies.actors._id': mongoose.Types.ObjectId(req.params.actor_id),
},
},
]);
This did not return data in the following format...
actor: {
fields...
}
but returns it instead like this...
user: {
movies: {
actor: {
fields...
}
},
otherFields...
}
then sending the response back...
res.status(200).json({
status: 'success',
data: {
actor
}
})
gives that format I wanted. However, I would still want to know how to just get the data actor without getting the full document

Match $and based on array of filter items in MongoDB

I have a Collection in MongoDB of CatalogItems. Every CatalogItem contains a product that has an array of metafields.
In these metafields there are 2 fields that are Brand_ID and Article_No
Example CatalogItem document:
CatalogItem = {
product: {
metafields: [
{
key: "Brand_ID",
value: "317"
},
{
key: "Article_No",
value: "48630"
}
]
}
}
I have an array of filters that is used to match the CatalogItems documents based on these metafields
filter array
filter = [
{ brandId: '317', articleId: '48630' },
{ brandId: '257', articleId: 'ZSA04036' }
]
I want to return all CatalogItems that match any of the exact combinations in filter.
For example to return the stated CatalogItem I currently use this query
// Checks for { brandId: '317', articleId: '48630' }
query = {
$and: [
{ "product.metafields": { $elemMatch: { key: "Brand_ID", value: filter[0].brandId } } },
{ "product.metafields": { $elemMatch: { key: "Article_No", value: filter[0].articleId } } },
]
}
The issue that I have is that in order for me to look trough all the filter items I have to increment the filter index and rerunning the query.
For example looking for the second filter index I would have to change
filter[0].brandId to filter[1].brandId
Is there a way in Mongo to query using a predefined array of objects instead of rerunning the query multiple times?
I figured out a way to set variables to the query using $or and .forEach()
let query = {
$or: []
};
filter = [
{ brandId: '317', articleId: '48630' },
{ brandId: '257', articleId: 'ZSA04036' }
]
filter.forEach(filterItem => {
query.$or.push(
{
$and: [
{ "product.metafields": { $elemMatch: { key: "Brand_ID", value: filterItem.brandId } } },
{ "product.metafields": { $elemMatch: { key: "Article_No", value: filterItem.articleId } } },
]
}
)
})

How to increment property's value(integer) inside .update() and $set mongoose?

I'm trying to find a document in my database using findOne() and then search that document for options array that contains objects. Then I check object's property if it's equal to pollOption then I want to increment that object's another property votes by 1, but I can't get that property's value so I can increment it. Please help.
Routes.js
router.post('/submitVote', function(req, res){
const {pollId, pollOption} = req.body;
Polls.findOne({_id: pollId}
).update({'options.option': pollOption}, {'$set': {
'options.$.votes': '', // INCREMENT BY 1 //
}}, function(err){
if(err){
return console.log(err);
} else {
return res.send('success');
}
});
});
Sample Model:
{
"_id": {
"$oid": "5b2ec4852a51d06734f71e79"
},
"options": [
{
"option": "Amazing!",
"votes": 0
},
{
"option": "Good.",
"votes": 0
}
],
"creator": "Guest",
"name": "Rate this website!",
"__v": 0
}
Polls.js - Schema
var mongoose = require('mongoose');
const Poll = new mongoose.Schema({
name: { type: String, required: true },
options: { type: Array, required: true },
creator: { type: String, default: 'Guest' }
});
const Polls = mongoose.model('Polls', Poll);
module.exports = Polls;

Resources