How to get around E11000 MongoError without deleting 'unique: true' - node.js

I am trying to build a forum in order to learn the MEAN stack. I ran into an issue while using mongoose...
I have this...
var UserSchema = new Schema({
id: ObjectId,
firstName: String,
lastName: String,
role: String,
email: {
type: String,
unique: true
},
password: String,
workers: [WorkerSchema]
});
var TopicSchema = new Schema({
id: ObjectId,
title: String,
moderator: UserSchema,
posts: [PostSchema]
});
var Topic = mongoose.model('Topic', TopicSchema);
app.post('/topics', requireLogin, function(req, res) {
User.findOne({"email": req.session.user.email}, function(err, user) {
if (user.role == "moderator" || user.role == "admin") {
var topic = new Topic({
title: req.body.title,
moderator: req.session.user,
posts: []
});
topic.save(function(err) {
if (err) console.log(err);
res.status(204).end();
});
}
});
});
My issue is this... When I POST a topic to /topics, it works the first time, populating the topics collection with one item. But then, when I POST to /topics again, from the same user, I get an E11000 MongoError that looks like this:
message: 'E11000 duplicate key error index: MY_MONGO_DB.topics.$moderator.email_1 dup key: { : "myuser#example.com" }'
I know that removing the 'unique: true' property from the email field of UserSchema would fix this issue, but I don't want to remove that uniqueness property since I use it elsewhere in my code to ensure that users are unique by email.
Is there any way around this? In other words, is there any way to keep the 'unique: true' property and also retain the ability of users to be able to post multiple topics without triggering the E11000 error?

What you did was to embed the user. In your database, the resulting document would look something like
{
...
moderator: {..., email: "john#example.com"}
}
Which, of course, would violate the unique constraint if you have the same person as a moderator twice.
What you should do instead is to reference the user in your schema:
var user = mongoose.model('User', UserSchema);
var TopicSchema = new Schema({
id: ObjectId,
title: String,
moderator: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
posts: [PostSchema]
});

Related

Nodejs- combine two diffrent lists into one

I've got two models in mongoose:
const user = mongoose.Schema({
name: String,
...
});
and
const moderator = mongoose.Schema({
name: String,
...
});
When I access these collections with:
user.find()
I get in response list of users and moderators separetly.
What I want to achive is to join these two list together to get a list based on this model:
const myDataSchema = mongoose.Schema({
user: {type: Schema.Types.ObjectId, ref: 'user'},
moderator: {type: Schema.Types.ObjectId, ref: 'moderator'}
});
Where one of attributes (either user or moderator) will be set.
I solved my problem. Instead of using the mongoose schema I created my "own object". My code:
let list= [];
users.forEach( function (user)
{
list.push({user: user});
});
moderators.forEach( function (moderator)
{
list.push({moderator: moderator});
});
It's probaly not the most efficient way but it works.

Express: mongodb mongoose linking entities

I'm building a simple web app where a company sends out a question to its employees requesting for feedback. Still learning about mongodb. Been playing around with it all week & I'm slowly getting a good hang of it with some helpful assistance on the forums but only now I realize I have been using a flawed thought process to design the schema. I was initially using a user's response as a field in the UserSchema but I have now removed it (as commented out here) as I realized this is not a user's property but rather a variable that keeps changing (yes/no/null). I now have to create a separate AnswersSchema (I was told I'll need one but I stubbornly argued against it - saw no sense in at the time I started the project) which I have done now (correct me if it's wrongly written/thought out). My question now is how do I modify my query in the api to link all the three entities together on a save operation in the router post? Please note the save operation code shown here works but is flawed as it's for when the user has a response as one of their properties. So now only the user's name shows up on the angular front-end after I removed response on UserSchema which makes sense.
var QuestionSchema = Schema({
id : ObjectId,
title : String,
employees : [{ type: ObjectId, ref: 'User'}]
});
var UserSchema = Schema({
username : String,
//response : String,
questions : [{ type: ObjectId, ref: 'Question'}]
});
//new schema/collection I've had to create
var AnswerSchema = Schema({
response : {type :String, default:null},
question : { type: ObjectId, ref: 'Question'},
employees : [{ type: ObjectId, ref: 'User'}],
})
module.exports = mongoose.model('Question', QuestionSchema);
module.exports = mongoose.model('User', UserSchema);
module.exports = mongoose.model('Answer', AnswersSchema);
api.js
Question.findOne({ title: 'Should we buy a coffee machine?'}).exec(function(err, question) {
//example data
var user = new User([{
"username": "lindelof",
"response": "yes",
},{
"username": "bailly",
"response": "no",
},{
"username": "suzan",
"response": "yes",
}]);
question.employees = [user1._id];
user.questions = [question._id];
question.save(function(err) {
if (err) throw err;
console.log(question);
user1.save(function(err) {
if (err) throw err;
});
});
});
console.log('entry saved >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>');
}
UPDATE
You did the right thing by adding AnswerSchema, as it's a many to many relationship. A question can be answered by many users (employees). A user can answer many questions. Therefore, it's good to have answer as an associative collection between the two.
With this relationship in mind, you need to change your schema a little:
var QuestionSchema = Schema({
id : ObjectId,
title : String,
//employees : [{ type: ObjectId, ref: 'User'}]
});
var UserSchema = Schema({
username : String,
//response : String,
//questions : [{ type: ObjectId, ref: 'Question'}]
});
var AnswerSchema = Schema({
response : {type :String, default:null},
question : { type: ObjectId, ref: 'Question'},
employee : { type: ObjectId, ref: 'User'}, //a single employee
});
Now, to know if a certain user has answered a question already, just search Answer with his and the question's ids:
Answer.findOne({
question: questionId,
employee: userId
})
.exec(function(err, answer) {
if (err) {
} else if (!answer) {
//the employee has not answered this question yet
} else {
//answered
}
});
Lastly, your submit-answer API should expect a body that contains questionId and userId (if signed in, you can get userId from session or token also). This route updates existing answer, else creates it (for create-only use create function)
router.post('/', function(req, res) {
//req.body = {question: "594315b47ab6ecc30d5184f7", employee: "594315d82ee110d10d407f93", response: "yes"}
Answer.findOneAndUpdate({
question: req.body.question,
employee: req.body.user
},
req.body,
{
upsert: true //updates if present, else inserts
}
})
.exec(function(err, answer) {
//...
});
});

Retrieve Array in Subdocument MongoDB

I have a Users model structure somewhat like this:
const userSchema = new mongoose.Schema({
email: { type: String, unique: true },
password: String,
todosDo: [models.Do.schema],
}
And the child "Do" schema somewhat like this (in a different file):
const doSchema = new mongoose.Schema({
name: {type: String, default : ''},
user: {type: mongoose.Schema.ObjectId, ref: 'User'},
createdAt: {type : Date, default : Date.now}
});
And I'm trying to figure out how to retrieve the todosDo array for the signed in user. This is what I've got so far:
// Get all "Do" todos from DB
// Experimenting to find todos from certain user
User.findById(req.user.id, function(err, user){
if(err){
console.log(err);
} else {
doTodos = user.todosDo, // this obviously doesn't work, just an idea of what I was going for
console.log(doTodos);
finished();
}
});
Am I referencing the child/parent wrong or am I just not retrieving the array right? Any help is greatly appreciated!
As far I guess you may want to edit as raw js objects so you need to use lean() function. without using lean() function user is mongoose object so you can't modify it.
can try this one:
User.findById(req.user.id)
.lean()
.exec(function (err, user) {
if(err){
console.log(err);
return res.status(400).send({msg:'Error occurred'});
}
if(!user) {
return res.status(400).send({msg:'User Not found'});
}
doTodos = user.todosDo;
console.log(user.todosDo); // check original todos
console.log(doTodos);
return res.status(200).send({doTodos : doTodos }); // return doTodos
});
and to refer child schema in parent schema from different model you can access a Model's schema via its schema property.
say in doSchema.js file
const doSchema = new mongoose.Schema({
name: {type: String, default : ''},
user: {type: mongoose.Schema.ObjectId, ref: 'User'},
createdAt: {type : Date, default : Date.now}
});
module.exports = mongoose.model( 'DoSchema', doSchema );
in user.js file
var DoModel = require('./doSchema');// exact path
const userSchema = new mongoose.Schema({
email: { type: String, unique: true },
password: String,
todosDo: [DoModel.schema],
}
Thanks for your help everybody! My problem was that I needed to push all the newly created todos in the post route to todosDo, so then I could retrieve them at the get route. Everything's working now!

Store objects in array using mongodb, node and express

I'm trying to build a messaging system for my school project. I have created a schema in mongodb that looks like this:
var userSchema = new mongoose.Schema({
firstName: String,
lastName: String,
messages: []
});
I want to store an object in the messages that looks something similar to this:
{
from: 'fromUsername',
to: 'toUsername',
time: new Date(),
msg: 'message is here'
}
I want to be able to store that in the schema under the messages array. Is there a way to push that to the shcema? Not sure how to approach this task. Thanks!
You can define the schemas separately with the message schema embedded as part of the user document (as items within the messages array):
var messageSchema = new mongoose.Schema({
from: String,
to: String,
time: { type: Date, default: Date.now },
msg: String
})
var userSchema = new mongoose.Schema({
firstName: String,
lastName: String,
messages: [messageSchema]
});
mongoose.model('User', userSchema);
To add the given message to the messages array, follow the pattern:
// retrieve user model
var User = mongoose.model('User');
// create user instance
var user = new User({
firstName: 'Alice',
lastName: 'Bob'
});
// create a message
user.messages.push({
from: 'fromUsername',
to: 'toUsername',
msg: 'message is here'
});
You have an option to exclude the time field since you would have defined its default value when you persist your model.
user.save(function (err, user) {
if (!err) console.log(JSON.stringify(user, null, 4));
});
You can use th $push operator to appends your object value in the message array.
An example of how to use $push for your userSchema
// How to update a user already created
var User = mongoose.model('User');
User.update(
{ _id: id },
{ $push: {messages : {
from: 'fromUsername',
to: 'toUsername',
time: new Date(),
msg: 'message is here'}}
})

How to parse request (or end points) to query mongoose

I am going to to implement a web api like this
POST /groups/:groupname/chats/:chatType
There could be several groups and each group has at most two chats, a private one or a public one.
Currently, in my /models directory, two related schemas look like this:
// group.js
...
var groupSchema = new mongoose.Schema({
groupname: {type: String, required: true, index:{unique: true}},
privateChat: {type: mongoose.Schema.Types.ObjectId, ref: 'Chat'},
publicChat: {type: mongoose.Schema.Types.ObjectId, ref: 'Chat'}
});
module.exports = mongoose.model('Group', groupSchema)
// chat.js
var chatSchema = new mongoose.Schema({
chatType: String, // public or private
message: [{text: String}]
});
module.exports = mongoose.model('Chat', chatSchema);
So the question is how can I post a message like "Hello World!" to
/groups/boygroup/chats/private
...
I have finished the request GET /groups/:groupname by findOne() method like this:
router.get('/groups/:groupname', function(req, res) {
var groupname = req.body.groupname || req.params.groupname;
Group.findOne({
groupname: groupname
}, function(err, group) {
if (err)
return next(err);
if (!group) {
res.status(404).send({
success: false,
message: "group not found"
});
} else {
res.json(group);
}
});
});
But I have no idea how to get to a specific chat in a specific group. Maybe my mongoose schema is not good.
Any suggestion is appreciated.

Resources