mongoose mongodb query find - javascript

i'm new in mongo and mongoose.
i want to do a simple query in relational database but i have strong problems to do in mongo
here is me schema:
const GroupSchema = new Schema({
name: { type: String , required:true},
image: { type: String , required:true},
location : {type:String , required: true},
locationCode: {type:String , required:true },
created: {type:Date, default:Date.now() , required:true },
createdBy: {type: Schema.Types.ObjectId , ref:'User' , required:true},
//category: [{type:String,enum:[ config.TYPES ]}],
pendingUsers: [
{ text:String,
user:{type: Schema.Types.ObjectId , ref: 'User'}
}
],
rejectedUsers: [ {type: Schema.Types.ObjectId , ref: 'User'} ],
users: [ {type: Schema.Types.ObjectId , ref: 'User' , required:true } ],
adminUsers:[{type:Schema.Types.ObjectId , ref:'User', required:true}],
events :[Event],
activity:[Activity]
})
and i my controller file i want to do the following query:
let groupId = '123123hgvhgj
let userId = 'asdfsadf3434
Group.find()
.where('_id').equals(groupId)
.where('pendingUsers.user')
.in(userId)
.where('users')
.in(userId)
.where('adminUsers')
.in(userId)
.where('rejectedUsers')
.in(userId)
.exec(function (err, records) {
//make magic happen
console.log(records)
});
i have to get the record WHERE _id match with a group id AND (userId exists in pendingUsers OR userid exists in rejectedUsers OR userid exists in users OR userid exists in adminUsers )
i know that seems to be a simple query but returns empty when should be returned the record i have something wrong in the query?
thanks

Even if mongoose seems to support some simple and + or operations (docs)
it seems to me as if you would still need to mix some pure mongodb query into it.
Considering that, i would go with a pure mongodb style query. This one should fit your needs(untested) or will at least point you in the right direction:
Group.find({
$and: [
{_id: groupId},
{
$or: [
{ pendingUsers.user: { $elemMatch: {userId} } },
{ rejectedUsers: { $elemMatch: {userId} } },
{ users: { $elemMatch: {userId} } },
{ adminUsers: { $elemMatch: {userId} } },
]
}
]
});

Group.find({
$and: [
{ _id: groupId},
{
$or: [
{ "pendingUsers": { $elemMatch: { "user": userId } } },
//{ 'pendingUsers.user' : { $elemMatch: {userId} } },
{ rejectedUsers: { $elemMatch: {userId} } },
{ users: { $elemMatch: {userId} } },
{ adminUsers: { $elemMatch: {userId} } },
]
}
]
})

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
});
});

findByIdAndUpdate pull from array objects equal to a specific value

I want to remove one or more objects of type tweet from the timeline list within the user model. The tweet objects that I want to remove are those whose author id matches a specific id user._id.
I have tried this:
router.get("/follow/:userId", isLoggedIn, catchAsync(async (req, res) => {
try {
const currentUser = await User.findById(req.user._id).populate("timeline")
const user = await User.findById(req.params.userId).populate("followers tweets")
for (let tweet of currentUser.timeline) {
if (tweet.author._id.equals(user._id)) {
currentUser.timeline.pull(tweet._id)
}
}
req.flash("error", `Unfollowed to ${user.username}`)
user.save();
currentUser.save()
res.redirect(`/${user._id}`)
} catch (err) {
req.flash("error", err.message);
res.redirect("back")
}
}));
and this:
await User.findbyIdAndUpdate(currentuser._id, { $pull: { timeline: { author : user._id } } }
but none of them are working.
My user model:
const userSchema = new Schema({
name: {
type: String,
required: true
},
biography: { type: String, maxlength: 160 },
location: {type: String, maxlength: 30 },
email: {
type: String,
unique: true,
required: true
},
image: {
url: String,
filename: String,
},
followers: [{ type: Schema.Types.ObjectId, ref: "User" }],
following: [{ type: Schema.Types.ObjectId, ref: "User" }],
tweets: [{ type: Schema.Types.ObjectId, ref: "Tweet"}],
timeline: [{ type: Schema.Types.ObjectId, ref: "Tweet"}]
});
My tweet model :
const tweetSchema = new Schema({
images: [{
url: String,
filename : String
}],
text: { type: String, maxlength: 260},
date: { type: Date, default: Date.now },
author: { type: Schema.Types.ObjectId, ref: "User" },
parent: { type: Schema.Types.ObjectId, ref: "Tweet", default:null },
replies: [{ type: Schema.Types.ObjectId, ref: "Tweet" }],
likes: [{ type: Schema.Types.ObjectId, ref: "User" }],
retweets: [{ type: Schema.Types.ObjectId, ref: "Tweet" }],
retweetStatus: {type: Schema.Types.ObjectId, ref: "Tweet", default: null}
});
If your collection looks like this:
[
{
"_id" : ObjectId("60254276259a60228cbe5707"),
"name" : "Mary",
"timeline" : [
ObjectId("60254276259a60228cbe5703"),
ObjectId("60254276259a60228cbe5704"),
ObjectId("60254276259a60228cbe5705")
]
},
{
"_id" : ObjectId("60254276259a60228cbe5706"),
"name" : "Dheemanth",
"timeline" : [
ObjectId("60254276259a60228cbe5700"),
ObjectId("60254276259a60228cbe5701"),
ObjectId("60254276259a60228cbe5702")
]
}
]
then the solution is:
usersSchema.updateOne(
{
"_id": ObjectId("60254276259a60228cbe5706"),
"timeline": ObjectId("60254276259a60228cbe5700"),
},
{
$pull: {
"timeline": ObjectId("60254276259a60228cbe5700")
}
}
)
.then()
.catch()
// or
usersSchema.findOneAndUpdate(
{
"_id": ObjectId("60254276259a60228cbe5706"),
"timeline": ObjectId("60254276259a60228cbe5700"),
},
{
$pull: {
"timeline": ObjectId("60254276259a60228cbe5700")
}
},
{
new: true
}
)
.then()
.catch()
I finally found the issue! The problem I was having is that I was trying to remove items from a list of objects while looping through that list. The solution is easy: you can just create an auxiliar empty array and push the items that you want to remove, then loop through that auxiliar array and pull the items from the original array.
In my case, I've already had an array with the tweets that I wanted to remove, user.tweets. The solution is:
router.get("/follow/:userId", isLoggedIn, catchAsync(async (req, res) => {
try {
const currentUser = await User.findById(req.user._id).populate("timeline")
const user = await User.findById(req.params.userId).populate("followers tweets")
for (let tweet of user.tweets) {
currentUser.timeline.pull(tweet._id)
}
req.flash("error", `Unfollowed to ${user.username}`)
user.save();
currentUser.save()
res.redirect(`/${user._id}`)
} catch (err) {
req.flash("error", err.message);
res.redirect("back")
}
}));

How can I get the total sum ($sum) of an array from a nested field?

I need the total sum of all the elements in an array that is nestet in my schema.
This is the schema:
const mongoose = require('mongoose');
let historySchema = new mongoose.Schema({
time: {
type:String
}
})
//users schema
let userSchema = new mongoose.Schema({
name:{
type:String,
},
dob:{
type:String,
},
email:{
type:String,
},
noOfpeopleClimbing:{
type: Number,
default:0
},
details:{
type:String,
},
status:{
type: Boolean,
default: false
},
timeIn:{
type: Number,
default: 0
},
timeOut:{
type: Number,
default: 0
},
timeFinal:{
type: Number,
default: 0
},
history:[{
time:{
type: Number
},
date:{
type:Date,
default:Date.now()
},
climbers:{
type: Number
},
names:{
type: String
}
}]
})
let User = module.exports = mongoose.model("User", userSchema);
The nested field in disscusion is:
history:[{
time:{
type: Number
}]
And the find method is:
app.get('/user/:id', function(req,res){
Users.findById(req.params.id, function(err, users){
res.render("user",
{
title:users.name,
users:users,
});
})
})
Can I attach to my find route an aggregate with $sum in order for me to send the data with the sum to my render view?.
For example totalTimeHistory:$sum aggregate data.
Use the following snippet below:
const result = Users.aggregate([
{
$match: {
//Your find block here
}
},
{
$unwind: "$history"
},
{
$project: {
totalTimeHistory: { $sum: "$history.time"}
}
}
])
Try this query:
db.collection.aggregate([
{
"$match": {
"name": "name" //or whatever you want
}
},
{
"$project": {
"total": {
"$sum": "$history.time"
}
}
}
])
In this way you don't need $unwind
Example here
db.getCollection('users').aggregate([
{
$match: {
<your find query goes here>
}
},
{
$unwind: '$history'
},
{
$group: {
_id: <your user object id (or) null>,
history: { $push: "$$ROOT.history" }
}
},
{
$addFields: {
totalSumOfHistoryTypes: { $sum: "$history.type" }
}
},
])
your output will look like
explanation:
$match: to find in the collection
$unwind: to unwind the history array so that the values of history can be grouped
$group: here we have created an array called history and pushed the history object($$ROOT.history) into it
$addFiled: used to add a new field which is not present on the schema
Hope this explains cheers

Mongoose Model find using an attribute from anohter Schema

Basically I have 2 Schemas.
User and Post.
User have an array which contains _ids from posts.
And post have an attribute that tells if he's an active post. -> is_active.
So, i want to filter User that have at least, one active post.
UserSchema
const UserSchema = new Schema(
{
name: {
type: String,
trim: true,
required: true
},
posts: [
{
type: Schema.Types.ObjectId,
ref: 'Post'
}
],
created_at: {
type: Date,
required: true,
default: Date.now()
}
}
)
export default mongoose.model<User>('User', UserSchema)
Post Schema
const postSchema = new Schema(
{
name: String,
is_active: boolean
}
)
As an alternative to #Tunmee's answer
Since the pipeline $lookup is available from v3.6 and as of v4.2 still has some performance issues. You could also use the "regular" $lookup available from v3.2
db.Users.aggregate([
{
$lookup: {
from: "Posts",
localField: "posts",
foreignField: "_id",
as: "posts"
}
},
{
$match: {
"posts.is_active": true
}
}
])
You can try this:
Users.aggregate([
{
$lookup: {
from: "Posts",
let: { postIds: "$posts", },
pipeline: [
{
$match: {
$expr: {
$and: [
{
$in: [ "$_id", "$$postIds" ]
},
{
$eq: [ "$is_active", true ]
},
]
}
},
},
// You can remove the projection below
// if you need the actual posts data in the final result
{
$project: { _id: 1 }
}
],
as: "posts"
}
},
{
$match: {
$expr: {
$gt: [ { $size: "$posts" }, 0 ]
}
}
}
])
You can test it out in a playground here
I'm not sure about your application's query requirement but you can add a compound index on _id and is_active properties in Posts collection to make the query faster.
You can read more about MongoDB data aggregation here.

Categories

Resources