Populate nested collections with Sails Js and async - javascript

Im trying to query a deep association using sails js. In my example i have a one to many association between room and users and also a one to many association between user and pictures. My concern is if this is the correct way to assign pictures to users and also how can i map each collection of pictures to the corresponding user. Thanks.
// RoomController
show: function(req, res) {
async.auto({
room: function(cb) {
var slug = req.param('slug');
Room
.findOneBySlug(slug)
.populate('users', { where: { is_active: true, is_online: true } })
.populate('messages', { limit: 30, sort: 'createdAt DESC' })
.exec(cb)
},
pictures: ['room', function(cb, results) {
if (!results.room) return res.badRequest('User not found.');
Picture
.find({user: _.pluck(results.room.users, 'id')})
.where({'is_primary': true})
.exec(cb);
}],
map: ['pictures', function(cb, results) {
var room = results.room.toJSON();
room.users = room.users.map(function(user) {
user.pictures = results.pictures;
return user;
});
return cb(null, room);
}]
},
function finish(error, results) {
if (error) return res.serverError(error);
console.log(results.map)
}
);
}

Related

How to filter data from mongo collection subarray with subarray data of other collection

Baiscally making a node.js, mongodb add friends functionality where having the option of list user to add in friends list, sent friends request, accept friends request, delete friends request, block friends request.
Register Collection
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
let Register = new Schema(
First_Name:{
type: String,
required: true
},
Last_Name: {
type: String
},
Email: {
type: String,
unique: true,
lowercase: true,
required: true
},
Friends:[{type: String}],
});
module.exports = mongoose.model('Register', Register);
Friends Collection
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
var ObjectId = require('mongodb').ObjectID;
let Friends = new Schema({
Requester: {
type: ObjectId,
required: true
},
Recipients: [{Recipient:{type:ObjectId},Status:{type:Number}}],
});
module.exports = mongoose.model('Friends', Friends);
Inside Node.js Post API
var Register = require('../models/register.model');
var Friends =require('../models/friends.model');
router.post('/getdata',function(req,res)
{
let Email="example#example.com";
Register.findOne({ Email : Emails }, function(err, user) {
Friends.findOne({ Requester :user._id }, function(err, user1) {
Register.find({$and:[{Friends:{$nin:[user._id]}},{_id:{$ne:user1.Recipients.Recipient}}]},function(err, user2) {
console.log("user2",user2);
//Here User2 data is not coming
//How to get data so can able to list user that is not added yet in FriendList
//Mainly user1.Recipients.Recipient this is not working because //Recipients is array so how can match all data with array, if i am //using loop then find return data scope ends on inside find closing //braces only.
//Any suggestion
});
});
});
So if I have it correct, you want to do the following:
Find a registration based on a given email
Find the friends related to this user
Find registrations that are not yet in the friend list of the user
Also, given what you've typed, I'm assuming A can be the friend of B, but that doesn't mean B is the friend of A.
While the data structure you currently have may not be optimal for this, I'll show you the proper queries for this:
var Register = require('../models/register.model');
var Friends =require('../models/friends.model');
router.post('/getdata',function(req,res) {
const email = "example#example.com";
Register.findOne({ Email: email }, function(err, user) {
if (err) {
console.error(err);
return;
}
Friends.findOne({ Requester: user._id }, function(err, friend) {
if (err) {
console.error(err);
return;
}
const reciptientIds = friend.Recipients.map(function (recipient) {
return recipient.Recipient.toString();
});
Register.find({Friends: { $ne: user._id }, {_id: { $nin: recipientIds }}, function(err, notFriendedUsers) {
if (err) {
console.error(err);
return;
}
console.log(notFriendedUsers);
});
});
});
});
P.S. This "callback hell" can be easily reduced using promises or await/defer
Finally able to solve it, below is the solution
var Register = require('../models/register.model');
var Friends =require('../models/friends.model');
router.post('/getdata',function(req,res)
{
let Emails="example#example.com";
Register.findOne({$and:[{ Email : Emails}] }, function(err, user) {
if (err) {
console.error(err);
return;
}
Friends
.findOne({ Requester: user._id },
{ _id: 0} )
.sort({ Recipients: 1 })
.select( 'Recipients' )
.exec(function(err, docs){
docs = docs.Recipients.map(function(doc) {
return doc.Recipient; });
if(err){
res.json(err)
} else {
console.log(docs,"docs");
Register.find({$and:[{Friends: { $ne: user._id }},{_id: { $nin: docs }},{_id:{$ne:user._id}}]}, function(err, notFriendedUsers) {
if (err) {
console.error(err);
return;
}
console.log(notFriendedUsers);
});
}
})
});

Using Multiple FindOne in Mongodb

I am trying to extend the amount of fields that our API is returning. Right now the API is returning the student info by using find, as well as adding some information of the projects by getting the student info and using findOne to get the info about the project that the student is currently registered to.
I am trying to add some information about the course by using the same logic that I used to get the project information.
So I used the same findOne function that I was using for Projects and my logic is the following.
I created a variable where I can save the courseID and then I will put the contents of that variable in the temp object that sending in a json file.
If I comment out the what I added, the code works perfectly and it returns all the students that I require. However, when I make the additional findOne to get information about the course, it stops returning anything but "{}"
I am going to put a comment on the lines of code that I added, to make it easier to find.
Any sort of help will be highly appreciated!
User.find({
isEnrolled: true,
course: {
$ne: null
}
},
'email pantherID firstName lastName project course',
function(err, users) {
console.log("err, users", err, users);
if (err) {
return res.send(err);
} else if (users) {
var userPromises = [];
users.map(function(user) {
userPromises.push(new Promise(function(resolve, reject) {
///////// Added Code START///////
var courseID;
Course.findOne({
fullName: user.course
}, function(err, course) {
console.log("err, course", err, course);
if (err) {
reject('')
}
courseID = course ? course._id : null
//console.log(tempObj)
resolve(tempObj)
}),
///// ADDED CODE END //////
Project.findOne({
title: user.project
}, function(err, proj) {
console.log("err, proj", err, proj);
if (err) {
reject('')
}
//Course ID, Semester, Semester ID
//map to custom object for MJ
var tempObj = {
email: user.email,
id: user.pantherID,
firstName: user.firstName,
lastName: user.lastName,
middle: null,
valid: true,
projectTitle: user.project,
projectId: proj ? proj._id : null,
course: user.course,
courseId: courseID
}
//console.log(tempObj)
resolve(tempObj)
})
}))
})
//async wait and set
Promise.all(userPromises).then(function(results) {
res.json(results)
}).catch(function(err) {
res.send(err)
})
}
})
using promise could be bit tedious, try using async, this is how i would have done it.
// Make sure User, Course & Project models are required.
const async = require('async');
let getUsers = (cb) => {
Users.find({
isEnrolled: true,
course: {
$ne: null
}
}, 'email pantherID firstName lastName project course', (err, users) => {
if (!err) {
cb(null, users);
} else {
cb(err);
}
});
};
let findCourse = (users, cb) => {
async.each(users, (user, ecb) => {
Project.findOne({title: user.project})
.exec((err, project) => {
if (!err) {
users[users.indexOf(user)].projectId = project._id;
ecb();
} else {
ecb(err);
}
});
}, (err) => {
if (!err) {
cb(null, users);
} else {
cb(err);
}
});
};
let findProject = (users, cb) => {
async.each(users, (user, ecb) => {
Course.findOne({fullName: user.course})
.exec((err, course) => {
if (!err) {
users[users.indexOf(user)].courseId = course._id;
ecb();
} else {
ecb(err);
}
});
}, (err) => {
if (!err) {
cb(null, users);
} else {
cb(err);
}
});
};
// This part of the code belongs at the route scope
async.waterfall([
getUsers,
findCourse,
findProject
], (err, result) => {
if (!err) {
res.send(result);
} else {
return res.send(err);
}
});
Hope this gives better insight on how you could go about with multiple IO transactions on the same request.

Using async.js for deep populating sails.js

I have a big issue with my function in sails.js (v12). I'm trying to get all userDetail using async (v2.3) for deep populating my user info:
UserController.js:
userDetail: function (req, res) {
var currentUserID = authToken.getUserIDFromToken(req);
async.auto({
//Find the User
user: function (cb) {
User
.findOne({ id: req.params.id })
.populate('userFollowing')
.populate('userFollower')
.populate('trips', { sort: 'createdAt DESC' })
.exec(function (err, foundedUser) {
if (err) {
return res.negotiate(err);
}
if (!foundedUser) {
return res.badRequest();
}
// console.log('foundedUser :', foundedUser);
cb(null, foundedUser);
});
},
//Find me
me: function (cb) {
User
.findOne({ id: currentUserID })
.populate('myLikedTrips')
.populate('userFollowing')
.exec(function (err, user) {
var likedTripIDs = _.pluck(user.myLikedTrips, 'id');
var followingUserIDs = _.pluck(user.userFollowing, 'id');
cb(null, { likedTripIDs, followingUserIDs });
});
},
populatedTrip: ['user', function (results, cb) {
Trip.find({ id: _.pluck(results.user.trips, 'id') })
.populate('comments')
.populate('likes')
.exec(function (err, tripsResults) {
if (err) {
return res.negotiate(err);
}
if (!tripsResults) {
return res.badRequest();
}
cb(null, _.indexBy(tripsResults, 'id'));
});
}],
isLiked: ['populatedTrip', 'me', 'user', function (results, cb) {
var me = results.me;
async.map(results.user.trips, function (trip, callback) {
trip = results.populatedTrip[trip.id];
if (_.contains(me.likedTripIDs, trip.id)) {
trip.hasLiked = true;
} else {
trip.hasLiked = false;
}
callback(null, trip);
}, function (err, isLikedTrip) {
if (err) {
return res.negotiate(err);
}
cb(null, isLikedTrip);
});
}]
},
function finish(err, data) {
if (err) {
console.log('err = ', err);
return res.serverError(err);
}
var userFinal = data.user;
//userFinal.trips = data.isLiked;
userFinal.trips = "test";
return res.json(userFinal);
}
);
},
I tried almost everthing to get this fix but nothing is working...
I am able to get my array of trips(data.isLiked) but I couldn't get my userFInal trips.
I try to set string value on the userFinal.trips:
JSON response
{
"trips": [], // <-- my pb is here !!
"userFollower": [
{
"user": "5777fce1eeef472a1d69bafb",
"follower": "57e44a8997974abc646b29ca",
"id": "57efa5cf605b94666aca0f11"
}
],
"userFollowing": [
{
"user": "57e44a8997974abc646b29ca",
"follower": "5777fce1eeef472a1d69bafb",
"id": "5882099b9c0c9543706d74f6"
}
],
"email": "test2#test.com",
"userName": "dany",
"isPrivate": false,
"bio": "Hello",
"id": "5777fce1eeef472a1d69bafb"
}
Question
How should I do to get my array of trips (isLiked) paste to my user trips array?
Why my results is not what I'm expecting to have?
Thank you for your answers.
Use .toJSON() before overwriting any association in model.
Otherwise default toJSON implementation overrides any changes made to model associated data.
var userFinal = data.user.toJSON(); // Use of toJSON
userFinal.trips = data.isLiked;
return res.json(userFinal);
On another note, use JS .map or _.map in place of async.map as there is not asynchronous operation in inside function. Otherwise you may face RangeError: Maximum call stack size exceeded issue.
Also, it might be better to return any response from final callback only. (Remove res.negotiate, res.badRequest from async.auto's first argument). It allows to make response method terminal

Node.JS / Express: Connecting two endpoints together

So I'm working on a project in node and Express, and while I've gotten a bit of experience playing around with it already and through a few other frameworks, I'm still trying to figure out the best way to connect two of them together.
I have set up models and databases using POSTGRES to hold the ids and connecting tables.
Here is what I have so far:
GET User
app.get('/api/users/:id', function(req, res) {
User.findOne({ where: { phone: req.params.id }}).then(function(user) {
if (!user) {
//////// Create a user
var newUser = User.build({ phone: req.params.id});
newUser.save().then(function(user) {
var json = userJson(user);
res.json(json)
}).catch(function(err) {
res.json(err);
});
} else {
//////// Existing user
var json = userJson(user);
json.events = []
Event.findAll({ where: { user_id: user.id }}).then(function(events) {
for( var i in events) {
json.events.push({
id: events[i].dataValues['id'],
hosting: events[i].dataValues['hosting'],
attending: attending[i].dataValues['attending'],
invites: events[i].dataValues['invites']
})
}
res.json(json)
})
}
})
});
GET Events
app.get('/api/events/:id', function(req, res) {
Event.findOne({ where: { id: req.params.id }}).then(function(event) {
// console.log('-------------',event);
var item = {
id: event.dataValues['id'],
event_name: event.dataValues['event_name'],
event_at: event.dataValues['event_at'],
address: event.dataValues['address'],
description: event.dataValues['description'],
image: event.dataValues['image'],
categories: []
}
res.json(item);
})
});
POST Events
app.post('/api/events', function(req, res) {
var newEvent = Event.build({
user_id: req.body.user_id,
event_name: req.body.event_name,
event_at: req.body.event_at,
address: req.body.address,
description: req.body.description,
image: req.body.image
});
newEvent.save().then(function(event) {
res.redirect('/api/events/'+event.id);
}).catch(function(err) {
// console.log(err);
return res.json({error: 'Error adding new event'});
})
});
Model: event_invite.js
var bcrypt = require('bcrypt-nodejs');
module.exports = function(sequelize, DataTypes) {
return sequelize.define('event_hostings', {
user_id : DataTypes.INTEGER,
event_id : DataTypes.INTEGER
}
);
}
I know that there is a way to connect the two together, but I'm trying to figure out the best way to interconnect the two when a USER creates an EVENT, and able to have it connect back to the user and recognize them as the creator.
Any advice or pointing me in the right direction would be greatly appreciated! Thank you in advance!

Sequelize: Issue with join table and querying for results

Problem exists in the INDEX function, on var friends. I want to set it to an empty array initially so that I can push individual user objects from a friendship join table of users to said array. This is because the signed in user could exist on the friendship join table under the userID column or the friendID column, depending on who initiated the friendship.
The problem is due to the async nature, when I call console.log(friends) the querying of the database is milliseconds behind, so it is still logging an empty array. I want the array to be full of user objects who are *NOT the current user, which in essence would be a "myfriends" index page serving up JSON from the backend API. Keep in mind the fact I'm trying to serve Json, so all the friend user objects HAVE to end up in one single array.
Thanks!
var user = require("../../models/index").user;
var friendship = require("../../models/index").friendship;
var friendshipController = {
index: function(req, res) {
var friends = [];
var request = friendship.findAll({
where: {
status: "pending",
$and: {
$or: [
{
userId: req.session.user.id
},
{
friendId: req.session.user.id
}
]
}
}
}).then(function(friendships) {
res.json(friendships)
var friends = []
friendships.forEach(function(friendship) {
if (req.session.user.id === friendship.userId) {
user.findById(friendship.friendId).then(function(user) {
friends.push(user);
})
} else {
user.findById(friendship.userId).then(function(user) {
friends.push(user);
})
}
})
console.log(friends);
})
},
create: function(req, res) {
friendship.create({
userId: req.session.user.id,
friendId: 3,
}).then(function() {
res.redirect("/")
})
},
destroy: function(req, res) {
friendship.findAll().then(function(f) {
f.forEach(function(fn) {
fn.destroy()
})
})
}
}
module.exports = friendshipController;
SOLUTION: For each friendship, push each adjacent friend's id of the logged in user to an array...THEN run a query to find all users in that array (a feature of sequelize apparently), then respond with that json data.
index: function(req, res) {
var friends = [];
var query = friendship.findAll({
where: {
status: "pending",
$and: {
$or: [
{
userId: req.session.user.id
},
{
friendId: req.session.user.id
}
]
}
}
}).then(function(friendships) {
var friendIds = [];
friendships.forEach(function(friendship) {
if (req.session.user.id === friendship.userId) {
friendIds.push(friendship.friendId);
}
else {
friendIds.push(friendship.userId);
}
});
user.findAll({
where: {
id: friendIds
}
}).then(function(users) {
res.json(users);
})
})
}

Categories

Resources