MongoDB Creating document and on success create another document - node.js

I am working on application using Node.js and MongoDB. I have a particular use case wherein I create a new user and on success add the user's ObjectId into another collection called 'cities' by fetching the user's city if not existing or create a new one and append User's ObjectId to Subscriber's List field of the city document.
The Schemas look like below:
CitiesSchema:
var CitiesSchema = new Schema({
City:{
type: String
},
SubscribersList: [{type: Schema.ObjectId}]
});
User Schema:
var UsersSchema = new Schema({
emailId: {
type: String,
required: 'Mandatory field'
},
city: {
type: String
},
subscribedOn: {
type: Date,
default: Date.now
},
lastEmailSent: {
type: Date,
default: null
},
isActive: {
type: Boolean,
default: true
}
});
Please let me know how I can tackle this in the cleanest way possible or is there any design pattern I need to follow ?

You can use the then notation to continue processing after you have created your User. Like this
UserSchema.create({
emailId: 'email#exmaple.com',
city: 'Rome'
})
.then(user => {
// For example if you want to push to the city of the user
CityScema.update({
City: user.city
}, {
$push: {
SubscribersList: user._id
}
}).then(() => {
res.sendStatus(200);
}).catch(err => res.status(500).send(err));
}).catch(err => {
// Handle your errors here
console.error(err);
});
Make sure you check the Mongoose docs on Models for more information

Related

Mongoose query to filter the collection based on nested field

We want to see if mongoose could do heavy lifting to get the user's role for a given Organization Name and UserId.
This can be done easily by first finding out the organization data and use javascript to filter out based on User's ID. But i think it would give better performance if the query can do it instead of doing outside the mongo collection.
What we want to try something like below, but it is not giving the role of the user correctly.
Query (not working)
public async getUserOrgRole(orgName: string, userId) {
const result = await this.organizationModel
.findOne({ name: orgName, 'orgMembers.userId': userId })
.select('orgMembers.role')
.exec();
if (result) {
const orgMember = _.get(result, 'orgMembers');
return orgMember[0].role;
}
return null;
}
Query (working but we want the above query to work instead of pulling entire document)
public async getUserOrgRole(orgName: string, userId) {
const org = await this.organizationModel.findOne({ name: orgName })
if (!org)
return null;
const userInOrg = org.orgMembers.find(om => om.userId === userId)
console.log('--getUserOrgRole', userInOrg)
if (userInOrg)
return userInOrg.role
return null;
}
Schema
const UserOrgSchema = new Schema({
role: { type: Schema.Types.String, enum: ['MEMBER', 'OWNER', 'ADMIN'], default: 'MEMBER' },
inactive: { type: Schema.Types.Boolean, default: false },
userId: { type: Schema.Types.String, required: true },
});
const OrganizationSchema = new Schema({
name: { type: Schema.Types.String, unique: true },
picture: { type: Schema.Types.String },
orgMembers: { type: [UserOrgSchema] },
createdAt: { type: Schema.Types.Date, default: Date.now },
updatedAt: { type: Schema.Types.Date, default: Date.now },
});
You almost got it right. The reason why your attempt does not quite work is explained by Sunil. No matter what filters you apply to .find(), it will always return the whole document. If you want to select specific subdocuments, you need to do that using an additional select operator. This ought to work:
const result = await this.organizationModel
.findOne({ name: orgName, "orgMembers.userId": userId })
.select({
orgMembers: {
$elemMatch: {
userId,
},
},
})
.select("orgMembers.role")
.exec();
Note the use of $elemMatch! It does exactly what you wanted - filters the subdocuments by selecting only the ones that match the provided filter.

How to insert a user's id into a separate collection in Mongodb?

I have two models, one that holds the user and one that holds their text(Blog). I simply want to append the user's id every time they post their text so I can later go ahead and query for my own use.
This is what I have tried doing but nothing happens. Do we need to use req.body.user_id? Why is req.session.user not working(not being added along with the new instance of Blog on save) when I intentionally made it carry the user's id
router.route("/blog/add").post((req, res) => {
// Retrieve the uid form the user
// Save uid to the db along with the whole Blog instance
let blog = new Blog({
user_blog: req.body.user_blog,
createdAt: req.body.createdAt,
date: req.body.date,
user_id: req.session.user //Not working even though it holds the id already
});
blog.save()
.then(blog => {
res.status(200).json({
message: "Blog saved succeccfully"
});
})
.catch(err => {
res.status(400).send("Failed to save users blog");
});
});
Schema Blog
let Blog = new Schema({
user_blog: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now
},
date: {
type: String,
default: moment(new Date()).format("MMM Do YY, HH:mm")
},
user_id: {
type: mongoose.Schema.Types.ObjectId // This will be the users own id
}
}, { collection: "users_blogs" });
The user_id is not being appended, why?

It is possible to pull elements from a referred objects' array using mongoose?

I have 2 mongo schemas related one with the other using ObjectId:
var User = new Schema({
username: {
type:String,
unique: true
},
password: {
type:String
},
verified: {
type: Boolean,
default: false
},
lastActivity:{
type:Date,
default:Date.now
}
});
And a watitingRoom schema with lists all the users:
var WaitingRoom = new Schema({
lastActivity:{
type:Date,
default:Date.now
},
clients: [{
type : mongoose.Schema.ObjectId,
ref: 'User'
}],
videocalls: [{
type: mongoose.Schema.ObjectId,
ref:'VideoCall'
}]
});
So, I want to 'refresh' my clients array pulling all the clients which a lastActivity less than the current time. I tried it by using the $pull tool present in mongoose. After googling and mixing different examples I tried things like:
WaitingRoom.findOneAndUpdate({}, { lastActivity: new Date(),
$pull : {clients : {"clients.lastActivity": { $lt: new Date() }}}
}, options)
.populate("clients")
.exec( function(error, waitingRoom) {
if (err) { return res.status(500).send({ msg: err.message }); }
})
Which finds the unique waiting room, updates the lastActivity field and tries to pull all the clients that has a clients.lastActivity less than the current date.
(Obviously this snipped code doesn't work)
The problem is that I didn't find any documentation or example that explains if it is possible to pull elements from a referred ObjectId schema using a nested condition clients.lastActivity
You need to first find the ids from User database and then need to $pull them from the WaitingRoom database
User.find({ lastActivity: new Date() }).then((users) => {
const ids = []
users.map((user) => {
ids.push(user._id)
})
WaitingRoom.update({}, { $pull: { clients: ids }}, { multi: true }).then(() => {
console.log('removed')
})
})

Save array of ObjectId in Schema

I have a model called Shop whos schema looks like this:
'use strict';
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var ShopSchema = new Schema({
name: { type: String, required: true },
address: { type: String, required: true },
description: String,
stock: { type: Number, default: 100 },
latitude: { type: Number, required: true },
longitude: { type: Number, required: true },
image: String,
link: String,
tags: [{ type: Schema.ObjectId, ref: 'Tag' }],
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
});
module.exports = mongoose.model('Shop', ShopSchema);
I want to use the array tags to reference to another model via ObjectId obviously. This set up works fine when I add ids into the property via db.shops.update({...}, {$set: {tags: ...}}) and the ids get set properly. But when I try to do it via the Express.js controller assigned to the model, nothing gets updated and there even is no error message. Here is update function in the controller:
// Updates an existing shop in the DB.
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Shop.findById(req.params.id, function (err, shop) {
if (err) { return handleError(res, err); }
if(!shop) { return res.send(404); }
var updated = _.merge(shop, req.body);
shop.updatedAt = new Date();
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, shop);
});
});
};
This works perfect for any other properties of the Shop model but just not for the tags. I also tried to set the type of the tags to string, but that didn't help.
I guess I am missing something about saving arrays in Mongoose?
It looks like the issue is _.merge() cannot handle merging arrays properly, which is the tags array in your case. A workaround would be adding explicit assignment of tags array after the merge, if it is ok to overwrite the existing tags.
var updated = _.merge(shop, req.body);
if (req.body.tags) {
updated.tags = req.body.tags;
}
Hope this helps.. If the workaround is not sufficient you may visit lodash forums.

Mongoose - Better solution with appending additional information

I have two Schemas:
var ProgramSchema = new Schema({
active: Boolean,
name: String,
...
});
var UserSchema = new Schema({
username: String,
email: { type: String, lowercase: true },
...
partnerships: [{
program: { type: Schema.Types.ObjectId, ref: 'Program' },
status: { type: Number, default: 0 },
log: [{
status: { type: Number },
time: { type: Date, default: Date.now() },
comment: { type: String },
user: { type: Schema.Types.ObjectId, ref: 'User' }
}]
}]
});
Now I want to get all Program docs, but also append 'status' to each doc, to return if the program is already in a partnership with the logged in user.
My solution looks like this:
Program.find({active: true}, 'name owner image user.payments', function (err, p) {
if(err) { return handleError(res, err); }
})
.sort({_id: -1})
.exec(function(err, programs){
if(err) { return handleError(res, err); }
programs = _.map(programs, function(program){
var partner = _.find(req.user.partnerships, { program: program._id });
var status = 0;
if(partner){
status = partner.status;
}
program['partnership'] = status;
return program;
});
res.json(200, programs);
});
The req.user object contains all information about the logged in user, including the partnerships array.
To get this solution to work, I have to append
partnership: Schema.Types.Mixed
to the ProgramSchema.
This looks a bit messy and thats why I am asking for help. What do you think?
When you want to freely modify the result of a Mongoose query, add lean() to the query chain so that the docs (programs in this case) are plain JavaScript objects instead of Mongoose doc instances.
Program.find({active: true}, 'name owner image user.payments')
.lean() // <= Here
.sort({_id: -1})
.exec(function(err, programs){ ...
Then you can remove partnership from your schema definition. Your query will also execute faster.

Resources