Sequelize join on where condition returned from first table or return object values in an array derrived from foreach coming up empty - javascript

I've been trying to figure out this for a while now so any help would be very much appreciated.
I have one table called Interaction that searches with the client user's id and returns all interactions where they are the target user. Then I want to return the names of those users who initiated the interaction through the User table.
I tried using include to join the User table but I can't get the user's names using the where clause because it is based on a value returned in the first search of the Interaction table and don't know if I can search on a value that isn't the primary key or how?
The closest I've gotten is to use foreach and add the users to an array but I can't get the array to return in my response, because outside of the loop it is empty. I've tried suggestions I've found but can't figure out how to return the array outside of the foreach, if this is the best option. I am sure it is something really stupid on my behalf. TIA.
This is my attempt at include function:
getInvited: (req, res, next) => {
var user = {}
user = req.user;
let usrId = user[0]['facebookUserId'];
var userObjArray = [];
Interaction.findAll({
where: {
targetUserId: usrId,
status: 'invited',
},
include: [{
model: User,
attributes: [
'firstName'
],
where: {
facebookUserId: IwantToJoinOnInteraction.userId // replace with working code?
}]
}).then(function (users) {
res.send(users);
}).catch(next);
}
Or my attempt at foreach:
getInvited: (req, res, next) => {
var user = {}
user = req.user;
let usrId = user[0]['facebookUserId'];
var userObjArray = [];
Interaction.findAll({
where: {
targetUserId: usrId,
status: 'invited',
}
}).then(function (interactions) {
interactions.forEach((interaction) => {
User.findOne({
where: {
facebookUserId: interaction.userId // this is the where clause I don't know how to add in my first attempt with include
},
attributes: ['firstName', 'facebookUserId']
}).then(function (user) {
userObjArray.push(user['dataValues']);
console.log(userObjArray); // on the last loop it contains everything I need
})
})
res.status(200).send(userObjArray); // empty
}).catch(next);
},

You have to wait for all promises before sending the response. Your code runs async. With the forEach you are calling User.findOne async but you don't wait for all User.findOne to finish. A convenient way to make this work is Promise.all. You can pass an array of promises and the returned promise resolves to an array of all the resolved promises.
Promise.all(interactions.map(interaction => User.findOne(...)))
.then(users => {
res.status(200).send(users.map(user => user.dataValues))
})
You could write this much more easy to read woth async/await
getInvited: async (req, res, next) => {
...
const interactions = await Interaction.findAll(...)
const users = await Promise.all(interactions.map(interaction => User.findOne(...)))
res.status(200).send(users.map(user => user.dataValues))
}

Related

How can I remove bookmarked posts of user (1) from user (2) tab after user (1) deletes his account?

After creating a node.js, express, mongoDb REST api for a social media web app with almost all basic social media actions (login, signup, add a post, delete a post, delete account, follow users ...),
I'm currently facing a problem, where after implementing bookmarking a post feature, I'm unable to come up with a solution to remove a bookmarked post from another user's bookmarked posts page, after the first user deletes his account. I'll provide my code below:
(P. S. Bookmarks is an array inside User model. I'd also like to mention the steps that I initially intended for the task:
Get current user by ID
Then get all posts created by this user, which returns an array, so I mapped it to get each Posts id
After that I fetched all users accross the app, and initially intended to compare the posts that live inside bookmarks array inside each user to the posts that the current user have created. Then I'd pull these same posts out of the bookmarks array from each of these users.
--> I think the logic that I've analyzed is maintainable, but it's just not working with me. This is the Code below:
export const deleteUser = async (req, res) => {
try {
let user = await User.findById(req.params.userId)
const userPosts = await Post.find({ creatorId: user._id })
const allUsers = await User.find()
const myPostsIds = userPosts.map((post) => post._id.toString())
//This is the section I've implemented for my task, but obviously
something isn't right
await Promise.all(
myPostsIds.forEach((id) =>
allUsers.map((user) => {
user.bookmarks.includes(id) &&
user.updateOne({ $pull: { bookmarks: id } })
})
)
)
await Post.deleteMany({ creatorId: user._id })
await user.remove()
res.status(200).json({
message: "Account has been deleted successfully!",
})
} catch (err) {
errorHandler(res, err)
}
}
As mentioned in my comments, the value you pass to Promise.all is no array of Promise/array of async functions.
The 2nd error is inside the (currently) forEach function at the .map() you are not returning anything in the map-call.
So this should do it:
// first convert all ids to a promise
await Promise.all(myPostsIds.map(id => new Promise(resolve => {
// during this, await every test and update
return Promise.all(allUsers.map(user => new Promise(resolve => {
// if it includes the id, cast the update and then resolve
if (user.bookmarks.includes(id)) {
// if found, resolve the promise for this user after the change
user.updateOne({ $pull: { bookmarks: id } }).then(resolve)
} else {
// resolve directly if not found.
resolve()
}
// when all users are done for this id, resolve the Promise for the given id
}))).then(resolve)
})))
An easier to read and shorter method would be:
for (const id of myPostIds) {
for (const user of allUsers) {
if (user.bookmarks && user.bookmarks.includes(id)) {
await user.updateOne({ $pull: { bookmarks: id } });
}
}
}

Mongoose: save() is not a function when using find() and atributing value to variable

This is the basic structure of the Schema I am working with using mongoose:
const User = {
uid: {
type: String
},
routes: {
type: Array
}
}
In my application there is a POST to /route, in which uid and a new route are provided as "body parameters". In order to add to the routes array, I wrote a code similar to this (the only diference is that I check if the route already exists):
var user = await User.find({uid: uid}) // user is found, as expected
user[0].routes.push(route //parameter)
user.save()
When a POST request is made, though, it throws an error:
TypeError: user.save is not a function
What am I doing wrong?
user in your code is an array of documents
so you'll have mongo documents inside that array
you can't do array.save, you've to do document.save
await user[0].save()
var user = await User.find({uid: uid}) // user is found, as expected
if (user && user.length) {
user[0].routes.push(route //parameter)
await user[0].save(); // save the 1st element of the object
}
if your query returns only 1 record better use https://mongoosejs.com/docs/api.html#model_Model.findOne
var user = await User.findOne({uid: uid}) // user is found, as expected
if (user) {
user.routes.push(route //parameter)
await user.save(); // save the 1st element of the object
}
if you need to find only one specific user you should use findOne function instead
User.findOne({uid: uid})
.then(
(user) => {
user[0].routes.push(route //parameter);
user.save();
},
(err) => {
console.error(err);
}
)
I think bulkSave() can be what you're looking for:
var user = await User.find({uid: uid}
enter code user[0].routes.push(route //parameter)
await User.bulkSave(user)

How do I return an array of documents using an array of users object ids in mongoose?

I am trying to create a simple back end blog api with user authentication and authorization. It is built with mongoose and express. In my userSchema, I have a property that is an array called "subscribedTo". Here, users can subscribe to different users to get their blogs. The subscribedTo array stores objectIDs of the users that wished to be subscribed too.
Here is my code:
router.get('/blogs', auth, async (req, res) => {
//auth middleware attaches user to the request obj
try {
let blogs = []
req.user.subscribedTo.forEach(async (id) => {
let ownersBlogs = await Blog.find({owner:id})
blogs = [...blogs, ...ownersBlogs]
console.log(blogs)//consoles desired output of users blogs
})
console.log(blogs)//runs first and returns []
res.send(blogs)
}catch(e){
res.status(500).send(e)
}
})
When I use postman for this route it returns [] which is understandable. I can't seem to res.send(blogs) even though the blogs variable returns correctly in the forEach function.
Is there a better way to do this?
You can use without loop like as bellow
Blog.find({ owner: { $in: req.user.subscribedTo } }, function (err, blogResult) {
if (err) {
response.send(err);
} else {
response.send(blogResult);
}
});
OR
send response after loop completed like as bellow
router.get('/blogs', auth, async (req, res) => {
//auth middleware attaches user to the request obj
try {
let blogs = []
let len = req.user.subscribedTo.length;
let i = 0;
if (len > 0) {
req.user.subscribedTo.forEach(async (id) => {
let ownersBlogs = await Blog.find({ owner: id })
blogs = [...blogs, ...ownersBlogs]
console.log(blogs)//consoles desired output of users blogs
i++;
if (i === len) {
//send response when loop reached at the end
res.send(blogs)
}
})
} else {
res.send(blogs);
}
} catch (e) {
res.status(500).send(e)
}
});
You can find all the documents without a foreach loop, use $in
Blog.find({owner:{$in:[array of ids]}});

How can i save results from mongoose query to a variable

I'm trying to save some objects into an array by looping through a list of songs in an album, looking for relevant songs and trying to save into array for later use. is there any way to achieve this?
I need some explanation using mongoose.
exports.playlistPlayer = function (req, res, next) {
Playlist.findById({
_id: req.body.playlist._id
}, (err, playlist) => {
var customAlbum = []; //This variable it's inside the same block i believe
playlist.songs.forEach(function (song) {
Song.findById({
_id: song.song_id
}, (err, songs) => {
var customSong = {
title: songs.title,
time: songs.time,
source: songs.source,
song_id: songs._id
}
customAlbum.push(customSong)
console.log(customAlbum) //it works here
});
});
console.log(customAlbum) //it returns an empty array here where i need the data
});
};
The problem is that the findById method is also asynchronous. I recommend you to learn about promises in javascript. One possible solution would be using the async/await feature from ES7:
// asynchronous function
exports.playlistPlayer = async (req, res, next) => {
// wait for the findById method promise to resolve
const playlist = await Playlist.findById({
_id: req.body.playlist._id
})
// wait for finding all songs in db whose id's are in
// the playlist.songs array
const songs = await Song.find({
_id: { $in: playlist.songs }
})
// create the customAlbum by using the map method to
// tramsform the song objects to the required form
const customAlbum = songs.map(song => ({
title: song.title,
time: song.time,
source: song.source,
song_id: song._id
}))
// and there you should now have your customAlbum array
console.log(customAlbum)
// now you can use it for example
// to return a response to the client:
// res.json(customAlbum)
}

how to retrieve and pass data from more than 1 Schema in a function

im newbie in expressjs and wondering how to retrieve and pass data from more than 1 schema in my controller.
here is the case, pretend i wanna open add_new_blog page and below is
the router;
router.get('/add_new_blog', BlogController.index);
well then in BlogController.index i need to retrieve Category and Tag Models.
const Category = require('models/categorySchema');
const Tag = require('models/tagSchema');
module.exports = {
index(req, res, next){
Category.find({});
Tag.find({});
// how to find/retrieve data from both Schema then i pass them to Views.
res.render('/blog/blogForm');
}
}
The Question is What the coding will look like to retrieve the data from both then pass it to the view?
You can use Promise.all(), get the two mongoose calls data and then render it.
const categoryFind = Category.find({}).exec(); // exec() returns a Promise.
const tagsFind = Tags.find({}).exec();
Promise.all(categoryFind, tagsFind).then((values) => {
res.render('/blog/blogForm', { categories: values[0], tags: values[1] });
});
Notice that I render inside the callback, that is because mongoose calls are asynchronous. Otherwise you will be rendering before the queries have completed.
That is the same as:
Category.find({}, (err, catData) => {
Tags.find({}, (err, tagsData) => {
res.render('/blog/blogForm', { categories: catsData, tags: tagsData });
}
}

Categories

Resources