Joi apply different validation on nested object based on parent key - node.js

i just started using Joi and kind of lost about this case and don't know if its even possible the way i am going about it.
i have an object that has multiple keys with each key being a nested object with some shared props between them, something like this.
{
a: {
x: true,
y: ''
},
b: {
x: false
}
}
i am trying to make Joi validate that y exists when my parent is a and it should validate that y doesn't exist otherwise.
i am using pattern like this
const allowedKeys ['a', 'b']
Joi.object().pattern(
Joi.string().valid(...allowedKeys),
Joi.object().keys({
x: Joi.boolean(),
y: // this should exist if i am inside `a` but shouldn't otherwise
})
),
i have done a few searches and tried using alternatives, ref and when and a combo of them but i can't find a way to test the current parent key's value.
am i going about this correctly or should i follow another pattern to get what i need ?

Related

Sequelize INSERT handling Object that has sometimes value of NULL as an attribute

I am trying to insert multiple objects to a Postgres database using Sequelize ORM. These objects are obtained from another project that I personally can't change or modify. These objects will have attributes that have object inside them.
Example object:
{
id:1,
name:'foo',
created:{user:{'insertattributehere'},date:'insertdatehere'},
modify:{user:{'insertattributehere'},date:'insertdatehere'},
}
For simplicity purposes, I have created a table that has each attribute of object as a column which will have a datatype of String(id as string, name as string, created_user_attribute, created_date etc).
So when inserting the object, I would simply the following INSERT.
const new_user = await ClassName.create({id: object.id, name: object.name, created_user_attribute: object.user.attribute ...})
However, sometimes, the attribute that contains another object can be null, for example
{
id:2,
name:'bar',
created:{date:'insertdatehere'}, notice that created doesnt have attribute User
modify:{user:{'insertattributehere'},date:'insertdatehere'},
}
This will result on TypeError, since 'created' doesn't have 'user' attribute. What I want is a method to somehow will handle this TypeError, and insert a NULL value (or "" for the string)
I could, as a last resort, manually check every attribute for a null value to handle the TypeError, and then create a nested statement such that I will insert a no value string instead. However, this looks very repetitive and inelegant.
Is there a way to handle this problem in a better way? Note that I can't change the objects that I want to insert to my database.
You can use lodash function omitBy along with the optional chaining operator to get only defined props and if omitted props has no default value in DB they will have null values by default:
const new_user = await ClassName.create(
_.omitBy({
id: object.id,
name: object.name,
created_user_attribute: object.user?.attribute
}, _.isUndefined)
)

Option.order is broken when passing it to class method

Background
Error: Order must be type of array or instance of a valid sequelize method
I thought I've solved this issue but turns out that I mess up with this error again.
I'm trying to make class method to calculate some properties for a model.
Let this be Model A.
Model A is associated with Model B. Because I have issue for generating proper column name for Model B when using array parameter.
I'm planning to bypass this issue by using sequelize.literal()
Pattern
Make a class method for Model A (a lot of business layer is used this function. So I can't take this away)
Prototype of this method is Model.function(options). This options object is validated inside of method function and if it needed mutated somehow.
Validated option object is passed to Model.findAll(options)
I'm impletmenting this solution as like code below
Router
const sequelize = require('sequelize')
const { ModelA } = require('../models')
...
router.get('/', ..., async (req, res, next) => {
try {
...
if(page < lastpage + 1){
const products = await ModelA.classMethod({
subquery : false,
include: [
...
],
...
order : sequelize.literal(`"Product.rating" DESC`)
})
...
}
...
} catch (e) {
...
}
})
Class method
ModelA.classMethod = async function(options){
const sequelize = require('sequelize')
const { ModelB } = require('.')
let { include, where, order, limit, offset, product } = options
...
const items = await ModelA.findAll({
subquery: false,
include: [
{
model: ModelB,
as: 'ModelB',
required: true,
where: product.where,
include: include
}
],
where: where,
limit: limit,
order: order
})
...
}
Weird thing is happening here. While passing parameter (Pattern 3), I got an error Error: Order must be type of array or instance of a valid sequelize method and this error seems that because the option passed is invalid sequelize.literal()
But actually what I passed is just sequelize.literal(`'Product.name' DESC`), no mutation in here.
So I tried to figure out what's wrong with my literal.
let { order } = option
console.log(order)//Literal { val: "'Product.rating' DESC" }
console.log(order instanceof sequelize.Utils.SequelizeMethod)//false
if(!order) order = null
console.log(order)//Literal { val: "'Product.rating' DESC" }
ModelA.findAll({ ..., order : order })
console.log('good!!!')//I want to see this log
Order itself looks fine but I think somewhere of prototype is broken.
The most weired part is if I replace the order with sequelize.literal(`'Product.name' DESC`)
,which is the same as what I passed into classMethod parameter, some kind of magic happens and error is gone.
const sequelize = require('sequelize')
let order = sequelize.literal("'Product.rating'")
console.log(order)//Literal { val: "'Product.rating' DESC" }
console.log(order instanceof sequelize.Utils.SequelizeMethod)//true!!
if(!order) order = null
ModelA.findAll({ ..., order : order })
console.log('good!!!')//I see this log and I can finally rest in peace.
If anyone has similar problem like me, would you please share some insight to solve this problem? So far I tried like below.
Passing router sequelize instance to class method. console.log(order instanceof sequelize.Utils.SequelizeMethod)//true so seems not broken actual query is not executed somehow.
Statically add order : sequelize.literal("'Product.rating' DESC") : work perfect but useless in production. This option should be dynanic so that user can control it.
My bad What were weird was not the error but me using different version between applications and project root. This problem is caused by version issue with nested directory.
/* MY PROJECT STRUCTURE */
project
|
|--api (sequelize 5.22)
|
|--auth
|
|--batch
|
|-- models (sequelize 6.3)
Recently, I upgraded sequelize from 5.22 to 6.3 while doing that, I missed upgrading sequelize of the applications. So the version between root and applications were being different. Since migration to typescript is on going in sequelize, sequelize inner types of 6.0 and 5.22 must be different.
Error: Order must be type of array or instance of a valid sequelize method
So that, this error was reasonable enough but I couldn't get that at the moment. There are many advices not to install same module in nested directory. I should've listened to them carefully.
But still, ordering with array (ex > [model, 'column', 'type']) gives me wrong query and ended up with unknown column error.

Aliasing fields in Apollo GraphQL Server

Aliasing is very handy and works great when aliasing a specific resolver. For instance:
{
admins: users(type:"admin"){
username
}
moderators: users(type:"moderators"){
moderators
}
}
I'm not sure how to handle aliasing of the fields themselves though. For example:
{
site_stats {
hits: sum(field: "hits")
bounces: sum(field: "bounces")
}
}
If the resolver returns any sum value, the same value is aliased to both hits and bounces (which makes sense, since only a single sum value could even be returned). If I make the resolver use the alias names as the field names when returning the results, hits and bounces both become null.
I could simply break those fields out into separate resolvers, but that complicates integration for the front end devs. We would also lose a ton of efficiency benefits, since I can aggregate all the data needed in a single query to our data source (we're using ElasticSearch).
Any help from you geniuses would be greatly appreciated!
Using aliases and single fields has very limited usability.
You can use complex filters (input params), f.e. list of keys to be returned and their associated params, f.e.
[{name:"hits", range:"month"},
{name:"bounces", range:"year"}]
With query - expected structure
{
stats {
name
sum
average
}
}
Required fields may vary, f.e. only name and sum.
You can return arrays of object f.e.
{ stats: [
{ name:"hits",
sum:12345,
average: 456 }
Aliases can be usable here to choose different data sets f.e. name and sum for hits, bounces additionally with average.
... more declarative?
PS. There is nothing that "complicates integration for the front end devs". Result is json, can be converted/transformed/adapted after fetching (clinet side) when needed.
It sounds like you're putting all your logic inside the root-level resolver (site_stats) instead of providing a resolver for the sum field. In other words, if your resolvers look like this:
const resolvers = {
Query: {
site_stats: () => {
...
return { sum: someValue }
},
},
}
you should instead do something like:
const resolvers = {
Query: {
site_stats: () => {
return {} // empty object
},
},
SiteStats: {
sum: () => {
...
return someValue
},
},
}
This way you're not passing down the value for sum from the parent and relying on the default resolver -- you're explicitly providing the value for sum inside its resolver. Since the sum resolver will be called separately for each alias with the arguments specific to that alias, each alias will resolve accordingly.

How to use $or operator in URL query string for node.js express mongoose backend

Is there a (proper) way to use the $oroperator in a URL query string to realize a client customizable query which does not only consist of and combined fields against my node.js powered RESTful API?
Let's say I have objects like
[{
A: 1,
B: 2,
C: 42
}, {
A: 3,
B: 1,
C: 42
}]
How do I query the MongoDB using mongoose to get both of them if searched in multiple fields? Something like $and: [{C:42}, $or: [{A:1}, {B:1}]] as URL parameters?
I tried GET /api/orders?$or=[{A:1},{B:1}]], but that results in a query like orders.find({ '$or': '[{A:1},{B:1}]]' }}), where the array is a string. The MongoDB driver then complains about $or needs an array.
All libraries like mongo-querystring have some complex operators for $gtand so on, but not for a simple OR of parameters. What am I missing here?
My goal is to build a convience search field, where the user can enter a simple string which then in turn is searched in multiple fields, returning all documents where at least one field (or a substring of that field) matches. So the frontend should decide in which fields and how the server should search, leading to a dynamic set of AND/OR combined field/value pairs.
Thanks in advance,
Ly
One option is to use the qs library which is a querystring parser with nested object support.
const qs = require('qs');
const query = {$or:[{A:1},{B:1}]};
let stringQuery = qs.stringify(query);
console.log(stringQuery); // => %24or%5B0%5D%5BA%5D=1&%24or%5B1%5D%5BB%5D=1
console.log(qs.parse(stringQuery)); // => { '$or': [ { A: '1' }, { B: '1' } ] }

Neo4j Get All Properties IFF Relationship 'Met' Exists, Partial Properties Otherwise

I am still learning Neo4j and using the browser console with REST transactions to perform queries. I have a question on how to accomplish a particular task. Given the following scenario how would one go about completing the following:
I have 3 users in the database
2 users are connected with a relationship :Met label.
The 3rd user does not have any relationship connections
I want to be able to create a Cypher query to do the following:
IFF a :Met relationship exists between the user with whom we are making the query context and the desired user, return all of the properties for the desired user.
If no relationship exists between the user with whom we are making the query context and the desired user, only return back a public subset of data (avatar, first name, etc.)
Is there a way to execute a single query which can check if this relationship connection exists, return all User information? And if not, return only a subset of properties?
Thanks all!
In this query, p1 is the "query context", and p2 is all other people. A result row will only have the bar property if p1 and p2 have met.
MATCH (p1:Person { name: 'Fred' }),(p2:Person)
USING INDEX p1:Person(name)
WHERE p1 <> p2
RETURN
CASE WHEN (p1)-[:Met]-(p2)
THEN { name: p2.name, foo: p2.foo, bar: p2.bar }
ELSE { name: p2.name, foo: p2.foo }
END AS result;
For efficiency, this query assumes that you have first created an index on :Person(name).
You could do something like this whereby you match the first person and then optionally match the second person connected to the first via the :MET relationship. If the relationship exists then the results set you return could have more sensitive data in it.
match (p1:Person {name: '1'})
with p1
optional match (p1)-[r:MET]->(p2:Person {name: '2'})
with p1, case when r is not null then
{ name: p1.name, birthday: p1.birthday }
else
{ name: p1.name }
end as data
return data
EDIT:
OR maybe this is a better fit instead. Match both users and if a relationship exists return more data for the second person.
match (p1:Person {name: '1'}), (p2:Person {name: '2'})
with p1, p2
optional match (p1)-[r:MET]->(p2)
with p2, case when r is not null then
{ name: p2.name, birthday: p2.birthday }
else
{ name: p2.name }
end as data
return data

Resources