Trouble implementing validator for MongoDB Schema - node.js

platform: [{
validate: {
validator: array => { //our custom validator, array is the provided array to be validated
const filtered = array.filter((obj, index, self) => self.findIndex(el => el.platformName === obj.platformName) === index); //this removes any duplicates based on object key
return array.length === filtered.length //returns true if the lengths are the same; if the lengths aren't the same that means there was a duplicate key and validation fails
},
message: 'Detected duplicate keys in {VALUE}!',
},
platformName: {
type: String,
enum: Platform, //category must be in this enum
},
link: {
type: String,
},
}],
I'm using the above method to get something like
platform: [{"platformName": "FACEBOOK", "link": "DJNgJD"}, {"platformName": "MEETUP", "link": "DJNgJD"}]
and trying to use this validator I got from stack overflow it self to prevent duplicate key values for platformName key,
but getting the error below. link to stack overflow answer i got this code from How to validate object keys and values in Mongoose Schema?
throw new Error('`' + firstPieceOfPath + '` may not be used as a schema pathname');
Using Ts btw, someone help me figure out what I'm doing wrong

Related

Storing and querying JSON arrays in Redisjson with nodejs

What I was hoping to do was store an array of objects using RedisJSON very simply and then query that array.
I have something similar to this:
const data = [
{
_id: '63e7d1d85ad7e2f69df8ed6e',
artist: {
genre: 'rock',
},
},
{
_id: '63e7d1d85ad7e2f69df8ed6f',
artist: {
genre: 'metal',
},
},
{
_id: '63e7d1d85ad7e2f69df8ed6g',
artist: {
genre: 'rock',
},
},
]
then I can easily store and retrieve this:
await redisClient.json.set(cacheKey, '$', data)
await redisClient.json.get(cacheKey)
works great. but now I want to also query this data, I've tried creating an index as below:
await redisClient.ft.create(
`idx:gigs`,
{
'$.[0].artist.genre': {
type: SchemaFieldTypes.TEXT,
AS: 'genre',
},
},
{
ON: 'JSON',
PREFIX: 'GIGS',
}
)
and when I try and search this index what I expect is it to return the 2 documents with the correct search filter, but instead it always returns the entire array:
const searchResult = await redisClient.ft.search(`idx:gigs`, '#genre:(rock)')
produces:
{
total: 1,
documents: [
{ id: 'cacheKey', value: [Array] }
]
}
I can't quite work out at which level I'm getting this wrong, but any help would be greatly appreciated.
Is it possible to store an array of objects and then search the nested objects for nested values with RedisJSON?
The Search capability in Redis stack treats each key containing a JSON document as a separate search index entry. I think what you are doing is perhaps storing your whole array of documents in a single Redis key, which means any matches will return the document at that key which contains all of your data.
I would suggest that you store each object in your data array as its own key in Redis. Make sure that these will be indexed by using the GIGS prefix in the key name, for example GIGS:63e7d1d85ad7e2f69df8ed6e and GIGS:63e7d1d85ad7e2f69df8ed6f.
You'd want to change your index definition to account for each document being an object too so it would look something like this:
await redisClient.ft.create(
`idx:gigs`,
{
'$.artist.genre': {
type: SchemaFieldTypes.TEXT,
AS: 'genre',
},
},
{
ON: 'JSON',
PREFIX: 'GIGS:',
}
)
Note I also updated your PREFIX to be GIGS: not GIGS - this isn't strictly necessary, but does stop your index from accidentally looking at other keys in Redis whose name begins GIGS<whatever other characters>.

Unique key only in array of subschemas?

I have a schemas. One of its keys takes the type of an array of schemas.
let instanceSchema = new Mongoose.Schema({
name: { type: String, required: true, unique: true }
})
let mainSchema = new Mongoose.Schema({
instances: { type: [instanceSchema], required: true }
})
So the mainSchema has a key that is an array of instanceSchemas. I want the behavior to be such that I can have instanceSchemas with the same name if they are members of different mainSchemas, like so:
let MainModel = mongoose.model("MainModel", mainSchema);
// Succeeds
main1 = new MainModel({
"instances": [ {"name": "Instance1"}, {"name": "Instance2"} ];
});
// Succeeds
main2 = new MainModel({
"instances": [ {"name": "Instance1"}, {"name": "Instance2"} ];
});
As shown, we have two named "Instance1" and two named "Instance2" but they are members of different documents (main1 and main2) so my target behavior is that this should be allowed. However, using unique prevents this from happening as Mongoose checks all instanceSchema models. Is there a way to allow duplicates as long as they are members of different documents?
Short answer: no, unique doesn't work like that.
When you tag a field unique in a mongoose schema, it will create a unique index on that field in MongoDB.
MongoDB index entries are made per-document, not per array entry.
For example, if you have an index on {field :1} and insert the document {field:["a","b","a","b","c","d"]}, then the entries in that index for this document will be:
"a"
"b"
"c"
"d"
In MongoDB, when an index is created with the unique: true option, it enforce that any value only appears once #in the index#. This means that the above document would be perfectly acceptable even if the index on {field: 1} were unique.
To quickly demonstrate this, I used your model defined above, and executed:
res1 = await (new MainModel({instances:[{name:"1"},{name:"2"},{name:"1"}]})).save();
console.log("Inserted: ",JSON.stringify(res1));
res2 = await MainModel.collection.getIndexes({full: true});
console.log("Indexes: ",JSON.stringify(res2));
res3 = await (new MainModel({instances:[{name:"3"},{name:"2"},{name:"4"}]})).save();
console.log("Inserted: ",JSON.stringify(res3));
This logged:
Inserted: {"_id":"60d273060ffb1ac8e48359e5","instances":[{"_id":"60d273060ffb1ac8e48359e6","name":"1"},{"_id":"60d273060ffb1ac8e48359e7","name":"2"},{"_id":"60d273060ffb1ac8e48359e8","name":"1"}],"__v":0}
Indexes: [{"v":2,"key":{"_id":1},"name":"_id_"},{"v":2,"unique":true,"key":{"instances.name":1},"name":"instances.name_1","background":true}]
/Users/joe/temp/mongoose/node_modules/mongodb/lib/core/error.js:57
return new MongoError(options);
^
MongoError: E11000 duplicate key error collection: test.mainmodels index: instances.name_1 dup key: { instances.name: "2" }
at Function.create
... snip ... {
driver: true,
index: 0,
code: 11000,
keyPattern: { 'instances.name': 1 },
keyValue: { 'instances.name': '2' }
}
As you can see, it create a unique index on { 'instances.name': 1 }, permitted duplicate entries within a single document, and prohibited an identical entry in the other document.

Node Js and Moongoose: How to loop into array of objects and delete a particular object and then update the whole document

My document schema is as follows:
const CollectionSchema = mongoose.Schema({
ImageCategory:{type:String,required:true},
imgDetails: [
{
_id: false,
imageUrl:{type:String},
imageName:{type:String},
imageMimeType:{type:String},
}
],
Date: {
type: String,
default: `${year}-${month}-${day}`,
},
},{timestamps: true,})
So in the database for example one document has multiple images with a single image category. What I am trying to do is I want to delete an object from imgDetails array.
Let me explain my question more precisely: imgDetails is an array
Explanation: I want to loop in imgDetails and then find (where imgageUrl === req.body.imageUrl) if its match delete that whole object which have that req.body.imageUrl and then update the document.
Please guide me on how to write such a query. Regards
Demo - https://mongoplayground.net/p/qpl7lXbKAZE
Use $pull
The $pull operator removes from an existing array all instances of a value or values that match a specified condition.
db.collection.update(
{},
{ $pull: { "imgDetails": { imageUrl: "xyz" } } }
)

Mongoose js validation on sub object multiple fields

I am attempting to validate an entire object (multipel fields) within mongoose.
I have the following custom schema definition:
module.exports.ItemQuantityPair = (ItemModel, strictStacking)=>{
return {
type: {
itemID: { type:Number, required:true },
quantity: { type:Number, min:1, default:1 },
},
validate: {
validator: async value => { //stacking
if(!strictStacking || value.quantity===1) //if no strict stacking, or only quantity of 1 don't validate
return true;
let item = await ItemModel.findOne({itemID:value.itemID}); //otherwise get the item and check if its stackable
if(!item.stackable)
return false; //invalid, quantity > 1 and not stackable
else
return true; //valid, quantity > 1 and stackable
},
message: props=>`${props.value} this item was supplied with a quantity larger than 1, but the item was found to not be stackable. Ensure only stackable items have quantity > 1`
},
}
}
Then later in my schema I use this custom object like so:
const AccountSchema = new mongoose.Schema({
inventory: [ItemQuantityPair(ItemModel, true)] //inventory is a list of ItemQuantity pairs
});
When I try this, I'm presented with the error TypeError: Invalid schema configuration: 'undefined' is not a valid type within the array 'inventory' I assume this is because I can't assign an object tp the type field since these are the only built-in types: https://mongoosejs.com/docs/schematypes.html#what-is-a-schematype. Normally I would validate each field separately, but this specific validation requires both fields' values (quantity and itemID) to be determined as valid or not.
How can I cleanly accomplish validation on multiple fields?
Thanks

Sequelize is returning integer as string

I using nodejs v4 with sequelize, and I have a model like this:
var Device = sequelize.define('Device', {
id: {
type: DataTypes.BIGINT,
primaryKey: true,
autoIncrement: true
},
tenantId: {
type: DataTypes.BIGINT,
allowNull: false
},
token: {
type: DataTypes.STRING,
allowNull: false
}
}, {
tableName: 'devices'
});
When I select a device by id the type of id is a string, exemple:
Device.findById(9).then( function(result) {
console.log(result.toJSON().id + 10);
});
The output will be 910, rather than 19, so I look at json and a saw this:
{
id: "9"
tenantId: "123"
token: "adsadsdsa"
}
The id in found device is a string, but I defined it as a number...
Doesn't it should be { "id": 9 } ?
How can I select a device with the types that I defined previously?
BIGINT maximum value is 2^63-1, javascript can safely represent up to 2^53. To be on the safe side libraries return those numbers as strings.
If you want to have numbers instead of strings, you can use this library https://github.com/mirek/node-pg-safe-numbers which deals with this issue.
I found a fix to this problem on sequelize repo.
https://github.com/sequelize/sequelize/issues/4523
The pg module used for sequelize returns bigint as string because bigints are not guaranteed to fit into js numbers. So I change my model to use integer (DataTypes.INTEGER)
IMO, a smoother solution is to force parsing of int to int, instead of strings. Checkout this issue and comments.
Trying to put this line before your logic code worked for me, this forces parsing of int8 to int, instead of strings:
require("pg").defaults.parseInt8 = true;
...
try {
const list = await Posts.findAll();
console.log(JSON.stringify(list));
} catch (e) {
console.log(e.message);
}
By default, sequelize try format your results. You can set raw :true for get raw data

Resources