I have following mongodb/mongoose data model::
var UserSchema = new Schema({
name: String
, roles: [] // admin or client
/* others object */
});
mongoose.model('User', UserSchema);
var ReviewSchema = new Schema({
title: String
, user: {
type: Schema.ObjectId,
ref: 'User'
}
});
mongoose.model('Review', ReviewSchema);
Now I want to get all review where user's role is admin. I have tried the following way:
Review
.find( { 'user.roles': 'admin' } )
.populate('user')
.exec(function(err, review) {
console.log("review : ", review); // null
});
NB: "mongoose": "^4.2.9",
How can I solve this problem? Thanks in advance.
I hope, this code may be solved your problem
Review.find()
.populate('user', null, { roles:"admin" })
.exec(function (err, review) {
if(err) {
console.log(err);
res.json(err);
}else {
var review = review.filter(function (review) {
return review.user !== null;
}).pop();
console.log(review);
res.json(review);
}
});
Related
I'm working on a website with a catalog of shops where users can leave comments and rate these shops.
My code so far:
Schemas:
const journalSchema = new mongoose.Schema({
title: String,
category: String,
subcategory: String,
review: [{type: mongoose.Schema.Types.ObjectId, ref: 'Review'}],
link: String,
description: String,
});
const userSchema = new mongoose.Schema ({
username: String,
nickname: String,
password: String,
journal: [{type: mongoose.Schema.Types.ObjectId, ref: 'Journal'}]
});
const reviewSchema = new mongoose.Schema({
author: {type: mongoose.Schema.Types.String, ref: 'User'},
content: String,
date: Date,
rating: Number
});
const Journal = mongoose.model("Journal", journalSchema);
const User = mongoose.model("User", userSchema);
const Review = mongoose.model("Review", reviewSchema);
Get route for individual shop page:
app.get("/journals/:journalId", function(req, res){
const requestedJournalId = req.params.journalId;
Journal.findOne({_id: requestedJournalId}, function(err, foundJournal){
Review.aggregate([
{$match: {_id: {$in: foundJournal.review}}},
{$group: {_id: foundJournal.review, average: {$avg: "$rating"}}}
], function(err, result){
if(err){
console.log(err);
}
else{
result.forEach(function(review){
Review.find({_id: review._id}, function(err, reviews){
res.render("stats", {
_id: foundJournal._id,
title: foundJournal.title,
subcategory: foundJournal.subcategory,
link: foundJournal.link,
description: foundJournal.description,
reviews: reviews,
avg: review.average
});
})
})
}
});
})
});
Post route:
app.post("/stats/review", function(req, res){
if(req.isAuthenticated()){
const userId = req.user.id;
const userReview = req.body.journalReview;
const userRating = req.body.reviewRating;
const journalId = req.body.journalId;
User.findById(userId, function(err, foundUser){
if(err){
console.log(err);
}
else{
const review = new Review();
review.author = foundUser.nickname;
review.content = userReview;
review.rating = userRating;
review.save()
.then((result) =>{
Journal.findOneAndUpdate(
{_id: journalId},
{$push: {
review: review
}},
{useFindAndModify: false},
function(err, success){
if(err){
console.log(err);
}
else{
res.redirect("back");
}
}
);
})
.catch((error) =>{
console.log(error);
})
}
});
}
else{
res.redirect("/login");
}
});
Currently, the code is working and doing what I want it to do. Any logged in user can leave a comment and rating for any shop. But my concern is how to improve the existing code, I'm sure there are better ways to achieve the same result with a more clean and efficient code. I'm new to mongoose and learned about the aggregate method just recently. Thanks in advance
You can use ES6 for better formatting your code. here is the link you can follow:https://www.w3schools.com/js/js_es6.asp
I formatted get route for your understanding:
app.get("/journals/:id", async (req, res) => {
const journal = await Journal.findOne(req.params.id);
if (!journal) {
res.status(404).sent({ error: "journal not found" });
}
try {
const reviews = await Review.aggregate([
{ $match: { _id: { $in: journal.review } } },
{
$group: {
_id: journal.review,
average: { $avg: "$rating" },
},
},
]);
reviews.forEach((review) => {
const reviews = await Review.find({ _id: review._id });
res.render("stats", {
...journal,
reviews,
avg: review.average,
});
});
} catch (error) {
console.log(error);
res.status(400).send(error);
}
});
you can use mongoose-autopopulate package for auto-populating your document. https://www.npmjs.com/package/mongoose-autopopulate
avoid using a nested Database queries.
try to implement async await for better readability.
I have a model of courses with the following structure:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const user_shortid = require('shortid');
// Create Course schema
const CourseSchema = new Schema({
courseDetail: {
type: String
},
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
enrolledUsers: [{
type: Schema.Types.ObjectId,
ref: 'users'
}],
currentStatus: {
type: String,
default: 'Planned'
}
});
mongoose.model('courses', CourseSchema);
I have created a post request for adding a signed in user to the array of enrolledUsers, the problem is, I want to check first if the req.user.id exists in the enrolledUsers array. Following is my post request:
router.post('/joincourse', [ensureAuthenticated], (req, res) => {
Course.findByIdAndUpdate({ _id: req.body.coursecode },
{ $push: { enrolledUsers: req.user.id } },
{ safe: true, upsert: true },
function (err, doc) {
if (err) {
req.flash('error_msg', 'Could not enroll in the course');
res.redirect('/dashboard');
} else {
req.flash('success_msg', 'You are now enrolled in the course');
res.redirect('/dashboard');
}
}
);
});
Right now the behavior is that a user can enroll again and again in the same course.
Is there some way I can check for the req.user.id in the enrolledUsers array before it is added?
you can do find the user first by using find() and then if a user exists, update it , else
give an error like this
router.post('/joincourse', [ensureAuthenticated], (req, res) => {
Course.findById({ _id: req.body.coursecode },
function (err, doc) {
if (err) {
req.flash('error_msg', 'Could not enroll in the course');
res.redirect('/dashboard');
} else {
if(doc){
if(!doc.enrolledUsers.includes(req.user.id)){ // here is the checking
doc.enrolledUsers.push(req.user.id);
doc.save();
req.flash('success_msg', 'You are now enrolled in the course');
res.redirect('/dashboard');
}
}else{
// show error msg
}
}
}
);
});
I'm building my first mean stack app. It's a review site that contains three models: User, Review, and Company.
When I make a review, I want the new review to be saved to the 'review' collection, and for that review to be connected by reference to the company being reviewed and the user who wrote the review. I also want the user to hold a reference to the review, and the company to hold a reference to all the reviews it has. Here are my models:
Review
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const reviewSchema = new Schema ({
companyName: String,
companyId: { type: Schema.Types.ObjectId, ref: 'Company'},
starRating: Number,
subject: String,
commentBody: String,
createdBy: { type: Schema.Types.ObjectId, ref: 'User'},
});
const Review = mongoose.model("Review", reviewSchema);
module.exports = Review;
Company
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const companySchema = new Schema ({
companyName: String,
about: String,
basedIn: String,
materialOrigins: [String],
productRange: [String],
category: String,
reviews: [ {type: Schema.Types.ObjectId, ref: 'Review'} ],
socialRating: Number,
environmentalRating: Number,
priceRange: Number
});
const Company = mongoose.model("Company", companySchema);
module.exports = Company;
User
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const userSchema = new Schema ({
email: String,
firstName: String,
lastName: String,
password: String,
image: Object,
aboutText: String,
reviews: [ { type: Schema.Types.ObjectId, ref: "Review" } ]
// comments: { type: Schema.Types.ObjectId, ref: 'Comment' }
});
const User = mongoose.model("User", userSchema);
module.exports = User;
This is my current route, which currently saves the review to the collection and attaches the user. However, the user doesn't get the review.
route
router.post('/:category/:company', (req, res) => {
var subject = req.body.subject;
var commentBody = req.body.commentBody;
var starRating = req.body.starRating;
var userId = req.body.userId;
if(!subject || !commentBody || !starRating) {
res.status(400).json({ message: "Subject, comment body, and star rating are required." });
return;
}
var newReview = Review({
starRating,
subject,
commentBody,
userId
});
User.findById(userId, {
}, (err, user) => {
if (err) {
return res.send(err);
} else {
console.log("checking out user in route", user);
user.reviews.push(newReview);
user.save();
newReview.save((err, review) => {
if (err) {
return res.status(400).json({ message: err });
} else {
res.status(200).json({ message: 'Review saved', review });
}
});
}
});
I haven't tried adding the company in because I'm trying to do one thing at a time. I've been looking at 'populate', but all of the documentation seems to only use two models at once. Is it possible to do three at once? Or am I overcomplicating this?
Apologies if this is all overcomplicated. I'm fairly new to MongoDB and MEAN stack in general. Thanks for your help.
Ok, I did it, for any people landing on this page wondering the same thing in the future.
Here's my route:
router.post('/:category/:company', (req, res, next) => {
var companyName;
var companyId;
var subject = req.body.subject;
var commentBody = req.body.commentBody;
var starRating = req.body.starRating;
var createdBy = req.body.createdBy;
if(!subject || !commentBody || !starRating) {
res.status(400).json({ message: "Subject, comment body, and star rating are required." });
return;
}
var newReview = Review({
starRating,
subject,
commentBody,
createdBy
});
//I need the companyId and companyInfo for later use in my review save. I'm calling the company with the name I have from my params, and setting the id and name with the received data from Mongo.
Company.findOne({"companyName": req.params.company}, (err, company) => {
if (err) {
return res.status(400).json({ message: err });
} else {
this.companyName = company.companyName;
this.companyId = company.id;
}
});
newReview.save((err, review) => {
//Push the review id to the user
if (err) {
return res.status(400).json({ message: err });
} else { User.findByIdAndUpdate({_id: createdBy },{$push: {reviews: review.id} }, (err) => {
if (err) {
console.log("There was an error pushing review to user");
next(err);
//Push the review id to the company
} else { Company.findOneAndUpdate({ "companyName": req.params.company}, {$push: {reviews: review.id}}, (err, company) => {
if (err) {
console.log("There was an error pushing review to company");
next(err);
} else {
//Updates the review by setting companyId and companyName properties to review for Mongo
Review.update({_id: review.id}, {$set: {companyId: this.companyId, companyName: this.companyName}}, (err, changes) => {
if(err) {
return res.status(400).json({message : err});
} else {
console.log("updating review successfully with company info", changes);
}
});
console.log ("Review successfully saved");
res.json({
review: review,
});
}
});
}
});
}
});
});
If anyone has feedback on how this could be done better/more efficiently, let me know. Cheers.
I am using Mongoose/MongoDB and I am trying to associate many comments to one article. My app begins by scraping from a website and then the user has the option to save each article that was scraped into the MongoDB. When the user chooses to save one article, I save it into database. So when a user clicks on one of their saved articles, they can comment on them. Each article has its own comment section I need to retrieve the correct comments.
//My post comment request in JS file
function postComment(){
var articleComment = {
comment: $('#comment').val().trim()
}
$.post('/comments/' + articleID, articleComment).done(function(data){
$('.main-popup').fadeOut();
console.log('DONNE', data);
});
}
//Post route in controller
router.post('/comments/:id', function(req, res){
var newComment = new Comment(req.body);
newComment.save(function(err, doc){
if(err){
console.log(err);
}else{
Comment.findOneAndUpdate({ "_id": doc._id }, { "article": req.params.id }).exec(function(err, doc){
if(err){
console.log(err);
res.send(err);
}else{
res.send(doc);
}
});
}
});
});
//Get request to get correct comments when clicked on specific article
function showCommentBox(){
$('.comments').empty();
$('#comment').val("");
articleID = $(this).attr('data-article-id');
$.get('/comments/' + articleID, function(data){
if(data.article){ //This is undefined*********************
for(var x = 0; x < data.comment.length; x++){
$('.comments').append("<div><h2>" + data.comment[x].comment + "</h2><span><button>×</button></span></div>");
}
}
$('.main-popup').fadeIn();
});
}
//Get route in controller
router.get('/comments/:id', function(req, res){
Comment.findOne({ "article": req.params.id }).populate("article").exec(function(err, doc){
if(err){
console.log(err)
}else{
res.json(doc);
}
});
});
//Article Model
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ArticleSchema = new Schema({
title: {
type: String
},
link: {
type: String
},
description: {
type: String
},
img: {
type: String
}
});
var Article = mongoose.model("Article", ArticleSchema);
module.exports = Article;
//Comment Model
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var CommentSchema = new Schema({
comment: {
type: String
},
article: {
type: Schema.Types.ObjectId,
ref: 'Article'
}
});
var Comment = mongoose.model('Comment', CommentSchema);
module.exports = Comment;
First, you're missing $set when you do .findOneAndUpdate. Also I think you should convert a string to Mongo ObjectId before setting it.
So it might look likt this:
const ObjectId = mongoose.Types.ObjectId;
Comment.findOneAndUpdate({ "_id": doc._id }, {$set: {"article": new ObjectId(req.params.id) }})
Also you don't need to make 2 database calls. You could article id before saving newComment and then simply send it as a response like this:
//Please notice that mongoose.Schema.Types.ObjectId and mongoose.Types.Object are different types.
//You need this one here:
const ObjectId = mongoose.Types.ObjectId;
router.post('/comments/:id', function(req, res){
var newComment = new Comment(req.body);
newComment.article = new ObjectId(req.params.id);
newComment.save(function(err, doc){
if (err) {
console.error(err);
res.send(err);
return;
}
res.send(doc);
});
});
I have two models:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ProjectSchema = new Schema({
title: { type: String },
images: [{
type: Schema.Types.ObjectId,
ref: 'Image'
}]
});
module.exports = mongoose.model('Project', ProjectSchema);
and
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ImageSchema = new Schema({
fileName: { type: String },
fileSize: { type: Number }
});
module.exports = mongoose.model('Image', ImageSchema);
Existing projects are filled with images as follows:
Project.findById(req.params.project_id, function(err, project) {
if (err) { res.status(400).send(err); }
var image = new Image({
fileName: req.file.name,
fileSize: req.file.size
});
image.save(function(err) {
if (err) { res.status(400).send(err); }
project.images.push(image);
project.save();
);
});
There are no problems in getting images from the project:
Project.findById(req.params.project_id)
.populate('images')
.exec(function(err, project) {
if (err) { res.status(400).send(err); }
res.status(200).json(project.images);
});
i try removing an image from a story, using Mongoose documentation:
http://mongoosejs.com/docs/subdocs.html
http://mongoosejs.com/docs/api.html#types_documentarray_MongooseDocumentArray.id
Project
.findById(req.params.project_id)
.populate('images')
.exec(function(err, project) {
if (err) { res.status(400).send(err); }
project.images.id(req.params.image_id).remove();
project.save();
});
But i keep getting errors:
/api-server/app/admin/images/index.js:170
project.images.id(req.params.image_id).remove();
^
TypeError: project.images.id is not a function
I searched here for solutions, but i only got some things on $pull from 2013.
Is the .id() method broken, or am i doing something wrong.
As i'm fairly new to mongoose, are there ways to do this better?
You just need to delete the image from the database. I hope the following code helps you.
Project
.findById(req.params.project_id)
.exec(function(err, project) {
if (err) { res.status(400).send(err); }
project.save();
Image.remove({"_id":project.images._id},function(){})
});
You can delete subdocuments by using findByIdAndUpdate and $pull.
Seting options to {new: true} overwrites the existing document
var fieldsToRemove= {
$pull: {
images: {
_id: req.params.type
}
}
};
var options = { new: true };
Project.findByIdAndUpdate(req.params.project_id, fieldsToRemove, options,
function(err, project) {...
it will remove the subdocument with specified _id