aggregate then update to other collection - javascript

I want to insert the returned data of my aggregate (controller function) to another collection called User. Below is my schema for the User:
const userSchema = new mongoose.Schema({
firstName: {
type: String,
required: [true, 'First name is required']
},
lastName: {
type: String,
required: [true, 'Last name is required']
},
email: {
type: String,
required: [true, 'Email is required']
},
loginType: {
type: String,
required: [true, 'Login type is required']
},
password: {
type: String
},
transactions: [{transactionId: String}, date:{type:Date, default:new Date()}],
totalIncome : [{id:String},{totalIncome:Number}]
})
module.exports = mongoose.model('User', userSchema);
I want to insert the result of this controller function into User collection as another variable
return Transaction.aggregate([ { $match : { userId : userId, type:"Income",isActive:true} }, {
$group: {
_id: "$type",
totalIncome: {
$sum: "$amount"
}
}
}
]).then((totalIncome,err)=> {
return (err) ? false : totalIncome
})
The result of my aggregate above is this:
[
{
"_id": "Income",
"totalIncome": 18000
}
]
Please help :( I tried adding $out but it replaces the whole user collection.
I just want it to add to a specific user and update its content every time I use the controller function above. Thank you!

return Transaction.aggregate([ { $match : { userId : userId, type:"Income",isActive:true} }, {
$group: {
_id: "$type",
amount: {
$sum: "$amount"
}
}
}
]).then((totalIncome,err)=> {
// return (err) ? false : totalIncome
console.log(totalIncome)
if (err) {
return false
} else {
User.findByIdAndUpdate(userId, {"$set" : {"totalIncome": totalIncome[0].amount }}, { "new": true })
.then((transaction, err ) => {
return (err) ? false : true
})
}
})
I was able to add totalIncome to my User collection by using findByIdAndUpdate() and $set operator. Feel free to provide different solution for my question above.

Related

How to add an object to an array of object, using addToSet, or push operators in mongodb

I have an array of reviews, I want to add a review using addToSet that will check if user is present in the array, then we do not want to add since one user can only review once.
My schema looks like this:
const sellerSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
unique: true,
},
reviews: [
{
by: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
unique: true,
},
title: {
type: String,
},
message: {
type: String,
},
rating: Number,
imagesUri: [{ String }],
timestamp: {
type: Date,
default: Date.now,
},
},
],
});
I might be doing the query wrong, but can't figure out how to add a review and check if current user has not reviewed before.
Here is the query where I add the review:
router.post("/review/:_id/", async (req, res) => {
try {
const stylist_id = mongoose.Types.ObjectId(req.params._id);
const review = {
by: req.user._id,
title: req.body.title,
message: req.body.message,
rating: parseFloat(req.body.rating),
};
if (req.body.imagesUri) {
//if there is images, we need to set it up
review.imagesUri = req.body.imagesUri;
}
await Seller.updateOne(
{ _id: seller_id },
{ $addToSet: { reviews: review } } //get the review that matches to the user_id
);
return res.status(200).send(true);
}catch(err){
res.status(502).send({
error: "Error creating a review.",
});
}
});
I'm thinking of checking for seller's id and also check that no review is by current user, but it is not working.
const userID = req.user._id;
await Seller.updateOne(
{ _id: seller_id, reviews: { $elemMatch: { by: { $ne: { userID } } } } },
{ $addToSet: { reviews: review } } //get the review that matches to the user_id
);
ANSWER:
I was able to solve the issue, in case other people have same issue. I did this:
await Seller.updateOne(
{
_id: seller_id,
"reviews.by": { $nin: [req.user.id] },
//knowing req.user._id is a mongoose.Types.ObjectId.
//You can also use [id1, id2, ...] to the array to check for other id's
},
{ $addToSet: { reviews: review } } //get the review that matches to the user_id
);
Here is the documentation for $nin operator: https://www.mongodb.com/docs/manual/reference/operator/query/nin/
You are pushing the review object inside an object.
Instead do this:
await Seller.updateOne(
{ _id: seller_id },
{ $addToSet: { reviews: review } }
);

Populating in Mongodb Aggregating

I just asked a related question here:
Mongoose/Mongodb Aggregate - group and average multiple fields
I'm trying to use Model.aggregate() to find the average rating of all posts by date and then by some author's subdocument like country.name or gender. Having trouble with this though. I know for the first stage I just need to use $match for the date and I think I need to use $lookup to "populate" the author field but not sure how to implement this.
This works for finding an average rating for all posts by date:
Post.aggregate([
{ $group: { _id: "$date", avgRating: { $avg: '$rating' }}}
]).
then(function (res) {
console.log(res);
})
And this is basically what I want to do but it doesn't work:
Post.aggregate([
{$match: {"date": today}},
{$group: {_id: {"country": "$author.country.name"}, avgRating: {$avg: "$rating"}}}
]).then(function(res) {
console.log(res)
})
User model:
const userSchema = new Schema({
email: {
type: String,
required: true,
unique: true
},
birthday: {
type: Date,
required: true,
},
gender:{
type: String,
required: true
},
country:{
name: {
type: String,
required: true
},
flag: {
type: String,
// default: "/images/flags/US.png"
}
},
avatar: AvatarSchema,
displayName: String,
bio: String,
coverColor: {
type: String,
default: "#343a40"
},
posts: [
{
type: Schema.Types.ObjectId,
ref: "Post"
}
],
comments: [
{
type: Schema.Types.ObjectId,
ref: "Comment"
}
],
postedToday: {
type: Boolean,
default: false
},
todaysPost: {
type: String
}
})
You can populate an aggregation after you fetched the data from the MongoDB. Your `Query will look a bit like this:
modelName.aggregate([{
$unwind: ''//if Needed
}, {
$group: {
_id: {"country":"$author.country.name"},
avgRating: {
$avg: '$rating'
}
}])
.exec(function(err, transactions) {
// ERRORHANDLING
// CallsBacks
modelName.populate(columnName, {path: '_id'}, function(err, populatedModel) {
// Your populated columnName inside TaleName
});
});

Checking if a user has a certain role

So I use mongoDb in my express server and store the user information like this:
var UserSchema = new Schema({
username: { type: String, required: true, index: { unique: true } },
password: { type: String, required: true },
email: {type: String, required: true , index: { unique: true } },
isActive: {type:Boolean, default:false},
signUpDate: {type: Date, default: Date.now()},
roles: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Role"
}
]
});
With Role having a simple single field : name.
To find out if a user is an admin for example I do:
User.findById(req.SecureUserId).populate('roles').exec((err, user) => {
if (err) {
res.status(500).send({ message: err });
return;
}
for(let roles of user.roles){
if(roles.name === 'admin'){
next();
return;
}
}
However, this seems like something I can better query to my database ?
How would I do this in mongoDB?
You can use .aggregate() to "join" single user with his role and then run $match to verify if there's any role named admin. If particular user doesn't have such role then aggregation will return an empty array:
User.aggregate([
{ $match: { _id: req.SecureUserId } },
{
$lookup: {
from: "roles",
localField: "roles",
foreignField: "_id",
as: "roles"
}
},
{ $match: { "roles.name": "admin" } }
]).exec((err, user) => {
if(user.length === 1){
next();
}
})

How to change boolean value within an object of a document sub-array in Mongoose?

I have a rooms model. Within it is an array of User's which has its own model.
Each user has a bunch of different attributes, some of them being boolean. Knowing the ID of the specific room and the specific user, I am attempting to change the boolean value within a specific User element within the sub array like this:
Room.findOne({_id: roomId, "users" : user}, { "$set" : { mutedAudio : false}})
.then(doc => {
console.log("Unmuted audio");
res.json(doc)
io.in(roomId).emit('userchange');
})
.catch(err => {
console.log(err);
})
(I'm using a user model instead of a user ID for seeking the user within the sub array. Could not get ID to work but can fetch object by comparing it to itself entirely.)
I get the error:
MongoError: Unsupported projection option: $set: { mutedAudio: true }
Anyone know the answer to this?
Thank you.
EDIT:
const RoomSchema = new Schema({
owner: {
id: {
type: String
},
username: {
type: String
}
},
roomname: {
type: String,
required: true
},
category: {
type: String,
required: true
},
password: {
type: String,
required: false
},
users: [UserSchema],
messages: [{
username: {
type: String
},
message: {
type: String
},
time: {
type: String
}
}],
date: {
type: Date,
default: Date.now
}
});
const UserSchema = new Schema({
id: {
type: String
},
username: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
avatar: {
type: String
},
date: {
type: Date,
default: Date.now
},
micEnabled: {
type: Boolean,
default: false
},
mutedAudio: {
type: Boolean,
default: true
}
});
Model.findOne() takes 4 parameters, the second being "optional fields to return", that's why you're getting the error, mongoose is trying to select fields to return according to $set: { mutedAudio: true } which is being passed as a second parameter (therefore considered to be a projection option).
Use Model.findOneAndUpdate() which takes an update object as a second parameter, along with the positional operator $.
Room.findOneAndUpdate(
{ "_id": roomId, "users._id": userID },{ "$set": { "users.$.mutedAudio": false } } )
.then(doc => {
console.log("Unmuted audio");
res.json(doc)
io.in(roomId).emit('userchange');
})
.catch(err => {
console.log(err);
})
Original answer by #Neil Lunn in Mongoose find/update subdocument

Mongoose node return multiple arrays of referenced objects from document

I am working on a node backend API with mongoose. I have 2 schemas one User schema and one Follow schema(saved as users and follows in mongo). The follow schema fields followers and following hold an array of ObjectIds that refer to User objects. I am trying to return the Referenced objects in in the arrays so that I can respond with an object to the client that contains an array of userFollowing and userFollowers containing user objects, a but I am unable to populate the output.
I will also need to be able to filter the returned objects to only return 'username bio image email first_name surname join_date'.
My current incorrect output is below. I am not sure if its an error in my query or if I am using the correct approach.
[ { _id: 5c7dc1b92f3f1dd8ad9df993,
user: 5c7d93b57a29ce05a096c492,
userFollowing: [],
userFollowers: [] } ]
var mongoose = require('mongoose');
let Schema = mongoose.Schema;
var FollowSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
followers: [{
type: Schema.Types.ObjectId,
ref: 'User'
}],
following: [{
type: Schema.Types.ObjectId,
ref: 'User'
}]
}, { toJSON: { virtuals: true } }
);
module.exports = mongoose.model('Follow', FollowSchema);
// username must be unique and is required
var UserSchema = new mongoose.Schema({
username: {
type: String,
unique: true,
required: true
},
email: {
type: String,
unique: true,
},
first_name: {
type: String,
required: true
},
surname: {
type: String,
required: true
},
join_date: {
type: Date,
default: Date.now
},
bio: {
type: String,
default: 'Tell me about yourself'
},
image:{
type: String,
default: 'profile.jpg'
},
password: {
type: String,
required: true
}
});
User.findOne({
'username': username
}, function (err, user) {
if (!user) {
return res.json({
'state': false,
'msg': `No user found with username ${username}`
})
} else {
const user_id = user._id;
Follow.aggregate([{
$match: {
"user": mongoose.Types.ObjectId(user_id)
}
},
{
$lookup: {
"from": "follows",
"localField": "following",
"foreignField": "_id",
"as": "userFollowing"
}
},
{
$lookup: {
"from": "follows",
"localField": "followers",
"foreignField": "_id",
"as": "userFollowers"
}
}, {
$project: {
"user": 1,
"userFollowers": 1,
"userFollowing": 1
}
}
]).exec(function (err, doc) {
console.log(doc);
res.json({
'state': true,
'msg': 'Follow list',
'doc': doc
})
})
}
})

Categories

Resources