Make Mongoose property no longer unique - node.js

Im getting a bizarre error with Mongoose. When a user registers they give the name of the organisation they belong to. Previously, this needed to be unique. My schema was:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var Organisation = new mongoose.Schema({
name: {
type: String,
required: true,
unique: true
},
createdById: {
type: Schema.Types.ObjectId,
ref: 'User'
},
createdOn: {
type: Date,
"default": Date.now
},
availableUntil: {
type: Date
}
});
Organisation.path('name').validate(function(value, done) {
var self = this;
if (!self.isNew) {
return done(true);
} else {
//mongoose.models['Organisation'] or self.model('Organisation')
mongoose.models['Organisation'].count({
name: value
}, function(err, count) {
if (err) {
return done(err);
}
return done(!count)
});
}
}, "This email already exists");
mongoose.model('Organisation', Organisation);
But now it doesn't matter, an organisation name doesn't need to be unique. So I changed to:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var Organisation = new mongoose.Schema({
name: {
type: String,
required: true
},
createdById: {
type: Schema.Types.ObjectId,
ref: 'User'
},
createdOn: {
type: Date,
"default": Date.now
},
availableUntil: {
type: Date
}
});
mongoose.model('Organisation', Organisation);
Note how unique: true has been removed.
I've restarted the server but bizarrely, I still get the error:
name: 'MongoError',
message: 'E11000 duplicate key error index: redmatter.organisations.$name_1 dup key: { : "delete me" }',
driver: true,
code: 11000,
index: 0,
errmsg: 'E11000 duplicate key error index: redmatter.organisations.$name_1 dup key: { : "delete me" }',
getOperation: [Function],
toJSON: [Function],
toString: [Function] }
How is this possible? Why am I getting a duplicate key error when I don't care if the name is not unique?

Related

Node.js MongoError: E11000 duplicate key error collection:

Im working on a djs bot and i ran into an error that i dont know how to fix
i have looked at https://docs.mongodb.com/manual/indexes/ and NodeJs, Mocha and Mongoose
but nothing seems to help here is the info -
the error happens here -
const Data = await serverModel.findOne({ serverID: message.guild.id });
try{
console.log(`checking if a database exists for ${message.guild}`);
console.log(Data);
if(!Data) {
console.log(`Data base doent exist for ${message.guild}`);
console.log(`Creating a database for ${message.guild}`);
const server = await serverModel.create({
serverID: message.guild.id,
calling: 'no',
channel: null,
talkingWith: null,
stickyChannel: null,
stickyMessage: null,
stickyID: null,
});
console.log('shit');
server.save();
return;
}
}
catch(err) {
console.log(err);
}
this is my serverModel/serverSchema -
const mongoose = require('mongoose');
// ServerSchema
const ServerSchema = new mongoose.Schema({
serverID: { type: String, require: true, unique: true, sparse:true },
calling: { type: String, require: true, unique: false, sparse:true },
channel: { type: String, require: true, unique: false, sparse:true },
talkingWith: { type: String, require: true, unique: false, sparse:true },
stickyChannel: { type: String, require: true, unique: false, sparse:true },
stickyMessage: { type: String, require: true, unique: false, sparse:true },
stickyID: { type: String, require: true, unique: false, sparse:true },
});
const model = mongoose.model('ServerSchema', ServerSchema);
module.exports = model;
and lastly this is the error i get -
checking if a database exists for Vixo
null
Data base doent exist for Vixo
Creating a database for Vixo
MongoError: E11000 duplicate key error collection: database.serverschemas index: stickyChannel_1 dup key: { stickyChannel: null }
it seems like there are already records with 'stickyChannel' parameter as null value. So can you please try creating new records by simply assigning values to every parameter?
try following code for creating new record:
`const server = await serverModel.create(
{ serverID: message.guild.id,
calling: 'no',
channel: 'test',
talkingWith: 'test',
stickyChannel: 'test,
stickyMessage: 'test,
stickyID: 'test',
});`

StrictModeError on using upsert with updateOne in Mongoose

I want to add a list of users in DB if they do not exist in DB and skip for the ones which already exists, for this, I am using writeMany operation with updateOne and upsert set as true. But it gives me StrictModeError.
Here is the function which creates the insertMany
const bulkWriteData = blacklistData.map(value => {
return {
updateOne : {
upsert: true,
filter : {
email : value
},
update : {
$set : {
_id : mongoose.Types.ObjectId(),
status : STATUS.BLOCKED,
createdAt : new Date(),
updatedAt : new Date()
}
}
}
}
})
await EmailUsers.bulkWrite(bulkWriteData)
Here is the EmailUsers Model
const mongoose = require('../config/connection')
const Schema = mongoose.Schema
const validator = require('../utils/validator.util')
const uniqueValidator = require('mongoose-unique-validator')
const EmailUsers = new Schema({
email: {
type: String,
required: true,
validate: validator.mongooseEmailValidator,
unique: true,
},
status: {
type: String,
enum: ['active', 'bounced'],
required: true,
default: 'active',
},
createdAt: {
type: Date,
required: true,
default: () => new Date(),
},
updatedAt: {
type: Date,
required: true,
default: () => new Date(),
},
bounceResponse: [
{
type: Object,
required: false,
},
],
})
EmailUsers.index({ email: 1, status: 1 })
EmailUsers.plugin(uniqueValidator, { type: 'mongoose-unique-validator' })
module.exports = mongoose.model('email_users', EmailUsers)
This is the validator used for email
const mongooseEmailValidator = validate({
validator: 'matches',
arguments: constants.regex.email,
message: 'Email should be a valid email address',
})
Here is the array which was sent to bulkwrite
[{
updateOne : {
upsert : true,
filter : { email: 'test#gmail.com' },
update : {
'$set': {
_id: 607ec7fd009fc824c5c34b5d,
status: 'blocked',
createdAt: 2021-04-20T12:24:29.693Z,
updatedAt: 2021-04-20T12:24:29.693Z
}
}
}
}]
Here is the error
error: Path "email" is not in schema, strict mode is `true`, and upsert is `true`. {"trace":"StrictModeError: Path \"email\" is not in schema, strict mode is `true`, and upsert is `true`.\n at cast
This doesn't make sense as I do have email in the schema
This actually updates the DB with the data and then throws the error.
What could be the possible solution?

E11000 duplicate key error with MongoDB/Mongoose

I have a user model schema, a work model schema, and a critique model schema. The relationship between these schema's is a user can submit many works (like blog posts), and can comment/review (which we call critiques) other people's posts (works).
So when a user submits a critique (think of it like a review), this is my post route. I find the work by the id, then create a new critique model object, and pass that to the .create() mongoose function. All goes seemingly well until I hit the foundWork.critiques.push(createdCritique) line. the console log errors out saying:
BulkWriteError: E11000 duplicate key error collection: zapper.critiques index: username_1 dup key: { : null }
Obviously, it is saying that there are two username keys in the objects and they're conflicting with each other, but I'm not familiar enough with this to find the root of the issue and fix it in the mongoose models. The models are below. If anyone could help, that'd be greatly appreciated.
// post route for getting the review
router.post('/:id', isLoggedIn, function(req, res) {
Work.findById(req.params.id, function(err, foundWork) {
if (err) {
console.log(err);
} else {
// create a new critique
var newCritique = new Critique ({
reviewerName: {
id: req.user._id,
username: req.user.username
},
work: {
id: foundWork._id,
title: foundWork.title
},
critique : req.body.critique,
date: Date.now(),
rating: 0
});
// save new critique to db
Critique.create(newCritique, function(err, createdCritique) {
if (err) {
console.log(err)
} else {
console.log("Created critique is ");
console.log(createdCritique);
// push the new critique into array of critiques of the work
foundWork.critiques.push(createdCritique);
// save to db
foundWork.save();
}
});
}
});
User model:
var mongoose = require('mongoose');
var passportLocalMongoose = require('passport-local-mongoose');
var UserSchema = new mongoose.Schema({
firstname: String,
lastname: String,
username: String,
password: String,
email: String,
zip: String,
bio: {
type: String,
default: ''
},
influences: {
type: String,
default: ''
},
favBooks: {
type: String,
default: ''
},
notWriting: {
type: String,
default: ''
},
favHero: {
type: String,
default: ''
},
favVillain: {
type: String,
default: ''
},
works: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Work'
}
],
critiques: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Critique'
}
],
friends: [
{
friendId: String,
friendName : String,
friendPic: String
}
],
friendRequests: [
{
sendingFriendId: String,
sendingFriendName : String,
sendingFriendPic: String
}
],
createdDate: {
type: Date,
default: Date.now
},
lastLogin: {
type: Date,
default: Date.now
}
});
UserSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("User", UserSchema);
Work model:
var mongoose = require('mongoose');
var WorkSchema = new mongoose.Schema({
title: String,
genre: String,
workType: String,
length: Number,
ageRange: String,
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
},
manuscriptText: String,
critiques: [
{
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "Critique"
}
}
],
ratingNumber: [Number],
ratingSum: {
type: Number,
default: 0
},
date: {
type: Date,
default: Date.now
},
isPublic: {
type: Boolean,
default: true
}
});
module.exports = mongoose.model("Work", WorkSchema);
Critique model:
var mongoose = require('mongoose');
var passportLocalMongoose = require('passport-local-mongoose');
var CritiqueSchema = new mongoose.Schema({
reviewerName: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
},
work: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "Work"
},
title: String
},
critique: String,
date: {
type: Date,
default: Date.now
},
rating: [Number]
});
CritiqueSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("Critique", CritiqueSchema);
When you create a unique index in MongoDB, the default behavior is that it will index null values also.
This means if you have a document in your collection with a username of null, you can not add another one with a username of null.
What you need is a sparse index which only indexes actual values (and ignores documents with null for that field).
Check this link It shows how to create a sparse index vs "normal" one in mongoose (index: true, vs spare: true). Most of the time you would want sparse indexes.

Referencing Object Id not working in Mongoose 4.11.6

I have this problem. Basically, I have 2 schemas - a User schema and a Document schema. The Document schema has an owner which references the _id field of documents in the User collection.
The problem is that I am still able to save documents in the Document collection with owner ids that do not exist in the User collection which should not be so.
Here is my User schema and Document schema respectively
const UserSchema = new Schema({
firstName: {
type: String,
required: true,
},
lastName: {
type: String,
required: true,
},
email: {
type: String,
validate: [{ validator: value => isEmail(value), msg: 'Invalid email.'
}],
unique: true,
required: true,
},
password: {
type: String,
required: true,
},
isAdmin: {
type: Boolean,
default: false,
},
}, {
timestamps: true,
});
const User = mongoose.model('User', UserSchema);
And the Document Schema
const DocumentSchema = new Schema({
title: {
type: String,
required: true,
},
text: {
type: String,
},
access: {
type: String,
enum: ['public', 'private'],
default: 'public',
},
owner: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true,
},
}, {
timestamps: true,
});
const Document = mongoose.model('Document', DocumentSchema);
Any help will be appreciated thanks.
For that situation you can add pre save function in your Document schema that will call before save your Document.
const DocumentSchema = new Schema({
// ...
}, {
timestamps: true,
});
DocumentSchema .pre("save",function(next) {
var self = this;
if (self.owner) {
mongoose.models['User'].findOne({_id : self.owner }, function(err, existUser){
if(err){
return next(false, err);
}
if(!existUser)
return next(false, "Invalid user reference");
else
return next(true);
});
} else {
next(false, "Owner is required");
}
});
const Document = mongoose.model('Document', DocumentSchema);

Mongoose Virtual field with async getter

I have a item model where it a virtual field to refer stock badges.
'use strict';
const mongoose = require('mongoose');
const mongooseHidden = require('mongoose-hidden')();
const Badge = mongoose.model('Badge');
const validateProperty = function(property) {
return (property.length);
};
const Schema = mongoose.Schema;
const ItemSchema = new Schema({
itemCode: {
type: Number,
index: {
unique: true,
sparse: true // For this to work on a previously indexed field, the index must be dropped & the application restarted.
},
required: true
},
itemName: {
type: String,
uppercase: true,
trim: true
},
barcode: {
type: String,
trim: true
},
category: {
type: Schema.Types.ObjectId,
ref: 'Category'
},
subCategory: {
type: Schema.Types.ObjectId,
ref: 'SubCategory'
},
updated: {
type: Date
},
created: {
type: Date,
default: Date.now
},
status: {
type: String,
enum: [
'active', 'inactive', 'removed'
],
default: 'active'
}
}, {id: false});
ItemSchema.virtual('badges').get(function() {
return this.getAvailableBadges();
});
ItemSchema.methods.getAvailableBadges = function() {
Badge.find({
item: this._id
}, (err, badges) => {
if (badges) {
return badges;
} else {
return [];
}
});
};
ItemSchema.set('toJSON', {virtuals: true});
ItemSchema.set('toObject', {virtuals: true});
ItemSchema.plugin(mongooseHidden, {
hidden: {
_id: false,
__v: true
}
});
mongoose.model('Item', ItemSchema);
And batch model as below
'use strict';
const mongoose = require('mongoose');
const mongooseHidden = require('mongoose-hidden')();
const validateProperty = function(property) {
return (property.length);
};
const Schema = mongoose.Schema;
const BadgeSchema = new Schema({
item: {
type: Schema.Types.ObjectId,
ref: 'Item'
},
qty: {
type: Number,
validate: [validateProperty, 'Please enter Quantity !']
},
purchasingPrice: {
type: Number,
validate: [validateProperty, 'Please enter purchasingPrice !']
},
sellingPrice: {
type: Number,
validate: [validateProperty, 'Please enter sellingPrice !']
},
updated: {
type: Date
},
created: {
type: Date,
default: Date.now
},
status: {
type: String,
enum: [
'active', 'inactive', 'removed'
],
default: 'active'
}
});
BadgeSchema.plugin(mongooseHidden, {
hidden: {
_id: false,
__v: true
}
});
mongoose.model('Badge', BadgeSchema);
Item's badge virtual field doesn't got populated.
How are we going to work with async getter method
I have put some console log statements and found that getAvailableBadges is getting data.
I need to send json object with virtual field values via express. How to I do it?
What I did was create an virtual property
ItemSchema.virtual('badges', {
ref: 'Badge',
localField: '_id',
foreignField: 'item'
});
And populate it with
{
path: 'badges',
select: [
'qty', 'purchasingPrice', 'sellingPrice'
],
options: {
sort: {
'created': -1
}
}
}
Well, the operations are asynchronous so you have to wait for the callback to fire.
You can only return the values by passing it in the callback (or you can set the values of the current object prior to calling the callback).
I think it would be something like this:
ItemSchema.virtual('badges').get(function (callback) {
Badge.find({ item: this._id }, callback);
};
Then you would use it like
item.badges(function (err, badges) {
// do something with badges
});

Resources