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!
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 created a Mongo DB schema with Mongoose in Express.js and I am building the REST API. However when I try to update existing records the values that I do not update from the schema automatically become null. I understand why this happens just not sure exactly how it should be coded.
This is the route:
router.patch("/:projectId", async (req, res) => {
try {
const updatedProject = await Project.updateOne(
{ _id: req.params.projectId },
{
$set: {
title: req.body.title,
project_alias: req.body.project_alias,
description: req.body.description
}
}
);
res.json(updatedProject);
} catch (err) {
res.json({ message: err });
}
});
also here is the schema:
const ProjectsSchema = mongoose.Schema({
title: {
type: String,
required: true,
unique: true
},
project_alias: {
type: String,
unique: true,
required: true
},
description: String,
allowed_hours: Number,
hours_recorded: {
type: Number,
default: 0
},
date_added: {
type: Date,
default: Date.now
}
});
My problem is that when I want to update just the title:
{
"title" : "Title Updated33"
}
description and alias become null. Should I implement a check?
Just use req.body for the update object like this:
router.patch("/:projectId", async (req, res) => {
try {
const updatedProject = await Project.updateOne(
{ _id: req.params.projectId },
req.body
);
res.json(updatedProject);
} catch (err) {
res.json({ message: err });
}
});
Or even better, create a helper function like this so that we can exclude the fields in the body that doesn't exist in the model:
const filterObj = (obj, ...allowedFields) => {
const newObj = {};
Object.keys(obj).forEach(el => {
if (allowedFields.includes(el)) newObj[el] = obj[el];
});
return newObj;
};
router.patch("/:projectId", async (req, res) => {
const filteredBody = filterObj(
req.body,
"title",
"project_alias",
"description",
"allowed_hours",
"hours_recorded"
);
try {
const updatedProject = await Project.updateOne(
{ _id: req.params.projectId },
filteredBody
);
res.json(updatedProject);
} catch (err) {
res.json({ message: err });
}
});
I am working on MEAN application(Angular2+). I want to maintain a seprate data for each user. As of now the data are like anyone can view any of the details but i want like, If i login and enter details, only I can view those details. Basically I want to link user collection with other collection. Since I am new to Mongo, I have no idea about this.
user.ts
import * as mongoose from 'mongoose'
const userSchema = new mongoose.Schema({
username: String,
email: { type: String, unique: true, lowercase: true, trim: true },
password: String,
role: String
});
cat.ts
import * as mongoose from 'mongoose';
const catSchema = new mongoose.Schema({
name : String,
height: String,
weight: String,
});
I have no idea what is this
base.ts
abstract class BaseCtrl {
abstract model: any;
// Get all
getAll = (req, res) => {
this.model.find({}, (err, docs) => {
if (err) { return console.error(err); }
res.json(docs);
});
}
// Count all
count = (req, res) => {
this.model.count((err, count) => {
if (err) { return console.error(err); }
res.json(count);
});
}
// Insert
insert = (req, res) => {
const obj = new this.model(req.body);
obj.save((err, item) => {
// 11000 is the code for duplicate key error
if (err && err.code === 11000) {
res.sendStatus(400);
}
if (err) {
return console.error(err);
}
res.status(200).json(item);
});
}
// Get by id
get = (req, res) => {
this.model.findOne({ _id: 'req.params.id '}, (err, obj) => {
if (err) { return console.error(err); }
res.json(obj);
});
}
// Update by id
update = (req, res) => {
this.model.findOneAndUpdate({ _id: req.params.id }, req.body, (err) => {
if (err) { return console.error(err); }
res.sendStatus(200);
});
}
// Delete by id
delete = (req, res) => {
this.model.findOneAndRemove({ _id: req.params.id }, (err) => {
if (err) { return console.error(err); }
res.sendStatus(200);
});
}
}
export default BaseCtrl;
Reference project : https://github.com/DavideViolante/Angular-Full-Stack
You need to add user_id field to catSchema. The user_id will be the reference for the user who add/save that cat.
cat.ts
import * as mongoose from 'mongoose';
const catSchema = new mongoose.Schema({
name : String,
height: String,
weight: String,
user_id: String, // Or may be ObjectId
});
And you need to query the user from users collection to retrieve the data everytime.
Or you can use DBRef of mongodb. To implement this in mongoose you can follow this link.
Basically your cat model will be
cat.ts
import * as mongoose from 'mongoose';
const catSchema = new mongoose.Schema({
name : String,
height: String,
weight: String,
user: { type: mongoose.Schema.ObjectId, ref: 'User' }
});
To insert the cat, get the userid from login details/sessions and add to collection.
For you, you need to add the 'user' field to req.body as in base controller you are creating model from req.body.
If you don't want to add to req.body, you can override the insert method for cat controller and manually create the cat model with userid.
controller/cat.ts
import Cat from '../models/cat';
import BaseCtrl from './base';
export default class CatCtrl extends BaseCtrl {
model = Cat;
insert = (req, res) => {
const data = {
title: req.body.title,
height: req.body.height,
weight: req.body.weight,
user: getUser() // Get the logged in userid
};
const obj = new this.model(data);
obj.save((err, item) => {
if (err && err.code === 11000) {
res.sendStatus(400);
}
if (err) {
return console.error(err);
}
res.status(200).json(item);
})
}
}
Just like above, you can modify and filter all your documents according to user.
Edit:
To make it simple, send the current logged in user in the form itself. cats.component.ts
addCat() {
this.addCatForm.value.userid = this.authService.currentUser._id; // You might need to use callback here
... // All other code
}
Revert the cat controller to as it was before.
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 try to call a related list of logs for a certain user via Mongoose populate. Who can help me with finishing the response?
These are the schemes:
const logSchema = new Schema({
logTitle: String,
createdOn:
{ type: Date, 'default': Date.now },
postedBy: {
type: mongoose.Schema.Types.ObjectId, ref: 'User'}
});
const userSchema = new Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
}
logs: { type: mongoose.Schema.Types.ObjectId, ref: 'logs' }
});
mongoose.model('User', userSchema);
mongoose.model('logs', logSchema);
Inspired by the Mongoose documentary (see above) and other questions in relation to this subject I think I got pretty far in making a nice get. request for this user. I miss the expierence to 'translate it' to Express.
const userReadLogs = function (req, res) {
if (req.params && req.params.userid) {
User1
.findById(req.params.userid)
.populate('logs')
.exec((err, user) => {
if (!user) { }); // shortened
return;
} else if (err) {
return; // shortened
}
response = { //question
log: {
user: user.logs
}
};
res
.status(200)
.json(response);
});
} else { }); //
}
};
The response in Postman etc would be something like this:
{
"log": {5a57b2e6f633ce1148350e29: logTitle1,
6a57b2e6f633ce1148350e32: newsPaper44,
51757b2e6f633ce1148350e29: logTitle3
}
First off, logs will not be a list of logs; it will be an object. If you want multiple logs for each user, you will need to store is as an array: logs: [{ type: mongoose.Schema.Types.ObjectId, ref: 'logs' }]
From the Mongoose docs: "Populated paths are no longer set to their original _id , their value is replaced with the mongoose document returned from the database by performing a separate query before returning the results." In other words, in your query user.logs will be the logs document for each user. It will contain all the properties, in your case logTitle, createdOn, and postedBy.
Sending user.logs as json from the server is as easy as: res.json(user.logs). So your query can look like this:
const userReadLogs = function (req, res) {
if (req.params && req.params.userid) {
User1
.findById(req.params.userid)
.populate('logs')
.exec((err, user) => {
if (!user) { }); // shortened
return;
} else if (err) {
return; // shortened
}
res.status(200).json(user.logs)
});
} else { }); //
}
};
I hope this makes it a little bit clearer!