Can i have a mongoose model that refers itself? - node.js

I have a model that looks like this
const employeeSchema = new mongoose.Schema({
empFirstName:{
type: String,
reuired: true
},
empLastName:{
type: String,
reuired: true
},
empEmail:{
type: String,
reuired: true
},
empPassword:{
type: String,
reuired: true
},
empConfirmPass:{
type: String,
reuired: true
},
empContactNum:{
type: Number,
reuired: true
},
empPosition:{
type: String,
reuired: true
},
empTeam:{
type: String,
reuired: true
}
});
const Employee = mongoose.model('EMPLOYEE', employeeSchema);
module.exports = Employee;
I'm trying to have another field named "mentor" in the employee model, who will again be an employee (a user from employee model) , so how do I make a model refer itself?
i want something like this
mentor :[{
type: mongoose.Schema.Types.ObjectId, ref: 'Employee'
}]
Does this work?
what is the correct way to do this?
also I wanted a teamMembers field which will be an array of 0 or more employees from employee model
how do i do this?

This will work. You had it correct in your question:
const employeeSchema = new Schema ({
mentor: [{ type: Schema.Types.ObjectId, ref: 'Employee' }],
teamMembers: [{ type: Schema.Types.ObjectId, ref: 'Employee' }],
})
I've added some routes as well just to give you an idea on how to use it.
// add team member to employee
index.post('/addTeamMember', (req, res) => {
Employee.findById(req.body.employeeId, (err, employee) => {
if (err) {
console.log(err);
} else {
employee.teamMembers.push(req.body.teamMemberId);
employee.save((err) => {
if (err) {
console.log(err);
} else {
res.redirect('/');
}
});
}
});
});
// add mentor to employee
index.post('/addMentor', (req, res) => {
Employee.findById(req.body.employeeId, (err, employee) => {
if (err) {
console.log(err);
} else {
employee.mentor.push(req.body.mentorId);
employee.save((err) => {
if (err) {
console.log(err);
} else {
res.redirect('/');
}
});
}
});
});
You can also populate those fields:
index.get('/populate', (req, res) => {
Employee.findById(req.body.employeeId)
.populate('mentor')
.populate('teamMembers')
.exec((err, employee) => {
if (err) {
console.log(err);
} else {
res.send(employee);
}
});
});
and just some suggestions:
Don't name your fields emFirstName, emLastName, etc. This is redundant as you already have your model named Employee. It also makes your code less legible. Instead just use firstName, lastName, etc.
I wouldn't recommend using all caps for a model name like 'EMPLOYEE' as this is not the recommended case for mongoose model names. They suggest upercase first letter, so Employee would be more correct.

Related

Mongoose: Child document does not update on parent document update

I've been on this problem for quite a while. I have 2 schemas:
**The User Schema**
const mongoose = require('mongoose');
const PaymentSchema = require('../models/payments').schema;
const UserSchema = new mongoose.Schema({
firstname :{
type : String,
required : true
} ,
lastname :{
type : String,
required : true
} ,
email :{
type : String,
required : true
} ,
country :{
type : String,
required : true
} ,
state :{
type : String,
required : true
} ,
city :{
type : String,
required : true
} ,
postal :{
type : String,
required : true
} ,
phone :{
type : String,
required : true
} ,
plan :{
type : String,
} ,
amount :{
type : String,
} ,
profit:{
type: String,
},
amountDue:{
type: String,
},
password :{
type : String,
required : true
} ,
withdrawals :[PaymentSchema],
payDate :{
type : Date,
default : Date.now
},
date :{
type : Date,
default : Date.now
}
});
const User= mongoose.model('User', UserSchema);
module.exports = User;
The payments Schema
const mongoose = require('mongoose');
const PaymentSchema = new mongoose.Schema({
firstname: {
type: String
},
lastname:{
type: String
},
balance:{
type: String
},
address: {
type: String,
required: true
},
amount: {
type: Number,
required: true
},
paid: {
type: Boolean,
required: true
},
userid:{
type: String
},
date: {
type: Date,
default: Date.now
}
});
const Payment = mongoose.model('Payment', PaymentSchema);
module.exports = Payment;
The logic is this: When a user makes payment, a POST request is sent and the information from the payment request is displayed on an admin dashboard. The POST request looks like this:
router.post('/users/:id/payments', function (req, res) {
User.findById(req.params.id, function (err, fuser) {
if (err) {
req.flash('error_msg', 'Something went wrong. Please login and try again')
res.redirect('/logout')
} else {
var pay = new Payment({
firstname: fuser.firstname,
lastname: fuser.lastname,
balance: fuser.amount,
amount: req.body.usd,
address: req.body.to,
paid: false,
userid: fuser.id
})
pay.save(function (err, pays) {
if (err) {
console.log(err)
} else {
User.findById(req.params.id, function (err, userr) {
userr.withdrawals.push(pays)
userr.save(function (err, user) {
if (err) {
req.flash('error_msg', 'Something went wrong. Please login and try again')
res.redirect('/logout')
} else {
res.redirect('/dashboard')
}
})
})
}
})
}
})
The information sent to the admin dashboard is then approved and the admin makes a PUT request to that particular payment, updating the "paid" property to "true". Like so:
router.put('/admin/withdrawals/:id', function (req, res) {
var update = {
paid: true
}
Payment.findByIdAndUpdate(req.params.id, update, function (err, user) {
if (err) {
res.send("error!")
} else {
User.findById(user.userid, function (err, uza) {
if (err) {
res.send("error!")
} else {
var amount = uza.amount
var deduct = req.body.aamount
var balance = parseInt(amount) - parseInt(deduct)
var updated = {
amount: balance,
}
User.findByIdAndUpdate(user.userid, updated, function (err, pays) {
if (err) {
res.send(err)
} else {
req.flash('success_msg', 'Withdrawal has been confirmed!')
res.redirect('/admin/dashboard')
}
})
}
})
}
})
})
The payment document gets updated to "true" but the embedded payment document on the User Schema still remains "false".
Please what do I need to do to update the paid property from "false" to "true" on both the main payment document and on the embedded payment document?
You need to link between the schemas like so:
withdrawals: [{ type: Schema.Types.ObjectId, ref: 'Payment' }],
Otherwise you have to update the data in both schemas manually!
See the docs here: https://mongoosejs.com/docs/api.html#schematype_SchemaType-ref

dynamic references by sending it from the route

I have been looking at the documentation and some other questions made, but I have not been able to do it, I need to consult another collection for an objectId, but these collections come dynamically in the routes.
The collections come req, as they are from many different clients
https://mongoosejs.com/docs/populate.html
//route
// example collection dynamic ssdf2451_users, ssdf2451_campus, ssdf2451_programs
router.get('/school/prueba/tipos/', async (req, res, next) => {
let users
let school
let prueba
try {
const Users = model(`ssdf2451_users`, UserSchema)
console.log(Users)
await Users.findOne({ _id: '5ef56f70d19aea6e70c82a50' })
.populate('schoolId')
.exec(function (err, usersDocuments) {
console.log(usersDocuments, err)
// handle err
prueba = usersDocuments
res.status(200).json(prueba)
})
} catch (err) {
next(err)
}
})
// Schema
import { Schema, model } from 'mongoose'
const UserSchema = new Schema({
state: {
type: Boolean,
default: false,
required: false
},
accountNumber: {
type: Number,
required: false
},
schoolId: {
type: Schema.Types.ObjectID,
required: true,
ref: 'schools'
},
campusId: {
type: Schema.Types.ObjectID,
required: true,
ref: dynamic
},
programsId: {
type: [Schema.Types.ObjectID],
required: false,
ref: dynamic
},
})
const User = model('users', UserSchema)
export { User, UserSchema }

Duplicate item returned from collection

Blog Schema:
{
body: { type: String, required: true },
title: { type: String, required: true },
published: { type: String, default: false },
date: { type: Date, default: Date.now },
user: { type: Schema.Types.ObjectId, ref: 'BlogUser' },
comments: [{ type: Schema.Types.ObjectId, ref: 'Comments' }],
likes:[{user:{ type: Schema.Types.ObjectId, ref: 'BlogUser' }}]
}
Like Route for adding a like:
exports.likeBlog = async (req, res) => {
const blog_id = req.params.blog_id;
const user_id = req.body.user_id;
await Blog.findByIdAndUpdate(
blog_id,
{
$push: {
likes: {
user: user_id,
},
},
},
{ new: true },
(err, newBlog) => {
if (err) res.status(422).json(err);
console.log(newBlog);
res.json(newBlog);
}
);
};
Blog Route for reciveing a blog:
exports.getBlogByID = async (req, res) => {
const blog_id = req.params.blog_id;
try {
const blog = await Blog.findById(blog_id)
.populate("comments")
.populate("user");
console.log(blog);
res.json(blog);
} catch (error) {
res.status(401).json(error);
}
};
When I add a like by calling Like route from client, I get a blog with correct amount of likes i.e only 1. But when I request blog from Blog Route it returns me with two objects inside "likes" array, with both same as each other(same id too). Why am I getting such result? Mind you that I call 'Blog Route' after calling 'Like Route'.
It worked fine after I changed "like route" to this:
exports.likeBlog = async (req, res) => {
const blog_id = req.params.blog_id;
const user_id = req.body.user_id;
const blog = await Blog.findById(blog_id);
blog.likes.unshift({ user: user_id });
await blog.save();
Blog.findById(blog_id)
.then((result) => {
res.json(result);
})
.catch((error) => {
res.status(501).json({ error });
});
};
I still don't know what's the difference between the two though.

Axios.delete() not triggering as expected

So I've got a classes Model which contains an array of people who will attend the class, I am trying to remove people from the classes.
So this is the Model:
const mongoose = require('mongoose');
const classMembersSchema = mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user',
}
})
const classSchema = mongoose.Schema({
location: {
type: String,
required: true
},
type: {
type: String,
required: true
},
name: {
type: String,
required: true
},
time: {
type: String,
required: true
},
classMembers: [classMembersSchema]
});
module.exports = mongoose.model('createClass', classSchema);
The classMembers Array is the one I mentioned that I am trying to remove members from. classMembers: [classMembersSchema].
This is the axios.delete:
deleteClassHandler = () => {
axios.delete('/api/classes/' + this.props.id + '/user/' + this.props.userId)
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
});
}
This is the route:
router.delete('/:id/user/:userId', ClassesController.deleteUser);
This is the controller:
exports.deleteUser = (req, res) => {
GymClass.findById({
_id: req.params.id
}, 'classMembers', (err) => {
if (err) {
res.status(401).json({
message: "Error Occured!"
})
} else {
GymClass.findByIdAndDelete({
"classMembers.userId" : mongoose.Types.ObjectId(req.params.userId)
}, (err) => {
if(err) {
console.log('Keeps hitting here!');
res.status(401).json({
message: "Error Occured!"
})
} else {
res.status(200).json({
message: "Success!"
})
}
});
}
})
}
Everything works fine until it hits the console.log('Keeps hitting here!');
At the start of the function the req.params.id which is the class Id of which class we want to modify and the req.params.userId which is the user we want to remove from the Array inside the Model do have the right values but when it gets to that step it gives me the Error.
I'm thinking it could be that it is not finding this:
GymClass.findByIdAndDelete({
"classMembers.userId" : mongoose.Types.ObjectId(req.params.userId)
Since it's in an Array within the classMembers. Any idea or advice to get this to work? Many thanks.

How to obtain object id and also how to update

I am trying to obtain the object id for any article already in db so that I can validate that the article exists before comments are made.
The issue is on the router (/blog/article/comment). I cannot get the article object id from /blog/article/:postid. I want to pass this id to articleId like this:
articleId: req.params.postid
I have also tried:
articleId: req.article._id
model structure: comment.js
var mongoose = require('mongoose');
var CommentSchema = new mongoose.Schema({
content: { type: String },
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
articleId: { type: mongoose.Schema.Types.ObjectId, ref:'Article' },
dateCommented: { type: Date, default : Date.now }
});
Article model: article.js
var ArticleSchema = new mongoose.Schema({
category: { type: mongoose.Schema.Types.ObjectId, ref: 'Category' },
commentId:{type: mongoose.Schema.Types.ObjectId, ref:'Comment'},
title: String,
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User'},
blog: [{
topic: { type: String, unique: false, lowercase: true },
body: { type: String, unique: false, lowercase: true },
tags: [ 'first', 'mongodb', 'express'],
created: Date,
modified: { type : Date, default : Date.now },
state: { type: String, unique: false, lowercase: true }
}]
});
main.js
router.param('postid', function(req, res, next, id) {
if (id.length !=24) return next(new Error ('The post id is not having the correct length'));
//articleId: req.param('postid'),
Article.findOne({ _id: ObjectId(id)}, function(err, article) {
if (err) return next(new Error('Make sure you provided correct post id'));
req.article = article;
next();
});
});
router.get('/blog/article/:postid', function (req, res, next) {
Article.findById({ _id: req.params.postid }, function (err, article) {
if (err) return next(err);
res.render('main/publishedArticle', {
article: article
});
});
});
router.post('/blog/article/comment', function(req, res, next) {
async.waterfall([
function(callback) {
var comment = new Comment({
articleId: req.params.postid,
content: req.body.content,
user: req.user._id
});
comment.save(function(err) {
if (err) return next (err);
req.flash('success', 'Thank you for your comment');
callback(err, comment);
});
},
function(comment) {
Article.update({_id : comment.articleId }, { $set: { commentId: {} }}, function(err, updated) {
if (updated) {
res.redirect('/')
}
});
}
]);
});
Another issue I have is how to update the commentId for each comment in the Article
Article.update({_id : comment.articleId }, { $set: { commentId: {} }}, function(err, updated)
Since the /blog/article/comment route is a post request. Just submit your articleId in the body of that request. You'll have to send it up from the client. You can access it with req.body.articleID (If that is what you call the variable).
See here for more info on POST requests in node.
For your second question:
Within your article schema you have commentId, That is a single record. What you want is an array of comments. Something like this:
comments: [{type: mongoose.Schema.Types.ObjectId, ref:'Comment'}]
Then within your code...
...
function(comment) {
//comment should contain all the comments
//Grab the article
Article.findOne({ _id: comment.articleId}, function(err, article){
//Go through all the comments in 'comment' compare them with the ones in artcle.comments.
//The ones that aren't already in the article object get put into newComments...
var newComments = [];
Article.update({ _id: comment.articleId }, { $addToSet: { comments: newComments } }, function(err, updated) {
if (updated) {
res.redirect('/')
}
});
});
}
...
I didn't fully implement the code, but it should get you off to the right start.
addToSet Documentation
Some more examples of add to set

Resources