applies newly created field recursion pattern to all operations

This commit is contained in:
James
2020-06-18 20:23:16 -04:00
parent 6903a415dc
commit 1d6a75e79e
16 changed files with 100 additions and 361 deletions

View File

@@ -1,41 +0,0 @@
const createPolicyPromise = async (args, data, field, operation) => {
const resultingData = data;
const result = await field.policies[operation](args);
if (!result) {
delete resultingData[field.name];
}
};
const iterateFields = async (args, data, fields, operation, promises) => {
fields.forEach((field) => {
const dataToValidate = data || {};
if (data[field.name] || field.name === undefined) {
if (field.policies[operation]) {
promises.push(createPolicyPromise(args, data, field, operation));
} else if (field.fields) {
if (field.name === undefined) {
iterateFields(args, dataToValidate, field.fields, operation, promises);
} else if (field.type === 'repeater' || field.type === 'flexible') {
dataToValidate[field.name].forEach((rowData) => {
iterateFields(args, rowData, field.fields, operation, promises);
});
} else {
iterateFields(args, dataToValidate[field.name], field.fields, operation, promises);
}
}
}
});
};
module.exports = async (args, data, fields, operation) => {
try {
const promises = [];
iterateFields(args, data, fields, operation, promises);
await Promise.all(promises);
return data;
} catch (error) {
throw error;
}
};

View File

@@ -1,7 +1,6 @@
const passport = require('passport');
const executePolicy = require('../executePolicy');
const executeFieldHooks = require('../../fields/executeHooks');
const validateCreate = require('../../validation/validateCreate');
const performFieldOperations = require('../../fields/performFieldOperations');
const register = async (args) => {
try {
@@ -16,25 +15,7 @@ const register = async (args) => {
let options = { ...args };
// /////////////////////////////////////
// 2. Execute field-level policies
// /////////////////////////////////////
// Field-level policies here
// /////////////////////////////////////
// 3. Validate incoming data
// /////////////////////////////////////
await validateCreate(args.data, args.collection.config.fields);
// /////////////////////////////////////
// 4. Execute before register field-level hooks
// /////////////////////////////////////
options.data = await executeFieldHooks(options, args.collection.config.fields, args.data, 'beforeCreate');
// /////////////////////////////////////
// 5. Execute before register hook
// 2. Execute before register hook
// /////////////////////////////////////
const { beforeRegister } = args.collection.config.hooks;
@@ -43,6 +24,12 @@ const register = async (args) => {
options = await beforeRegister(options);
}
// /////////////////////////////////////
// 3. Execute field-level hooks, policies, and validation
// /////////////////////////////////////
options.data = await performFieldOperations(args.collection.config, { ...options, hook: 'beforeCreate', operationName: 'create' });
// /////////////////////////////////////
// 6. Perform register
// /////////////////////////////////////

View File

@@ -1,7 +1,7 @@
const { NotFound } = require('../../errors');
const executePolicy = require('../executePolicy');
const validate = require('../../validation/validateUpdate');
const performFieldOperations = require('../../fields/performFieldOperations');
const update = async (args) => {
try {
@@ -15,19 +15,7 @@ const update = async (args) => {
let options = { ...args };
// /////////////////////////////////////
// 2. Execute field-level policies
// /////////////////////////////////////
// Field-level policies here
// /////////////////////////////////////
// 3. Validate incoming data
// /////////////////////////////////////
await validate(args.data, args.config.fields);
// /////////////////////////////////////
// 4. Execute before update hook
// 2. Execute before update hook
// /////////////////////////////////////
const { beforeUpdate } = args.config.hooks;
@@ -37,7 +25,13 @@ const update = async (args) => {
}
// /////////////////////////////////////
// 5. Perform update
// 3. Execute field-level hooks, policies, and validation
// /////////////////////////////////////
options.data = await performFieldOperations(args.config, { ...options, hook: 'beforeUpdate', operationName: 'update' });
// /////////////////////////////////////
// 4. Perform update
// /////////////////////////////////////
const {
@@ -73,7 +67,7 @@ const update = async (args) => {
user = user.toJSON({ virtuals: true });
// /////////////////////////////////////
// 6. Execute after update hook
// 5. Execute after update hook
// /////////////////////////////////////
const afterUpdateHook = args.config.hooks && args.config.hooks.afterUpdate;
@@ -83,7 +77,7 @@ const update = async (args) => {
}
// /////////////////////////////////////
// 7. Return user
// 6. Return user
// /////////////////////////////////////
return user;

View File

@@ -6,7 +6,7 @@ const { MissingFile } = require('../../errors');
const resizeAndSave = require('../../uploads/imageResizer');
const getSafeFilename = require('../../uploads/getSafeFilename');
const recurseFields = require('../../fields/recurseBeforeOperation');
const performFieldOperations = require('../../fields/performFieldOperations');
const create = async (args) => {
try {
@@ -19,13 +19,23 @@ const create = async (args) => {
let options = { ...args };
// /////////////////////////////////////
// 2. Execute field-level policies
// 2. Execute before collection hook
// /////////////////////////////////////
options.data = await recurseFields(args.config, { ...options, hook: 'beforeCreate', operationName: 'create' });
const { beforeCreate } = args.config.hooks;
if (typeof beforeCreate === 'function') {
options = await beforeCreate(options);
}
// /////////////////////////////////////
// 5. Upload and resize any files that may be present
// 3. Execute field-level policies, hooks, and validation
// /////////////////////////////////////
options.data = await performFieldOperations(args.config, { ...options, hook: 'beforeCreate', operationName: 'create' });
// /////////////////////////////////////
// 4. Upload and resize any files that may be present
// /////////////////////////////////////
if (args.config.upload) {
@@ -58,17 +68,7 @@ const create = async (args) => {
}
// /////////////////////////////////////
// 6. Execute before collection hook
// /////////////////////////////////////
const { beforeCreate } = args.config.hooks;
if (typeof beforeCreate === 'function') {
options = await beforeCreate(options);
}
// /////////////////////////////////////
// 7. Perform database operation
// 5. Perform database operation
// /////////////////////////////////////
const {
@@ -92,7 +92,7 @@ const create = async (args) => {
result = result.toJSON({ virtuals: true });
// /////////////////////////////////////
// 8. Execute after collection hook
// 6. Execute after collection hook
// /////////////////////////////////////
const { afterCreate } = args.config.hooks;
@@ -102,7 +102,7 @@ const create = async (args) => {
}
// /////////////////////////////////////
// 9. Return results
// 7. Return results
// /////////////////////////////////////
return result;

View File

@@ -1,7 +1,6 @@
const merge = require('lodash.merge');
const executePolicy = require('../../auth/executePolicy');
const executeFieldPolicies = require('../../auth/executeFieldPolicies');
const executeFieldHooks = require('../../fields/executeHooks');
const performFieldOperations = require('../../fields/performFieldOperations');
const find = async (args) => {
try {
@@ -86,19 +85,11 @@ const find = async (args) => {
doc.setLocale(locale, fallbackLocale);
}
const json = doc.toJSON({ virtuals: true });
return executeFieldPolicies(args, json, args.config.fields, 'read');
})),
};
const data = doc.toJSON({ virtuals: true });
// /////////////////////////////////////
// 5. Execute field-level afterRead hooks
// /////////////////////////////////////
result = {
...result,
docs: await Promise.all(result.docs.map(async (doc) => {
return executeFieldHooks(options, args.config.fields, doc, 'afterRead', doc);
return performFieldOperations(args.config, {
...options, data, hook: 'afterRead', operationName: 'read',
});
})),
};

View File

@@ -1,6 +1,6 @@
const { Forbidden, NotFound } = require('../../errors');
const executePolicy = require('../../auth/executePolicy');
const executeFieldHooks = require('../../fields/executeHooks');
const performFieldOperations = require('../../fields/performFieldOperations');
const findByID = async (args) => {
try {
@@ -78,39 +78,36 @@ const findByID = async (args) => {
result.setLocale(locale, fallbackLocale);
}
let json = result.toJSON({ virtuals: true });
let data = result.toJSON({ virtuals: true });
// /////////////////////////////////////
// 4. Execute after collection field-level hooks
// 4. Execute field-level hooks and policies
// /////////////////////////////////////
result = await executeFieldHooks(options, options.config.fields, result, 'afterRead', result);
result = performFieldOperations(args.config, {
...options, data, hook: 'afterRead', operationName: 'read',
});
// /////////////////////////////////////
// 5. Execute field-level policies
// /////////////////////////////////////
// Field-level policies here
// /////////////////////////////////////
// 6. Execute after collection hook
// 5. Execute after collection hook
// /////////////////////////////////////
const { afterRead } = args.config.hooks;
if (typeof afterRead === 'function') {
json = await afterRead({
data = await afterRead({
...options,
result,
doc: json,
}) || json;
doc: data,
}) || data;
}
// /////////////////////////////////////
// 7. Return results
// 6. Return results
// /////////////////////////////////////
return json;
return data;
} catch (err) {
throw err;
}

View File

@@ -1,7 +1,6 @@
const executePolicy = require('../../auth/executePolicy');
const executeFieldHooks = require('../../fields/executeHooks');
const { NotFound, Forbidden } = require('../../errors');
const validate = require('../../validation/validateUpdate');
const performFieldOperations = require('../../fields/performFieldOperations');
const resizeAndSave = require('../../uploads/imageResizer');
@@ -17,25 +16,7 @@ const update = async (args) => {
let options = { ...args };
// /////////////////////////////////////
// 2. Execute field-level policies
// /////////////////////////////////////
// Field-level policies here
// /////////////////////////////////////
// 3. Validate incoming data
// /////////////////////////////////////
await validate(args.data, args.config.fields);
// /////////////////////////////////////
// 4. Execute before update field-level hooks
// /////////////////////////////////////
options.data = await executeFieldHooks(options, args.config.fields, args.data, 'beforeUpdate', args.data);
// /////////////////////////////////////
// 5. Execute before collection hook
// 2. Execute before update hook
// /////////////////////////////////////
const { beforeUpdate } = args.config.hooks;
@@ -45,7 +26,13 @@ const update = async (args) => {
}
// /////////////////////////////////////
// 6. Perform database operation
// 3. Execute field-level hooks, policies, and validation
// /////////////////////////////////////
options.data = await performFieldOperations(args.config, { ...options, hook: 'beforeUpdate', operationName: 'update' });
// /////////////////////////////////////
// 4. Perform database operation
// /////////////////////////////////////
let {
@@ -80,7 +67,7 @@ const update = async (args) => {
}
// /////////////////////////////////////
// 7. Upload and resize any files that may be present
// 5. Upload and resize any files that may be present
// /////////////////////////////////////
if (args.config.upload) {
@@ -111,7 +98,7 @@ const update = async (args) => {
result = result.toJSON({ virtuals: true });
// /////////////////////////////////////
// 8. Execute after collection hook
// 6. Execute after collection hook
// /////////////////////////////////////
const { afterUpdate } = args.config.hooks;
@@ -121,7 +108,7 @@ const update = async (args) => {
}
// /////////////////////////////////////
// 9. Return results
// 7. Return results
// /////////////////////////////////////
return result;

View File

@@ -1,40 +0,0 @@
const executeFieldHooks = async (operation, fields, value, hookName, data = null) => {
const fullData = value || data;
if (Array.isArray(data)) {
const rowResults = await Promise.all(data.map(async (row, i) => {
return executeFieldHooks(operation, fields, fullData[i], row, hookName);
}));
return rowResults;
}
const hookPromises = [];
fields.forEach((field) => {
if (field.hooks && typeof field.hooks[hookName] === 'function' && fullData[field.name]) {
const hookPromise = async () => {
fullData[field.name] = await field.hooks[hookName]({
...operation,
data: fullData,
value: data[field.name],
});
};
hookPromises.push(hookPromise());
}
if (field.fields && data && data[field.name]) {
const hookPromise = async () => {
fullData[field.name] = await executeFieldHooks(operation, field.fields, fullData[field.name], hookName, data[field.name]);
};
hookPromises.push(hookPromise());
}
});
await Promise.all(hookPromises);
return fullData;
};
module.exports = executeFieldHooks;

View File

@@ -67,13 +67,15 @@ module.exports = async (config, operation) => {
}
}
if (field.type === 'repeater' || field.type === 'flexible') {
const hasRowsOfData = Array.isArray(data[field.name]);
const rowCount = hasRowsOfData ? data[field.name].length : 0;
if (operationName === 'create' || (operationName === 'update' && data[field.name] !== undefined)) {
if (field.type === 'repeater' || field.type === 'flexible') {
const hasRowsOfData = Array.isArray(data[field.name]);
const rowCount = hasRowsOfData ? data[field.name].length : 0;
validationPromises.push(createValidationPromise(rowCount, field, path));
} else {
validationPromises.push(createValidationPromise(data[field.name], field, path));
validationPromises.push(createValidationPromise(rowCount, field, path));
} else {
validationPromises.push(createValidationPromise(data[field.name], field, path));
}
}
});
};

View File

@@ -1,6 +1,6 @@
const executePolicy = require('../../auth/executePolicy');
const { NotFound } = require('../../errors');
const executeFieldHooks = require('../../fields/executeHooks');
const performFieldOperations = require('../../fields/performFieldOperations');
const findOne = async (args) => {
try {
@@ -62,36 +62,31 @@ const findOne = async (args) => {
result.setLocale(locale, fallbackLocale);
}
let json = result.toJSON({ virtuals: true });
let data = result.toJSON({ virtuals: true });
// /////////////////////////////////////
// 4. Execute field-level policies
// 4. Execute field-level hooks and policies
// /////////////////////////////////////
// Field-level policies here
result = performFieldOperations(args.config, {
...options, data, hook: 'afterRead', operationName: 'read',
});
// /////////////////////////////////////
// 5. Execute after collection field-level hooks
// /////////////////////////////////////
result = await executeFieldHooks(options, args.config.fields, result, 'afterRead', result);
// /////////////////////////////////////
// 6. Execute after collection hook
// 5. Execute after collection hook
// /////////////////////////////////////
const { afterRead } = args.config.hooks;
if (typeof afterRead === 'function') {
json = await afterRead(options, result, json) || json;
data = await afterRead(options, result, data) || data;
}
// /////////////////////////////////////
// 7. Return results
// 6. Return results
// /////////////////////////////////////
return json;
return data;
} catch (error) {
throw error;
}

View File

@@ -1,6 +1,5 @@
const executePolicy = require('../../auth/executePolicy');
const executeFieldHooks = require('../../fields/executeHooks');
const validate = require('../../validation/validateUpdate');
const performFieldOperations = require('../../fields/performFieldOperations');
const update = async (args) => {
try {
@@ -13,25 +12,7 @@ const update = async (args) => {
let options = { ...args };
// /////////////////////////////////////
// 2. Execute field-level policies
// /////////////////////////////////////
// Field-level policies here
// /////////////////////////////////////
// 3. Validate incoming data
// /////////////////////////////////////
await validate(args.data, args.config.fields);
// /////////////////////////////////////
// 4. Execute before update field-level hooks
// /////////////////////////////////////
options.data = await executeFieldHooks(options, args.config.fields, args.data, 'beforeUpdate', args.data);
// /////////////////////////////////////
// 5. Execute before global hook
// 2. Execute before global hook
// /////////////////////////////////////
const { beforeUpdate } = args.config.hooks;
@@ -41,7 +22,13 @@ const update = async (args) => {
}
// /////////////////////////////////////
// 6. Perform database operation
// 3. Execute field-level hooks, policies, and validation
// /////////////////////////////////////
options.data = await performFieldOperations(args.config, { ...options, hook: 'beforeUpdate', operationName: 'update' });
// /////////////////////////////////////
// 4. Perform database operation
// /////////////////////////////////////
const {
@@ -72,7 +59,7 @@ const update = async (args) => {
result = result.toJSON({ virtuals: true });
// /////////////////////////////////////
// 7. Execute after global hook
// 5. Execute after global hook
// /////////////////////////////////////
const { afterUpdate } = args.config.hooks;
@@ -82,7 +69,7 @@ const update = async (args) => {
}
// /////////////////////////////////////
// 8. Return results
// 6. Return results
// /////////////////////////////////////
return result;

View File

@@ -1,31 +0,0 @@
exports.createValidationPromise = async (data, field) => {
const result = await field.validate ? field.validate(data, field) : true;
return { result, field };
};
exports.getErrorResults = async (resultPromises, path) => {
const results = await Promise.all(resultPromises);
return results.reduce((formattedResults, result) => {
const { field, result: validationResult } = result;
if (Array.isArray(result)) {
return [
...formattedResults,
...result,
];
}
if (validationResult === false || typeof validationResult === 'string') {
return [
...formattedResults,
{
field: `${path}${field.name}`,
message: validationResult,
},
];
}
return formattedResults;
}, []);
};

View File

@@ -1,50 +0,0 @@
const { ValidationError } = require('../errors');
const { createValidationPromise, getErrorResults } = require('./utilities');
const iterateFields = async (data, fields, path = '') => {
const validationPromises = [];
fields.forEach((field) => {
const dataToValidate = data || {};
if (!field.condition) {
// If this field does not have a name, it is for
// admin panel composition only and should not be
// validated against directly
if (field.name === undefined && field.fields) {
field.fields.forEach((subField) => {
validationPromises.push(createValidationPromise(dataToValidate[subField.name], subField));
});
} else if (field.fields) {
if (field.type === 'repeater' || field.type === 'flexible') {
const isArray = Array.isArray(dataToValidate[field.name]);
const rowCount = isArray ? dataToValidate[field.name].length : 0;
validationPromises.push(createValidationPromise(rowCount, field));
if (isArray) {
dataToValidate[field.name].forEach((rowData, i) => {
validationPromises.push(iterateFields(rowData, field.fields, `${path}${field.name}.${i}.`));
});
}
} else {
validationPromises.push(iterateFields(dataToValidate[field.name], field.fields, `${path}${field.name}.`));
}
} else {
validationPromises.push(createValidationPromise(dataToValidate[field.name], field));
}
}
});
return getErrorResults(validationPromises, path);
};
module.exports = async (data, fields) => {
try {
const errors = await iterateFields(data, fields);
if (errors.length > 0) {
throw new ValidationError(errors);
}
} catch (error) {
throw error;
}
};

View File

@@ -1,44 +0,0 @@
const { ValidationError } = require('../errors');
const { createValidationPromise, getErrorResults } = require('./utilities');
const iterateFields = async (data, fields, path = '') => {
const validationPromises = [];
Object.entries(data).forEach(([key, value]) => {
const field = fields.find(matchedField => matchedField.name === key);
if (field && value !== undefined) {
if (field.fields) {
if (field.type === 'repeater' || field.type === 'flexible') {
const isArray = Array.isArray(value);
const rowCount = isArray ? value.length : 0;
validationPromises.push(createValidationPromise(rowCount, field));
if (isArray) {
value.forEach((rowData, i) => {
validationPromises.push(iterateFields(rowData, field.fields, `${path}${field.name}.${i}.`));
});
}
} else {
validationPromises.push(iterateFields(value, field.fields, `${path}${field.name}.`));
}
} else {
validationPromises.push(createValidationPromise(value, field));
}
}
});
return getErrorResults(validationPromises, path);
};
module.exports = async (data, fields) => {
try {
const errors = await iterateFields(data, fields);
if (errors.length > 0) {
throw new ValidationError(errors);
}
} catch (error) {
throw error;
}
};