From 6903a415dc6f13030d6786baeb027d2b4d237706 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 18 Jun 2020 19:21:43 -0400 Subject: [PATCH] flattens create field recursion --- src/collections/operations/create.js | 19 +---- src/fields/recurseBeforeOperation.js | 100 +++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 src/fields/recurseBeforeOperation.js diff --git a/src/collections/operations/create.js b/src/collections/operations/create.js index b4634df64e..ed86088ee4 100644 --- a/src/collections/operations/create.js +++ b/src/collections/operations/create.js @@ -1,13 +1,13 @@ const mkdirp = require('mkdirp'); const executePolicy = require('../../auth/executePolicy'); -const executeFieldHooks = require('../../fields/executeHooks'); -const validateCreate = require('../../validation/validateCreate'); const { MissingFile } = require('../../errors'); const resizeAndSave = require('../../uploads/imageResizer'); const getSafeFilename = require('../../uploads/getSafeFilename'); +const recurseFields = require('../../fields/recurseBeforeOperation'); + const create = async (args) => { try { // ///////////////////////////////////// @@ -22,20 +22,7 @@ const create = async (args) => { // 2. Execute field-level policies // ///////////////////////////////////// - // Field-level policies here - - // ///////////////////////////////////// - // 3. Validate incoming data - // ///////////////////////////////////// - - await validateCreate(args.data, args.config.fields); - - // ///////////////////////////////////// - // 4. Execute before create field-level hooks - // ///////////////////////////////////// - - options.data = await executeFieldHooks(options, args.config.fields, args.data, 'beforeCreate', args.data); - + options.data = await recurseFields(args.config, { ...options, hook: 'beforeCreate', operationName: 'create' }); // ///////////////////////////////////// // 5. Upload and resize any files that may be present diff --git a/src/fields/recurseBeforeOperation.js b/src/fields/recurseBeforeOperation.js new file mode 100644 index 0000000000..70aba3c04a --- /dev/null +++ b/src/fields/recurseBeforeOperation.js @@ -0,0 +1,100 @@ +const { ValidationError } = require('../errors'); + +module.exports = async (config, operation) => { + const { + data: fullData, + operationName, + hook, + } = operation; + + // Maintain a top-level list of promises + // so that all async field policies / validations / hooks + // can run in tandem + const validationPromises = []; + const policyPromises = []; + const hookPromises = []; + const errors = []; + + const createValidationPromise = async (data, field, path) => { + const shouldValidate = field.validate && !field.condition; + const dataToValidate = data || field.defaultValue; + const result = shouldValidate ? await field.validate(dataToValidate, field) : true; + + if (!result || typeof result === 'string') { + errors.push({ + message: result, + field: `${path}${field.name}`, + }); + } + }; + + const createPolicyPromise = async (data, field) => { + const resultingData = data; + + if (field.policies && field.policies[operationName]) { + const result = await field.policies[operationName](operation); + + if (!result) { + delete resultingData[field.name]; + } + } + }; + + const createHookPromise = async (data, field) => { + const resultingData = data; + + if (field.hooks && field.hooks[hook]) { + resultingData[field.name] = await field.hooks[hook](data[field.name]); + } + }; + + const traverseFields = (fields, data = {}, path) => { + fields.forEach((field) => { + policyPromises.push(createPolicyPromise(data, field)); + hookPromises.push(createHookPromise(data, field)); + + if (field.fields) { + if (field.name === undefined) { + traverseFields(field.fields, data, path); + } else if (field.type === 'repeater' || field.type === 'flexible') { + if (Array.isArray(data[field.name])) { + data[field.name].forEach((rowData, i) => { + traverseFields(field.fields, rowData, `${path}${field.name}.${i}.`); + }); + } + } else { + traverseFields(field.fields, data[field.name], `${path}${field.name}.`); + } + } + + 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)); + } + }); + }; + + // ////////////////////////////////////////// + // Entry point for field validation + // ////////////////////////////////////////// + + try { + traverseFields(config.fields, fullData, ''); + await Promise.all(validationPromises); + + if (errors.length > 0) { + throw new ValidationError(errors); + } + + await Promise.all(policyPromises); + await Promise.all(hookPromises); + + return fullData; + } catch (error) { + throw error; + } +};