Remove document with references Mongoose - javascript

I'm having trouble removing Question when Survey gets deleted which is referenced in the Survey model. The survey gets deleted, but the question still remains in the database.
Survey Schema:
let surveyModel = mongoose.Schema(
{
Title: String,
Type: [String],
Questions: { type: mongoose.Schema.Types.ObjectId, ref: "questions" },
Answered: { type: Number, default: 0 }, // how many times users answered
DateCreated: { type: Date, default: Date.now }, // date created
Lifetime: { type: Date, default: Date.now }, // Survey expiry
User: { type: mongoose.Schema.Types.ObjectId, ref: "users" }
},
{
collection: "surveys",
}
);
Question Schema:
let questionModel = mongoose.Schema(
{
MC: {
QuestionText: String,
Options: [String],
},
TF: {
QuestionText: String,
Options: Boolean,
}
},
{
collection: "questions",
}
);
module.exports = mongoose.model("Question", questionModel);
Code I have right now:
// process survey delete
module.exports.processDeletion = (req, res, next) => {
let id = req.params.id;
Survey.remove({ _id: id }, (err) => {
Question.remove({_id: { $in: req.body.Questions }}, (err, res) => {
if (err) {
console.log(err);
res.end(err);
}
});
if (err) {
console.log(err);
res.end(err);
} else {
// refresh survey list
res.redirect("/live-surveys");
}
});
};

Your first step should be delete childrens, that is Question.
Note: i think "Questions" should be more of 1, then it must be an array of Reference in the Survey model. But, for this example it will to be as you have setted.
Then, your delete route, may to be some as:
router.delete("/delete/:surveyById", deleteSurvey");
router.param("surveyById", surveyId"); //This one is your middleware
//surveyController.js
const Survey = require("../models/Survey");
const Question = require("../models/Question");
exports.surveyId = (req, res, next, id) => {
Survey.findById(id).exec((err, data) => {
if(!data || err) return res.status(400).json({error: "Survey not found")};
else {
req.survey = data;
next();
}
)};
};
exports.deleteSurvey = (req, res) => {
Questions.findByIdAndRemove(req.survey.Questions) //Here your Questions Id
.exec((err, data)) => {
if(err) return res.status(400).json({error: "Error to delete questions"});
Survey.findByIdAndRemove(req.survey._id).exec((err, data) => {
if(err) return res.status(400).json({error: "Error to delete Survey"});
return res.json({ message: "Deleted")};
});
});
};
Also you can do with async await if you prefer, is the same, and you will have a better control about your code.

Related

Mongoose v6.2.7 new Model.save() method not working tried promise, callback and async await in try-catch nothing works

Initially, the project was set up with promise support, and all queries used promise like method.then().catch() later some were converted to try-catch with async await. All worked fine until a few weeks ago when all of a sudden some methods stopped working, I have tried converting the methods to many different variations from promise to callback and to try-catch. await new Model(object).save() does not save the record. I am using mongoose.createConnection because I need to connect to two databases.
Here is how I init my DB
const mongoose = require("mongoose");
mongoose.Promise = require('bluebird');
function makeNewConnection(uri, id) {
const db = mongoose.createConnection(uri);
db.on("error", function(error) {
console.log(
`MongoDB :: connection ${this.name} :: ${id} ${JSON.stringify(error)}`
);
db.close().catch(() =>
console.log(`MongoDB :: failed to close connection ${this.name}`)
);
});
db.on("connected", async function() {
mongoose.set("debug", function(col, method, query, doc) {
console.log(
`MongoDB :: ${
this.conn.name
} :: ${id} ${col}.${method}(${JSON.stringify(query)},${JSON.stringify(
doc
)})`
);
});
console.log(`MongoDB :: connected ${this.name} :: ${id}`);
require("../models/notification.model");
if (process.env.DATABASE_ENV === "local" && id === "cloud") {
require("../helpers/data.sync.helper");
}
});
db.on("disconnected", function() {
console.log(`MongoDB :: disconnected ${this.name} :: ${id}`);
});
return db;
}
// Use
let local, cloud;
if (process.env?.DATABASE_ENV === "local") {
// Connect to local database
local = makeNewConnection(
`mongodb://${process.env.DATABASE_USER}:${process.env.DATABASE_PASS}#127.0.0.1:27017/Eyemasters?retryWrites=true&authSource=admin&useNewUrlParser=true&useUnifiedTopology=true&w=majority`,
"local"
);
// Connect to cloud database
cloud = makeNewConnection(
`mongodb://${process.env.DATABASE_USER}:${process.env.DATABASE_PASS}#64.227.44.132:27017/Eyemasters?retryWrites=true&w=majority`,
"cloud"
);
// Start Database sync helper
} else {
// Connect to cloud local database
local = makeNewConnection(
`mongodb://${process.env.DATABASE_USER}:${process.env.DATABASE_PASS}#localhost:27017/Eyemasters?retryWrites=true&w=majority`,
"local"
);
}
module.exports = {
local,
cloud
};
And here is one of my models having the issue.
const mongoose = require("mongoose");
mongoose.Promise = require('bluebird');
const { local, cloud } = require("../config/database.config");
const { genId } = require("../helpers/doc.id.generator");
const validator = require("validator");
const UserSchema = mongoose.Schema(
{
_id: mongoose.Schema.Types.ObjectId,
email: {
type: String,
required: true,
unique: true,
validate: {
validator: validator.isEmail,
message: "{VALUE} is not a valid email",
isAsync: false
}
},
hash: { type: String, bcrypt: true, rounds: 10 },
firstname: { type: String, required: true },
lastname: { type: String, required: true },
phone: { type: String },
dateOfBirth: { type: Date },
designation: { type: String },
role: { type: mongoose.Schema.Types.ObjectId, ref: "Role" },
passport: { type: String },
accountDetails: {
name: String,
number: Number,
bank: String
},
defaultBranch: {
type: mongoose.Schema.Types.ObjectId,
ref: "Branch"
},
branches: [{ type: mongoose.Schema.Types.ObjectId, ref: "Branch" }],
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
lastModifiedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
webpush: { type: Object },
inactive: { type: Boolean, default: true },
approved: { type: Boolean, default: false },
activationCode: { type: String, unique: true },
activationExpiresIn: { type: Date }
},
{ toJSON: { virtuals: true }, timestamps: true }
);
UserSchema.plugin(require("mongoose-bcrypt"));
genId(UserSchema);
UserSchema.pre("save", function(next) {
if (!this.createdBy) this.createdBy = this._id;
if (!this.lastModifiedBy) this.lastModifiedBy = this._id;
});
exports.User = exports.User || local.model("User", UserSchema);
exports.OnlineUser = exports.OnlineUser || cloud.model("User", UserSchema);
And Lastly my controller setup;
exports.create = async (req, res) => {
// Validating entered data
if (
!req.body.firstname ||
!req.body.lastname ||
req.body.firstname.length < 3 ||
req.body.lastname.length < 3 ||
!req.body.email ||
!req.body.role ||
req.body.email.length < 3
) {
return res.status(400).send({
message: "Please fill in all required fields"
});
}
try {
const user = await User.findOne({
email: req.body.email.toLowerCase()
});
if (user) {
throw new Error("User with email " + req.body.email + " already exist");
}
console.log("Before create");
let newUser = new User({
...req.body,
activationCode: randtoken.uid(16),
activationExpiresIn: moment.utc().add(30, "minutes"),
email: req.body.email.toLowerCase()
});
console.log(newUser.save);
const userData = await newUser.save();
console.log("Saved");
let transaction = new DbTransaction({
transactionType: "insert",
modelName: "User",
data: userData,
clients: [process.env.DATABASE_CLIENT_ID],
isProcessed: false
});
await transaction
.save()
.then(d => console.log("Transaction updated successfully"))
await User.populate(userData, populateQuery, (err, data) => {
if (err) throw new Error(err);
return res
.status(201)
.send({ message: "User created successfully", user: data });
});
} catch (err) {
console.log(err);
console.log(err.kind);
return res.status(500).send({
message: err.message
});
}
};
I have tried different variants of javascript promise based work flow. Like Model.method().then().catch(), async try-await Model.method()-catch and lastly callback Model.method((err, data)=>{ //do something }).
None of the above conbination has worked. My observation is that mongoose just logs "done" into the console for this method but never action is never actually performed.
Your help is greatly appreciated, I have absolutely no idea why this is not working.
Thank you.
To all who made effort to assist, Thank you for the help.
I don't know why I am seeing the problem after posting here.
The issue was coming from not calling next in the middleware inside the model setup;
UserSchema.pre("save", function(next) {
if (!this.createdBy) this.createdBy = this._id;
if (!this.lastModifiedBy) this.lastModifiedBy = this._id;
});
Replace with;
UserSchema.pre("save", function(next) {
if (!this.createdBy) this.createdBy = this._id;
if (!this.lastModifiedBy) this.lastModifiedBy = this._id;
next();
});
Thank you all once again for your support.

Express doesn't get another user with .map()

I came to a problem, where I can create conversations with multiple people 2 and so on. However, I can't understand why it doesn't store data to seperate User models.
Here is a code that you only need to know:
router.post(
"/",
auth,
[
check("conversators", "There should be at least two conversators").isLength(
{ min: 2 }
),
],
async (req, res) => {
const { conversators } = req.body;
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
try {
let conversation = new Conversation({
user: req.user.id,
conversators: conversators,
});
await conversators.map(async (conversator) => {
let user = await User.findById(conversator);
let newData = user;
newData.conversations.push(conversation.id);
console.log('Created data', newData);
let newUser = await User.findOneAndUpdate(
{ user: conversator },
{
$set: {
newData,
},
},
{ new: true }
);
await newUser.save();
console.log(newUser);
});
await conversation.save();
res.status(200).json(conversation);
} catch (error) {
console.error(error.message);
res.status(500).send("Server error.");
}
}
);
module.exports = router;
What I can assure is that this line: console.log('Created data', newData); prints the desired data. However, the next console: console.log(newUser); prints the same User model as the previous one.
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const UserSchema = new Schema({
name: {
type: String,
required: true,
},
surname: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
conversations: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "conversation",
},
],
date: {
type: Date,
default: Date.now,
},
});
module.exports = User = mongoose.model("user", UserSchema);
The reason might be the difference in search methods used to get a record for newData and newUser. You have used User.findById for newData, which will obviously return different objects for different ids. But User.findOneAndUpdate uses filter criteria that may satisfy several results, but only first will be returned. So it boldly depends on what that user field is.
Here is the part that I changed and started to see the data on MongoDB:
await conversators.map(async (conversator) => {
let user = await User.findById(conversator);
let newData = user;
newData.conversations.push(conversation.id);
new Promise(async (resolve, reject) => {
user = await User.findOneAndUpdate(
{ id: conversator },
{
$set: {
newData,
},
},
{ new: true }
);
return resolve;
})
return await user.save();
});
Posted on behalf of the question asker

Trying to increment by 1 every time the page is viewed

I'm trying to figure out how to update the field by incrementing +1 each time the page is visited and if it has never been visited then add it to the DB.
Currently, this is what I have got but it does not seem to do much. I must have gone wrong somewhere and I have not yet implemented the part where if the page has never been viewed then create a new object in the array which is stored in the database.
Little note: Where I created the map they do match with the same ID if I view the page with the same ID as the one stored in the database but no increment happens.
exports.pageVisitCount = (req, res, next) => {
User.findById({
_id: req.userData.userId
}, 'visits', function (err, pageVists) {
if (err) {
res.status(401).json({
message: "Error Occured!"
})
} else {
const pageCounts = pageVists.visits;
pageCounts.map(page => {
const postViewed = req.body.postId;
if (page.postId.toString() === postViewed) {
User.findByIdAndUpdate({
_id: req.userData.userId
}, {
$set: {
visits: [{
"postId": postViewed,
$inc: { visitCount: 1 }
}]
}
}, {
upsert: false
},
(err) => {
if (err) {
res.status(401).json({
message: "Error Occured!"
})
} else {
res.status(200).json({
message: "Update successful!"
})
}
});
}
});
}
});
}
This is the schema I am using:
const visitsSchema = new Schema ({
postId: {
type: String
},
visitCount: {
type: Number
}
})
const userSchema = mongoose.Schema({
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
role: {
type: String,
required: true
},
answers: {
type: String
},
visits: [visitsSchema]
});
Any feedback would be highly appreciated, I would like to mention that I am new to backend, thanks!
To avoid using the map to filter the visits after querying the visits of the user under consideration, I suggest you let mongodb do that for you. In this case you first do a find based on both the user id and the postId. If you get a record matching both criteria you are sure you can easily update the user visits by incrementing the particular visits visitCount by 1.
Otherwise i.e. if they don't match any records then since u might be using a valid user id then such user has not visited such post. So you now create a new visit with the postId and initialize its visitCount to 1 (Although we intend to create, but since its a subdocument you'll need use $push). Enough of the talking try the code below.
exports.pageVisitCount = (req, res, next) => {
User.findOne({
_id: req.userData.userId, "visits.postId": req.body.postId
}, 'visits.$', function (err, user) {
if (err) {
res.status(401).json({
message: "Error Occured!"
});
} else {
if(user == null){
User.findByIdAndUpdate({
_id: req.userData.userId
}, {
$push: {
visits: {
"postId": req.body.postId,
visitCount: 1
}
}
}, function (err) {
if(err)
return res.status(401).json({
message: "Error Occured when creating new visit!"
})
return res.status(200).json({
message: "Success"
})
})
}
User.update({
_id: req.userData.userId, "visits.postId": req.body.postId
}, {
$inc: { "visits.$.visitCount": 1 }
},(err) => {
if (err) {
res.status(401).json({
message: "Error Occured!"
})
} else {
res.status(200).json({
message: "Update successful!"
})
}
});
}
});
};

How could I reference a model to my User model with Express/Mongoose

I have two models, one being my User model and the other being my Course model. I would like to have it so when a User (Teacher) creates a course, it assigns that course to them and vice versa. Here are my models to explain better:
Course Schema/Model:
var CourseSchema = new Schema({
courseID: {
type: Number,
unique: true
},
courseName: String,
courseDesc: {
type: String,
default: "No course description provided."
},
coursePicture: {
type: String,
required: false
},
teacher: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
],
students: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Student'
}
]
})
User Schema/Model:
var UserSchema = new mongoose.Schema({
firstName: String,
lastName: String,
email: String,
courses: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Course'
}
],
password: String
});
Basically, I want to have it so on my frontend, I could do things like course.teacher.firstName or user.courses. My schemas are in two different files, but I believe that is fine. It's like assigning a user a post when they create it. I don't know how I could do this, as I've tried multiple things.
Right now, I currently have this for creating a course.
// Creates a new course
router.post('/create', function (req, res) {
Course.create({
courseID : req.body.courseID,
courseName : req.body.courseName,
courseDesc : req.body.courseDesc,
coursePicture : req.body.coursePicture,
teacher : req.body.id,
students: req.body.students
},
function (err, course) {
if (err) return res.status(500).send("There was a problem adding the information to the database.");
res.status(200).send(course);
});
});
I have already referenced the User model in the controller where that code ^ belongs as so var User = require('../user/User');
I believe that is needed to pull this off. If you have any questions, please let me know as I'm not the best at explaining things like this.
Hope someone can help me out!
Thanks.
// Creates a new course
router.post('/create', function (req, res) {
Course.create({
courseID : req.body.courseID,
courseName : req.body.courseName,
courseDesc : req.body.courseDesc,
coursePicture : req.body.coursePicture,
teacher : req.body.id, // find this user
students: req.body.students,
attendance: req.body.attendance
},
function (err, course) {
User.findById(req.body.id, function(err, user) {
user.update({
$push: {
courses: course._id
}
}, function(err) {
if (err) return res.status(500).send("There was a problem adding the information to the database.");
res.status(200).send(course);
})
})
});
});
This is an issue of database design. There should only be one place where information about a course is stored, the Courses table, and the Users table should know nothing about courses. There should be a table the relates a course to a user: a UserCourseRelations table.
I would strongly avoid the approach of storing an array of courseIds that a user is related in the user table as this is unnecessary coupling and so is not good database design. Also, it'll bog down reads to your Users table as those arrays grow on every row.
Here's how I would approach this. Note that some of this code uses ES6 syntax. The following code is untested, but should work. Take a look:
Create CourseSchema and CourseModel
var CourseSchema = new mongoose.Schema({
courseID: {
type: Number,
unique: true
},
courseName: String,
courseDesc: {
type: String,
default: "No course description provided."
},
teacherId: {
type: mongoose.Schema.Types.ObjectId,
}
coursePicture: {
type: String,
required: false
},
students: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Student'
}
]
})
CourseSchema.statics.createNew = function(data, callback) {
// do some verification here
// insert the new course
return new this(data).save((err, dbCourse) => {
if (err) {
return callback(err)
}
UserCourseRelationSchema.insertNew('teacher', userId, courseID, (err, dbUserCourseRelation) => {
if (err) {
return callback(err)
}
// done. return the new course
callback(null, dbCourse)
})
})
CourseSchema.statics.getByIds = function(courseIDs, callback) {
// find all of the courses where the courseID is in the courseIDs array
// see https://docs.mongodb.com/manual/reference/operator/query/in/
this.find({courseID: {$in: courseIDs}}, (err, courses) => {
if (err) {
// something went wrong
return callback(err)
}
callback(null, courses)
})
}
}
let CourseModel mongoose.model('courses', CourseSchema);
Create UserCourseRelationSchema and UserCourseRelationModel that relates a course to a user and vice versa
var UserCourseRelationSchema = new mongoose.Schema({
userId: {
type: String,
required: true,
},
courseID: {
type: Number,
required: true,
},
type: {
type: String,
enum: ['teacher', 'student'],
required: true,
},
});
UserCourseRelationSchema.statics.createNew = function(type, courseID, userId, callback) {
// do some verification here. I suggest making sure this relation doesn't already exist
// insert the new course
return new this({
courseID: courseID,
userId: userId,
type: type,
}).save((err, dbUserCourseRelation) => {
if (err) {
return callback(err)
}
// return the new relation
callback(null, dbRelation)
})
}
UserCourseRelationSchema.statics.getTeacherRelationCourseIdsByUserId = function(userId, callback) {
let query = this.find({userId: userId, type: 'teacher'})
query.distinct('courseID') // get an array of only the distinct courseIDs
query.exec((err, courseIDs) => {
if (err) {
// something went wrong
return callback(err)
}
callback(null, courseIDs)
})
}
let UserCourseRelationModel = mongoose.model('user_course_relations', UserCourseRelationSchema);
Create UserSchema and UserModel
var UserSchema = new mongoose.Schema({
firstName: String,
lastName: String,
email: String,
password: String
});
UserSchema.statics.getAllCoursesById = function(userId, callback) {
// get the relations for the courses the user is a teacher of
UserCourseRelationModel.getTeacherRelationCourseIdsByUserId(userId, (err, courseIDs) => {
// get the courses by the returned coursIDs
CourseModel.getByIds(courseIDs, (err, courses) => {
if (err) {
// something went wrong
return callback(err)
}
callback(nul, courses)
})
})
}
let UserModel = mongoose.model('users', UserSchema);
// -- create the router
// Creates a new course
router.post('/create', function (req, res) {
CourseModel.createNew({
courseID : req.body.courseID,
courseName : req.body.courseName,
courseDesc : req.body.courseDesc,
coursePicture : req.body.coursePicture,
teacher : req.body.id,
students: req.body.students
}, function (err, course) {
if (err) return res.status(500).send("There was a problem adding the information to the database.");
res.status(200).send(course);
});
});
// -- done
I also suggest using promises if possible as it makes all of this logic much simpler.

Mongoose assign a collection to another

I am trying to add a post to a user collection after the user was created with empty posts. I have tried with populate with no success .. any help is much appreciated.
// Post Model
const mongoose = require('mongoose');
const { Schema } = mongoose;
const UserModel = require('./user-model');
let PostSchema = new Schema({
author: {
ref: 'users',
type: Schema.Types.ObjectId
},
content: String,
description: String,
date: {
default: new Date(),
type: Date
},
title: String,
updatedAt: {
default: new Date(),
type: Date
}
});
let PostModel = mongoose.model('posts', PostSchema);
module.exports = PostModel;
// User Model
const mongoose = require('mongoose');
const { Schema } = mongoose;
const PostModel = require('./post-model');
let UserSchema = new Schema({
name: {
type: String
},
email: {
lowercase: true,
type: String,
trim: true,
unique: true
},
password: {
type: String,
},
postList: [{
ref: 'posts',
type: Schema.Types.ObjectId
}],
});
const UserModel = mongoose.model('users', UserSchema);
module.exports = UserModel;
// save post controller
exports.savePost = (request, response, next) => {
let { author, description, title } = request.body;
let post = new PostModel({ author, description, title }).save();
UserModel.findById(author)
.then((user) => {
user.postList.push(post);
// Still Fails
// How can i assign the post to the user ?
});
}
Is there any way of doing this other then push or populate ?
To solve this problem I prefer to use $push of mongo
UserModel.findOneAndUpdate({
_id: author.id,
{
$push: {
postList: post
}
}
});
You need to follow this process to save successfully
Save post if success then
Update user to push postId in postlist
can try this one
exports.savePost = (request, response, next) => {
let post = new PostModel(request.body)
post.save(function(err, data) {
if(err) {
//return error
}
// check by console your author is present or not
// let author in your req.body
let author = req.body.author
UserModel.findOneAndUpdate({_id: author},{$push: {postList: post._id}},{new:true} function(error, user) {
if(error) {
// return error
}
console.log(user)
// return success
})
});
}
exports.savePost = (request, response, next) => {
let { user, description, title } = request.body;
let post = new PostModel({ user, description, title });
post.save()
.catch((error) => {
if (error)
throw new Error(error);
})
.then((post) => {
UserModel.findOneAndUpdate({ _id: user }, {$push: { postList: post._id } })
.populate('postList')
.catch((error) => {
if (error)
throw new Error(error);
})
.then((user) => {
user.postList.forEach((item, postion) => {
console.log(`${item} -at ${postion} \n`);
});
});
});
}
This is what i did and it worked after all. I don't know if this is the correct solution but this is working.

Categories