Multiple async queries in nodejs (mongoose) - javascript

I am a nodejs newbie. I have two simple models, User and Story. Here is what I want to do:
I want to retrieve all stories that have {status:"public"} and store it in an array called retrievedStories.
Then for each story I want to use its "user" field (which contains the object id of the user) to lookup the name of the user from User
Then add a new key in each element of retrievedStories called authorName with the name of the user.
Here are the models:
const UserSchema = new mongoose.Schema({
googleId: {
type: String,
required: true
},
displayName: {
type: String,
required: true
},
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
image: {
type: String,
},
createdAt: {
type:Date,
default: Date.now()
}
})
const StorySchema = new mongoose.Schema({
title: {
type: String,
required: true,
trim: true
},
body: {
type: String,
required: true
},
status: {
type: String,
default: 'public',
enum: ['public', 'private']
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
createdAt: {
type:Date,
default: Date.now()
}
})
And here is what I tried, but doesn't work. The stories are retrieved but the authorName is not added. Any help (possibly a better way to do this?) will be highly appreciated!
router.get('/',async (req,res)=>{
try {
const retrievedStories = await Story.find(
{status: "public"}
)
await Promise.all(retrievedStories.map(async (story) =>{
const author = await User.findById(story.user)
story.authorName = author.displayName
}))
return res.json(retrievedStories)
} catch (error) {
console.log(error)
}
})

You can simplify your query by using populate to retrieve User's data:
router.get('/', async (req, res) => {
try {
const retrievedStories = await Story.find({ status: 'public' })
.populate('user')
.exec();
return res.json(retrievedStories);
} catch (error) {
console.log(error);
}
});
You can then access User's displayName data on each Story by accessing story.user.displayName.
For more information on query population see the official docs.

Related

How do I update a subdocument with mongoose?

I'm trying to update a subdocument using mongoose. The document that the subdocument exists on is structured like so :
const UserSchema = new mongoose.Schema({
email: {
type: String,
unique: true,
lowercase: true,
required: true,
match: [/.+#.+\..+/, "Please enter a valid email."]
},
password: {
type: String,
required: true
},
characters: [characterSchema]
})
The actual subdocument looks like this:
const mongoose = require("mongoose")
const CharacterSchema = new mongoose.Schema({
playerName: {
type: String,
unique: true,
trim: true,
lowercase: true
},
playerLevel: Number,
highestSlot: Number,
numberOfSlots: [{
id: Number,
slots: Number
}]
})
module.exports = CharacterSchema
I've been trying to update the subdocument like so:
updateCharacter: async function (req, res) {
try {
const user = await db.Users.findOneAndUpdate(
{ _id: req.user._id, "characters.name": req.params.characterName },
{ $set: { "characters.$": req.body } }
)
res.json(user)
} catch (error) {
console.log(error)
}
}
I get back a successful response but nothing happens to the subdocument and I have no idea what I need to change.

CastError: Cast to [undefined] failed for value "[]" (type string) at path "comments.undefined"

I'm quite new to node and mongoose. I'm trying to do a project using them, but i'm running into an error while trying to populate. The comment is saved to the Comment schema perfectly, but throws an error when i reference it Organization Schema.Please advise me on what i'm doing wrong. Any form of assistance will be appreciated.
// Post route for comment(on the Organization's profile page)
router.post('/comment/:id', ensureAuthenticated,(req, res) =>{
let id = req.params.id;
console.log(mongoose.Types.ObjectId.isValid(id))
const commentObject = new Comment({
sender: 'Fred kimani',
commentBody: req.body.commentBody
})
console.log(commentObject);
commentObject.save((err, result) =>{
if(err){console.log(err)}
else{
Organization.findByIdAndUpdate(id, {$push: {comments: result}}, {upsert: true}, (err, organization) =>{
if(err){console.log(err)}
else{
console.log('======Comments====')
}
})
res.redirect('/users/organizationprofilepage/:id')
}
})
});
//Organization Schema
const mongoose = require('mongoose');
const OrganizationSchema = new mongoose.Schema({
organization_name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
},
category: {
type: String,
required: true
},
isApproved: {
type: Boolean,
default: false
},
image:{
type:String,
required:true
},
description: {
type: String,
required: true,
},
comments: [{
type: mongoose.Types.ObjectId,
ref: 'Comment'
}],
},
//{ typeKey: '$type' }
);
OrganizationSchema.statics.getOrganizations = async function () {
try {
const organizations = await this.find();
return organizations;
} catch (error) {
throw error;
}
}
//defines the layout of the db schema
const Organization = mongoose.model('0rganization', OrganizationSchema);
module.exports = Organization;
//Comment schema
const mongoose = require('mongoose');
const CommentSchema = mongoose.Schema({
sender: {
type: String,
},
commentBody: {
type: String,
required: false,
},
date: {
type: Date,
default: Date.now
},
})
CommentSchema.statics.getComments= async function () {
try {
const comments = await this.find();
return comments ;
} catch (error) {
throw error;
}
}
const Comment= mongoose.model('Comment', CommentSchema);
module.exports = Comment;
Try to change the comments type to mongoose.Schema.Types.ObjectId:
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment',
},
],
Try to push the new commend _id into the Organization object after its creation, not the whole object:
commentObject.save((err, result) => {
if (err) {
console.log(err);
} else {
Organization.findByIdAndUpdate(
id,
{ $push: { comments: result._id } }, // <- Change this line
{ upsert: true },
(err, organization) => { }
);
...
}
});
If you just updated the schema you will need to make sure all of the comments are following the new form you created, when you save it will attempt to validate them, that is why an updateOne will work but not await save()

How to write mongoose query to combine data from two model's?

Technology: MongoDB, ExpressJS
I have 3 schema
userSchema:
userSchema = {
name: {type: String},
password: {type: String},
email: {type: String},
friends: {type: [mongoose.Types.ObjectId]}
}
textPostSchema =
textPostSchema = {
text: {type: String},
postType: {type: String, default: "textPost"},
userId: {type: mongoose.Types.ObjectId}
}
articalPostSchema:
articalPostSchema = {
title: {type: String},
content: {type: String}
postType: {type: String, default: "articalPost"},
userId: {type: mongoose.Types.ObjectId}
}
now I have one social media application in which I have to show these two post when user's friend post's a post, and include infinite scroll. Both textPost and articalPost should be send if to frontend and only total 10 post should be sent at a time. How should I write a API for timeline?
output should look like:
{
post: [
{
title: "artical Post title",
content: "artical post content",
postType: "articalPost",
userId: "60b9c9801a2a2547de643ccd"
},
{
text: "text post ",
postType: "textPost",
userId: "60b9c9801a2a2547de643ccd"
},
... 8 more
]
}
UPDATE:
I got the solution:-
I created on more schema:
timelineSchema = {
postId: {
type: mongoose.Types.ObjectId,
required: true,
ref: function () {
switch (this.postCategoryType) {
case 'articleposts':
return 'ArticlePost';
case 'textposts':
return 'TextPost';
}
},
},
postCategoryType: {
type: String,
required: true,
},
userId: {
type: mongoose.Types.ObjectId,
required: true,
ref: 'User',
},
},
and then I created one function to get only friends post:
exports.getTimelinePosts = async (req, res) => {
try {
const timelinePosts = await TimelineModel.find({
userId: { $in: [...req.user.friends, req.params.id] },
})
.skip((req.params.page - 1) * 10)
.limit(10)
.sort({ createdAt: -1 })
.populate('postId');
return res.status(200).json({ status: 'success', data: timelinePosts });
} catch (error) {
return res.status(500).json(error);
}
};
To implement the pagination with Mongoose, You can do something like that.
const getPosts = async (userId, pageNumber) => {
let result = await Post.find({ userId })
.skip((pageNumber - 1) * 10)
.limit(10);
return result;
};
pageNumber is a counter that you need to pass from the frontend and will be incremented by 1 whenever a user hits the scroll limit.
If you want to query and merge data from multiple collections you need to update your schema to use populate. Just include ref where you are referring to other collections.
This may help.
https://mongoosejs.com/docs/populate.html
Assuming you are using express and mongoose. The code to fetch both,
// first bring all those schema from your mongoose models
const Article = require('./models/ArticleSchema');
const Text = require('./models/TextSchema');
const fetchArticleAndTextPost = async (req, res)=>{
//find all data
const articles = await Article.find();
const texts = await Text.find();
//join them together
const post = articles.concat(texts);
return res.status(200).json({
status: 200,
data: post,
})
}

How can I solve this referencing Problem in mongoose/Node JS

I have route and model for User and then another for Loan. I'm trying to reference the user inside the Loan route but I get this error anytime I test on PostMan:
TypeError: Cannot read property '_id' of undefined
at C:\Users\Micho\Documents\GBENGA\BE\src\routes\loans\index.js:38:47
Loan Model code is:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const loanSchema = new Schema({
customerName: {
type: String,
required: true
},
gender: {
type: String
},
address: {
city: String,
state: String,
},
amount: {
type: Number
},
loanTenure: {
type: Number
},
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
loanStatus: {
type: String,
default: "created"
}
}, {
timestamps: true
})
My route is this:
router.post("/", async (req, res) => {
try {
let loan = await new Loan({...req.body});
loan.save();
await User.findByIdAndUpdate(req.user._id, { $push: { loans: loan._id } })
console.log(req.user)
loan = await Loan.findById(loan._id).populate("user");
res.send(loan);
} catch (error) {
console.log(error)
res.status(500).send(error);
}
});
Kindly help. Thanks

How do I assign a created contact to the current user in Mongoose?

I am trying to create a contact which is pushed to the current user's array of contacts.
My controller currently only creates a contact generically and isn't specific to the user.
Controller:
function contactsCreate(req, res) {
Contact
.create(req.body)
.then(contact => res.status(201).json(contact))
.catch(() => res.status(500).json({ message: 'Something went wrong'}));
}
Contact Model:
const contactSchema = new Schema({
firstName: String,
lastName: String,
email: String,
job: String,
address: String,
number: Number
});
User model:
const userSchema = new mongoose.Schema({
username: { type: String, unique: true, required: true },
email: { type: String, unique: true, required: true },
passwordHash: { type: String, required: true },
contacts: [{ type: mongoose.Schema.ObjectId, ref: 'Contact' }]
});
Assuming you have access to the username on the request object, something like this should work:
async function contactsCreate(req, res) {
const username = request.User.username
try {
const newContact = await Contact.create(req.body)
const user = await User.findOne({username})
user.contacts.push(newContact)
await user.save()
return res.status(201).json(contact)
} catch ( err ) {
return res.status(500).json({ message: 'Something went wrong'})
}
}
Thank you to LazyElephant above. The solution (tweaked) was:
async function contactsCreate(req, res) {
const userId = req.user.id;
try {
const newContact = await Contact.create(req.body);
const user = await User.findById(userId);
user.contacts.push(newContact);
await user.save();
return res.status(201).json(newContact);
} catch ( err ) {
return res.status(500).json({ message: 'Something went wrong'});
}
}

Categories