Creating sub-module in MongoDB while loading test data from json file - node.js

I am trying to load default data into my MongoDB database from a node.js backend.
This is the data I am loading as JSON:
[
{
"datetime": "28/08/2021 16:01:00",
"sensor": {
"id": 1,
"type": "Temperature"
},
"value": 2502
},
{
"datetime": "28/08/2021 16:02:00",
"sensor": {
"id": 2,
"type": "Temperature"
},
"value": 2252
}
]
And these are the mongoose models:
const SensorType = Object.freeze({
Temperature: "Temperature"
});
const SensorSchema = new mongoose.Schema({
id: { type: Number, required: true },
type: { type: Object.values(SensorType), required: true },
});
Object.assign(SensorSchema.statics, { SensorType });
const Sensor = mongoose.model('Sensor', SensorSchema);
const DataEntrySchema = new mongoose.Schema({
datetime: { type: String, required: true },
sensor: { type: SensorSchema, required: true },
value: { type: Number, required: true }
});
const DataEntry = mongoose.model('DataEntry', DataEntrySchema);
Loading the DataEntries like this:
mongoose.connect("mongodb://127.0.0.1:27017/",{
useCreateIndex:true,
useNewUrlParser: true,
useUnifiedTopology: true}
).then(() => {
console.log('Database Successfully Connected')
if(fill_default_data) {
DataEntry.create(
JSON.parse(fs.readFileSync(path.resolve(__dirname, 'test_data.json'), 'utf8'))
);
}
}, error => {
console.log(error)
}
);
However, I am noticing that no Sensor-objects are created inside MongoDB, only DataEntries - why is that? And how can I create Sensor-objects as well?
Of course, a DataEntry object has the sensor attached but if I call Sensor.find().then( sensors => res.json(sensors) ) an empty array is returned.

You probably can't use a schema in another schema. You need to use refs instead.
So something like this sensor: { type: SensorSchema, required: true } won't work.
You should replace it with sensor: { type: number, required: true, ref: 'Sensor' },, where the ref is the name of the model you want to refer to as a string. Notice that the type is a number as you want to pass the id of the relevant SensorDocument in the DataEntryDocument.
Moreover id is a virtual, you should use _id instead when you want to spec out ids in mongoose schemes.
So your mongoose schemes should look like:
const SensorSchema = new mongoose.Schema({
_id: { type: mongoose.Schema.Types.Number, required: true },
type: { type: mongoose.Schema.Types.String, required: true },
});
const Sensor = mongoose.model('Sensor', SensorSchema);
const DataEntrySchema = new mongoose.Schema({
datetime: { type: mongoose.Schema.Types.String, required: true },
sensor: { type: mongoose.Schema.Types.Number, ref: 'Sensor', required: true },
value: { type: mongoose.Schema.Types.Number, required: true }
});
const DataEntry = mongoose.model('DataEntry', DataEntrySchema);
I still don't know why the Object.freeze and Object.assign are here.
Now if you want a DataEntry, you first need to create a Sensor.
const sensor = new Sensor({ _id: 0, type: 'Temperature' })
await sensor.save()
const dataEntry = new DataEntry({ sensor: 0, datetime: 'some timestamp as string', value: 25 })
await dataEntry.save()
I am leaving the validation-specific logic out as it is out of the scope of this query.
You can checkout docs for mongoose populate for more information.

Related

how to insert into deeply nested element API nodejs mongoose

I have 4 nested documents as follow:
//Nested sub document subControl
const SubControlSchema = new Schema({
subControlNo: {
type: String
},
updated: Date,
created: {
type: Date,
default: Date.now
}
});
//Nested sub document control
const ControlSubSchema = new Schema({
mainControl: {
type: String
},
subControls: [SubControlSchema],
controlDescription: {
type: String,
trim: true
},
updated: Date,
created: {
type: Date,
default: Date.now
}
});
//Nested sub document domain
const DomainSubSchema = new Schema({
_id: {
type: Schema.ObjectId,
auto: true
},
domainNo: {
type: String,
trim: true
},
domainName: {
type: String,
trim: true
},
domainDescription: {
type: String,
trim: true
},
controls: [ControlSubSchema],
updated: Date,
created: {
type: Date,
default: Date.now
}
});
// framework Schema
const FrameworkSchema = new Schema({
name: {
type: String,
trim: true
},
description: {
type: String,
trim: true
},
regulator: {
type: Schema.Types.ObjectId,
ref: 'Regulator',
default: null
},
client: {
type: Schema.Types.ObjectId,
ref: 'Client',
default: null
},
domains: [DomainSubSchema],
updated: Date,
created: {
type: Date,
default: Date.now
}
});
module.exports = Mongoose.model('Framework', FrameworkSchema);
I'm trying to post a control under the domain which is inside the framework, here's what I have been trying to do:
//Add new control under a specific domain and framework
router.post('/add/:frameworkId/:domainId', auth, async (req, res) => {
try {
const control = req.body.controls; //take the request from the body
const query = { _id: req.params.frameworkId, _id: req.params.domainId };//pushing into the framework model by taking the ID from URL
await Framework.updateOne(query, { $push: { domains: control } }).exec(); //push the query into the framework model
res.status(200).json({
success: true,
controls: control
});
} catch (error) {
res.status(400).json({
// error: 'Your request could not be processed. Please try again.'
error
});
}
});
Data posted in postman:
Link: http://localhost:3000/api/framework/add/6233277f411377367f8ad1c0/6233277f411377367f8ad1c1
{
"controls":
{
"mainControl": "1-5",
"subControls": [{
"subControlNo": "1-4-1"
},
{
"subControlNo": "1-4-2"
}],
"controlDescription": "controlDescriptionTest"
}
}
Response:
{
"success": true,
"controls": {
"mainControl": "1-5",
"subControls": [
{
"subControlNo": "1-4-1"
},
{
"subControlNo": "1-4-2"
}
],
"controlDescription": "controlDescriptionTest"
}
}
Problem: I'm not getting any new data in mongodb , any idea if I'm approaching this the correct way? I'm guessing the data is posted correctly and It's a problem with saving it to the database
Picture of my schema: I want to be able to add elements under the controls:
First if you want your code to insert and not update you should use insertOne and not updateOne, regarding an "update" operation I can see 2 potential "issues" here:
req.params.frameworkId and req.params.domainId come as string type. And I assume the _id field is type ObjectId and not string.
To fix this you just need to cast it to the proper type, like so:
import { ObjectId } from 'mongodb';
...
{ _id: new ObjectId(req.params.frameworkId) }
Both parameters are "querying" the same field (_id), unless this is intentional somehow if these values are different it will never find a document to match, this should be changed.
Lastly if you want to update an existing object if exists, and if not insert then you should use updateOne with the upsert option:
await Framework.updateOne(query, { $push: { domains: control } }, { upsert: true }).exec();

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.

How to solve mongoose model statics error

I have the following model defined for my graphql API:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const Post = Schema({
_id: Schema.Types.ObjectId,
title: { type: String, required: true },
description: { type: String, required: true },
content: { type: String, required: true },
comments: { type: [String], required: true },
tags: { type: [String], required: true },
stats: { type: [Number], required: true },
datePublished: { type: Date, required: true },
dateUpdated: { type: Date, required: true }
});
// TODO: Implement methods here
// Post.statics.getPost = function(args) {
// console.log(this.title);
// return this.model
// };
Post.statics.getPost = function(args) {
this.find({});
};
module.exports = mongoose.model("Post", Post, "posts");
I am willing to create a function in the model to fetch information from the database. But when I request the API for this function, I get the following response which is an error. I have also tried changing this to Post unsuccessfully.
{
"errors": [
{
"message": "this.find is not a function",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"getPost"
]
}
],
"data": null
}
How can I solve this problem?
It does not look like you are instantiating an instance of mongoose schema. You need to use new. This will give you a reference to this.
const Post = new mongoose.Schema({ ...

property pre does not exist on schema in mongoose

I am trying to detect changes in the document via pre hook but it typescript is giving me error that this property does not exist.
following Structured style, not OOP
// category.schema.ts
const categorySchema = new Schema({
category_id: { type: Number, required: true, unique: true },
name: { type: String, required: true, unique: true },
icon_url: { type: String, required: true },
items_quantity: { type: Number, required: true },
items: [
item_id: { type: Number, required: true, unique: true },
item_name: { type: String, required: true }
]
})
const Category: Model<Category> = model<Category>('Category', categorySchema);
export default Category;
Now I want to check for document changes on deletion of subdocument.
import CategorySchema from "../schemas/category.schema"; // schema path
router.delete('/:category/:item', async (req, res) => { // removes an item
let itemsQuantity: number;
let category = await CategorySchema.findOneAndUpdate(
{ category_id: req.params.category },
{ $pull: { items: { item_id: req.params.item } } },
{ new: true });
// pre does not exist
CategorySchema.pre('save', function(next) {
if(category.isModified()) {
log('changed');
} else {
log('not changed')
}
})
const data = await category.save();
res.status(200).send(req.params.item);
})
How to get or enable this hook, any suggestions?

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