How to have a nested required unknown key-value object with Joi - joi

I am trying to create a schema validation that will accept this:
A:
{
"user_token": "test",
"accounts":{
"any_key":[0, 1]
}
}
or this
B:
{
"user_token": "e29a64c4c9f08414cc2999a0166664f8",
"accounts":{
"some_other_key":[0, 1]
}
}
but not this
C:
{
"user_token": "e29a64c4c9f08414cc2999a0166664f8",
"accounts":{}
}
it should basically accept an object under accounts that would accept any key-value.
I had a look at .unknown in Joi's documentation but I couldn't implement it because what I'm trying to implement this against is an array
const statementsSchema = {
body: Joi.object({
user_token: Joi.string().required(),
accounts: Joi.object({
Joi.array().unknown().required()
}).required()
})
}
I also tried .pattern which almost works but it doesn't enforce the object inside accounts required.
const statementsSchema = {
body: Joi.object({
user_token: Joi.string().required(),
accounts: Joi.object({}).pattern(/./, Joi.array()).required()
})
}
Is it possible to have accounts require a single child array without specifying the key?

You're extremely close. The missing piece is to specify the minimum number of keys allowed in the accounts object by using object.min(). In your case, the minimum number of keys is 1.
const statementsSchema = {
body: Joi.object({
user_token: Joi.string().required(),
accounts: Joi.object().pattern(/./, Joi.array()).min(1).required()
})
};

Related

How do I convert object to object of types

How do I convert an object to an object of Joi types/definitions?
I have a bunch of objects that I need to classify. I need to know what each entry is and it needs to be able to drill down through possibly multiple layers of arrays and/or objects (see the example below).
The reason I need this is that I'm using frisbyjs to validate an API response and it takes an object of Joi types to compare against the API response.
Input:
{
reservation: {
alterations: [],
arrival_details: {
guest_checkin_time_from: {
formatted_hour: "NOT_SELECTED",
localized_hour_string: "Select"
},
is_bringing_pets: false,
number_of_adults: 2
},
booked_at: "2019-03-15T14:35:28Z",
cancellation_host_fee: 0
}
}
Output:
{
reservation: Joi.object({
alterations: Joi.array(),
arrival_details: Joi.object({
guest_checkin_time_from: Joi.object({
formatted_hour: Joi.string(),
localized_hour_string: Joi.string()
}),
is_bringing_pets: Joi.bool(),
number_of_adults: Joi.number()
}),
booked_at: Joi.date(),
cancellation_host_fee: Joi.number()
})
}

how to find all if the array is empty, mongodb

i am trying to do some queries to my db... but i am new in mongodb and i dont know what operator i have to use, or maybe i just do not know how to do it.
i have a collection like this.
const userSchema= new Schema({
user: { type: String },
object: { type: String }
})
then i receive in my server a query like this
{
users: ['John', 'Michael', 'Peter'],
objects: ['Object1','Object2','Object3','Object4', 'Object5']
}
so.. in my controller i do this to find...
userModel.find({ user: { $in: req.body.users}, object: { $in: req.body.objects})
.then((users)=>{
res.json(users)
})
Ok, this is working... but, in the case that one of the arrays Users or Objects is empty, it doesn't find nothing... so, how i can handle it? there is any operator that can find all if the array is empty or something?
for example.. if the query comes like this.
{
users: ['John', 'Michael', 'Peter'],
objects: []
}
i would like to find the users in my Users array even if i dont receive any object.
Any help? thank you in advance
You have to create custom match criteria
const query = {
fieldName: { $gte: ..., $lte: ... }
}
if (req.body.users.length > 0) {
query.users = req.body.users
}
if (req.body.objects.length > 0) {
query.objects = req.body.objects
}
userModel.find(query).then((users) => {
console.log(users)
})

Access mongoose parent document for default values in subdocument

I have a backend API for an Express/Mongo health tracking app.
Each user has an array of weighIns, subdocuments that contain a value, a unit, and the date recorded. If no unit is specified the unit defaults to 'lb'.
const WeighInSchema = new Schema({
weight: {
type: Number,
required: 'A value is required',
},
unit: {
type: String,
default: 'lb',
},
date: {
type: Date,
default: Date.now,
},
});
Each user also has a defaultUnit field, that can specify a default unit for that user. If that user posts a weighIn without specifying a unit, that weighIn should use the user's defaultUnit if present or else default to 'lb'.
const UserSchema = new Schema({
email: {
type: String,
unique: true,
lowercase: true,
required: 'Email address is required',
validate: [validateEmail, 'Please enter a valid email'],
},
password: {
type: String,
},
weighIns: [WeighInSchema],
defaultUnit: String,
});
Where is correct location for this logic?
I can easily do this in the create method of my WeighInsController, but this seems at best not best practice and at worst an anti-pattern.
// WeighInsController.js
export const create = function create(req, res, next) {
const { user, body: { weight } } = req;
const unit = req.body.unit || user.defaultUnit;
const count = user.weighIns.push({
weight,
unit,
});
user.save((err) => {
if (err) { return next(err); }
res.json({ weighIn: user.weighIns[count - 1] });
});
};
It doesn't seem possible to specify a reference to a parent document in a Mongoose schema, but I would think that a better bet would be in my pre('validate') middleware for the subdocument. I just can't see a way to reference the parent document in the subdocument middleware either.
NB: This answer does not work as I don't want to override all of the user's WeighIns' units, just when unspecified in the POST request.
Am I stuck doing this in my controller? I started with Rails so I have had 'fat models, skinny controllers' etched on my brain.
You can access the parent (User) from a sub-document (WeighIn) using the this.parent() function.
However, I'm not sure if it's possible to add a static to a sub-document, so that something like this would be possible:
user.weighIns.myCustomMethod(req.body)
Instead, you could create a method on the UserSchema, like addWeightIn:
UserSchema.methods.addWeightIn = function ({ weight, unit }) {
this.weightIns.push({
weight,
unit: unit || this.defaultUnit
})
}
Then just call the user.addWeightIn function within your controller and pass the req.body to it.
This way, you get 'fat models, skinny controllers'.

Transform JSON Response Field Name In Mongoose Model

My JSON response contains a field first_name but I want my Mongoose model to represent this field as firstName. Is this possible and if so then how?
You can create a new object with different property names from the one Mongoose returns. A nice way of doing this is to create a transform function. For example, let's say this is your schema:
{
firstName: { type: String, required: true },
lastName: { type: String, required: true }
}
Then you can use this function to create a new object with the desired property names:
function transformDocument (doc) {
return {
first_name: doc.firstName,
last_name: doc.lastName
}
}
Then, when you query the DB, you apply this function to the response:
Person.findOne({ firstName: 'John', lastName: 'Smith' })
.then(transformDocument)
Doug W has a good solution, but if you don't want to be using Promises and chaining .thens, then you can simply add options to the schema like this:
const mongoose = require ('mongoose'); // I am using v5.9.1 at the moment
const { Schema } = mongoose.Schema;
// Specify an options object
const options = {
toJSON: {
versionKey: false
}
// If you ever send the query result as an object,
// you may remove it from there, too, if you wish
// toObject: {
// versionKey: false
// }
};
// Attach the options object to the schema by
// passing it into Schema as the second argument
const mySchema = new Schema({
/** define your schema */
}, options);
This will still save __v to the document in the database. But it will not appear on the json/object when it is the result of a mongoose query.
Besides setting versionKey: false in the options, you may also specify a transform function:
/* ... */
// Specify an options object
const options = {
toJSON: {
// versionKey: false,
transform: function(doc, ret) {
// ret is the object that will be returned as the result
// (and then stringified before being sent)
delete ret.__v;
return ret;
}
}
};
/* ... */
I know this question is nearly two years old, but I needed an answer to this question, and google was not kind to me at the time. I figured it out, and now I'm hoping someone else will be looking for an answer here and find that they have options. Pun not originally intended.

Implementing pagination in vanilla GraphQL

Every tutorial I have found thus far has achieved pagination in GraphQL via Apollo, Relay, or some other magic framework. I was hoping to find answers in similar asked questions here but they don't exist. I understand how to setup the queries but I'm unclear as to how I would implement the resolvers.
Could someone point me in the right direction? I am using mongoose/MongoDB and ES5, if that helps.
EDIT: It's worth noting that the official site for learning GraphQL doesn't have an entry on pagination if you choose to use graphql.js.
EDIT 2: I love that there are some people who vote to close questions before doing their research whereas others use their knowledge to help others. You can't stop progress, no matter how hard you try. (:
Pagination in vanilla GraphQL
// Pagination argument type to represent offset and limit arguments
const PaginationArgType = new GraphQLInputObjectType({
name: 'PaginationArg',
fields: {
offset: {
type: GraphQLInt,
description: "Skip n rows."
},
first: {
type: GraphQLInt,
description: "First n rows after the offset."
},
}
})
// Function to generate paginated list type for a GraphQLObjectType (for representing paginated response)
// Accepts a GraphQLObjectType as an argument and gives a paginated list type to represent paginated response.
const PaginatedListType = (ItemType) => new GraphQLObjectType({
name: 'Paginated' + ItemType, // So that a new type name is generated for each item type, when we want paginated types for different types (eg. for Person, Book, etc.). Otherwise, GraphQL would complain saying that duplicate type is created when there are multiple paginated types.
fields: {
count: { type: GraphQLInt },
items: { type: new GraphQLList(ItemType) }
}
})
// Type for representing a single item. eg. Person
const PersonType = new GraphQLObjectType({
name: 'Person',
fields: {
id: { type: new GraphQLNonNull(GraphQLID) },
name: { type: GraphQLString },
}
})
// Query type which accepts pagination arguments with resolve function
const PersonQueryTypes = {
people: {
type: PaginatedListType(PersonType),
args: {
pagination: {
type: PaginationArgType,
defaultValue: { offset: 0, first: 10 }
},
},
resolve: (_, args) => {
const { offset, first } = args.pagination
// Call MongoDB/Mongoose functions to fetch data and count from database here.
return {
items: People.find().skip(offset).limit(first).exec()
count: People.count()
}
},
}
}
// Root query type
const QueryType = new GraphQLObjectType({
name: 'QueryType',
fields: {
...PersonQueryTypes,
},
});
// GraphQL Schema
const Schema = new GraphQLSchema({
query: QueryType
});
and when querying:
{
people(pagination: {offset: 0, first: 10}) {
items {
id
name
}
count
}
}
Have created a launchpad here.
There's a number of ways you could implement pagination, but here's two simple example resolvers that use Mongoose to get you started:
Simple pagination using limit and skip:
(obj, { pageSize = 10, page = 0 }) => {
return Foo.find()
.skip(page*pageSize)
.limit(pageSize)
.exec()
}
Using _id as a cursor:
(obj, { pageSize = 10, cursor }) => {
const params = cursor ? {'_id': {'$gt': cursor}} : undefined
return Foo.find(params).limit(pageSize).exec()
}