Using existing validators inside custom validator in Sequelize - node.js

I am using Sequelize on my Node.JS project and I would like to know if there's a way to use existing validator inside user-defined one.
For example:
var User = sequelize.define('User', {
foo: {
type: Sequelize.STRING,
validate: {
newValidator: function (value) {
//something like
if (value.length == 10) {
return this.foo.isUrl && this.foo.contains('bar');
} else {
return this,foo.isEmail;
}
}
}
}
});
Is it possible to somehow refer existing validators?

If you are on a pretty new version (not sure when the sequelize validator was exposed to users ) you could do Sequelize.DAOValidator.Validator.isUrl() etc. If that doesn't work for you, try importing validator into your own project var Validator = require('validator'). Since validator is already a sequelize dependency, there should be no need to add it to your package

Related

usage of classMethods vs instanceMethods in sequilizejs?

I am new to sequilizejs and basically am trying to refactor code that i've written in the controller and came across classMethods and instanceMethods. I see instance methods defined like so:
/lib/model/db/users.js
module.exports = function(sequelize, DataTypes) {
var instance_methods = get_instance_methods(sequelize);
var User = sequelize.define("User", {
email : {
type : DataTypes.STRING,
allowNull : false
},
}, {
classMethods: class_methods,
instanceMethods : instance_methods,
});
return User;
};
function get_instance_methods(sequelize) {
return {
is_my_password : function( password ) {
return sequelize.models.User.hashify_password( password ) === this.password;
},
};
function get_class_methods(sequelize) {
return {
hashify_password : function( password ) {
return crypto
.createHash('md5')
.update(
password + config.get('crypto_secret'),
(config.get('crypto_hash_encoding') || 'binary')
)
.digest('hex');
},
};
My understanding of the above is that classMethods are generic functions defined for the whole model and instanceMethods are basically a reference to a given row in a table/model, am i right in assuming this ? this would be my primary question.
Also i don't see any reference of classMethods and instanceMethods in the docs HERE. I only found this previous answer HERE. That provides a somewhat comprehensive understanding of the difference between instanceMethods and classMethods.
Basically i'am just trying to confirm weather my understanding matches the intended usage for class vs instance methods and also links to the official docs for the same would be highly appreciated.
The official way to add both static and instance methods is using classes like this:
class User extends Model {
static classLevelMethod() {
return 'foo';
}
instanceLevelMethod() {
return 'bar';
}
getFullname() {
return [this.firstname, this.lastname].join(' ');
}
}
User.init({
firstname: Sequelize.TEXT,
lastname: Sequelize.TEXT
}, { sequelize });
See Models as classes
Your understand is correct. In short: classes can have instances. Models are classes. So, Models can have instances. When working with an instance method, you will notice the this — which is the context, which refers to that particular class/model instance.
Hence, if you have a User model that has:
an instance method called is_my_password
a class model called hashify_password
User.hashify_password('123') will return the hashed version of 123. The User instance is not needed here. hashify_password is general function attached to the User model (class).
Now, if you'd like to call is_my_password() you do need a User instance:
User.findOne({...}).then(function (user) {
if (user.is_my_password('123')) {
// ^ Here we call `is_my_password` as a method of the user instance.
...
}
}).catch(console.error)
In general, when you have functions that do not need the particular model instance data, you will define them as class methods. They are static methods.
And when the function works with the instance data, you define it as instance method to make it easier and nicer to call.

Fetching data using sequalise in which the date is lesser than that of now date

Here is My Fiddle. http://sqlfiddle.com/#!17/20c29
I am trying to get the rows in which the date is lesser than that of the current date. My Node js code is as follows.
let nowDate = moment().startOf('day').subtract(7, 'days').toDate();
console.log(nowDate);
const campaign = await Campaigns.findAll({
where: {
campaign_end_date: {
$lt: nowDate
}
},
attributes: ['campaign_id', 'campaign_end_date'],
required: true
});
This is not returning any data how can I do that? I cant change the date type on psql.
It might be because the syntax $lt is deprecated and need a specific configuration to be used.
Here is the part of the doc that talk about this.
Deprecated: Operator Aliases
In Sequelize v4, it was possible to specify strings to refer to operators, instead of using Symbols. This is now deprecated and heavily discouraged, and will probably be removed in the next major version. If you really need it, you can pass the operatorAliases option in the Sequelize constructor.
For example:
const { Sequelize, Op } = require("sequelize");
const sequelize = new Sequelize('sqlite::memory:', {
operatorsAliases: {
$gt: Op.gt
}
});
// Now we can use `$gt` instead of `[Op.gt]` in where clauses:
Foo.findAll({
where: {
$gt: 6 // Works like using [Op.gt]
}
});
So can you try using the new syntax.
Should be something like :
campaign_end_date: {
[Op.lt]: nowDate
}
Note that you need to import Op before.
And just for information the attribute required can't be use here.
It's just used while doing eager loading (inside an include) to turn an inner join into a left join (in a SQL point of view).
Here is the doc talking about it if you are interested :
https://sequelize.org/master/manual/eager-loading.html
If you're using v5 of Sequelize, you've to include Op because the key was moved into Symbol
const { Op } = require('sequelize')
const campaign = await Campaigns.findAll({
where: {
campaign_end_date: {
[Op.lt]: nowDate
}
},
attributes: ['campaign_id', 'campaign_end_date'],
required: true
});

Optional but non-nullable fields in GraphQL

In an update to our GraphQL API only the models _id field is required hence the ! in the below SDL language code. Other fields such as name don't have to be included on an update but also cannot have null value. Currently, excluding the ! from the name field allows the end user to not have to pass a name in an update but it allows them to pass a null value for the name in, which cannot be allowed.
A null value lets us know that a field needs to be removed from the database.
Below is an example of a model where this would cause a problem - the Name custom scalar doesn't allow null values but GraphQL still allows them through:
type language {
_id: ObjectId
iso: Language_ISO
auto_translate: Boolean
name: Name
updated_at: Date_time
created_at: Date_time
}
input language_create {
iso: Language_ISO!
auto_translate: Boolean
name: Name!
}
input language_update {
_id: ObjectId!
iso: Language_ISO!
auto_translate: Boolean
name: Name
}
When a null value is passed in it bypasses our Scalars so we cannot throw a user input validation error if null isn't an allowed value.
I am aware that ! means non-nullable and that the lack of the ! means the field is nullable however it is frustrating that, as far as I can see, we cannot specify the exact values for a field if a field is not required / optional. This issue only occurs on updates.
Are there any ways to work around this issue through custom Scalars without having to start hardcoding logic into each update resolver which seems cumbersome?
EXAMPLE MUTATION THAT SHOULD FAIL
mutation tests_language_create( $input: language_update! ) { language_update( input: $input ) { name }}
Variables
input: {
_id: "1234",
name: null
}
UPDATE 9/11/18: for reference, I can't find a way around this as there are issues with using custom scalars, custom directives and validation rules. I've opened an issue on GitHub here: https://github.com/apollographql/apollo-server/issues/1942
What you're effectively looking for is custom validation logic. You can add any validation rules you want on top of the "default" set that is normally included when you build a schema. Here's a rough example of how to add a rule that checks for null values on specific types or scalars when they are used as arguments:
const { specifiedRules } = require('graphql/validation')
const { GraphQLError } = require('graphql/error')
const typesToValidate = ['Foo', 'Bar']
// This returns a "Visitor" whose properties get called for
// each node in the document that matches the property's name
function CustomInputFieldsNonNull(context) {
return {
Argument(node) {
const argDef = context.getArgument();
const checkType = typesToValidate.includes(argDef.astNode.type.name.value)
if (checkType && node.value.kind === 'NullValue') {
context.reportError(
new GraphQLError(
`Type ${argDef.astNode.type.name.value} cannot be null`,
node,
),
)
}
},
}
}
// We're going to override the validation rules, so we want to grab
// the existing set of rules and just add on to it
const validationRules = specifiedRules.concat(CustomInputFieldsNonNull)
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules,
})
EDIT: The above only works if you're not using variables, which isn't going to be very helpful in most cases. As a workaround, I was able to utilize a FIELD_DEFINITION directive to achieve the desired behavior. There's probably a number of ways you could approach this, but here's a basic example:
class NonNullInputDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field) {
const { resolve = defaultFieldResolver } = field
const { args: { paths } } = this
field.resolve = async function (...resolverArgs) {
const fieldArgs = resolverArgs[1]
for (const path of paths) {
if (_.get(fieldArgs, path) === null) {
throw new Error(`${path} cannot be null`)
}
}
return resolve.apply(this, resolverArgs)
}
}
}
Then in your schema:
directive #nonNullInput(paths: [String!]!) on FIELD_DEFINITION
input FooInput {
foo: String
bar: String
}
type Query {
foo (input: FooInput!): String #nonNullInput(paths: ["input.foo"])
}
Assuming that the "non null" input fields are the same each time the input is used in the schema, you could map each input's name to an array of field names that should be validated. So you could do something like this as well:
const nonNullFieldMap = {
FooInput: ['foo'],
}
class NonNullInputDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field) {
const { resolve = defaultFieldResolver } = field
const visitedTypeArgs = this.visitedType.args
field.resolve = async function (...resolverArgs) {
const fieldArgs = resolverArgs[1]
visitedTypeArgs.forEach(arg => {
const argType = arg.type.toString().replace("!", "")
const nonNullFields = nonNullFieldMap[argType]
nonNullFields.forEach(nonNullField => {
const path = `${arg.name}.${nonNullField}`
if (_.get(fieldArgs, path) === null) {
throw new Error(`${path} cannot be null`)
}
})
})
return resolve.apply(this, resolverArgs)
}
}
}
And then in your schema:
directive #nonNullInput on FIELD_DEFINITION
type Query {
foo (input: FooInput!): String #nonNullInput
}

GraphQL custom directive without declaring explicitly in schema

I am trying to implement a custom GraphQL directive. My understanding is that if my SchemaDirectiveVisitor subclass implements static getDirectiveDeclaration(directiveName, schema) then I don't have to manually declare the directive in my SDL (Schema Definition Language).
Because AuthDirective implements getDirectiveDeclaration, it’s no longer necessary for the schema author to include the directive #auth ... declaration explicitly in the schema. The returned GraphQLDirective object will be used to enforce the argument types and default values, as well as enabling tools like GraphiQL to discover the directive using schema introspection. Additionally, if the AuthDirective class fails to implement visitObject or visitFieldDefinition, a helpful error will be thrown.
Source: https://blog.apollographql.com/reusable-graphql-schema-directives-131fb3a177d1
and
However, if you’re implementing a reusable SchemaDirectiveVisitor for public consumption, you will probably not be the person writing the SDL syntax, so you may not have control over which directives the schema author decides to declare, and how. That’s why a well-implemented, reusable SchemaDirectiveVisitor should consider overriding the getDirectiveDeclaration method
Source: https://www.apollographql.com/docs/apollo-server/features/creating-directives.html
In my code, despite having implemented static getDirectiveDeclaration(directiveName, schema) I still have to declare the directive in SDL.
Shouldn't it work without manually declaring in SDL?
Full Example Code:
const { ApolloServer, gql, SchemaDirectiveVisitor } = require('apollo-server');
const { DirectiveLocation, GraphQLDirective, defaultFieldResolver } = require("graphql");
class UpperCaseDirective extends SchemaDirectiveVisitor {
static getDirectiveDeclaration(directiveName, schema) {
console.log("inside getDirectiveDeclaration", directiveName)
return new GraphQLDirective({
name: directiveName,
locations: [
DirectiveLocation.FIELD_DEFINITION,
],
args: {}
});
}
visitFieldDefinition(field) {
console.log("inside visitFieldDefinition")
const { resolve = defaultFieldResolver } = field;
field.resolve = async function (...args) {
const result = await resolve.apply(this, args);
if (typeof result === 'string') {
return result.toUpperCase();
}
return result;
};
}
}
const books = [
{
title: 'Harry Potter and the Chamber of Secrets',
author: 'J.K. Rowling',
},
{
title: 'Jurassic Park',
author: 'Michael Crichton',
},
];
const typeDefs = gql`
#########################################
# ONLY WORKS WITH THIS LINE UNCOMMENTED #
#########################################
directive #upper on FIELD_DEFINITION
type Book {
title: String
author: String #upper
}
type Query {
books: [Book]
}
`;
const resolvers = {
Query: {
books: () => books,
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
schemaDirectives: {
upper: UpperCaseDirective
}
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
I have the same problem and was able to find this comment from graphql-tools issue #957.
From the changelog:
NOTE: graphql 14 includes breaking changes. We're bumping the major version of graphql-tools to accommodate those breaking changes. If you're planning on using graphql 14 with graphql-tools 4.0.0, please make sure you've reviewed the graphql breaking changes list.
This is likely caused by the fact that graphql-js now requires you to define your directives in your schema, before you attempt to use them. For example:
directive #upper on FIELD_DEFINITION
type TestObject {
hello: String #upper
}
You can likely work around this by pre-defining your directives in your schema, but I'd like to confirm this. If this works, we'll need to update the docs.

Node.js Testing with Mongoose. unique gets ignored

I'm having a little trouble with an integration test for my mongoose application. The problem is, that my unique setting gets constantly ignored. The Schema looks more or less like this (so no fancy stuff in there)
const RealmSchema:Schema = new mongoose.Schema({
Title : {
type : String,
required : true,
unique : true
},
SchemaVersion : {
type : String,
default : SchemaVersion,
enum: [ SchemaVersion ]
}
}, {
timestamps : {
createdAt : "Created",
updatedAt : "Updated"
}
});
It looks like basically all the rules set in the schema are beeing ignored. I can pass in a Number/Boolean where string was required. The only thing that is working is fields that have not been declared in the schema won't be saved to the db
First probable cause:
I have the feeling, that it might have to do with the way I test. I have multiple integration tests. After each one my database gets dropped (so I have the same condition for every test and precondition the database in that test).
Is is possible that the reason is my indices beeing droped with the database and not beeing reinitiated when the next text creates database and collection again? And if this is the case, how could I make sure that after every test I get an empty database that still respects all my schema settings?
Second probable cause:
I'm using TypeScript in this project. Maybe there is something wrong in defining the Schema and the Model. This is what i do.
1. Create the Schema (code from above)
2. Create an Interface for the model (where IRealmM extends the Interface for the use in mongoose)
import { SpecificAttributeSelect } from "../classes/class.specificAttribute.Select";
import { SpecificAttributeText } from "../classes/class.specificAttribute.Text";
import { Document } from "mongoose";
interface IRealm{
Title : String;
Attributes : (SpecificAttributeSelect | SpecificAttributeText)[];
}
interface IRealmM extends IRealm, Document {
}
export { IRealm, IRealmM }
3. Create the model
import { RealmSchema } from '../schemas/schema.Realm';
import { Model } from 'mongoose';
import { IRealmM } from '../interfaces/interface.realm';
// Apply Authentication Plugin and create Model
const RealmModel:Model<IRealmM> = mongoose.model('realm', RealmSchema);
// Export the Model
export { RealmModel }
Unique options is not a validator. Check out this link from Mongoose docs.
OK i finally figured it out. The key issue is described here
Mongoose Unique index not working!
Solstice333 states in his answer that ensureIndex is deprecated (a warning I have been getting for some time now, I thought it was still working though)
After adding .createIndexes() to the model leaving me with the following code it works (at least as far as I'm not testing. More on that after the code)
// Apply Authentication Plugin and create Model
const RealmModel:Model<IRealmM> = mongoose.model('realm', RealmSchema);
RealmModel.createIndexes();
Now the problem with this will be that the indexes are beeing set when you're connection is first established, but not if you drop the database in your process (which at least for me occurs after every integration test)
So in my tests the resetDatabase function will look like this to make sure all the indexes are set
const resetDatabase = done => {
if(mongoose.connection.readyState === 1){
mongoose.connection.db.dropDatabase( async () => {
await resetIndexes(mongoose.models);
done();
});
} else {
mongoose.connection.once('open', () => {
mongoose.connection.db.dropDatabase( async () => {
await resetIndexes(mongoose.models);
done();
});
});
}
};
const resetIndexes = async (Models:Object) => {
let indexesReset: any[] = [];
for(let key in Models){
indexesReset.push(Models[key].createIndexes());
}
Promise.all(indexesReset).then( () => {
return true;
});
}

Resources