UNPKG

strapi-plugin-publisher

Version:

A plugin for Strapi Headless CMS that provides the ability to schedule publishing for any content type.

407 lines (341 loc) 9.96 kB
// From https://github.com/strapi/strapi/blob/main/packages/core/admin/admin/src/content-manager/utils/schema.js import { translatedErrors as errorsTrads } from '@strapi/helper-plugin'; import get from 'lodash/get'; import isBoolean from 'lodash/isBoolean'; import isEmpty from 'lodash/isEmpty'; import isNaN from 'lodash/isNaN'; import toNumber from 'lodash/toNumber'; import * as yup from 'yup'; function isFieldTypeNumber(type) { return ['integer', 'biginteger', 'decimal', 'float', 'number'].includes(type); } yup.addMethod(yup.mixed, 'defined', function () { return this.test('defined', errorsTrads.required, (value) => value !== undefined); }); yup.addMethod(yup.array, 'notEmptyMin', function (min) { return this.test('notEmptyMin', errorsTrads.min, (value) => { if (isEmpty(value)) { return true; } return value.length >= min; }); }); yup.addMethod(yup.string, 'isInferior', function (message, max) { return this.test('isInferior', message, function (value) { if (!value) { return true; } if (Number.isNaN(toNumber(value))) { return true; } return toNumber(max) >= toNumber(value); }); }); yup.addMethod(yup.string, 'isSuperior', function (message, min) { return this.test('isSuperior', message, function (value) { if (!value) { return true; } if (Number.isNaN(toNumber(value))) { return true; } return toNumber(value) >= toNumber(min); }); }); const getAttributes = (data) => get(data, ['attributes'], {}); export function createYupSchema( model, { components }, options = { isCreatingEntry: true, isDraft: true, isFromComponent: false, isJSONTestDisabled: false, } ) { const attributes = getAttributes(model); return yup.object().shape( Object.keys(attributes).reduce((acc, current) => { const attribute = attributes[current]; if ( attribute.type !== 'relation' && attribute.type !== 'component' && attribute.type !== 'dynamiczone' ) { const formatted = createYupSchemaAttribute(attribute.type, attribute, options); acc[current] = formatted; } if (attribute.type === 'relation') { acc[current] = [ 'oneWay', 'oneToOne', 'manyToOne', 'oneToManyMorph', 'oneToOneMorph', ].includes(attribute.relationType) ? yup.object().nullable() : yup.array().nullable(); } if (attribute.type === 'component') { const componentFieldSchema = createYupSchema( components[attribute.component], { components, }, { ...options, isFromComponent: true } ); if (attribute.repeatable === true) { const { min, max, required } = attribute; let componentSchema = yup.lazy((value) => { let baseSchema = yup.array().of(componentFieldSchema); if (min) { if (required) { baseSchema = baseSchema.min(min, errorsTrads.min); } else if (required !== true && isEmpty(value)) { baseSchema = baseSchema.nullable(); } else { baseSchema = baseSchema.min(min, errorsTrads.min); } } else if (required && !options.isDraft) { baseSchema = baseSchema.min(1, errorsTrads.required); } if (max) { baseSchema = baseSchema.max(max, errorsTrads.max); } return baseSchema; }); acc[current] = componentSchema; return acc; } const componentSchema = yup.lazy((obj) => { if (obj !== undefined) { return attribute.required === true && !options.isDraft ? componentFieldSchema.defined() : componentFieldSchema.nullable(); } return attribute.required === true ? yup.object().defined() : yup.object().nullable(); }); acc[current] = componentSchema; return acc; } if (attribute.type === 'dynamiczone') { let dynamicZoneSchema = yup.array().of( yup.lazy(({ __component }) => { return createYupSchema( components[__component], { components }, { ...options, isFromComponent: true } ); }) ); const { max, min } = attribute; if (min) { if (attribute.required) { dynamicZoneSchema = dynamicZoneSchema .test('min', errorsTrads.min, (value) => { if (options.isCreatingEntry) { return value && value.length >= min; } if (value === undefined) { return true; } return value !== null && value.length >= min; }) .test('required', errorsTrads.required, (value) => { if (options.isCreatingEntry) { return value !== null || value !== undefined; } if (value === undefined) { return true; } return value !== null; }); } else { dynamicZoneSchema = dynamicZoneSchema.notEmptyMin(min); } } else if (attribute.required && !options.isDraft) { dynamicZoneSchema = dynamicZoneSchema.test('required', errorsTrads.required, (value) => { if (options.isCreatingEntry) { return value !== null || value !== undefined; } if (value === undefined) { return true; } return value !== null; }); } if (max) { dynamicZoneSchema = dynamicZoneSchema.max(max, errorsTrads.max); } acc[current] = dynamicZoneSchema; } return acc; }, {}) ); } const createYupSchemaAttribute = (type, validations, options) => { let schema = yup.mixed(); if (['string', 'uid', 'text', 'richtext', 'email', 'password', 'enumeration'].includes(type)) { schema = yup.string(); } if (type === 'blocks') { schema = yup.mixed().test('isJSON', errorsTrads.json, (value) => { // Disable the test for bulk publish, it's valid when it comes from the db if (options.isJSONTestDisabled) { return true; } // Don't run validations on drafts if (options.isDraft) { return true; } // The backend validates the actual schema, check if a value different than null is not an array if (value && !Array.isArray(value)) { return false; } return true; }); } if (type === 'json') { schema = yup .mixed(errorsTrads.json) .test('isJSON', errorsTrads.json, (value) => { // Disable the test for bulk publish, it's valid when it comes from the db if (options.isJSONTestDisabled) { return true; } if (!value || !value.length) { return true; } try { JSON.parse(value); return true; } catch (err) { return false; } }) .nullable() .test('required', errorsTrads.required, (value) => { if (validations.required && (!value || !value.length)) { return false; } return true; }); } if (type === 'email') { schema = schema.email(errorsTrads.email); } if (['number', 'integer', 'float', 'decimal'].includes(type)) { schema = yup .number() .transform((cv) => (isNaN(cv) ? undefined : cv)) .typeError(); } if (type === 'biginteger') { schema = yup.string().matches(/^-?\d*$/); } if (['date', 'datetime'].includes(type)) { schema = yup.date(); } Object.keys(validations).forEach((validation) => { const validationValue = validations[validation]; if ( !!validationValue || (!isBoolean(validationValue) && Number.isInteger(Math.floor(validationValue))) || validationValue === 0 ) { switch (validation) { case 'required': { if (!options.isDraft) { if (type === 'password' && options.isCreatingEntry) { schema = schema.required(errorsTrads.required); } if (type !== 'password') { if (options.isCreatingEntry) { schema = schema.required(errorsTrads.required); } else { schema = schema.test('required', errorsTrads.required, (value) => { // Field is not touched and the user is editing the entry if (value === undefined && !options.isFromComponent) { return true; } if (isFieldTypeNumber(type)) { if (value === 0) { return true; } return !!value; } if (type === 'boolean') { // Boolean value can be undefined/unset in modifiedData when generated in a new component return value !== null && value !== undefined; } if (type === 'date' || type === 'datetime') { if (typeof value === 'string') { return !isEmpty(value); } return !isEmpty(value?.toString()); } return !isEmpty(value); }); } } } break; } case 'max': { if (type === 'biginteger') { schema = schema.isInferior(errorsTrads.max, validationValue); } else { schema = schema.max(validationValue, errorsTrads.max); } break; } case 'maxLength': schema = schema.max(validationValue, errorsTrads.maxLength); break; case 'min': { if (type === 'biginteger') { schema = schema.isSuperior(errorsTrads.min, validationValue); } else { schema = schema.min(validationValue, errorsTrads.min); } break; } case 'minLength': { if (!options.isDraft) { schema = schema.min(validationValue, errorsTrads.minLength); } break; } case 'regex': schema = schema.matches(new RegExp(validationValue), { message: errorsTrads.regex, excludeEmptyString: !validations.required, }); break; case 'lowercase': if (['text', 'textarea', 'email', 'string'].includes(type)) { schema = schema.strict().lowercase(); } break; case 'uppercase': if (['text', 'textarea', 'email', 'string'].includes(type)) { schema = schema.strict().uppercase(); } break; case 'positive': if (isFieldTypeNumber(type)) { schema = schema.positive(); } break; case 'negative': if (isFieldTypeNumber(type)) { schema = schema.negative(); } break; default: schema = schema.nullable(); } } }); return schema; };