I try to show related articles based on categories. I use mongoose to query things from MongoDB.
When I try the code below, I get the error "TypeError: Cannot read properties of null (reading 'category')" but console.log from articlecategories first showing array with categories and then throw the error "cannot read"..
I'm a beginner in express js, maybe someone gives me a hint.
exports.articleDetail = async (req, res) => {
const article = await Article.findOne({ slug: req.params.slug }).populate('category').populate('author');
const articlecategories = article.category
categories = []
for(let i=0;i<articlecategories.length;i++){
const category = articlecategories[i]
categories.push(category._id)
}
console.log(categories)
const relatedarticles = await Article.find({ category : { $all : categories }})
console.log(article);
res.render('article', { article, relatedarticles })
}
Edit
Thank You all for answers. I have a solution. Problem is that when loop through article categories, I get no category ID but new ObjectId: new ObjectId("636bc1c64f7470f2557b61d7")
To let this work, I must use .toString() and get only Id and then push this Id to array.
This is working code:
exports.articleDetail = async (req, res) => {
const article = await Article.findOne({ slug: req.params.slug }).populate('category').populate('author');
categories = []
for(let i=0;i<article.category.length;i++){
const category = article.category[i]
const catid = category._id.toString()
categories.push(catid)
}
console.log(categories)
const articles = await Article.find({category: { $all: categories }}).populate('author')
res.render('article', { article, articles })
}
Probably your call to await Article.findOne just returns null since the object could not be found.
You should check if there is anything found and, if not, directly return an error like this:
exports.articleDetail = async (req, res) => {
const article = await Article.findOne({ slug: req.params.slug }).populate('category').populate('author');
if ( !article ) return res.status(404).json(/* include your error object here */);
const articlecategories = article.category
categories = []
for(let i=0;i<articlecategories.length;i++){
const category = articlecategories[i]
categories.push(category._id)
}
console.log(categories)
const relatedarticles = await Article.find({ category : { $all : categories }})
console.log(article);
res.render('article', { article, relatedarticles })
}
Related
I have a little question
I try to sort with mongoose I know the simple way
but I dont know how to do that with data I get in req.body lets say.
I add here a code I try
exports.sortBy = async (req, res, next) => {
const data1 = req.body.data1 //the data i want to sort
const sortBy = req.body.sortBy //sory by price,createdAt,length and more
const downUp = req.body.downUp // 1, -1
console.log(sortBy)
console.log(data1)
const data = await Book.aggregate([
{
$sort: { [sortBy]: downUp }
},
])
res.status(200).json({
status: 'success',
results: data.length,
data
})
}
so I can sort by the data I get in req.body
Try the following:
const data = await Book.sort({sortBy: downUp});
which seems to work and is much easier to read.
I am creating an e-commerce type site with shopping cart functionality.
When a Customer adds product to cart, I need to add the data of the product into an array in the session variable.
I tried like this:
router.post('/addtocart', authCheck.login, async(req, res) => {
// console.log(req.body)
req.session.cart = []
let doc = {
...req.body
}
await req.session.cart.push(doc)
console.log(req.session.cart)
What happens is each time a product is added to the cart, it doesn't keep the existing data in there. So if I go to add two products, only the latest one shows in there. How can I fix this?
To explain your problem: the req.session.cart = [] code is emptying your req.session.cart data by passing it a value of empty array = [] every time your /addtocart endpoint is triggered.
A solution would be:
router.post('/addtocart', authCheck.login, async(req, res) => {
// req.session.cart = []
let updatedCart = []
let { cart } = req.session
let doc = {
...req.body
}
// await req.session.cart.push(doc) //await is unnecessary here
updatedCart.push(...cart, doc)
req.session.cart = updatedCart
console.log("req.session.cart: ", req.session.cart)
You can read more about the await keyword at MDN documentations - await.
Let me know if you have any other question.
This is my subcommand code:
.addSubcommand(subcommand =>
subcommand
.setName('announcement')
.setDescription('Announce something to every user. ')
.addStringOption(option =>
option
.setName('announcement1')
.setDescription('Announcement content')
.setRequired(true))),
This is my code so far for the command:
if (interaction.options.getSubcommand() === 'announcement') {
const ann = interaction.options.getString('announcement1')
const notificationschema = require('./schemas/notificationschema')
}
I am trying to push the contents of the variable ann into everyone's notificationschema into the notifs array.
How would I do this?
This is my schema:
const mongoose = require('mongoose')
const notificationschema = mongoose.Schema({
User:String,
Notifs:Array,
Read:Boolean,
})
module.exports = mongoose.model('notification', notificationschema, 'notification')
You can use the find function from mongoose, then loop through all the found results.
const foundSchemas = await notificationSchema.find({});
foundSchemas.forEach(result => {
result.Notifs.push('Your Notification');
result.save();
})
You can use for looping function:
First, call your index:
const notificationschema = require('./schemas/notificationschema');
const notifs = await notificationschema.find({})
//This will make your data as array object.
Now After creating and calling your index. Use for loop:
for(let i = 0; i < notifs.length; i++) {
notificationschema.updateOne({
User: notifs[i].memberID //I assume that this column is a member_id
}, {
$push: {
Notifs: "Something you wanted to say here or whatsoever"
}
})
}
I am building my first solo project for a paper music magazine to offer their subscribers online reading. I already know that I will have to refactor a lot of duplicate into reusable code but I just want things to work for now.
These are the collections I use to build the models/controllers:
Artists
Albums
Users (admins, authors, subscribers)
Chronicles (album short reviews)
Articles (album(s) long reviews)
Interviews (related to articles most of the times)
Issues, referencing all Chronicles, Articles and Interviews (ObjectID arrays)
Artist is straightforward and simple, just 'name', 'country' and an empty 'albums' array:
exports.createArtist = catchAsync(async (req, res, next) => {
req.body.createdBy = mongoose.Types.ObjectId(req.user._id)
req.body.albums = []
const newArtist = await Artist.create(req.body)
res.status(201).json({
status: 'success',
data: {
article: newArtist,
},
})
})
Album is a bit more complex, but the artist 'name' is transformed into its ObjectId for referencing, and populates the artist's 'albums' array with the newly created album ObjectId
exports.createAlbum = catchAsync(async (req, res, next) => {
// Add createdBy automatically in the req.body
req.body.createdBy = mongoose.Types.ObjectId(req.user._id)
// Find the album's artist by its name
const relatedArtist = await Artist.findOne({ name: req.body.artist })
if (!relatedArtist) {
return next(new AppError('No artist with that name, please check', 404))
}
// replace the name of the artist with its objectID for auto referencing
req.body.artist = mongoose.Types.ObjectId(relatedArtist._id)
// save album
const newAlbum = await Album.create(req.body)
// add the saved album into the albums' array in the artist collection
relatedArtist.albums.push(mongoose.Types.ObjectId(newAlbum._id))
await relatedArtist.save()
res.status(201).json({
status: 'success',
data: {
album: newAlbum,
},
})
})
Chronicle is a bit similar but involves 3 other collections in the process: collection of album & artist data, and populating the 'chronicles' array of the current issue:
exports.createChronicle = catchAsync(async (req, res, next) => {
// Add author automatically in the req.body
req.body.author = mongoose.Types.ObjectId(req.user._id)
// Find artist & album of the chronicle in the respective collections
const artist = await Artist.findOne({ name: req.body.artist })
const album = await Album.findOne({ title: req.body.album })
const issue = await Issue.findOne({ issueNumber: req.body.belongsToIssue })
if (!artist) {
return next(new AppError('No artist with that name, please check.', 404))
}
if (!album) {
return next(new AppError('No album with that name, please check.', 404))
}
if (!issue) {
return next(new AppError('No issue with that number, please check.', 404))
}
// replace the name of the artist and album with its objectID for auto referencing
req.body.artist = mongoose.Types.ObjectId(artist._id)
req.body.album = mongoose.Types.ObjectId(album._id)
// Create Chronicle unique slug & add to req.body
req.body.slug = slugify(`${artist.name} ${album.title} ${album.year}`, {
lower: true,
})
// Save new Chronicle
const newChronicle = await Chronicle.create(req.body)
// Push new Chronicle ID into the array of the corresponding Issue
issue.chronicles.push(mongoose.Types.ObjectId(newChronicle._id))
await issue.save()
res.status(201).json({
status: 'success',
data: {
chronicle: newChronicle,
},
})
})
My problem comes now for 'Articles' :
An article can be about several albums (so not just an ObjectId, but an array of ObjectIds!) and can be signed by several writers (between 1 and 3). I have to loop through both arrays while performing each time:
await Album.find({title: req.body.title})
await User.find({author: req.body.author})
then swap the names in the req.body.albums & req.body.authors by its ObjectId and in the end, transforming the req.body.albums + authors from an array of strings into an array of ObjectIds, especially where arrays are pointers (I'm guessing I have to work with a destructured duplicate array).
I understood that I can't perform async operations within a forEach() of map() loop but haven't figured out how to make this work. My research makes me think I have to use Promise.all() but haven't figured out how to so far, all my trials and errors failed until now, so I must do this the wrong way or don't understand the process.
Thank you for the help and tricks!
Thank you Anatoly. Your tip led me to something that needs to be optimized and refactored but that has the huge advantage of being totally functional:
exports.createArticle = catchAsync(async (req, res, next) => {
// Add authors automatically in the req.body if not specified by user
if (!req.body.authors || req.body.authors === []) {
req.body.authors.push(mongoose.Types.ObjectId(req.user._id))
}
// loop through all authors names and swap with respective ObjectIds
const tempAuthors = []
await Promise.all(
req.body.authors.map(async (author, index) => {
const user = await User.findOne({ name: author })
if (!user) {
return next(
new AppError(
`No author with that name (position ${index + 1}), please check.`,
404
)
)
}
tempAuthors.push(mongoose.Types.ObjectId(user._id))
})
)
// Assign req.body.authors the values of tempAuthors
req.body.authors = [...tempAuthors]
// loop through all album titles and swap with respective ObjectIds
const tempAlbums = []
await Promise.all(
req.body.albums.map(async (title, index) => {
const album = await Album.findOne({ title })
if (!album) {
return next(
new AppError(
`No album with that title (position ${index + 1}), please check.`,
404
)
)
}
tempAlbums.push(mongoose.Types.ObjectId(album._id))
})
)
// Assign req.body.authors the values of tempAuthors
req.body.albums = [...tempAlbums]
// Find artist & issue of the article
const artist = await Artist.findOne({ name: req.body.artist })
if (!artist) {
return next(new AppError('No artist with that name, please check.', 404))
}
const issue = await Issue.findOne({ issueNumber: req.body.belongsToIssue })
if (!issue) {
return next(new AppError('No issue with that number, please check.', 404))
}
// replace the name of the artist with its objectID for auto referencing
req.body.artist = mongoose.Types.ObjectId(artist._id)
// Create Article unique slug & add to req.body
req.body.slug = slugify(
`${issue.issueNumber} ${artist.name} ${req.body.title}`,
{
lower: true,
}
)
// Save new Article
const newArticle = await Article.create(req.body)
// Push new Article ID into the array of the corresponding Issue
issue.articles.push(mongoose.Types.ObjectId(newArticle._id))
await issue.save()
res.status(201).json({
status: 'success',
data: {
article: newArticle,
},
})
})
The next step will be to outsource the swap function for Albums and Authors to avoid duplicate code.
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]}});