mongoose schema create empty array by default? - node.js

Im new to mongoose and mongoDB, by a tutorial, I have a user schema looks like this:
var userSchema = mongoose.Schema({
local: {
email: {
type: String,
index: {
unique: true,
dropDups: true
}
},
password: String,
displayName: String,
avatar: {
type: String,
default: "./img/user.png"
},
role: {
type: String,
default: "student"
},
ask_history: [
{
question_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'questionAnswer'
},
favorite: Boolean,
ask_time: Date
}
],
interest: [String]
},
facebook: {
id: String,
token: String,
email: String,
displayName: String,
avatar: String,
familyName: String,
givenName: String,
gender: String,
ageMin: Number,
role: {
type: String,
default: "student"
},
ask_history: [
{
question_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'questionAnswer'
},
favorite: Boolean,
ask_time: Date
}
],
interest: [String]
},
twitter: {
id: String,
token: String,
email: String,
name: String,
avatar: String,
role: {
type: String,
default: "student"
},
ask_history: [
{
question_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'questionAnswer'
},
favorite: Boolean,
ask_time: Date
}
],
interest: [String]
}
}, {strict: true});
It creates user record by the way user is sign up for, either give local email or using oauth register by 3rd party.
Schema above will generate a document looks like this if I create a local user:
{
"_id": {
"$oid": "58792ee6e8a08204a0d68f7a"
},
"twitter": { ////////////////////////////////////////
"interest": [], //
"ask_history": [], //
"role": "student" //
}, // <-- nothing should created for a local user
"facebook": { //
"interest": [], //
"ask_history": [], //
"role": "student" //
}, ////////////////////////////////////////
"local": {
"password": "$2a$10$OtdbF7t52TyNAuwZXFT0u.Q/A.E5TeV4T.shHCxSSxDll2nX4bCbW",
"displayName": "aaa",
"email": "aaa#gmail.com",
"interest": [],
"ask_history": [],
"role": "admin",
"avatar": "./img/user.png"
},
"__v": 0
}
The problem I'm having is at
ask_history: [
{
question_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'questionAnswer'
},
favorite: Boolean,
ask_time: Date
}
],
interest: [String]
With out those array declaration in the schema the document looks just fine for any type of the user, for example, a local user, document would looks like this (no facebook or twitter field):
var userSchema = mongoose.Schema({
local: {
email: {
type: String,
index: {
unique: true,
dropDups: true
}
},
password: String,
displayName: String,
avatar: {
type: String,
default: "./img/user.png"
},
role: {
type: String,
default: "student"
}
},
facebook: {
id: String,
token: String,
email: String,
displayName: String,
avatar: String,
familyName: String,
givenName: String,
gender: String,
ageMin: Number,
role: {
type: String,
default: "student"
}
},
twitter: {
id: String,
token: String,
email: String,
name: String,
avatar: String,
role: {
type: String,
default: "student"
}
}
}, {strict: true});
{
"_id": {
"$oid": "5874d1d0cd045371b4e96eca"
},
"local": {
"password": "$2a$10$KpnIE2Uc2tX.YfcQCGm6EeDMexFFZXbKJVcQvxltBxrTkxb8E7mH.",
"displayName": "aaa",
"avatar": "./img/user.png",
"email": "aaa#gmail.com",
"role": "admin"
},
"__v": 0
}
And this is my function for creating a local user:
passport.use('local-signup', new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
}, function(req, email, password, done) {
if (!req.body.name || req.body.name.length == 0) {
return done(null, false, req.flash('signupMessage', 'Name can\'t be empty'));
}
User.findOne({
'local.email': email
}, function(err, user) {
if (err) {
return done(null, false, req.flash('signupMessage', 'Error: ' + err));
} else {
if (validator.validate(email)) {
if (user) {
return done(null, false, req.flash('signupMessage', 'Email already registered.'));
} else {
var newUser = new User();
newUser.local.email = email;
newUser.hashPassword(password);
newUser.local.displayName = req.body.name;
newUser.save(function(err) {
if (err) {
throw err;
}
return done(null, newUser);
});
}
} else {
return done(null, false, req.flash('signupMessage', 'Email not vaild'));
}
}
});
}));
My question is how can I set the schema to when the document is created, the array will not be created.

I think for your exact question the answer would be:
Arrays are special because they implicitly have a default value of [] (empty array).
const ToyBox = mongoose.model('ToyBox', ToyBoxSchema);
console.log((new ToyBox()).toys); // []
To overwrite this default, you need to set the default value to undefined
const ToyBoxSchema = new Schema({
toys: {
type: [ToySchema],
default: undefined
}
});
This is from the mongoose v6.4.4 docs but as I checked the
But on the other hand maybe you should create a schema for your subdocuments and set the default value for them to undefined. That would also do the trick and would be more exact definition.
app: {
type: AppDataSchema, // AppDataSchema is a schema defined elsewhere
default: undefined,
},

To create an empty array, you need to use default: null.
If you use undefined instead, the schema will not create the array at all because undefined is not a value but null is.
Try this:
const ToyBoxSchema = new Schema({
toys: {
type: [ToySchema],
default: null
}
});

Related

Adding a field to a nested document in mongoDB node js

Hello since there is not subcollections in mongodb i have found that you can nest document as an array ! i did that and it worked but what i want is to add a field to that existing array without losing the old ones ! i can do that by fetching the old array and adding the new field to it and returning that array but doing the fetch inside the patch method wouldn't be a good approach ! so is there any other way i can make that happen ?
Modal :
const mongoose = require("mongoose");
const notificationSchema = new mongoose.Schema(
{
notificationType: {
type: String,
required: false,
},
message: { type: String, required: true },
},
{
timestamps: true,
}
);
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
passwordHash: {
type: String,
required: true,
},
phone: {
type: String,
required: true,
},
isAdmin: {
type: Boolean,
default: false,
},
street: {
type: String,
default: "",
},
notifications: [notificationSchema],
apartment: {
type: String,
default: "",
},
zip: {
type: String,
default: "",
},
city: {
type: String,
default: "",
},
country: {
type: String,
default: "",
},
});
exports.User = mongoose.model("User", userSchema);
exports.Notification = mongoose.model("Notification", notificationSchema);
router.patch("/:id", async (req, res) => {
try {
console.log("hh");
const user = await User.findByIdAndUpdate(req.params.id, {
notifications: [
{
notificationType: "Order",
message: "New Notification",
},
],
});
if (!user) {
return res.status(404).send("The user cannot be found");
}
res.send(user);
} catch (error) {
return res.status(400).json({ success: false, error });
}
});

Node.js express How update arrays object using PATCH

I have model
username: {
type: String,
required: true,
},
firstname: {
type: String,
},
lastname: {
type: String,
},
email: {
type: String,
required: true,
},
comments: [
{
name: {
type: String,
},
text: {
type: String,
},
},
],
And I want update userModel using Controllers this.router.patch(${this.path}/:id, this.editProfile);
const user = await userModel.findByIdAndUpdate(
req.params.id,
{
...req.body,
},
{ new: true, runValidators: true },
);
return res
.status(StatusCodes.OK).send(user)
Everything works but the problem is when I update comments:
When I sent req "comments": [
{
"name": "coment",
"text":"text"
}
]
It's okay, but when I update without name
{
"text":"text2"
}
the name disappears, but I want that text2 was updated, and the name still exists. What I should use?

Add data in an array of object with mongoDB

I need your help, I try to add(if it not exists) or update if exists datas in an array of Object in MongoDB.
Here is my Model
import { Schema, model } from "mongoose";
const userSchema = new Schema({
firstName: {
type: String,
required: true,
unique: false,
trim: true
},
pseudo: {
type: String,
required: true,
unique: true,
trim: true,
minlength: 3
},
email: {
type: String,
required: false,
trim: true
},
password: {
type: String,
required: true
},
// password2: {
// type: String,
// required: true
// },
tags: {
type: Array,
required: false
},
address: {
type: String,
required: true,
unique: false,
trim: true
},
coord: {
type: Object,
required: false,
unique: false,
trim: true
},
poll: [
{
tag: String,
dates: Array
}
]
},
{
timestamps: true,
});
const User = model('User', userSchema);
export default User;
My route
router.route('/calendar/:email').post((req, res) => {
User.findOne({ email: req.body.email }).then( (user) =>{
console.log("user 1", user)
User.bulkWrite([
{
insertOne: {
"poll": {
"tag": req.body.selectedTag,
"dates": req.body.datesArray
}
}
},
{
updateOne: {
"filter": {
"tag" : req.body.selectedTag
},
"update": {
$set: {
"dates": req.body.datesArray
}
},
}
}
])
})
});
and the datas sended :
email: 'john#gmail.com',
selectedTag: 'work',
dateArray: [ '2020-07-16T22:00:00.000Z' ]
I try many things like by findOneaAndUpdate, but I don't know how to add in the array "poll", objects with tag and the dates associated.
If somebody could help me it would be very nice !
I shoul use add $addToSet or $push, depending if the element is unique or not.
Something like this:
"update": {
"$addToSet": {
"poll": { /*...*/ }
}
}
For reference:
http://docs.mongodb.org/manual/reference/operator/update/addToSet/
http://docs.mongodb.org/manual/reference/operator/update/push/

Mongoose - 5.3.4 - Prevent cast to ObjectId for query with string

I am attempting to findOneAndUpdatea string-based token on a User model. And
I receive the error:
Cast to ObjectId failed for value "{ passwordResetToken: '4946d72f19b9649d3f306a0f5be59005c884ae453fc049c7',
passwordResetExpires: { '$gt': 1543196590882 } }" at path "_id" for model "User"
the document is stored like so:
{
"_id": {
"$oid": "5bfb424da0cc0923f05b67f1"
},
"local": {
"email": "XXXXXXXXXXXXXXXXX",
"password": "XXXXXXXXXXXXXXXXX"
},
"isVerified": false,
"method": "local",
"__v": 0,
"passwordResetExpires": {
"$date": "2018-11-26T02:41:17.851Z"
},
"passwordResetToken": "4946d72f19b9649d3f306a0f5be59005c884ae453fc049c7"
}
and I query the document like so:
req.params.token = "4946d72f19b9649d3f306a0f5be59005c884ae453fc049c7"
User.findByIdAndUpdate({
'passwordResetToken': req.params.token,
'passwordResetExpires': { $gt: Date.now() }
},
{
'local.password' : req.body.password,
'passwordResetExpires' : null,
'passwordResetToken' : null
}, {new: true})
.then(user => {
res.send(user);
})
.catch(err => next(err))
This is my current Schema:
var userSchema = mongoose.Schema({
method: {
type: String,
enum: ['local', 'google', 'facebook']
},
local: {
email: {
type: String,
lowercase: true
},
password: String,
},
google: {
id: String,
email: {
type: String,
lowercase: true
},
name: String,
token: String
},
facebook: {
id: String,
name: String,
token: String
},
isVerified: {
type: Boolean,
default: false,
required: true
},
passwordResetToken: String,
passwordResetExpires: Date
});
I guess mongoose Is attempting to cast this hex string into a _id value? Is there some way to prevent mongoose from casting the string into an ObjectId Type?
In mongoose, if you use findByIdAndUpdate(), you have to provide a value that is the objectID. So, in your case, it tries to find an Object ID but cannot and hence you get an error. Something more appropriate to your use case would be findOneAndUpdate(). Here, you are free to use other parameters.

Mongoose Discriminators unable to add dicriminator details

Im currently working on adding discriminators to my express rest api. I have added different types of users to the user schema using the discriminators as different user require additional information. The problem I am facing is that when I post to the api get no errors when adding the information and only the general information is added to the schema, the details within the discriminators are ignored.
The schema is as follows:
var options = { discriminatorKey: 'type' };
var UserSchema = new Schema({
local: {
email: {
type: String,
sparse: true,
lowercase: true,
},
password: { type: String },
},
facebook: {
id: String,
token: String,
email: String,
name: String,
profileIMG: String,
},
twitter: {
id: String,
token: String,
displayName: String,
username: String
},
google: {
id: String,
token: String,
email: String,
name: String,
profileIMG: String,
}
}, options);
var addressSubschema = {
street: {
type: String,
required: true
},
number: {
type: String,
required: true
},
city: {
type: String,
required: true
},
};
var workingHoursSchema = {
start: {
type: String,
required: true
},
finish: {
type: String,
required: true
}
};
var adminSchema = new Schema({
description: {
type: String,
required: true
},
category: {
type: String,
required: true
},
workingHours: workingHoursSchema,
address: addressSubschema,
workingRadius: {
type: Number,
required: true
},
}, options);
var User = mongoose.model('User', UserSchema);
var Admin = User.discriminator('AdminUser', adminSchema);
module.exports = User;
I then export the model and when saving a new user I get a success however the admin details are not saved.
User.findOne({'local.email': email}, function(err, existingUser) {
if (err) { return next(err) }
if (existingUser) {return res.status(422).json({error: "Email already exists"})}
var user = new User({
"local.email": req.body.email,
"local.password": req.body.password,
"description": req.body.description,
"category": req.body.category,
"workingRadius": req.body.workingRadius,
"street": req.body.street,
"number": req.body.number,
"city": req.body.city,
"start": req.body.start,
"finish": req.body.finish
});
user.save(function(err) {
if (err) { return next(err) }
res.json({success: true});
});
});
Im new to using the discriminator so any help is greatly appreciated.

Resources