findByIdAndUpdate pull from array objects equal to a specific value - javascript

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

Related

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

Push To Mongoose Subdocuments

Hello I am creating a series of groupings describing the roles certain users are taking within the context of helping a client. The object in the Prospect model is called caseworkers. In caseworkers is a series of arrays for the different types of roles done. The equation is to allow the user to push his info as a subdocument called CaseWorker. Basically creating an object with 6 arrays that users can push to. Ive tried a few things and settled on Subdocuments. Any help would be awesome.
Here is my code:
const mongoose = require("mongoose");
const CaseWorker = require("./CaseWorker");
const ProspectSchema = mongoose.Schema({
caseWorkers: {
originators: [CaseWorker.schema],
loanProcessors: [CaseWorker.schema],
documentProcessors: [CaseWorker.schema],
upsells: [CaseWorker.schema],
primaryReso: [CaseWorker.schema],
taxPreparers: [CaseWorker.schema],
secondaryReso: [CaseWorker.schema],
}
module.exports = mongoose.model("prospect", ProspectSchema);
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const CaseWorkerSchema = new Schema({
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
role: { type: String },
resoCred1: { type: String },
resoCred2: { type: String },
reminders: [
{
_id: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
userReminded: { type: mongoose.Schema.Types.ObjectId },
reminderDate: { type: Date },
reminderDueDate: { type: Date },
status: { type: String },
daysTilDue: { type: Number },
id: { type: String },
text: { type: String },
clientId: { type: mongoose.Schema.Types.ObjectId, ref: "Prospect" },
},
],
});
module.exports = mongoose.model("caseWorker", CaseWorkerSchema);
router.put("/:_id/caseWorkers/loanProcessors", auth, async (req, res) => {
const prospect = await Prospect.findByIdAndUpdate(req.params._id, {
"$push": {
"loanProcessors": {
"caseWorker": {
"name": req.body.name,
"email": req.body.email,
"role": req.body.role,
"resoCred1": req.body.resoCred1,
"resoCred2": req.body.resoCred2,
},
},
},
});
res.json(prospect);
console.log(prospect);
});
In your approach when updating the document you put caseWorker under loanProcessors but it's declared in the schema the other way around.
To update a nested object you have to use the dot notation to reference the field.
Don't forget to put the object key that represent the field as a string like this "caseWorkers.loanProcessors", because caseWorkers.loanProcessors is an invalid object key in javascript
"$push": {
"caseWorkers.loanProcessors": {
"name": req.body.name,
"email": req.body.email,
"role": req.body.role,
"resoCred1": req.body.resoCred1,
"resoCred2": req.body.resoCred2,
},
},

Try to populate in mongoose and it doesn't work (nodejs)

i Make mini cart with Product and user Auth, Evereting work perfect but whan i try to make a route that pickup all the product from the user and view them in specific page and it not work for me.
it returns the user but not the product.
UserSchema
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const UserSchema = new Schema({
product: {
type: [mongoose.Schema.Types.ObjectId],
ref: "product"
},
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
address: {
type: String,
required: true
},
password: {
type: String,
required: true
},
data: {
type: Date,
default: Date.now
}
});
module.exports = User = mongoose.model("user", UserSchema);
ProductScheama
const mongoose=require('mongoose');
const ProductSchema = new mongoose.Schema({
name:{
type:String,
required:true,
unique:true
},
description:{
type:String,
},
price:{
type:Number,
required:true
},
quantity:{
type:Number,
required:true
},
data:{
type:Date,
default:Date.now
}
})
module.exports=Product=mongoose.model('product',ProductSchema)
I am trying to create a function that gives me the name, price and description of the product and it fails.
my router:
router.get("/products/:id", auth, async (req, res) => {
try {
let pro = await User.find({ product: req.params.id }).populate("product", [
"name",
"price",
"description"
]);
if (!pro) {
return res.json({ msg: "This user not have products to show" });
}
res.json(pro);
} catch (err) {
console.error(err.message);
res.status(500).send("Server errors");
}
});
result from Postman:
[
{
"product": [],
"_id": "5d5bfb96963ca600ec412bca",
"name": "Anonny Annon",
"email": "Annony#gmail.com",
"address": "Israel",
"password": "$2a$10$gESTIaBVifzhRDR2zOKsw.Q79gCT07IK2VnDoyT2oU5htqfBuAj8W",
"data": "2019-08-20T13:54:30.267Z",
"__v": 0
}
]
I think product should be defined this way :
product: [{
type: mongoose.Schema.Types.ObjectId,
ref: "product"
}]
instead of type: [mongoose.Schema.Types.ObjectId]
Solution found here

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

Can't POST to nested array with express js

It's my first post here so please let me know if there's anything incomplete about my question, or if there's anything else that is missing :)
I'm trying to make a POST request to an array in my data structure called features:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const CategorySchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
categoryname: {
type: String,
required: true
},
items: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
itemname: {
type: String,
required: true
},
features: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
firstfeature: {
type: String
},
date: {
type: Date,
default: Date.now
}
},
{
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
secondfeature: {
type: String
},
date: {
type: Date,
default: Date.now
}
}
],
date: {
type: Date,
default: Date.now
}
}
],
date: {
type: Date,
default: Date.now
}
});
module.exports = Category = mongoose.model('category', CategorySchema);
I don't have any issues with posting to the items array with the following code:
router.post(
'/item/:id',
passport.authenticate('jwt', { session: false }),
(req, res) => {
const { errors, isValid } = validateItemInput(req.body);
// Check Validation
if (!isValid) {
// if any errors, send 400 with erros object
return res.status(400).json(errors);
}
Category.findById(req.params.id)
.then(category => {
const newItem = {
itemname: req.body.itemname,
user: req.user.id
};
// Add to item array
category.items.unshift(newItem);
// Save
category.save().then(category => res.json(category));
})
.catch(err =>
res.status(404).json({ categorynotfound: 'No category found' })
);
}
);
But I can't figure out what I need to change here in order to add data to the features array:
router.post(
'/feature/:id/:item_id',
passport.authenticate('jwt', { session: false }),
(req, res) => {
Category.findById(req.params.id)
.then(category => {
const newFeature = {
firstfeature: req.body.firstfeature,
secondfeature: req.body.secondfeature,
user: req.user.id
};
// Add to item array
category.items.features.unshift(newFeature);
// Save
category.save().then(category => res.json(category));
})
.catch(err => res.status(404).json({ itemnotfound: 'Item not found'
}));
}
);
Issue solved with the following data structure:
features: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
price: {
type: String
},
size: {
type: String
},
date: {
type: Date,
default: Date.now
}
}
]
And then simply make a post request for one feature at a time.

Categories

Resources