How to add Schema Dynamically in MongoDB/Mongoose - node.js

I want to create a database thats defined by user, each user can have his own flavor of database. So i used strict: false But Now the problem is I cant make the user define the type of each schema under the model
Example
const mongoose = require('mongoose');
const testSchema = new mongoose.Schema({
label: {
required: 'please enter label',
trim: true,
type: String
},
url: {
type: String,
trim: true,
},
settings: {} //User defined
}, {
timestamps: true, strict: false
});
module.exports = mongoose.model('test', testSchema);
In the above case, I want the setting to be defined by user
like,
{
"label": "About Us",
"url": "www.google.com",
"settings": {
"name": {
"type": "String", //Problem is Here, i can't send datatype directly
"required": true
},
"age": {
"type": "Number",
"required": true,
"enum": [10, 12]
}
}
}
So please tell help me, how can i make the user define the type of the schema?

strict: true does not mean You can pass anything to settings field.
It means Your schema format is dynamic - You can have unexpected field names in document that is not defined in schema.
Answer to Your issue:
Seems like You want subdocument, let's make another schema and attach it as type:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const types = Schema.Types;
const testSettingsSchema = new Schema({
name: {
type: types.String,
required: true
},
age: {
type: types.Number,
required: true
enum: [10, 12]
}
},
{
_id : false,
timestamps: false,
strict: false
});
const testSchema = new Schema({
label: {
required: 'please enter label',
trim: true,
type: types.String
},
url: {
type: types.String,
trim: true,
},
settings: {
type: testSettingsSchema,
required: true
}
},
{
timestamps: true,
strict: true
});
module.exports = mongoose.model('test', testSchema);
But to gain more flexibility and avoid creating big test document (since user may push unpredictable big object), create another schema: testSettings that points to test_settings collection and make settings field to be reference:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const types = Schema.Types;
const testSettingsSchema = new Schema({
name: {
type: types.Mixed
},
age: {
type: types.Mixed
}
},
{
collection: 'test_settings',
timestamps: false,
strict: false // tells to mongoose that schema may "grow"
});
mongoose.model('testSettings', testSettingsSchema);
const testSchema = new Schema({
label: {
required: 'please enter label',
trim: true,
type: types.String
},
url: {
type: types.String,
trim: true,
},
settings: {
type: types.ObjectId,
ref: 'testSettings'
default: null
}
},
{
collection: 'tests',
timestamps: true,
strict: true
});
module.exports = mongoose.model('test', testSchema);
create it as:
const Test = mongoose.model('test');
const TestSettings = mongoose.model('testSettings');
app.post('/tests', async (req, res) => {
try {
const testSettings = await TestSettings.create(req.body.settings);
const test = new Test(req.body);
test.settings = testSettings._id;
await test.save();
res.status(201).send({_id: test._id});
}
catch(error) {
res.status(500).send({message: error.message});
}
});
and on request time get it as:
const Test = mongoose.model('test');
app.get('/tests/:id', async (req, res) => {
try {
const test = await Test.findById(req.params.id)
.populate('settings')
.lean();
res.status(200).send(test);
}
catch(error) {
res.status(500).send({message: error.message});
}
});

define your setting field as Schema.Types.Mixed , so you can able to set any kind of fields inside it , like Number , String , Array , Date , Boolean ..etc
const mongoose = require('mongoose');
const testSchema = new mongoose.Schema({
label: {
required: 'please enter label',
trim: true,
type: String
},
url: {
type: String,
trim: true,
},
settings: {
type:Schema.Types.Mixed ,
default: {}
}
}, {
timestamps: true, strict: false
});
module.exports = mongoose.model('test', testSchema);
While saving the document :
app.post('/save',function(req,res){
var setting = {};
setting.age= req.body.age;
setting.name= req.body.name;
var test = new Test({
test.label: req.body.label;
test.url :req.body.url;
test.setting: setting
});
test.save(function(err){
if(err) {return res.json(err);}
else{ res.json({status:'success',message:'saved.'});}
});
});

Just in case someone is having this issue with NestJS and schemaFactory, that's how I solved it:
...
#Schema({ strict: false })
export class Content {}
#Schema()
export class Deadletter extends Document {
#Prop({type: Header})
header: Header;,
#Prop({type: Metadata})
_metadata?: Metadata;
#Prop({type: Content})
content: any;
}
export const deadLetterFullSchema = SchemaFactory.createForClass(Deadletter);

Related

why I dont get user id from mongodb if I want to change password if user is singup /login?

Here I have defined the routes mentioned below:-
import UserController from "../controllers/userController.js";
import checkUserAuth from "../middlewares/auth-middleware.js";
// route level middleware
router.use("/changepassword", checkUserAuth);
router.post("/changepassword", UserController.changePassword);
In this system, if the user has a valid token after login then they can change their password.
let token;
const { authorization } = req.headers;
if (authorization && authorization.startsWith("Bearer")) {
try {
token = authorization.split(" ")[1];
// verify user token
console.log(authorization);
console.log(token);
const { userId } = Jwt.verify(token, "5DFE4FG0125DRHcgng");
// get user from token
req.user = await UserModel.findById("62fc541298faae7fa6db9035").select(
"-password"
);
console.log(req.user);
next();
} catch (error) {
res.send({ status: "failed", message: "unauthorized user" });
}
}
Here I have debugged that I can see the value of the token in console.log() and the token value is available here. but nothing was found in const{userid} that I can get user details and proceed for a password reset. All data is fetching, Whenever I passed userid like this
req.user = await UserModel.findById("62fc541298faae7fa6db9035").select("-password");
instead of
req.user = await UserModel.findById(userid).select("-password");
Here is the mongoose Schema.
import mongoose from "mongoose";
// Define Schema
const userSchema = new mongoose.Schema({
name: { type: "String", required: true, trim: true },
email: { type: "String", required: true, trim: true },
password: { type: "String", required: true, trim: true },
tc: { type: "String", required: true },
});
// Model
const UserModel = mongoose.model("user", userSchema);
export default UserModel;
Maybe you need to cast to ObjectId if your schema has it.
Please share your schema.
var ObjectId = require('mongoose').Types.ObjectId;
req.user = await UserModel.findById(new ObjectId("62fc541298faae7fa6db9035")).select("-password");
EDIT1
This code is working.
//defining schema
const userSchema = new mongoose.Schema({
name: { type: "String", required: true, trim: true },
email: { type: "String", required: true, trim: true },
password: { type: "String", required: true, trim: true },
tc: { type: "String", required: true },
});
// Model
const UserModel = mongoose.model("testuser", userSchema);
(async () => {
//CLEAR Collection!
const user = await UserModel.create({
"name": "Max Mustermann",
"email": "max.mustermann#gmail.de",
"password": "$2a$10$Ovywsn4SDhBejIQ8BEWQSepfxE0CmGZ8TYa6k/LaCmkYd3bPVGyqe",
"tc": "unknown"
})
const querySameUser = await UserModel.findById(user._id).select("-password");
console.log(querySameUser);
})();
and it is also working with fixed string of id.
//defining schema
const userSchema = new mongoose.Schema({
name: { type: "String", required: true, trim: true },
email: { type: "String", required: true, trim: true },
password: { type: "String", required: true, trim: true },
tc: { type: "String", required: true },
});
// Model
const UserModel = mongoose.model("testuser", userSchema);
(async () => {
const querySameUser = await UserModel.findById("62fe43de9e960798b93bf9d4").select("-password");
console.log(querySameUser);
})();

Mongoose Virtual Referencing Other Model/Collection

Trying to reference one collection in mongo via another model's virtual. Currently the only output I'm getting is 'undefined'. Was able to execute this in my main js file as a standalone query but can't figure out how to get this to function as a virtual. Help appreciated.
const mongoose = require('mongoose');
const { stylesList } = require('../styles.js')
const Schema = mongoose.Schema;
const Post = require('./post')
const options = { toJSON: { virtuals: true } };
// options object for the schema, passed in after the actual schema.
const BrandSchema = new Schema({
name: {
type: String,
unique: true,
required: true
},
linkedBrands: [String],
styles: [{ type: String, enum: stylesList }],
links: {
homepage: {
type: String,
required: true
},
grailed: {
type: String,
required: true
},
wikipedia: String
},
bannerImage: String,
likes: {
type: Number,
default: 0
}
}, options);
BrandSchema.set('toObject', { getters: true });
BrandSchema.virtual('amountWorn').get(async function() {
const wornPosts = await Post.find({includedBrands: this._id});
return wornPosts.length
});
module.exports = mongoose.model('Brand', BrandSchema)

I want to store or push whole reference document not a single reference id in Mongoose using Node.js

I want to store or push whole reference document not a single reference id in Mongoose using Node.js.
Here user api details schema:
const {Schema} = require("mongoose");
const mongoose = require("mongoose");
const apiDetailSchema = new Schema({
endpoint:
{
type: String,
required: true,
trim: true
},
method:
{
type: String,
required: true,
trim: true,
},
secret:
{
type: String,
required: true,
trim: true
},
timeout:
{
type: Number,
required: true,
},
microservice:
{
type: String,
required: true,
},
status:
{
type: Number,
required: true,
},
user_type_id:
{
type: Schema.Types.ObjectId,
ref: 'UserType',
required: true
}
});
module.exports = apiDetailSchema;
Permissoin Schema
const {Schema} = require("mongoose");
const mongoose = require("mongoose");
const permission = new Schema({
role:
{
type: Schema.Types.ObjectId,
ref: 'Role'
},
Here I want to store whole api details document not single id
api_details:
[
{
type: Schema.Types.ObjectId,
ref: 'ApiDetail'
}
],
group:
{
type: String,
default: 0,
trim: true
},
status:
{
type: Number,
default: 0,
trim: true
}
});
module.exports = permission;
And here I push the ids like this:
async createPermission(req, res)
{
const permission = new Permission({
"role": req.body.role,
"group": req.body.group,
"status": req.body.status
});
const newPermissoin = await permission.save();
if(newPermissoin) {
const updatePermission = await Permission.findByIdAndUpdate(
newPermissoin._id,
{ $push: { "api_details": req.body.api_details_id } },
{ new: true, useFindAndModify: false }
);
return res.json( Response.success({message:
"Permission created successfully!", data: updatePermission}) );
}
}
How do I store the whole documents of api details in array form but here only reference of ids are stored.

MongoDB collection.aggregrate accept only two arguments

I am trying the aggregation in mongoose. When I run that aggregation, it show the error. What am I missing?
const data = await Rooms.aggregate([{ $match: { adminID: "1234" } }]);
Error is like that
MongoInvalidArgumentError: Method "collection.aggregate()" accepts at most two arguments
Edit -- code for Rooms Schema
const mongoose = require('mongoose');
const Rooms = new mongoose.Schema(
{adminID: {
type: String,
required: true,
},
roomID: {
type: String,
required: true,
},
roomName: {
type: String,
required: true,
},
users: [
{
id: {
type: String,
required: true,
unique: true,
},
},
],
},
{ timestamps: true } );
module.exports = mongoose.model("rooms", Rooms);
solution 1 : downgrade mongoose to version 5
solution 2 :
const data = await Rooms.aggregate([{ $match: { adminID: "1234" } }],"adminID roomID roomName users");
in new version second argument is selected fields in out put,
or use :
const data = await Rooms.aggregate.match({ adminID: "1234" } )

How to make parent value optional and child value required in mongoose

I'm working on a project and stuck at a point where the docOne is optional. But if docOne is there, the children of the docOne should be required. Is there any way of achieving this behavior through the schema model? Thank you.
const MySchema = new Schema(
{
name: { type: String },
docOne: {
required: false,
type: {
docTwo: {
required: true,
type: {
isActive: { type: Boolean, required: true },
},
},
},
},
},
{ timestamps: true },
);
try defining the child schema first, and use the child schema in your parent schema.
this way you can make use of sub-documents / embedded-documents where, you can make only required to true.
you can refer https://mongoosejs.com/docs/subdocs.html and https://mongoosejs.com/docs/2.7.x/docs/embedded-documents.html#:~:text=Embedded%20documents%20are%20documents%20with,error%20handling%20is%20a%20snap! for this.
If you are using mongoose Mongoose 5.8.0 or above, just add typePojoToMixed: false to the scheme options and the validations should work fine.
const mySchema = new Schema(
{
// ...Schema definition remains the same
},
{ timestamps: true, typePojoToMixed: false },
);
const myModel = mongoose.model('myModel', mySchema);
const sampleDocument = {
name: 'John Doe',
};
myModel.validate(sampleDocument); // No Error
sampleDocument.docOne = {};
myModel.validate(sampleDocument); // Throws ValidationError for docOne.docTwo
If you are using a lesser version of Mongoose, the other thing that can work is declaring the schema type of the nested objects as a separate schemas:
const docTwoSchema = new Schema({
isActive: { type: Boolean, required: true },
},
// Use the "_id: false" option to avoid an autogenerated _id property
{ _id: false }
);
const docOneSchema = new Schema({
docTwo: {
type: docTwoSchema,
required: true,
}
},
// Use the "_id: false" option to avoid an autogenerated _id property
{ _id: false }
);
const mySchema = new Schema(
{
name: { type: String },
docOne: {
type: docOneSchema,
required: false,
},
},
{ timestamps: true },
);
const myModel = mongoose.model('myModel', mySchema);
const sampleDocument = {
name: 'John Doe',
};
myModel.validate(sampleDocument); // No Error
sampleDocument.docOne = {};
myModel.validate(sampleDocument); // Throws ValidationError for docOne.docTwo
Useful Links
Mongoose SubDocuments

Resources