Mongoose: Allow users one review per journal - node.js

I have three schemas:
const journalSchema = new mongoose.Schema({
title: String,
category: String,
subcategory: String,
review: [{type: mongoose.Schema.Types.ObjectId, ref: 'Review'}],
link: String,
description: String,
subscribers: Number,
posts: Number,
image: {
data: Buffer,
contentType: String},
date: Date
});
const userSchema = new mongoose.Schema ({
username: String,
nickname: String,
password: String,
journal: [{type: mongoose.Schema.Types.ObjectId, ref: 'Journal'}],
googleId: String,
age: {type: Date},
gender: {type: String, enum: ["male", "female"]},
admin: Boolean,
role: String
});
const reviewSchema = new mongoose.Schema({
author: {type: mongoose.Schema.Types.String, ref: 'User'},
content: String,
date: Date,
rating: {type: Number, min: 1.0, max: 5.0}
});
const Journal = mongoose.model("Journal", journalSchema);
const User = mongoose.model("User", userSchema);
const Review = mongoose.model("Review", reviewSchema);
Right now any user can leave any number of reviews on the same journal. I want to make it so that a user can leave only one review per journal.
Post route for getting reviews:
app.post("/stats/review", function(req, res){
if(req.isAuthenticated()){
const userNickname = req.user.nickname;
const userId = req.user.id;
const userReview = req.body.journalReview;
const userRating = req.body.rating;
const journalId = req.body.journalId;
Journal.findById({_id: journalId}, function(err, journal){
Review.find({_id: {$in: journal.review}}, function(err, foundReview){
foundReview.forEach(function(review){
if(review.author == userNickname){
console.log("Review from this user already exists");
}
else{
var date = new Date();
const review = new Review();
review.author = userNickname;
review.content = userReview;
review.rating = userRating;
review.date = date;
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");
}
});
Is it possible to achieve this with the use of addToSet mongoose method? Couldn't find a fitting solution from similar problems.

I think you need to do some checks here
if the _id already exists in the review field just return some err msg

Related

Embedded documents only return id and created at

I am making an api to store a practice that contains workposts. When I GET my practice I get the array of workposts but it only includes the id and created/updated at properties
This is my workpost schema
'use strict';
// Requirements
const mongoose = require('mongoose');
// Workpost Schema
const WorkpostSchema = new mongoose.Schema({
workpost_name: { type: String},
workpost_icon: { type: String},
workpost_info: { type: String},
created_at: {type: Date, default: Date.now},
updated_at: {type: Date, default: Date.now},
},{versionKey: false});
// Export the Workpost schema
let Workpost = mongoose.model('Workpost', WorkpostSchema);
module.exports.WorkpostSchema = WorkpostSchema;
module.exports.Workpost = Workpost;
This is the Practice schema that uses it
'use strict';
// Requirements
const mongoose = require('mongoose');
const WorkScheduleSchema = require('./workschedule-module').WorkScheduleSchema;
const WorkpostSchema = require('./workpost-module').WorkpostSchema;
// Practice Schema
const PracticeSchema = new mongoose.Schema({
practice_name: {type: String, required: true},
address: String,
email: String,
tel: String,
workschedules: [WorkScheduleSchema],
workposts: [WorkpostSchema],
created_at: {type: Date, default: Date.now},
updated_at: {type: Date, default: Date.now},
},{versionKey: false});
// Export the Practice schema
let Practice = mongoose.model('Practice', PracticeSchema);
module.exports.Practice = Practice;
When I try to get the practice I get this:
address: "Some adress"
created_at: "2019-03-25T12:42:05.288Z"
email: "email#email.com"
practice_name: "Some practice"
tel: "00000473975832"
updated_at: "2019-03-25T12:42:05.288Z"
workposts: Array(2)
0:
created_at: "2019-03-25T12:42:05.291Z"
updated_at: "2019-03-25T12:42:05.291Z"
_id: "5c94f6eb1a0a6200128461d2"
__proto__: Object
1:
created_at: "2019-03-25T12:42:05.290Z"
updated_at: "2019-03-25T12:42:05.290Z"
_id: "5c94f7121a0a6200128461d3"
None of the other attributes are included in the return. I make a practice using the following code and return it with a simple get request
router.post('/', (req, res, next) => {
WorkPosts.find({_id: {$in: req.body.workposts}}, {_id:1}).exec( function (err, workposts) {
if (err) {
err.message = "Workposts not found";
return next(err);
}
let practice = new Practice(req.body);
practice.workschedules = workschedules;
practice.workposts = workposts;
practice.save((err, practice) => {
if (err) {
err.message = "Practice couldn't be saved";
return next(err);
}
res.statusCode = 201;
res.json(practice);
});
});
});

MongoDB/Mongoose Populate

Working with Mongoose "Populate" - So far I'm unable to successfully get the "Food" model to populate the "User" model.
The goal is to be able to save a "Food" to a user.
USER MODEL:
var UserSchema = new mongoose.Schema({
username: String,
password: String,
foods: [{ type: mongoose.Schema.Types.ObjectId}],
easy: {type: Boolean, default: false},
});
UserSchema.plugin(passportLocalMongoose)
module.exports = mongoose.model("User", UserSchema);
FOOD MODEL:
var foodSchema = new mongoose.Schema({
name: { type: String, required: false, unique: true },
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
}
});
module.exports = mongoose.model("Food", foodSchema);
GET ROUTE
router.get("/dashboard", function (req, res) {
User.find({currentUser: req.user})
.populate({path: 'foods'}).
exec(function (err, foods) {
if (err) return (err);
console.log('The food is:', req.user.foods.name);
});
});
POST ROUTE:
router.post("/dashboard", function(req, res, next) {
User.update({ id: req.session.passport.user }, {
}, function(err, user) {
if (err) return next(err);
User.findById(req.user._id, function(err, user) {
var newFood = new Food({
name: req.body.currentBreakfast,
image: 'test',
});
user.foods = newFood
user.save();
});
});
res.redirect('/dashboard');
});
You need to add the ref field in your user schema for foods to be populated while querying user.
var UserSchema = new mongoose.Schema({
username: String,
password: String,
foods: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Food' }],
easy: {type: Boolean, default: false},
});
You can user this query.
await User.find({currentUser: req.user}).populate('foods')
Try this it will auto-populate data
var UserSchema = new mongoose.Schema({
username: String,
password: String,
foods: [{ type: mongoose.Schema.Types.ObjectId,ref: 'Food'}}],
easy: {type: Boolean, default: false},
});
UserSchema.pre('find', prepopulate)
function prepopulate(){
return this.populate('foods')
}

how to use of objectId in nodejs

I write 2 models in mongoose. first for Users another for Movies. I wrote this models below. first I save my all user in users collection. after that I want to save my movies to movies collection. but when I want to define my director field I get an error that "ReferenceError: director is not defined"
const userSchema = new Schema({
imdbId: String,
name: String,
});
var User = mongoose.model('user', userSchema);
const movieSchema = new Schema({
imdbId: String,
title: String,
rank: Number,
year: Number,
stars:[{
type: Schema.Types.ObjectId,
ref: 'userSchema'
}],
director:{
type: Schema.Types.ObjectId,
ref: 'userSchema'
}
});
var Movie = mongoose.model('movie', movieSchema);
module.exports = {Movie, User}
and this is my function:
async function findObjectIdByImdbId(str) {
const result = await User.findOne({ imdbId: str})
return result._id
}
async function insertMovieToDb (obj) {
var movie = new Movie ({
imdbId: obj.id,
title: obj.name,
rank: obj.rank,
rating: obj.rating,
year: obj.year,
director: await findObjectIdByImdbId(obj.director)
})
await movie.save(function(err) {
if (err) {console.log(err)
return
}})
}
insertJsonFileToDb().catch(console.log)
Move the await findObjectIdByImdbId(obj.director) out of Movie initialization
E.g.
var directorObjId = await findObjectIdByImdbId(obj.director);
var movie = new Movie ({
imdbId: obj.id,
title: obj.name,
rank: obj.rank,
rating: obj.rating,
year: obj.year,
director: directorObjId
})

How to implement partial document embedding in Mongoose?

I have a simple relation between topics and categories when topic belongs to a category.
So schema looks like this:
const CategorySchema = new mongoose.Schema({
name: String,
slug: String,
description: String
});
And topic
const TopicSchema = new mongoose.Schema({
category: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Category'
},
title: String,
slug: String,
body: String,
created: {type: Date, default: Date.now}
});
I want to implement particular embedding of category into topic
{
category: {
_id: ObjectId('abc'),
slug: 'catslug'
},
title: "Title",
slug: "topictitle",
...
}
It will help me avoid unnecessary population and obtain performance bonuses.
I don't want to embed whole document because I want to changes categories sometimes (it is a rare operation) and maintain references.
Hope this helps, done it in my own project to save some RTTs in common use cases. Make sure you're taking care of both copies on update.
parent.model.js:
const mongoose = require('mongoose');
const childEmbeddedSchema = new mongoose.Schema({
_id: {type: mongoose.Schema.Types.ObjectId, ref: 'Child', auto: false, required: true, index: true},
someFieldIWantEmbedded: {type: String}
});
const parentSchema = new mongoose.Schema({
child: { type: childEmbeddedSchema },
moreChildren: { type: [{type: childEmbeddedSchema }] }
});
module.exports = mongoose.model('Parent', parentSchema);
child.model.js:
const mongoose = require('mongoose');
const childSchema = new mongoose.Schema({
someFieldIWantEmbedded: {type: String},
someFieldIDontWantEmbedded: {type: Number},
anotherFieldIDontWantEmbedded: {type: Date}
});
module.exports = mongoose.model('Child', childSchema);
parent.controller.js:
const mongoose = require('mongoose');
const Parent = require('path/to/parent.model');
exports.getAll = (req, res, next) => {
const query = Parent.find();
// only populate if requested! if true, will replace entire sub-document with fetched one.
if (req.headers.populate === 'true') {
query.populate({
path: 'child._id',
select: `someFieldIWantEmbedded ${req.headers.select}`
});
query.populate({
path: 'moreChildren._id',
select: `someFieldIWantEmbedded ${req.headers.select}`
});
}
query.exec((err, results) => {
if (err) {
next(err);
} else {
res.status(200).json(results);
}
});
};

Mongoose.populate() not showing any change in DB

I have been trying to populate from a user table and have been unsuccessful. Any help would be appreciated.
I am checking a variable isProvider
if(true)
then the data is saved in a provider table
else
in a customer table.
I want the user table to be an Auth table, so I want to populate a field called "userId" in these models. The id is being saved. When i print the results of populate, It shows a populated json but when i see it in the database it shows only the Id. I want to access the details of user table through the photographer table. How do i achieve this ?
User model
/*
* Title: User model
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
passportLocalMongoose = require('passport-local-mongoose');
var bcrypt = require('bcrypt-nodejs');
//Data model
var UserSchema = new Schema({
email: {
type: String,
unique: true,
required: true
},
password: {
type: String,
required: true
},
token: String,
mobile: String,
type: String,
createdOn: {type: Date, default: Date.now},
lastModifiedOn: {type: Date},
deleted: {type: Number, default: 0},
isPhotographer: {type: Boolean, default: false},
verified: {type: Boolean, default: false}
});
UserSchema.pre('save', function(next) {
var user = this;
if(this.isModified('password') || this.isNew) {
bcrypt.genSalt(10, function (err, salt) {
if(err) {
return next(err);
}
bcrypt.hash(user.password, salt, null, function (err, hash) {
if ( err) {
return next(err);
}
user.password = hash;
next();
});
});
} else {
return next();
}
});
UserSchema.methods.comparePassword = function (passw, cb) {
bcrypt.compare(passw, this.password, function( err, isMatch) {
if(err) {
return cb(err);
}
cb(null, isMatch);
});
};
UserSchema.plugin(passportLocalMongoose);
user = mongoose.model('User', UserSchema);
module.exports = user;
Provider model
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var User = require('./User');
//Data model
var providerSchema = new Schema({
userId: {
type: Schema.Types.ObjectId,
ref: 'User'
},
firstName: String,
lastName: String,
profilePicture: {
type: mongoose.Schema.Types.ObjectId,
ref: 'GFS'
},
email: String,
phone: Number,
address: String,
dob: Date,
createdOn: {type: Date, default: Date.now},
lastModifiedOn: {type: Date},
deleted: {type: Number, default: 0},
});
providerSchema.pre('save', function(next) {
this.lastModifiedOn = new Date;
next();
});
provider= mongoose.model('provider', providerSchema);
module.exports = provider;
Customer model
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var User = require('./User');
//Data model
var customerSchema = new Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
firstName: String,
lastName: String,
createdOn: {type: Date, default: Date.now},
lastModifiedOn: {type: Date},
deleted: {type: Number, default: 0},
});
customerSchema.pre('save', function(next) {
this.lastModifiedOn = new Date;
next();
});
customer = mongoose.model('Customer', customerSchema);
module.exports = customer;
Controller
if(user.isProvider) {
var provider= new providermodel({
userId: user._id,
firstName: req.body.firstName,
lastName: req.body.lastName
});
provider.save(function(err, docs) {
if(!err) {
pprovidermodel.findOne({_id: provider._id}).populate('userId').exec(function(err, docs) {
if(err) {
console.log(err);
}
else {
console.log(docs); ----> **Here populate works, but no changes in the database**
console.log("SO " + docs.userId.email);
}
})
}
})
}else {
var customer = new customermodel({
userId: user.id,
firstName: req.body.firstName,
lastName: req.body.lastName
});
customer.save(function(err) {
if(!err) {
customermodel.findOne({}).populate('userId').exec(function(err, docs)
{
console.log(err);
console.log(docs);
})
}
})
}
I think it's right. Populate don't change values in database only retrieve values when code is running.

Resources