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.
Related
I am trying to join two mongoose collection and fetch all related details using the query which i have mention below. Problem is I am getting only one table details employee collection not able get two collection detail when I call GetEmployeeDetails(emp_id) . Need a suggestion. How to get two collection data in one query.
const EmployeeInfoSchema = mongoose.Schema({
employee_id: String,
client_id: {
type: Schema.Types.Number,
ref: "client",
},
email: String,
contact: String,
});
const ClientInfoSchema = mongoose.Schema({
client_id: Number,
employee_id: {
type: Schema.Types.String,
ref: "employee",
},
project: String,
organization: String,
});
let employeeInfo = mongoose.model("employee", EmployeeInfoSchema);
let clientInfo = mongoose.model("client", ClientInfoSchema);
module.exports = { employeeInfo, clientInfo };
Query
async function GetEmployeeDetails(emp_id) {
let employee_info = await Storage.employeeInfo
.find()
.where({ employee_id: emp_id })
.populate({
path: "client",
})
.exec(function (err, block) {
if (err) {
console.log("%s", err);
}
console.log("Employee details is %s", employee_info);
});
return employee_info;
}
Try to change your GetEmployeeDetails method like this:
async function GetEmployeeDetails(emp_id) {
try {
let employee_info = await Storage.employeeInfo
.find({ employee_id: emp_id })
.populate('client')
.exec(function (err, block) {
if (err) console.log('%s', err);
else console.log('Employee details is %s', employee_info);
});
return employee_info;
} catch (err) {
res.status(400).send('Error getting details');
}
}
This is my schema:
const productSchema = new mongoose.Schema({
name: String,
imageUrl: String,
category: String,
price: Number,
description: String,
featured: {
default: false
},
rating: [
{
userName: String,
score: Number,
comment: String
}
]
});
And this is how I was trying to push data into my database.
app.route("/review").post(function(req, res) {
const score = req.body.score;
const comment = req.body.comment;
if (req.isAuthenticated()) {
const review = {
$push: {
rating: {
userName: req.user.fName,
score: score,
comment: comment
}
}
};
console.log(review, req.body.productName);
Product.updateOne({ name: req.body.productName }, review, function(
err,
done
) {
if (err) {
console.log(err);
} else {
res.redirect("/products");
}
});
} else {
res.redirect("/login");
}
});
In the official documentation, it says that this is the way to push data in the MongoDB array. But still having no luck.
Help me to push reviews in the rating array field.
Thank You.
for my answer im using mongoose function findOneAndUpdate you can use updateOne instead , The writing to the database will be in a async function in order to not block the node process (will be alot faster).
Puting all the code in a try-catch block will allow you to control any errors if accrued.
You can read more about mongoose driver function and findOneAndUpdate here:
https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndUpdate/
more about async function:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
app.route('/review').post(async function(req, res) {
//destructuring values from body-parser
const { score, comment, productName } = req.body;
if (req.isAuthenticated()) {
//creating the rating obj
const rating = {
userName: req.user.fName,
score: score,
comment: comment
};
console.log(review, productName);
//setting it all in a try-catch block
try {
const product = await Product.findOneAndUpdate(
{name: productName},
{ $set: { rating: rating } },
{ new: true }
).then(err => {
if (err) throw err;
return res.redirect('/products');
});
res.redirect('/login');
} catch (error) {
console.log(error);
}
}
});
Hopefully this makes sense, good luck!
My app have a User that contains an array of Projects, and when I click to open one single project, I have a button 'delete' so I can delete that project (by its ID). That works, but my problem is:
I can delete the Project from database but I can't delete that project from User array projects where it keeps projects ID.
Here's my User Schema:
let mongoose = require("mongoose");
let passportLocalMongoose = require("passport-local-mongoose");
let UserSchema = new mongoose.Schema({
username: String,
password: String,
companyname: String,
companyimageURL: String,
projects: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Project'
}]
});
UserSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("User", UserSchema);
my Project Schema:
let mongoose = require("mongoose");
let projectSchema = new mongoose.Schema({
projectname: String,
typeofproject: String,
imageURL: String,
dateMonthFrom: String,
dateYearFrom: String,
dateMonthTo: String,
dateYearTo: String,
tasks: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Tasks'
}],
user: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}]
})
module.exports = mongoose.model('Project', projectSchema);
and my server side: I think I have this all wrong! this was what I tried to do by finding that project by Id and delete and then finding the user by id and update, but this don't work.
//Delete Project
app.delete('/dashboard/project/:id/edit', (req, res) => {
let id = req.params.id;
let userid = req.user._id;
let project = {
projectname: req.body.projectname,
typeofproject: req.body.typeofproject,
imageURL: req.body.imageURL,
dateMonthFrom: req.body.dateMonthFrom,
dateYearFrom: req.body.dateYearFrom,
dateMonthTo: req.body.dateMonthTo,
dateYearTo: req.body.dateYearTo,
};
Project.findByIdAndDelete(id, (err) =>{
if(err){
console.log(err);
}
User.findByIdAndUpdate(userid, {$set:project}, {new: true},(err) => {
if(err){
console.log(err)
}
console.log('Project Deleted: ' + id)
res.redirect('/dashboard#/projects');
});
});
};
Thank you so much!
You can use $pull operator. Visit this page for more details.
Your user update query should look like:
User.findByIdAndUpdate(
userid,
{ $pull: { projects: id } },
{ new: true }
);
Now it works! My Result:
app.delete('/dashboard/project/:id/edit', (req, res) => {
let id = req.params.id;
let userid = req.user._id;
Project.findByIdAndDelete(id, (err) =>{
console.log('deleting project', id)
if(err){
console.log(err);
}
User.findByIdAndUpdate(
userid,
{ $pull: { projects: id} },
{ new: true },(err) => {
if(err){
console.log(err)
}
console.log('Project Deleted: ' + id)
res.redirect('/dashboard#/projects');
})
})
};
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.
This is my MongoDB schema:
var partnerSchema = new mongoose.Schema({
name: String,
products: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Product'
}]
});
var productSchema = new mongoose.Schema({
name: String,
campaign: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Campaign'
}
]
});
var campaignSchema = new mongoose.Schema({
name: String,
});
module.exports = {
Partner: mongoose.model('Partner', partnerSchema),
Product: mongoose.model('Product', productSchema),
Campaign: mongoose.model('Campaign', campaignSchema)
}
And I'd like to send all documents (partner>product>campaign) to my View as a one object.
I know how to send partner with product ref. For example:
var campSchema = require('../model/camp-schema');
router.get('/partner-list', function (req, res) {
campSchema.Partner.find({}, function (err, partnerList) {
if (err) throw err;
res.json({ partnerList: partnerList });
}).populate('products');
});
And I can easily iterate at view in this way:
li(ng-repeat="product in partner.products")
a(href="#") {{ product.name }}
And here is the question. How can I pass ONE object as a document with partner, product and campaign. Because at the moment I have only partner and product in that object.
You can use this pattern to populate the nested campaign model:
var campSchema = require('../model/camp-schema');
router.get('/partner-list', function (req, res) {
campSchema.Partner
.find({})
.populate({
path: 'products',
model: 'Product',
populate: {
path: 'campaign',
model: 'Campaign'
}
}).exec(function(err, partnerList) {
if (err) throw err;
res.json({ partnerList: partnerList });
});
});