Save array of ObjectId's using Mongoose's Schema - javascript

I have a Mongoose Schema like:
const Role = new Schema({
guildID: {
type: Schema.Types.ObjectId,
ref: 'guilds',
required: true
},
roles: {
owner: {
id: {
type: Number,
required: false
},
commands: [[Schema.Types.ObjectId]]
}
}
})
And a small function to test whether it saves the data as desired which contains:
const roleTest = new Role({
guildID: "61a679e18d84bff40c2f88fd",
roles: {
owner: {
id: 123456789
},
commands: [
"61af57d828b9fd5a07dbdcba",
"61af5a6728b9fd5a07dbdcbb",
"61af5ab728b9fd5a07dbdcbc"
]
}
})
roleTest.save((err, doc) => {
if (err) return res.sendStatus(500)
console.log('Done')
})
It saves everything correctly except the array ObjectIds (commands). What is going wrong here?

You've written the commands in schema with nested array. Try with single array:
{
commands: [Schema.Types.ObjectId],
}

Try this:
commands: [
{ type: mongoose.Schema.Types.ObjectId }
]

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

MongoDB findOne using $and & $elemMatch not working?

I am trying to check if there is an existing conversation between two users before creating another one.
My Conversation object stores the conversation participants in an array, I need to return a conversation object that has BOTH participants (senderId & recId) that exists in my database but I am unable to build to correct MongoDB query to get it.
Please see the queries I have tried below because I have tried all manner of using $and & $elemMatch but can't get it to work.
Thank you
Conversation Model
const conversationSchema = mongoose.Schema(
{
participants: [participantSchema],
},
{timestamps: true}
)
const participantSchema = mongoose.Schema(
{
userId: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: `User`,
},
username: {
type: String,
required: true,
}
}
)
Conversation Object
{
_id: 61cb6316asas4b54e09168234,
participants: [
{
userId: 61b777ea6815a69a625b,
username: 'johnsmith'
},
{
userId: 61bc0dcbe7181ccfd806,
username: 'testuser'
}
],
createdAt: 2021-12-28T19:18:46.673Z,
updatedAt: 2021-12-28T23:41:12.364Z
}
Queries I have tried that ARE NOT what I need or don't work
// null - no convo found when the convo definitely exists in db
const existingConvo = await Conversation.findOne({
$and: [{ userId: senderId }, { userId: recId }],
})
// works but only checks for ONE id property
// if I make an array: "Query filter must be an object, got an array"
const existingConvo = await Conversation.findOne({
participants: { $elemMatch: { userId: senderId } },
})
// "Unknown operator $and"
const existingConvo = await Conversation.find({
participants: {
$and: [{ userId: senderId }],
},
})
// returns empty array when it should have the convo object
const existingConvo = await Conversation.find({
participants: { $all: [{ userId: senderId }, { userId: recId }] },
})

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 limit the returned items from a Mongoose array populate?

I have a model that we use for a conversational chat, that uses nested arrays. Each time a user sends a message, it gets pushed into the messages array. This is my model:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const ConversationSchema = new Schema({
sender: {
type: Schema.Types.ObjectId,
ref: 'User'
},
messages: [
{
message: String,
meta: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
delivered: Boolean,
read: Boolean
}
]
}
],
is_group_message: { type: Boolean, default: false },
participants: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
delivered: Boolean,
read: Boolean,
last_seen: Date
}
]
}, { timestamps: true });
module.exports = mongoose.model('Conversation', ConversationSchema);
I'm trying to find all conversations, that a user is a part of, but I'm unable to return only the most recent chat message, and it seems to ignore the options. Is this a model or populate issue on my end?
Conversation.find({ 'participants.user': { $all: [req.user._id] } })
.sort('-createdAt')
.populate({
path: 'sender',
select: '-_id -password -email -lastLogin -createdAt -updatedAt -__v',
})
.populate({
path: 'messages.meta.user',
select: 'username avatar',
options: {
limit: 1
}
})
.populate({
path: 'participants',
path: 'participants.user',
select: 'username avatar'
})
.then(conversation => {
if (!conversation) {
errors.noconversation = 'No conversation could be found';
return res.status(404).json(errors);
}
res.json(conversation)
})
.catch(err => res.status(404).json({ conversation: 'There are no conversations or an error occurred' }));

mongoose mongodb query find

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} } },
]
}
]
})

Categories

Resources