Validate content-type-buildeer group schema

This commit is contained in:
Alexandre Bodin 2019-07-31 09:12:49 +02:00
parent 81ae3aab1a
commit c530afa8a3
11 changed files with 338 additions and 39 deletions

View File

@ -7,7 +7,10 @@
}, },
"options": { "options": {
"increments": true, "increments": true,
"timestamps": true, "timestamps": [
"created_at",
"updated_at"
],
"comment": "" "comment": ""
}, },
"attributes": { "attributes": {
@ -24,14 +27,17 @@
"type": "json" "type": "json"
}, },
"number": { "number": {
"type": "integer" "type": "biginteger"
}, },
"date": { "date": {
"type": "date" "type": "date"
}, },
"enum": { "enum": {
"type": "enumeration", "type": "enumeration",
"enum": ["morning,", "noon"] "enum": [
"morning,",
"noon"
]
}, },
"bool": { "bool": {
"type": "boolean" "type": "boolean"
@ -54,18 +60,16 @@
}, },
"manyTags": { "manyTags": {
"collection": "tag", "collection": "tag",
"dominant": true, "via": "linkedArticles",
"via": "linkedArticles" "dominant": true
}, },
"fb_cta": { "fb_cta": {
"type": "group", "type": "group",
"group": "cta_facebook", "group": "cta_facebook"
"repeatable": false
}, },
"mainIngredient": { "mainIngredient": {
"type": "group", "type": "group",
"group": "ingredients", "group": "ingredients"
"repeatable": false
}, },
"ingredients": { "ingredients": {
"type": "group", "type": "group",
@ -73,6 +77,30 @@
"repeatable": true, "repeatable": true,
"min": 1, "min": 1,
"max": 10 "max": 10
},
"blabla": {
"enum": [
"one",
"two",
"three"
],
"type": "enumeration",
"unique": true,
"enumName": "azd",
"default": "azd",
"required": true
},
"article": {
"columnName": "azdazd",
"unique": true,
"model": "article",
"via": "articlea"
},
"articlea": {
"columnName": "azdazd",
"unique": true,
"model": "article",
"via": "article"
} }
} }
} }

View File

@ -1,26 +1,6 @@
'use strict'; 'use strict';
const yup = require('yup'); const validateGroupInput = require('./validation/group');
const formatYupErrors = require('./utils/yup-formatter');
const groupSchema = yup
.object({
name: yup.string().required('name.required'),
description: yup.string(),
connection: yup.string(),
collectionName: yup.string(),
attributes: yup.object().required('attributes.required'),
})
.noUnknown();
const validateGroupInput = async data =>
groupSchema
.validate(data, {
strict: true,
abortEarly: false,
})
.catch(error => Promise.reject(formatYupErrors(error)));
/** /**
* Groups controller * Groups controller
*/ */

View File

@ -0,0 +1,58 @@
'use strict';
const yup = require('yup');
const VALID_TYPES = [
// advanced types
'media',
// scalar types
'string',
'text',
'richtext',
'json',
'enumeration',
'password',
'email',
'integer',
'float',
'decimal',
'date',
'boolean',
];
const validators = {
required: yup.boolean(),
unique: yup.boolean(),
minLength: yup
.number()
.integer()
.positive(),
maxLength: yup
.number()
.integer()
.positive(),
};
const NAME_REGEX = new RegExp('^[A-Za-z][_0-9A-Za-z]*$');
const isValidName = {
name: 'isValidName',
message: '${path} must match the following regex: /^[_A-Za-z][_0-9A-Za-z]*/^',
test: val => NAME_REGEX.test(val),
};
const isValidKey = key => ({
name: 'isValidKey',
message: `Attribute name '${key}' must match the following regex: /^[_A-Za-z][_0-9A-Za-z]*/^`,
test: () => NAME_REGEX.test(key),
});
module.exports = {
validators,
isValidName,
isValidKey,
VALID_TYPES,
};

View File

@ -0,0 +1,60 @@
'use strict';
const yup = require('yup');
const _ = require('lodash');
const formatYupErrors = require('./yup-formatter');
const { isValidName, isValidKey } = require('./common');
const getTypeValidator = require('./types');
const getRelationValidator = require('./relations');
module.exports = data => {
return groupSchema
.validate(data, {
strict: true,
abortEarly: false,
})
.catch(error => Promise.reject(formatYupErrors(error)));
};
const groupSchema = yup
.object({
name: yup
.string()
.min(1)
.test(isValidName)
.required('name.required'),
description: yup.string(),
connection: yup.string(),
collectionName: yup.string().test(isValidName),
attributes: yup.lazy(obj => {
return yup
.object()
.shape(
_.mapValues(obj, (value, key) => {
return yup.lazy(obj => {
let shape;
if (_.has(obj, 'type')) {
shape = getTypeValidator(obj);
} else if (_.has(obj, 'target')) {
shape = getRelationValidator(obj);
} else {
return yup.object().test({
name: 'mustHaveTypeOrTarget',
message: 'Attribute must have either a type or a target',
test: () => false,
});
}
return yup
.object()
.shape(shape)
.test(isValidKey(key))
.noUnknown();
});
})
)
.required('attributes.required');
}),
})
.noUnknown();

View File

@ -0,0 +1,42 @@
'use strict';
const yup = require('yup');
const _ = require('lodash');
const { validators } = require('./common');
const VALID_NATURES = ['oneWay', 'manyWay'];
module.exports = () => {
return {
target: yup
.mixed()
.when('plugin', plugin => {
if (!plugin)
return yup
.string()
.oneOf(
Object.keys(strapi.models).filter(name => name !== 'core_store')
);
if (plugin === 'admin')
return yup.string().oneOf(Object.keys(strapi.admin.models));
if (plugin)
return yup
.string()
.oneOf(Object.keys(_.get(strapi.plugins, [plugin, 'models'], {})));
})
.required(),
nature: yup
.string()
.oneOf(VALID_NATURES)
.required(),
plugin: yup.string().oneOf(Object.keys(strapi.plugins)),
unique: validators.unique,
// TODO: remove once front-end stop sending them even if useless
columnName: yup.string(),
key: yup.string(),
targetColumnName: yup.string(),
};
};

View File

@ -0,0 +1,132 @@
'use strict';
const yup = require('yup');
const { validators, VALID_TYPES, isValidName } = require('./common');
module.exports = obj => {
return {
type: yup
.string()
.oneOf(VALID_TYPES)
.required(),
...getTypeShape(obj),
};
};
const getTypeShape = obj => {
switch (obj.type) {
/**
* complexe types
*/
case 'media': {
return {
multiple: yup.boolean(),
required: validators.required,
unique: validators.unique,
};
}
/**
* scalar types
*/
case 'string':
case 'text':
case 'richtext': {
return {
default: yup.string(),
required: validators.required,
unique: validators.unique,
min: validators.minLength,
max: validators.maxLength,
};
}
case 'json': {
return {
required: validators.required,
unique: validators.unique,
};
}
case 'enumeration': {
return {
enum: yup
.array()
.of(yup.string().test(isValidName))
.min(1)
.required(),
default: yup
.string()
.when('enum', enumVal => yup.string().oneOf(enumVal)),
enumName: yup.string().test(isValidName),
required: validators.required,
unique: validators.unique,
};
}
case 'password': {
return {
required: validators.required,
min: validators.minLength,
max: validators.maxLength,
};
}
case 'email': {
return {
default: yup.string().email(),
required: validators.required,
unique: validators.unique,
min: validators.minLength,
max: validators.maxLength,
};
}
case 'integer': {
return {
default: yup.number().integer(),
required: validators.required,
unique: validators.unique,
min: yup
.number()
.integer()
.positive(),
max: yup
.number()
.integer()
.positive(),
};
}
case 'float': {
return {
default: yup.number(),
required: validators.required,
unique: validators.unique,
min: yup.number().positive(),
max: yup.number().positive(),
};
}
case 'decimal': {
return {
default: yup.number(),
required: validators.required,
unique: validators.unique,
min: yup.number().positive(),
max: yup.number().positive(),
};
}
case 'date': {
return {
default: yup.date(),
required: validators.required,
unique: validators.unique,
};
}
case 'boolean': {
return {
default: yup.boolean(),
required: validators.required,
unique: validators.unique,
};
}
default: {
return {};
}
}
};

View File

@ -201,9 +201,9 @@ const convertAttributes = attributes => {
} }
if (_.has(attribute, 'target')) { if (_.has(attribute, 'target')) {
const { target, nature, required, unique, plugin } = attribute; const { target, nature, unique, plugin } = attribute;
// ingore relation which aren't oneWay or manyWay (except for images) // ingore relation which aren't oneWay or manyWay
if (!['oneWay', 'manyWay'].includes(nature)) { if (!['oneWay', 'manyWay'].includes(nature)) {
return acc; return acc;
} }
@ -211,7 +211,6 @@ const convertAttributes = attributes => {
acc[key] = { acc[key] = {
[nature === 'oneWay' ? 'model' : 'collection']: target, [nature === 'oneWay' ? 'model' : 'collection']: target,
plugin: plugin ? _.trim(plugin) : undefined, plugin: plugin ? _.trim(plugin) : undefined,
required: required === true ? true : undefined,
unique: unique === true ? true : undefined, unique: unique === true ? true : undefined,
}; };
} }

View File

@ -31,7 +31,7 @@ describe.only('Content Type Builder - Groups', () => {
method: 'POST', method: 'POST',
url: '/content-type-builder/groups', url: '/content-type-builder/groups',
body: { body: {
name: 'some-group', name: 'SomeGroup',
attributes: { attributes: {
title: { title: {
type: 'string', type: 'string',
@ -58,7 +58,7 @@ describe.only('Content Type Builder - Groups', () => {
method: 'POST', method: 'POST',
url: '/content-type-builder/groups', url: '/content-type-builder/groups',
body: { body: {
name: 'some-group', name: 'someGroup',
attributes: {}, attributes: {},
}, },
}); });
@ -121,7 +121,7 @@ describe.only('Content Type Builder - Groups', () => {
data: { data: {
uid: 'some_group', uid: 'some_group',
schema: { schema: {
name: 'some-group', name: 'SomeGroup',
description: '', description: '',
connection: 'default', connection: 'default',
collectionName: 'groups_some_groups', collectionName: 'groups_some_groups',
@ -176,7 +176,7 @@ describe.only('Content Type Builder - Groups', () => {
method: 'PUT', method: 'PUT',
url: '/content-type-builder/groups/some_group', url: '/content-type-builder/groups/some_group',
body: { body: {
name: 'New Group', name: 'NewGroup',
attributes: {}, attributes: {},
}, },
}); });