removing a string within nested array mongoose - node.js

const AllPostsSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
posts: [{
postid: {
type: String
},
title: {
type: String
},
category: {
type: String
},
subcategory: {
type: String
}, category: {
type: String
},
description: {
type: String
},
name: {
type: String
},
price: {
type: Number
},
email: {
type: String
},
phonenumber: {
type: Number
},
language: {
type: String
},
make: {
type: String
},
model: {
type: Number
},
odometer: {
type: Number
},
condition: {
type: String
},
state: {
type: String
},
town: {
type: String
},
city: {
type: String
},
links: [{ type: String }],
date: {
type: Date,
default: Date.now
}
}]
})
AllPosts.findOneAndUpdate({ 'user': req.query.userid },
{ $pull: { 'posts': { 'links': req.query.link } } }
)
.then(post => console.log(post))
i need to find a specific user and within that user match the post id then remove one of the links in links array. when I do it like above it removes the whole array instead i want it to remove specific link within links array in posts arrayy.
Each user has one or more than one posts. I need to update a single post of a specific user. if a user wants to delete an image i delete that from amazon s3 then, i need to remove the link from that post link array so it doesnt create broken img tags in the front end.
AllPosts.findOneAndUpdate({ 'user': req.query.userid, 'posts.postid': req.query.postid },
{ $pull: { 'links': req.query.link } }
)
.then(post => console.log(post))
this also didnt work.

Solved. For future reference :
AllPosts.findOneAndUpdate({ 'user': req.query.userid, 'posts.postid': req.query.postid },
{ $pull: { 'posts.$.links': req.query.link } }
)
.then(post => console.log(post))

Related

How can I update some fields of an embedded object using mongoose's findOneAndUpdate method and not lose the other fields?

router.put('/experience/update/:exp_id',
auth,
async (req, res) => {
const {
title,
company,
location,
from,
to,
current,
description
} = req.body;
const newExp = {};
newExp._id = req.params.exp_id;
if (title) newExp.title = title;
if (company) newExp.company = company;
if (location) newExp.location = location;
if (from) newExp.from = from;
if (to) newExp.to = to;
if (current) newExp.current = current;
if (description) newExp.description = description;
try {
let profile = await Profile.findOne({ user: req.user.id });
if (profile) {
//UPDATE Experience
profile = await Profile.findOneAndUpdate(
{ user: req.user.id });
const updateIndex = profile.experience.map(exp => exp._id).indexOf(req.params.exp_id);
profile.experience[updateIndex] = newExp;
console.log('Experience updated!')
}
await profile.save();
res.json(profile);
} catch (error) {
console.log(error.message);
res.status(500).send('Internal Server Error');
}
}
)
I am using the findOneAndUpdate method to update the experience field inside a profile mongoose model.
After accesssing the endpoint, I put the updated details, for eg. company and location. But I lose all the other fields. So how can I update only select fields while others remain unchanged ?
Below is the profile schema:
const mongoose = require('mongoose');
const ProfileSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
},
company: {
type: String
},
website: {
type: String
},
location: {
type: String
},
status: {
type: String,
required: true
},
skills: {
type: [String],
required: true
},
bio: {
type: String
},
githubusername: {
type: String
},
experience: [
{
title: {
type: String,
required: true
},
company: {
type: String,
required: true
},
location: {
type: String
},
from: {
type: Date,
required: true
},
to: {
type: Date,
},
current: {
type: Boolean,
default: false
},
description: {
type: String,
}
}
],
education: [
{
school: {
type: String,
required: true
},
degree: {
type: String,
required: true
},
fieldofstudy: {
type: String,
required: true
},
from: {
type: Date,
required: true
},
to: {
type: Date,
required: true
},
current: {
type: Boolean,
default: false
},
description: {
type: String,
}
}
],
social: {
youtube: {
type: String,
},
twitter: {
type: String,
},
facebook: {
type: String,
},
linkedIn: {
type: String,
},
instagram: {
type: String,
}
},
date: {
type: Date,
default: Date.now
}
});
module.exports = Profile = mongoose.model('profile', ProfileSchema);
There are some problems in your code.
You are passing only one argument to findOneAndUpdate. Ideally the syntax is findOneAndUpdate(filter, update). So basically you need to pass update query as 2nd argument.
profile = await Profile.findOneAndUpdate(
{ user: req.user.id });
In below code you are modifying the profile object and saving it. Which is not required. And this is also the reason why you are losing fields.
const updateIndex = profile.experience.map(exp => exp._id).indexOf(req.params.exp_id);
profile.experience[updateIndex] = newExp;
console.log('Experience updated!')
}
await profile.save();
Solution-
We need to figure out the update part of findOneAndUpdate(filter, update).
Here is the update query -
db.collection.update({
"user": "5f96dc85ac5ae03160a024a8",
"experience._id": "5f9826c3a3fa002ce0f11853"
},
{
"$set": {
"experience.$": {
"current": false,
"_id": "5f9826c3a3fa002ce0f11853",
"title": "Senior developer",
"company": "Morgan Stanley",
"location": "Pune",
"from": "2017-04-30T18:30:00.000Z",
"to": "2020-07-08T18:30:00.000Z",
"description": "testing"
}
}
})
Try it here
Trying Mongoose way :
const filter = { user: req.user.id, "experience._id": req.params.exp_id }
const update = { $set: { "experience.$": newExp } }
profile = await Profile.findOneAndUpdate(filter,update);

Mongoose populate not working upto 3 levels

I am trying to populate a fields that is nested inside my model but it is not populating.
This is a field inside my model.
pendingChanges: {
credentials: {
university: {
name: { type: String },
major: { type: String },
majorGpa: { type: Number },
},
school: {
name: { type: String },
degreeType: { type: String },
degree: { type: String },
},
subjects: [{ type: mongoose.Schema.Types.ObjectId, ref: 'subject' }],
workExperience: {
type: { type: String },
from: { type: Date },
to: { type: Date },
},
},
},
I am trying to populate subjects key nested inside.
This is what I have done so far.
const teacher = (await this.findById(id))
.populate({
path: 'pendingChanges',
populate: {
path: 'credentials',
populate: {
path: 'subjects',
},
},
});
I found the solution. Here's what I did to make it work.
const data = await this.findOne(query)
.populate({
path: 'pendingChanges.credentials.subjects',
});

updateMany and elemMatch in with nested schemas in mongoose (Node.js)

I'm trying to query a MongoDB database via mongoose to updateMany the fields of my database. I suppose that the first request is correct because mongoose doesn't fire any error, but for the nested schemas, I'm getting the following error.
My goal is to delete the occurences of the userTag in friends and remove the friendRequestsSent when userTarget equals userTag, friendRequestsReceived when userRequest equals userTag and notification when data equals userTag.
Here are the schemas of my Model
const NotificationSchema = new Schema({
title: String,
type: Number,
icon: String,
data: String,
createdAt: { type: Date, default: Date.now },
})
const FriendRequestSchema = new Schema({
userRequest: { type: String, required: true },
userTarget: { type: String, required: true },
createdAt: { type: Date, default: Date.now },
})
const UserSchema = new Schema({
tag: { type: String, required: true, unique: true },
friendRequestsSent: { type: [FriendRequestSchema] },
friendRequestsReceived: { type: [FriendRequestSchema] },
friends: { type: [String] },
notifications: { type: [NotificationSchema] },
})
The request
const updateResponse = await User.updateMany(
{
friends: { $elemMatch: { $eq: userTag } },
friendRequestsSent: {
userTarget: {
$elemMatch: { $eq: userTag },
},
},
friendRequestsReceived: {
userRequest: {
$elemMatch: { $eq: userTag },
},
},
notifications: {
data: {
$elemMatch: { $eq: userTag },
},
},
},
{
$pull: {
friends: userTag,
friendRequestsSent: { userTarget: userTag },
friendRequestsReceived: { userRequest: userTag },
notifications: { data: userTag },
},
}
)
The error
Error while deleting the user account: Cast to String failed for value "{ '$elemMatch': { '$eq': '0eQzaAwpt' } }" at path "userRequest" for model "User"
The userRequest field in friendRequestsReceived is type String, not array so $elemMatch will not work. Also, you don't need to use $elemMatch because you specify only a single condition in the $elemMatch expression as it says in the docs:
If you specify only a single condition in the $elemMatch expression, you do not need to use $elemMatch.
In your case, you just need to do something like (details here):
await User.updateMany({
friends: userTag,
"friendRequestsSent.userTarget" : userTag,
"friendRequestsReceived.userRequest": userTag,
"notifications.data": userTag
}...

Mongoose sub field aggregation with full text search and project

I have a Mongoose model called Session with a field named course (Course model) and I want to perform full text search on sessions with full text search, also I wanna aggregate results using fields from course sub field and to select some fields like course, date, etc.
I tried the following:
Session.aggregate(
[
{
$match: { $text: { $search: 'web' } }
},
{ $unwind: '$course' },
{
$project: {
course: '$course',
date: '$date',
address: '$address',
available: '$available'
}
},
{
$group: {
_id: { title: '$course.title', category: '$course.courseCategory', language: '$course.language' }
}
}
],
function(err, result) {
if (err) {
console.error(err);
} else {
Session.deepPopulate(result, 'course course.trainer
course.courseCategory', function(err, sessions) {
res.json(sessions);
});
}
}
);
My models:
Session
schema = new mongoose.Schema(
{
date: {
type: Date,
required: true
},
course: {
type: mongoose.Schema.Types.ObjectId,
ref: 'course',
required: true
},
palnning: {
type: [Schedule]
},
attachments: {
type: [Attachment]
},
topics: {
type: [Topic]
},
trainer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'trainer'
},
trainingCompany: {
type: mongoose.Schema.Types.ObjectId,
ref: 'training-company'
},
address: {
type: Address
},
quizzes: {
type: [mongoose.Schema.Types.ObjectId],
ref: 'quiz'
},
path: {
type: String
},
limitPlaces: {
type: Number
},
status: {
type: String
},
available: {
type: Boolean,
default: true
},
createdAt: {
type: Date,
default: new Date()
},
updatedAt: {
type: Date
}
},
{
versionKey: false
}
);
Course
let schema = new mongoose.Schema(
{
title: {
type: String,
required: true
},
description: {
type: String
},
shortDescription: {
type: String
},
duration: {
type: Duration
},
slug: {
type: String
},
slugs: {
type: [String]
},
program: {
content: {
type: String
},
file: {
type: String
}
},
audience: [String],
requirements: [String],
language: {
type: String,
enum: languages
},
price: {
type: Number
},
sections: [Section],
attachments: {
type: [Attachment]
},
tags: [String],
courseCategory: {
type: mongoose.Schema.Types.ObjectId,
ref: 'course-category',
required: true
},
trainer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'trainer'
},
trainingCompany: {
type: mongoose.Schema.Types.ObjectId,
ref: 'training-company'
},
status: {
type: String,
default: 'draft',
enum: courseStatus
},
path: {
type: String
},
cover: {
type: String,
required: true
},
duration: {
type: Number,
min: 1
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date
}
},
{ versionKey: false }
);
I am not sure if what I tried is gonna bring me what I want and I am getting this error concerning the $unwind operator:
MongoError: exception: Value at end of $unwind field path '$course'
must be an Array, but is a OID
Any kind of help will be really appreciated.
You can try below aggregation.
You are missing $lookup required to pull course document by joining on course object id from session document to id in the course document.
$project stage to keep the desired fields in the output.
Session.aggregate([
{
"$match": {
"$text": {
"$search": "web"
}
}
},
{
"$lookup": {
"from": "courses",
"localField": "course",
"foreignField": "_id",
"as": "course"
}
},
{
"$project": {
"course": 1,
"date": 1,
"address": 1,
"available": 1
}
}
])
Course is an array with one course document. You can use the $arrayElemAt to project the document.
"course": {"$arrayElemAt":["$course", 0]}

Push object in array with meteor 1.4?

I'm new in meteor and mongo I'd like to push one object in an array that is content in the other array. I'd like push giorni to cantieri. But I'd like push giorni in one specific cantieri. how can I make it? this my schema's collections.
`Clienti.Giorni = new SimpleSchema({
giorno: {
type: Date,
label: "giorno del lavoro"
},
oraPartenza: {
type: Date,
label: 'Giorno e ora partenza',
},
oraInizio: {
type: Date,
label: 'Giorno e ora inizio',
optional: true
},
oraFine: {
type: Date,
label: 'Giorno e ora fine',
optional: true
},
dipendenti: {
type: [Dipendenti]
}
});
Clienti.Cantieri = new SimpleSchema({
_id:{
type: String,
autoValue: function(){
var id = new Meteor.Collection.ObjectID();
return id._str
}
},
nome: {
type: String
},
luogo: {
type: String
},
inizio: {
type: Date
},
scadenza: {
type: Date
},
inCorso: {
type: Boolean,
defaultValue: false
},
createdAt: {
type: Date,
label: "Creato il",
autoValue: function() {
return new Date()
}
},
giorni: {
type: [Clienti.Giorni],
optional: true,
autoform: {
type: "hidden"
}
}
});
Clienti.ClienteSchema = new SimpleSchema({
nome: {
type: String,
label: "nome"
},
iva: {
type: String,
label: "Partita iva",
max: 16
},
referente: {
type: String,
label: "Nome persona di rifermento"
},
email: {
type: String,
label: "email"
},
indirizzo:{
type:String,
label: 'Indirizzo'
},
createdAt: {
type: Date,
label: "Creato il",
autoValue: function() {
return new Date()
},
autoform: {
type: "hidden"
}
},
cantieri: {
type: [Clienti.Cantieri],
optional: true,
autoform: {
type: "hidden"
}
}
});
Clienti.attachSchema( Clienti.ClienteSchema );`
I'm surprised you are not getting errors when trying to update your Clienti collection. According to the Simple Schema documentation in your schema definition, the type field should be a data type like String, Number, Boolean, Object or a constructor function like Date, and you can use any of these inside of square brackets to define it as an array of those data types (e.g., [String]).
So, one issue is that in your Clienti collection, you have defined your data type for cantieri as [Clienti.Cantieri]. This is not an acceptable data type. If I am understanding what you are trying to do correctly, you probably want the cantieri field definition in your Clienti collection to look like:
cantieri: {
type: [Object],
optional: true,
autoform: {
type: "hidden"
}
}
And after this, you need to add each cantieri field under this item using the format:
cantieri.$.nome: {
type: String
},
cantieri.$.luogo: {
type: String
}
You also want to add the giorni fields under the cantieri fields in the Clienti collection in the same format:
giorni: {
type: [Object],
optional: true,
autoform: {
type: "hidden"
}
},
giorni.$.giorno: {
type: Date,
label: "giorno del lavoro"
},
giorni.$.oraPartenza: {
type: Date,
label: 'Giorno e ora partenza',
}
Then, your method to update the database would look something like:
aggiungiGiorno: function(id, idC, doc,) {
Clienti.update({
_id: id,
"cantieri._id": idC
}, {
$push: {
"cantieri": doc
}
});
}
UPDATE:
If you want to combine your schemas as above, you should be able to also update the document using the query:
aggiungiGiorno: function(id, idC, doc,) {
Clienti.update({
_id: id,
"cantieri._id": idC
}, {
$push: {
"cantieri.$.giorni": doc
}
});
}

Resources