Joi validate optional array from dynamically added fields - node.js

I have a form where the user can add more authors, the first one is required and is it has a its own fields validation
When the user add more authors these authors are stored in an array, so if the user does not add more authors it shows this error message:
"\"authors_first_name\" must be an array"
"\"authors_last_name\" must be an array"
"\"authors_country\" must be an array"
How to make the array optional when thare are not more than one author?
But required when authors are added?
const schema = Joi.object().keys({
author_first_name: Joi.string().required().error(() => 'Author First Name is required'),
author_last_name: Joi.string().required().error(() => 'Author Last Name is required'),
author_country: Joi.string().valid(isoCodes).error(() => 'Author Country not valid'),
authors_first_name: Joi.array().items(Joi.string()).optional(),
authors_last_name: Joi.array().items(Joi.string()).optional(),
authors_country: Joi.array().items(Joi.string().valid(isoCodes).error(() => 'Country not valid')).optional()
});
I added the .optional() but the error keeps showing

EDIT: Oct/2022
The old code will not work with the current Joi version as today is 17.7.0, this will work:
const schema = Joi.object().keys({
first: Joi.array().items(Joi.string()).single(),
last: Joi.array().items(Joi.string()).single(),
country: Joi.array().items(Joi.string().valid(...isoCodes)).single(),
}).assert('.first.length', Joi.ref('last.length'))
.assert('.last.length', Joi.ref('country.length'));
Here the test on runkit
Old code
Alright I got it working with this code:
const schema = Joi.object().keys({
first: Joi.array().items(Joi.string()).single(),
last: Joi.array().items(Joi.string()).single(),
country: Joi.array().items(Joi.string().valid(isoCodes)).single(),
}).assert('first.length', Joi.ref('last.length'))
.assert('last.length', Joi.ref('country.length'));
Two additions to my original code:
.single() to the array().items(), this will accept string and arrays in the values
.assert() to check if the array length are equals
Thanks to this post, test code here

I think the approach you are using here is misleading, you should have array of objects even if the user inserts only one author or multiple authors. Taking different arrays for first_name and last_name can result in problems like what if you got 5 first_names and 6 last_names. you can add validations but still this is a kind of buggy approach. You should use schema like this
const schema = Joi.array().required().items(
Joi.object().required().keys({
author_first_name: Joi.string().required().error(() => 'Author First Name is required'),
author_last_name: Joi.string().required().error(() => 'Author Last Name is required'),
author_country: Joi.string().valid(isoCodes).error(() => 'Author Country not valid'),
}))

Related

Given a mongodb model's array I want to find records in which that array has the most matches using mongoose

I have a mongoose schema as such:
const userSchema = new mongoose.Schema({
keywords: [{ "type": String, "enum": ["yup", "nope"] }],
})
Here, I have one user with a set of keywords and I want to find the records in my database which have the most similar set of keywords to this particular user.
For example, If a user has ["yup" "nope"] as their keywords, I want to find all records of users who have "yup" or "nope" or both in their keywords array. This is just an example and in reality, the users will have a whole lot more keywords to choose from.
How can I do this using mongoose?
I was thinking about one-hot-encoding the values in the array and the records with the most matching 1s can be added to another table "Most similar values table or something" that maintains this list for every user with the user as the foreign key. But I haven't been able to come up with an efficient and/or working algorithm for this yet.
In my opinion the best aproach is regular expresion. I write you one example function how to search and filter data in MongoDB using mongoose. For example lets search customers by lastName example for starts, ends, contains string "yup". Be aware searching with regex is case sensitive default. If you add "i" after regex it will be case insensitive.
async function getCustomers() {
const customers = await Customer
//case search lastName whitch starts with "yup" - case sensitive
.find({lastName: /^yup/})
//or case search lastName whitch ends with "yup" - case insensitive
.find({lastName: /yup$/i })
//or case search lastName whitch contains "yup" in any part
.find({lastName: /.*yup.*/ })
.limit(20) //get top 20 results
.sort({lastName: 1}) // sort by lastName
console.log(customers)}
//searching in array
const customerList = ['Smith', 'Jackson'];
async function getCustomers(arr) {
return await Customer
.find({lastName: {$in: arr}})
.limit(20) //get top 20 results
.sort({lastName: 1}) // sort by lastName
}
getCustomers(customerList);
for more info chceck documentations:
https://docs.mongodb.com/manual/reference/operator/query/regex/

how to make categories section in mongoose schema design of product like electronics, fashion ,food, etc. help to build this type of model

How to make this type of model?
you can do something like this:
category: {
type: String,
enum: {
//Your categories
value: ['electronics', 'food', 'fashion'],
message: '{Value} //Your error message'
}
enum is validator in mongoose you can check it on their docs.
message -- this will warn user if he enters any other category not from value array
Before saving data mongoose will check if the provided category is in the categories of value array and will only save the data if category is on of them. U can add more categories in the array if you want.

using spread syntax with Mongoose Document after calling the .save method results in undefined keys

I'm using a Mongoose/MongoDB and I'm getting some odd behaviour when I try to use the spread syntax to return values from a document after I call .save() on it.
// Npc is a Mongoose schema
const npc = new Npc({
...input,
creator: userId
});
const createdNpc = await npc.save();
I have tried using the spead operator, but the name and description keys do not exist.
return {
...createdNpc
creator: userFromId(npc.creator)
}
however when I access those values directly they ARE defined
return {
description: createdNpc.description,
name: createdNpc.name,
creator: userFromId(npc.creator)
};
I've made sure that the spelling of description and name are correct. I've tried logging both {...createdNpc} and {...createdNpc, description: createdNpc.description, name: createdNpc.name}. In the logs I've confirmed that name and description are both not defined (the keys don't exist) inside of {...createdNpc}
I have also tried logging createdNpc and {...createdNpc} and have confirmed that they return different values.
here's createdNpc:
{
_id: 5d8d5c7a04fc40483be74b3b,
name: 'NPC Name',
description: 'My Postman NPC',
creator: 5d8d50e0b5c8a6317541d067,
__v: 0
}
it doesn't actually look like a Mongoose Document at all. I would post the result of {...createdNPC} to show the difference but it's a huge code snippet and I don't want to clutter the question. I'm happy to provide it if it will help!
I'm still very new to MongoDB & Mongoose. Why would using the spread syntax on a Mongoose Document change its value?
I don't think this should be relevant to the question but just in case I'll also mention this is for a graphql resolver.
This is because Mongoose uses getters for all of its attributes. Before you use the spread operator, call createdNpc.toObject() to get a normal Object.

Nodejs - Joi Check if string is in a given list

I'm using Joi package for server side Validation. I want to check if a given string is in a given list or if it is not in a given list.(define black list or white list for values)
sth like an "in" or "notIn" function. How can I do that?
var schema = Joi.object().keys({
firstname: Joi.string().in(['a','b']),
lastname : Joi.string().notIn(['c','d']),
});
You are looking for the valid and invalid functions.
v16: https://hapi.dev/module/joi/api/?v=16.1.8#anyvalidvalues---aliases-equal
v17: https://hapi.dev/module/joi/api/?v=17.1.1#anyvalidvalues---aliases-equal
As of Joi v16 valid and invalid no longer accepts arrays, they take a variable number of arguments.
Your code becomes
var schema = Joi.object().keys({
firstname: Joi.string().valid(...['a','b']),
lastname: Joi.string().invalid(...['c','d']),
});
Can also just pass in as .valid('a', 'b') if not getting the values from an array (-:
How about:
var schema = Joi.object().keys({
firstname: Joi.string().valid(['a','b']),
lastname : Joi.string().invalid(['c','d']),
});
There are also aliases: .allow and .only
and .disallow and .not
Update from 10 Oct 2022
Now valid function requires Object
Joi.string().valid({ ...values_array }),

finding objectIds between two given values in mongodb and nodejs

I am creaing schemas similar to newsposts with an option for users to like and dislike them.
Here are the schemas for same
Client= new mongoose.Schema({
ip:String
})
Rates = new mongoose.Schema({
client:ObjectId,
newsid:ObjectId,
rate:Number
})
News = new mongoose.Schema({
title: String,
body: String,
likes:{type:Number,default:0},
dislikes:{type:Number,default:0},
created:Date,
// tag:String,
client:ObjectId,
tag:String,
ff:{type:Number,default:20}
});
var newsm=mongoose.model('News', News);
var clientm=mongoose.model('Client', Client);
var ratesm=mongoose.model('Rates', Rates);
In order to retreive the ratingsgiven by a particular user having given a set of newsposts, I tried,
newsm.find({tag:tag[req.params.tag_id]},[],{ sort:{created:-1},limit: buffer+1 },function(err,news){
ratesm.find({
client:client._id,
newsid:{$lte:news[0]._id,$gte:news.slice(-1)[0]._id}
},
function(err,ratings){
})
})
This query returns empty list no matter what. I doubt whether $gte and $lte be used to compare objectIds. Am I right? How can I which posts a user has liked/disliked in a given set of newsposts?
Yes, ObjectIds can be queried with range queries like $gt/$lt etc. Can you post the exact values being used for news[0]._id and news.slice(-1)[0]._id that are giving you the empty result?
However, i'm not sure that $gt/$lt is what you want here. It seems like what you need to do is extract the _ids of the news items, and then use that in a $in filter in your query on ratesm.

Resources