Multiple chained promises is sailsjs - sails.js

This is my first attempt at attempting to chain multiple finds together. The debug running shows that all the code executes correctly but there is a delay in receiving the users array back and therefore unable to present the data back.
The concept is a user may belong to multiple organizations, and there may be more than one user (other than the current user) that may belong to organizations. The function is trying to receive all users for all the organizations the current user belongs to.
getUserOrganizationsUsers: function (userId) {
var users = [];
sails.log.info('Getting the current users organizations [' + userId + ']');
return UserOrganization.find({ user_id: userId, deleted: null })
.populate('organization_id', { deleted: null })
.populate('user_id', { deleted: null })
.then(function (userorganization) {
return userorganization;
})
.then(function (userorgs) {
/* From all the organizations I want to get all the users from those organizations */
_.forEach(userorgs, function (userorg) {
UserOrganization.find({ organization_id: userorg.organization_id.id })
.populate('organization_id', { deleted: null })
.populate('user_id', { deleted: null })
.then(function (otherusrs) {
_.forEach(otherusrs, function (otherusr) {
sails.log.info('other userss each loop ');
var users = _.find(otherusrs, {id: otherusr.organization_id.id});
users.push(users);
})
})
});
return Q.when(employees);
})
},
Organization.js
module.exports = {
attributes: {
companyName: {
type: 'string',
required: true
},
Address: {
type: 'string'
},
ABN: {
type: 'string'
},
City: {
type: 'string'
},
contactNumber: {
type: 'string'
},
country: {
type: 'string'
},
icon: {
type: 'string'
},
users:
{ collection: 'userorganization',
via : 'user_id'
},
deleted: {
type: 'date',
defaultsTo: null
},
toJSON: function () {
var obj = this.toObject();
obj = _.pick(obj, Organization.publicFields);
return obj;
}
},
editableFields: [
'companyName',
'users'
// 'industries'
],
publicFields: [
'id',
'companyName',
'users'
],
};
UserOrganization.js
module.exports = {
attributes: {
organization_id: {
model : 'organization',
required: true
},
user_id: {
model: 'user',
required: true
},
organizationRole: {
type: 'string',
required: true
},
deleted: {
type: 'date',
defaultsTo: null
},
toJSON: function () {
var obj = this.toObject();
obj = _.pick(obj, UserOrganization.publicFields);
return obj;
}
},
editableFields: [
'organization_id',
'user_id',
'organizationRole',
],
publicFields: [
'id',
'organization_id',
'user_id',
'organizationRole'
],
};
and the user.js
var bcrypt = require('bcrypt-nodejs');
module.exports = {
attributes: {
email: {
type: 'email',
required: true,
unique: true
},
password: {
type: 'string',
required: true
},
firstName: {
type: 'string'
},
lastName: {
type: 'string'
},
verified: {
type: 'boolean',
defaultsTo: false
},
organizations:
{ collection: 'userorganization',
via : 'user_id'
}, deleted: {
type: 'date',
defaultsTo: null
},
fullName: function () {
return this.firstName + ' ' + this.lastName;
},
toJSON: function () {
var obj = this.toObject();
obj = _.pick(obj, User.publicFields);
return obj;
}
},
// TODO: Add initialFields
editableFields: [
'password',
'email',
'firstName',
'lastName',
'organizations'],
publicFields: [
'id',
'email',
'verified',
'firstName',
'lastName',
'fullName',
'organizations'
],
comparePassword: function (password, user, cb) {
bcrypt.compare(password, user.password, function (err, match) {
if(err) return cb(err);
cb(null, match);
})
},
beforeCreate: function (user, cb) {
bcrypt.genSalt(10, function (err, salt) {
bcrypt.hash(user.password, salt, function () {}, function (err, hash) {
if (err) {
sails.log.error(err);
return cb(err);
}
user.password = hash;
cb(null, user);
});
});
}
};

Okay, I think I understand what you're doing. It would be a lot simpler to have the User belong to an organization directly.
Anyways, if I understood your model structure correctly, something like this should work:
getUserOrganizationsUsers: function (userId) {
UserOrganization.find({ user_id: userId, deleted: null })
.then(function (userOrgs) {
// return array of organization IDs
return _.map(userOrgs, function(org){
return org.id;
});
})
.then(function (userOrgs) {
Organization.find(userOrgs)
.populate('users') // users is a collection of UserOrganization
.exec(function(err, orgs){ // lookup organizations
if(err) //handle error
else {
return _.flatten( // return basic array for next promise handler
_.map(orgs, function(org){ // for each organization
return _.map(org.users, function(user){ // return an array of user_ids
return user.user_id;
})
})
)
}
})
})
.then(function(allUserOrgs){
UserOrganization.find(allUserOrgs)
.populate('user_id')
.exec(function(err, userOrgsList){
return _.map(userOrgsList, function(user){
return user.user_id;
})
})
})
.then(function(users){
// users should be an array of all the users form allt he organizations that the current users belongs to
})
},

Related

How to update a user profile which has a property which is a ref in MongooseJS?

I have a User schema which has reference to a profile schema.
const UserSchema = new Schema(
{
_id: mongoose.Schema.Types.ObjectId,
email: {
....email props...
},
password: {
...password props...
},
profile: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Profile",
}],
},
);
const Profile = new Schema({
_user: {
type: Schema.Types.ObjectId, ref: 'User'
},
'displayName': {
type: String,
default: ''
},
'interestedActivities': ['Ping-pong'], <---- This bad boy/girl is an array
'memberSince': { type: Date, default: Date.now }
}
)
I'd like to create a route which can update the User properties AND the Profile properties in one shot—with a caveat one of the properties on the Profile model is an array!!!
I tried this....
handler
.use(auth)
.put((req, res, next) => {
emailValidator(req, res, next, 'email');
},
async (req, res, next) => {
await connectDB()
const {
profileDisplayName,
profileEmail,
interestedActivities } = req.body;
const update = {
email: profileEmail,
'profile.$.displayName': profileDisplayName,
'profile.$.interestedActivities': interestedActivities
}
const filter = { _id: req.user.id };
const updatedUser = await User.findOneAndUpdate(filter, update, { new: true })
try {
console.log("updatedUser ", updatedUser);
if (updatedUser) {
return res.status(200).send({
updatedUser,
msg: `You have updated your profile, good job!`
});
}
} catch (error) {
errorHandler(error, res)
}
})
export default handler;
My response is:
Status Code: 500 Internal Server Error
Cast to ObjectId failed for value "[
{
id: 'ae925393-0935-45da-93cb-7db509aedf20',
name: 'interestedActivities',
value: []
}
]" (type Array) at path "profile.$"
Does anyone know how I could also afford for the property which is an array?
Thank you in advance!

Mongoose - Update/Find Specific Object in an Array Not Working As Expected

I am following the docs without luck and am at a standstill while trying to update an object in an object in an array using MongoDB and Mongoose.
Here is my document:
{
fields: [
{ id: 603d63086db2db00ab09f50f, data: [Object] },
{ id: 603d63086db2db00ab09f510, data: [Object] },
{ id: 603d63086db2db00ab09f511, data: [Object] },
{ id: 603d63086db2db00ab09f512, data: [Object] },
{ id: 603d63086db2db00ab09f513, data: [Object] },
{ id: 603d63086db2db00ab09f514, data: [Object] },
{ id: 603d63086db2db00ab09f515, data: [Object] }
],
layouts: [],
_id: 603d631a6db2db00ab09f517,
bandId: '603d63146db2db00ab09f516',
eventType: 'private',
ownerId: '6039354906410800c14934c1',
__v: 0
}
I am trying to updateOne of the fields.data in the fields array. fields.data is an object as well.
I call my Express/Node Backend to this route.
//Update
router.put("/:id", async (req, res) => {
try {
let updating = await QuoteGenerator.updateOne(
{ _id: req.params.id, "fields.id": req.body.id },
{
"$set": {
"fields.$.data": req.body.data,
},
}
);
let item = await QuoteGenerator.findOne({ _id: req.params.id });
res.json({ success: "Item Updated.", item });
} catch (err) {
console.log(err);
res.json({ error: "Something went wrong when updating this item." });
}
});
Where req.body is:
{ id: '603d63086db2db00ab09f50f', data: { type: 1, rate: '200.30' } }
**Just in case it's helpful, here is what one of the fields objects looks like in the document,
{"id":"603d63086db2db00ab09f50f","data":{"type":1,"rate":300}}
I have even tried changing my route to find this document - which I have confirmed exists - Truly at a loss why it won't find the document.
Here is how I changed the above route to find the document.
//Update
router.put("/:id", async (req, res) => {
try {
let updating = await QuoteGenerator.find(
{ _id: req.params.id, "fields.id": req.body.id },
);
console.log(updating) //returns []
let item = await QuoteGenerator.findOne({ _id: req.params.id });
res.json({ success: "Item Updated.", item });
} catch (err) {
console.log(err);
res.json({ error: "Something went wrong when updating this item." });
}
});
The Model
//Create Schema - QG
const QuoteGeneratorSchema = new Schema({
bandId: {
type: String,
required: true,
},
ownerId: {
type: String,
required: true,
},
fields: {
type: Array,
default: defaultFields,
required: true,
},
eventType: {
type: String,
required: false,
},
layouts: {
type: Array,
required: false,
},
});
let QuoteGenerator = mongoose.model("QuoteGenerator", QuoteGeneratorSchema);
module.exports = QuoteGenerator;
Any nudge in the right direction to replacing that data object with a new data object would be extremely helpful! Thanks!

Mongoose.populate() producing no change in the model

Listing Schema:
const mongoose = require('mongoose');
const listingSchema = new mongoose.Schema({
title: String,
name: String,
tel: String,
service: String,
description: String,
location: Object,
isAvailible: Boolean,
canTravel: Boolean,
distance: Number,
isPublic: { type: Boolean, default: true},
pro: { type: mongoose.Types.ObjectId, ref: 'User' }
}, { timestamps: true });
const Listing = mongoose.model('Listing', listingSchema);
module.exports = Listing;
Request to DB:
Listing.find({ 'title': { '$regex' : service, '$options' : 'i' } , isPublic: { $gte: true }}, async (err, listings) => {
if (err) { return next(err); }
await listings[0].populate('pro');
console.log(listings[0].pro);
res.render('search', {
title: 'Search',
listings: listings,
search: {
service: service,
zip: zip
}
});
});
Screenshot of console
I'm also curious what is the best way to populate an array of models, however, I can't even get it to populate one. Any thoughts?
can you please tye execPopulate() method
try below code
Listing.find({ 'title': { '$regex' : service, '$options' : 'i' } , isPublic: { $gte: true }}, async (err, listings) => {
if (err) { return next(err); }
const listing=await listings[0].populate('pro').execPopulate();
console.log(listing.pro);
res.render('search', {
title: 'Search',
listings: listing,
search: {
service: service,
zip: zip
}
});
});
assignment to constant variable before the populate may work like so:
Listing.find({ 'title': { '$regex' : service, '$options' : 'i' } , isPublic: { $gte: true }}, async (err, listings) => {
if (err) { return next(err); }
const listings = await listings[0].populate('pro');
console.log(listings.pro);
res.render('search', {
title: 'Search',
listings: listings,
search: {
service: service,
zip: zip
}
});
});

How can I overwrite the entire document, instead of just updating the fields?

How can I overwrite the entire document, instead of just updating the fields?
Here is the method I use right now but doesn't work:
updateFilmTitle: function(req, res) {
var id = req.params.id;
console.log(id);
filmTitleModel.findByIdAndUpdate(id, req.body, {
overwrite: true
}, {
new: true
}, (error, response) => {
if (error) {
res.json(error);
console.error(error);
return;
}
console.log("filmTitle form has been updated!");
res.json(response);
console.log(response);
});
},
here how my model looks like,
var venueSchema = new Schema({
ticketServiceRequired: { type: Boolean, required: true },
filmSettings: {
type: {
filmContactName: { type: String, required: true },
filmSeatingAmount: { type: Number, required: true },
filmMediaDelivery: { type: Array, required: true },
filmRentalFee: {
price: { type: Number, required: true },
type: { type: String, required: true },
},
}
},
});
new and overwrite both are options, so it should be this:
filmTitleModel.findByIdAndUpdate(id, req.body, {
overwrite : true,
new : true
}, (error, response) => { ... });

Eager Loading : How to disable specific fields of included table

I am trying to do Eager Loading in Sequelize with PostgreSQL where I need to find the Users which have a given specific Mail id or basically, i am performing find operation on Mail model while using include to include User model
UserModel :
module.exports = function (sequelize, Sequelize) {
var User = sequelize.define('User', {
userId: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
firstname: {
type: Sequelize.STRING,
require: true
},
lastname: {
type: Sequelize.STRING,
require: true
},
age: {
type: Sequelize.INTEGER,
require: true
},
phone: {
type: Sequelize.STRING,
require: true
},
location: {
type: Sequelize.STRING,
require: true
},
createdAt: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW
},
updatedAt: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW
}
});
return User;
};
MailModel :
module.exports = function (sequelize, Sequelize) {
var User = require('./User.js')(sequelize, Sequelize)
var Mail = sequelize.define('Mail', {
name: {
type: Sequelize.STRING,
require: true
}
});
Mail.belongsTo(User);
return Mail;
};
MailController :
var db = require('../services/db.js');
module.exports = {
create: function (req, res, next) {
var Mailm = db.MailModel;
var name = req.body;
try {
db.sequelize.sync().then(function () {
Mailm.create(name).then(function (found) {
return res.json({
success: true,
message: found.get({
plain: true
})
});
})
});
} catch (ex) {
res.json({
success: false,
exception: ex
});
return;
}
},
query: function (req, res, next) {
var Mailm = db.MailModel;
var Userm = db.UserModel;
var name = req.body;
var option = {};
option.where = name;
option.include = [{
model: Userm
}];
try {
Mailm.findAll(option).then(function (found) {
console.log(found);
return res.json({
success: true,
message: found
});
});
} catch (ex) {
res.json({
success: false,
exception: ex
});
return;
}
}
};
It is returning me the records of both User and Mail table in exactly the right way .
Output :
{
"success": true,
"message":[
{
"id": 2,
"name": "Mailb2",
"createdAt": "2015-07-30T07:32:51.807Z",
"updatedAt": "2015-07-30T07:32:51.807Z",
"UserUserId": 2,
"User":{
"userId": 2,
"firstname": "Prerna",
"lastname": "Jain",
"age": 20,
"phone": "9812123456",
"location": "Sirsa",
"createdAt": "2015-07-30T07:30:48.000Z",
"updatedAt": "2015-07-30T07:30:48.000Z"
}
}
]
}
But I want to disable createdAt and updatedAt fields of User table so that it does not give me these two fields in the output for User.
I have tried a lot as of how to do this but still in vain.Can anyone please help me out.
I bet this is coming late, add attribute/properties to your models called timestamps, it accepts a boolean as a value. For example:
module.exports = function (sequelize, Sequelize) {
var User = require('./User.js')(sequelize, Sequelize)
var Mail = sequelize.define('Mail', {
name: {
type: Sequelize.STRING,
require: true
}
},
{
// This does the magic
timestamps: false,
});
Mail.belongsTo(User);
return Mail;
};
Also, add it to the User model:
var User = sequelize.define('User', {
userId: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
firstname: {
type: Sequelize.STRING,
require: true
},
lastname: {
type: Sequelize.STRING,
require: true
},
age: {
type: Sequelize.INTEGER,
require: true
},
phone: {
type: Sequelize.STRING,
require: true
},
location: {
type: Sequelize.STRING,
require: true
},
createdAt: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW
},
updatedAt: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW
}
},
{
timestamps: false
});
return User;
};
You can use
Model.findAll({
attributes: { exclude: ['baz'] }
});
more examples with attributes - http://docs.sequelizejs.com/en/latest/docs/querying/#attributes