adds validations to backend

This commit is contained in:
James
2020-04-19 14:18:34 -04:00
parent 45d8745f74
commit 6e39f39c6f
9 changed files with 148 additions and 39 deletions

View File

@@ -1,11 +1,13 @@
{
"ignore": [
".git",
"node_modules",
"node_modules/**/node_modules",
"src/client"
],
"watch": [
"src/"
"src/",
"demo/"
],
"ext": "js,json"
}

View File

@@ -8,9 +8,8 @@
"test:int": "cross-env NODE_ENV=test jest --forceExit",
"cov": "npm run core:build && node ./node_modules/jest/bin/jest.js src/tests --coverage",
"dev": "nodemon demo/server.js",
"server": "node demo/server.js",
"lint": "eslint **/*.js",
"debug:test:int": "node --inspect-brk node_modules/.bin/jest --runInBand"
"debug:test:int": "node --inspect-brk node_modules/.bin/jest"
},
"bin": {
"payload": "./src/bin/index.js"

View File

@@ -1,5 +1,6 @@
const executePolicy = require('../../users/executePolicy');
const executeFieldHooks = require('../../fields/executeHooks');
const validate = require('../../fields/validate');
const create = async (args) => {
try {
@@ -9,8 +10,6 @@ const create = async (args) => {
await executePolicy(args, args.config.policies.create);
// Await validation here
let options = {
Model: args.Model,
config: args.config,
@@ -22,13 +21,19 @@ const create = async (args) => {
};
// /////////////////////////////////////
// 2. Execute before create field-level hooks
// 2. Validate incoming data
// /////////////////////////////////////
await validate(args.config.fields, args.data);
// /////////////////////////////////////
// 3. Execute before create field-level hooks
// /////////////////////////////////////
options.data = await executeFieldHooks(args.config.fields, args.data, 'beforeCreate');
// /////////////////////////////////////
// 3. Execute before collection hook
// 4. Execute before collection hook
// /////////////////////////////////////
const { beforeCreate } = args.config.hooks;
@@ -38,7 +43,7 @@ const create = async (args) => {
}
// /////////////////////////////////////
// 4. Perform database operation
// 5. Perform database operation
// /////////////////////////////////////
const {
@@ -60,7 +65,7 @@ const create = async (args) => {
result = result.toJSON({ virtuals: true });
// /////////////////////////////////////
// 5. Execute after collection hook
// 6. Execute after collection hook
// /////////////////////////////////////
const { afterCreate } = args.config.hooks.afterCreate;
@@ -70,7 +75,7 @@ const create = async (args) => {
}
// /////////////////////////////////////
// 6. Return results
// 7. Return results
// /////////////////////////////////////
return result;

View File

@@ -0,0 +1,10 @@
const httpStatus = require('http-status');
const APIError = require('./APIError');
class ValidationError extends APIError {
constructor(results) {
super(results, httpStatus.BAD_REQUEST);
}
}
module.exports = ValidationError;

View File

@@ -5,6 +5,7 @@ const MissingCollectionLabel = require('./MissingCollectionLabel');
const MissingGlobalLabel = require('./MissingGlobalLabel');
const NotFound = require('./NotFound');
const Forbidden = require('./Forbidden');
const ValidationError = require('./ValidationError');
module.exports = {
APIError,
@@ -14,4 +15,5 @@ module.exports = {
MissingGlobalLabel,
NotFound,
Forbidden,
ValidationError,
};

View File

@@ -11,6 +11,12 @@ const formatErrorResponse = (incoming) => {
};
}
if (Array.isArray(incoming.message)) {
return {
errors: incoming.message,
};
}
if (incoming.name) {
return {
errors: [
@@ -21,9 +27,6 @@ const formatErrorResponse = (incoming) => {
};
}
}
// If the Mongoose error does not get returned with incoming && incoming.errors,
// it's of a type that we really don't know how to handle. Sometimes this means a TypeError,
// which we might be able to manipulate to get the error message itself and send that back.
return {
errors: [

View File

@@ -8,7 +8,7 @@ const sanitizeFields = (fields) => {
if (!field.type) throw new MissingFieldType(field);
if (typeof field.validation === 'undefined') {
field.validation = validations[field.type];
field.validate = validations[field.type];
}
if (!field.hooks) field.hooks = {};

View File

@@ -1,9 +1,39 @@
const executeFieldHooks = async (fields, data, hook) => {
for (const field of fields) {
if (typeof field.hooks[hook] === 'function') {
const result = await field.hooks[hook](data);
/* eslint-disable no-await-in-loop */
/* eslint-disable no-restricted-syntax */
const { ValidationError } = require('../errors');
const iterateFields = async (fields, data, errors, path = '') => {
if (Array.isArray(data)) {
await Promise.all(data.map(async (row, i) => {
await iterateFields(fields, row, errors, `${path}.${i}.`);
}));
} else {
for (const field of fields) {
if (field.required) {
const validationResult = await field.validate(data[field.name], field);
if (validationResult !== true) {
errors.push({
field: field.name,
message: validationResult,
});
}
}
if (field.fields) {
await iterateFields(field.fields, data[field.name], errors, `${path}${field.name}.`);
}
}
}
};
module.exports = executeFieldHooks;
const validate = async (fields, data) => {
const errors = [];
await iterateFields(fields, data, errors);
if (errors.length > 0) {
throw new ValidationError(errors);
}
};
module.exports = validate;

View File

@@ -1,35 +1,93 @@
const fieldToValidatorMap = {
number: (field, value) => {
number: (value, field) => {
const parsedValue = parseInt(value, 10);
if (typeof parsedValue !== 'number') return false;
if (field.max && parsedValue > field.max) return false;
if (field.min && parsedValue < field.min) return false;
if (typeof parsedValue !== 'number' || Number.isNaN(parsedValue)) {
return `${field.label} value is not a valid number.`;
}
if (field.max && parsedValue > field.max) {
return `${field.label} value is greater than the max allowed value of ${field.max}.`;
}
if (field.min && parsedValue < field.min) {
return `${field.label} value is less than the min allowed value of ${field.min}.`;
}
return true;
},
text: (field, value) => {
if (field.maxLength && value.length > field.maxLength) return false;
if (field.minLength && value.length < field.minLength) return false;
text: (value, field) => {
if (field.maxLength && value.length > field.maxLength) {
return `${field.label} length is greater than the max allowed length of ${field.maxLength}.`;
}
if (field.minLength && value.length < field.minLength) {
return `${field.label} length is less than the minimum allowed length of ${field.minLength}.`;
}
return true;
},
email: value => /\S+@\S+\.\S+/.test(value),
textarea: (field, value) => {
if (field.maxLength && value.length > field.maxLength) return false;
if (field.minLength && value.length < field.minLength) return false;
email: (value, field) => {
if (/\S+@\S+\.\S+/.test(value)) {
return true;
}
return `${field.label} is not a valid email address.`;
},
textarea: (value, field) => {
if (field.maxLength && value.length > field.maxLength) {
return `${field.label} length is greater than the max allowed length of ${field.maxLength}.`;
}
if (field.minLength && value.length < field.minLength) {
return `${field.label} length is less than the minimum allowed length of ${field.minLength}.`;
}
return true;
},
wysiwyg: value => (!!value),
code: value => (!!value),
checkbox: value => Boolean(value),
date: value => value instanceof Date,
upload: value => (!!value),
relationship: value => (!!value),
repeater: value => (!!value),
select: value => (!!value),
flexible: value => (!!value),
wysiwyg: (value, field) => {
if (value) return true;
return `${field.label} is required.`;
},
code: (value, field) => {
if (value) return true;
return `${field.label} is required.`;
},
checkbox: (value, field) => {
if (value) {
return true;
}
return `${field.label} can only be equal to true or false.`;
},
date: (value, field) => {
if (value instanceof Date) {
return true;
}
return `${field.label} is not a valid date.`;
},
upload: (value, field) => {
if (value) return true;
return `${field.label} is required.`;
},
relationship: (value, field) => {
if (value) return true;
return `${field.label} is required.`;
},
repeater: (value, field) => {
if (value) return true;
return `${field.label} is required.`;
},
select: (value, field) => {
if (value) return true;
return `${field.label} is required.`;
},
flexible: (value, field) => {
if (value) return true;
return `${field.label} is required.`;
},
};
module.exports = fieldToValidatorMap;