Mongoose Schema User Submitted Date Value - node.js

Hi I am trying to create a product schema with mongoose with a Expiry Date field to be submitted by the user.
It is just a date field but it needs to be selected by the user rather than hardcoded to a specific date in advance.
How can I achieve this?
Here is my basic model:
const productSchema = new mongoose.Schema({
name: {
type: String,
trim: true,
required: true,
maxlength: 32
},
description: {
type: String,
trim: true,
required: true,
maxlength: 20000
},
price: {
type: Number,
trim: true,
required: true,
maxlength: 32
},
category: {
type: ObjectId,
ref: 'Category',
required: true
}
}, {timestamps: true}
);

You can use a date picker and save its value as an ISO Date string to mongoose.
example: const date = new Date().toISOString();
Your mongoose Schema will look like this:
const productSchema = new mongoose.Schema({
expirationDate: {
type: Date,
default: new Date(), // Set a default date
required: true, // or you can mark it as required.
},
name: {
type: String,
trim: true,
required: true,
maxlength: 32
},
description: {
type: String,
trim: true,
required: true,
maxlength: 20000
},
price: {
type: Number,
trim: true,
required: true,
maxlength: 32
},
category: {
type: ObjectId,
ref: 'Category',
required: true
}
}, { timestamps: true }
);

Related

How to update a specific object in a array of objects by Item ID in Node.js and Mongoose

I need a code in Express.Js and Mongodb where I can change an exact string inside an object through the id of the item, I need to change the string "colorSelected" to a new value, changing only what I put in the body, in my previous failed attempts every change I made would change the entire object, I don't want that, thank you in advance.
Router Cart.js
//update color cart
router.patch("/cart/:id", Auth, async (req, res) => {
});
Model Cart.js
const mongoose = require('mongoose')
const ObjectID = mongoose.Schema.Types.ObjectId
const cartSchema = new mongoose.Schema({
owner : {
type: ObjectID,
required: true,
ref: 'User'
},
items: [{
itemId: {
type: ObjectID,
ref: 'Item',
required: true
},
img: String,
name: String,
colorSelected: String,
colors: Array,
stock: Number,
quantity: {
type: Number,
required: true,
min: 1,
default: 1
},
price: Number
}],
bill: {
type: Number,
required: true,
default: 0
}
}, {
timestamps: true
})
const Cart = mongoose.model('Cart', cartSchema)
module.exports = Cart
Model Item.js
const mongoose = require('mongoose')
const ObjectID = mongoose.Schema.Types.ObjectId
const reviewSchema = mongoose.Schema(
{
name: { type: String, required: true },
rating: { type: Number, required: true },
comment: { type: String, required: true },
user: {
type: ObjectID,
required: true,
ref: 'User'
},
},
{
timestamps: true,
}
)
const itemSchema = new mongoose.Schema({
images: [{
name: {
type: String,
required: true
},
src: {
type: String,
required: true
},
color: {
type: String,
required: true
},
}],
owner : {
type: ObjectID,
required: true,
ref: 'User'
},
name: {
type: String,
required: true,
trim: true
},
description: {
type: String,
required: true
},
category: {
type: Number,
required: true
},
price: {
type: Number,
required: true
},
stock: {
type: Number,
required: true
},
visibility: {
type: Boolean,
required: true
},
reviews: [reviewSchema],
rating: {
type: Number,
required: true,
default: 0,
},
numReviews: {
type: Number,
required: true,
default: 0,
}
}, {
timestamps: true
})
const Item = mongoose.model('Item', itemSchema)
module.exports = Item

How to Access a Field in Array of Objects With Joi Fork Method?

I am trying to write a reusable validation schema and I can change the rules of fields if I need it. I do this using a method called a fork. However, this time I couldn't imagine how can I access and change the rule of an object inside of an array. In some cases, some fields must be required. So I call the changed schema the default schema with the validation method. I am using this solution for a few models and generally, it works perfectly. Can you help me to imagine how can I solve this problem?
In this model, I have a subdocument field. This field is an array of objects field. That's why I have 2 different schemas. When the create method calls, I just need the title, description, and category object fields. If a user wants to add a question to the quiz record, I need a question array.
I was thinking that I can write a validator for only the question schema but if I don't add a field to the quiz validation schema then joi throws an error with the message "ABC field not allowed". I'm stuck because of this situation and I can't continue.
Quiz Model and Validation Schema and Method
const Joi = require('joi');
const mongoose = require('mongoose');
const slugCreator = require('mongoose-slug-updater');
mongoose.plugin(slugCreator);
const QuestionSchema = mongoose.Schema({
questionText: {
type: String,
trim: true,
minLength: 10
},
firstChoiceText: {
type: String,
trim: true,
minLength: 1
},
firstChoiceIsTrue: {
type: Boolean
},
secondChoiceText: {
type: String,
trim: true,
minLength: 1
},
secondChoiceIsTrue: {
type: Boolean
},
thirdChoiceText: {
type: String,
trim: true,
minLength: 1
},
thirdChoiceIsTrue: {
type: Boolean
},
fourthChoiceText: {
type: String,
trim: true,
minLength: 1
},
fourthChoiceIsTrue: {
type: Boolean
}
}, { timestamps: true });
const QuizSchema = mongoose.Schema({
title: {
type: String,
required: true,
trim: true,
minLength: 15,
maxLength: 250
},
description: {
type: String,
requried: true,
trim: true,
minLength: 50
},
coverImage: {
type: String,
trim: true
},
slug: {
type: String,
unique: true,
trim: true,
slug: ['title'],
slugPaddingSize: 3
},
category: {
title: {
type: String,
trim: true,
required: true
},
categoryId: {
type: mongoose.Types.ObjectId,
trim: true,
required: true
},
slug: {
type: String,
trim: true,
required: true
}
},
questions: [QuestionSchema],
createdBy: {
userId: {
type: mongoose.Types.ObjectId,
required: true
},
fullName: {
type: String,
required: true,
trim: true
},
email: {
type: String,
required: true,
trim: true
}
},
updatedBy: {
userId: {
type: mongoose.Types.ObjectId,
required: true
},
fullName: {
type: String,
required: true,
trim: true
},
email: {
type: String,
required: true,
trim: true
}
}
}, { collection: 'quizzes', timestamps: true });
const validationSchema = {
title: Joi.string().trim().min(15).max(250),
description: Joi.string().trim().min(50),
coverImage: Joi.string().trim(),
slug: Joi.string().trim(),
category: {
title: Joi.string().trim(),
categoryId: Joi.string().trim(),
slug: Joi.string().trim()
},
questions: Joi.array().items(
Joi.object({
questionText: Joi.string().trim().min(10),
firstChoiceText: Joi.string().trim().min(1),
firstChoiceIsTrue: Joi.boolean(),
secondChoiceText: Joi.string().trim().min(1),
secondChoiceIsTrue: Joi.boolean(),
thirdChoiceText: Joi.string().trim().min(1),
thirdChoiceIsTrue: Joi.boolean(),
fourthChoiceText: Joi.string().trim().min(1),
fourthChoiceIsTrue: Joi.boolean(),
})
),
createdBy: {
userId: Joi.string().trim(),
fullName: Joi.string().trim(),
email: Joi.string().email().trim()
},
updatedBy: {
userId: Joi.string().trim(),
fullName: Joi.string().trim(),
email: Joi.string().email().trim()
}
};
QuizSchema.statics.joiValidationForQuizCreate = async (quizObject) => {
const requiredSchema = Joi.object(validationSchema).fork(['title', 'description', 'createdBy.userId', 'createdBy.fullName', 'createdBy.email', 'updatedBy.userId', 'updatedBy.email', 'updatedBy.email'], item => item.required());
return await requiredSchema.validateAsync(quizObject);
};
module.exports = mongoose.model('quiz', QuizSchema);
I want to access these Question fields using the Fork method as in the joiValidationForQuizCreate method. Is this possible or is there a better method available? I don't want to write schematics over and over on a case-by-case basis.

How to populate object having array of object in mongoose?

I want to populate the adminId path to User Model.
Here is the code
adminInfo: {
_id: false,
adminId: [{
type: Schema.Types.ObjectId,
ref: 'User'
}]
}
Here is a part of user schema:
// user schema
const UserSchema = new mongoose.Schema({
name: {
firstName: {
type: String,
trim: true,
required: true,
},
lastName: {
type: String,
trim: true
}
},
email: {
type: String,
trim: true,
required: true,
unique: true,
lowercase: true
},
phone: {
type: String,
trim: true,
minlength: 10,
},
password: {
type: String,
trim: true,
required: true,
minlength: 6
}
});
I have tried using .populate('adminInfo.adminId') but it's giving empty array [] whereas .populate('adminInfo') giving array of admins ids but not getting populated to User model
i don't think there is any problem with .populate('adminInfo.adminId') method.
are you sure that ref field is in CamelCase .
If not, try to change ref field ->
adminInfo: {
_id: false,
adminId: [{
type: Schema.Types.ObjectId,
ref: 'user'
}]
}

Linking model in mongoose

Im new to mongoose and nosql databases. I have the following mongoose model (profile).
import { model, Schema } from 'mongoose';
import Joi from '#hapi/joi';
const profileSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'users',
},
handle: {
type: String,
minlength: 2,
maxlength: 20,
required: true,
trim: true,
},
company: {
type: String,
minlength: 1,
maxlength: 100,
trim: true,
},
website: {
type: String,
maxlength: 100,
trim: true,
},
location: {
type: String,
maxlength: 100,
trim: true,
},
status: {
type: String,
maxlength: 50,
trim: true,
required: true,
},
skills: {
type: [String],
required: true,
},
bio: {
type: String,
maxlength: 500,
trim: true,
},
githubUserName: {
type: String,
maxlength: 50,
trim: true,
},
socialLinks: {
youtube: {
type: String,
maxlength: 100,
trim: true,
},
twitter: {
type: String,
maxlength: 100,
trim: true,
},
facebook: {
type: String,
maxlength: 100,
trim: true,
},
linkedin: {
type: String,
maxlength: 100,
trim: true,
},
instagram: {
type: String,
maxlength: 100,
trim: true,
},
},
date: {
type: Date,
default: Date.now,
},
});
export default model('profile', profileSchema);
export const validateProfile = (profile) => {
const schema = Joi.object({
handle: Joi.string().trim().min(2).max(20).required(),
company: Joi.string().trim().min(2).max(20),
website: Joi.string().trim().max(100),
location: Joi.string().trim().min(2).max(20),
status: Joi.string().trim().min(2).max(20),
skills: Joi.array().required(),
bio: Joi.string().trim().max(500),
githubUserName: Joi.string().max(50),
youtube: Joi.string().trim().max(100),
twitter: Joi.string().trim().max(100),
facebook: Joi.string().trim().max(100),
linkedin: Joi.string().trim().max(100),
instagram: Joi.string().trim().max(100),
});
return schema.validate(profile);
};
Every profile can have several experiences. This is my experience model.
import { model, Schema } from 'mongoose';
import Joi from '#hapi/joi';
const experienceSchema = new Schema({
title: {
type: String,
maxlength: 100,
trim: true,
required: true,
},
company: {
type: String,
maxlength: 100,
trim: true,
required: true,
},
location: {
type: String,
maxlength: 100,
trim: true,
required: true,
},
from: {
type: Date,
required: true,
},
to: {
type: Date,
},
current: {
type: Boolean,
default: false,
},
description: {
type: String,
maxlength: 500,
trim: true,
},
});
export default model('experience', experienceSchema);
export const validateExperience = (profile) => {
const schema = Joi.object({
title: Joi.string().trim().max(100).required(),
company: Joi.string().trim().max(100).required(),
location: Joi.string().trim().max(100).required(),
from: Joi.date().required(),
to: Joi.date(),
current: Joi.boolean().default(false),
description: Joi.string().trim().max(500),
});
return schema.validate(profile);
}
;
I want to link this experience model to profile model. Experience is an array. So how can I link this experience model array to profile model? First I thought to keep the experience array inside the profile model, but it makes the model too big. I want to separate things here. What should be the better practice here??
you can add one field experience_ids in your Profile Schema , it will save ids of experience in Profile Schema
experience_ids: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'experience'
}]
you can populate experience like this :
Profile
.find()
.populate('experience_ids')
.exec(...)
Nesting experience array inside the profile model wouldn't make the model 'too big'. Consider this any Person wont ideally have more than 20 companies that he had worked for in his entire career. In fact we would mostly be looking at a no less than 20 .Which in any case isn't a length that would slow the query.
Now as you have decided to not nest the experience inside the Profile array
what you can now do is to create an array that would hold the ids for experience and keep it in your Profile Schema
experiences:[
{
type: mongoose.Schema.Types.ObjectId,
ref: "experience"
}
]
The ref keyword as you might have guessed is the name of the collection where the referenced document resides.
To retrieve the data for the user You would need to use
profile.findById(id).populate("experience").exec((err, User) =>{
console.log(User);
});
the populate method basically is used for populating the data inside the reference while the execute method basically takes the query object returned but the Mongoose query and runs this as a unit to get your result
read more about exec here : What does exec do ?

Mongoose model seperations

Im new to node and mongodb. I have the following mongoose model.
import { model, Schema } from 'mongoose';
import Joi from '#hapi/joi';
const profileSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'users',
},
handle: {
type: String,
minlength: 2,
maxlength: 20,
required: true,
trim: true,
},
company: {
type: String,
minlength: 1,
maxlength: 100,
trim: true,
},
website: {
type: String,
maxlength: 100,
trim: true,
},
location: {
type: String,
maxlength: 100,
trim: true,
},
status: {
type: String,
maxlength: 50,
trim: true,
required: true,
},
skills: {
type: [String],
required: true,
},
bio: {
type: String,
maxlength: 500,
trim: true,
},
githubUserName: {
type: String,
maxlength: 50,
trim: true,
},
experience: [
{
title: {
type: String,
maxlength: 100,
trim: true,
required: true,
},
company: {
type: String,
maxlength: 100,
trim: true,
required: true,
},
location: {
type: String,
maxlength: 100,
trim: true,
required: true,
},
from: {
type: Date,
required: true,
},
to: {
type: Date,
},
current: {
type: Boolean,
default: false,
},
description: {
type: String,
maxlength: 500,
trim: true,
},
},
],
education: [
{
school: {
type: String,
maxlength: 100,
trim: true,
required: true,
},
degree: {
type: String,
maxlength: 100,
trim: true,
required: true,
},
fieldOfStudy: {
type: String,
maxlength: 100,
trim: true,
required: true,
},
from: {
type: Date,
required: true,
},
to: {
type: Date,
},
current: {
type: Boolean,
default: false,
},
description: {
type: String,
maxlength: 500,
trim: true,
},
},
],
social: {
youtube: {
type: String,
maxlength: 100,
trim: true,
},
twitter: {
type: String,
maxlength: 100,
trim: true,
},
facebook: {
type: String,
maxlength: 100,
trim: true,
},
linkedin: {
type: String,
maxlength: 100,
trim: true,
},
instagram: {
type: String,
maxlength: 100,
trim: true,
},
},
date: {
type: Date,
default: Date.now,
},
});
export default model('profile', profileSchema);
I have created this model in a single file and it seems too big. So should I put experience, education and social into 3 seperate models? If so how should I do it? If I put these in to 3 seperate models, how can I link them with the profile model? An example would be highly appriciated.
Yes, you should seperate them. To link them you would just put the profile schema Id as a field on the other models.
const profileSchema = new Schema({
userId: Schema.Types.ObjectId
})
const experienceSchema = new Schema({
userId: Schema.Types.ObjectId
})
const educationSchema = new Schema({
userId: Schema.Types.ObjectId
})
Then you would just query the experience collection by the userId to get their experiences. This is the way I'd recommend.
Another way wouldbe to put experienceIds on the profile schema that would reference the Experience model and could use the populate method to fill the fields.

Resources