merge develop and regex validation

This commit is contained in:
Virginie Ky 2019-08-01 17:42:20 +02:00
commit e6589ce6c6
32 changed files with 649 additions and 350 deletions

View File

@ -8,47 +8,36 @@ module.exports = {
// Before saving a value. // Before saving a value.
// Fired before an `insert` or `update` query. // Fired before an `insert` or `update` query.
// beforeSave: async (model, attrs, options) => {}, // beforeSave: async (model, attrs, options) => {},
// After saving a value. // After saving a value.
// Fired after an `insert` or `update` query. // Fired after an `insert` or `update` query.
// afterSave: async (model, response, options) => {}, // afterSave: async (model, response, options) => {},
// Before fetching a value. // Before fetching a value.
// Fired before a `fetch` operation. // Fired before a `fetch` operation.
// beforeFetch: async (model, columns, options) => {}, // beforeFetch: async (model, columns, options) => {},
// After fetching a value. // After fetching a value.
// Fired after a `fetch` operation. // Fired after a `fetch` operation.
// afterFetch: async (model, response, options) => {}, // afterFetch: async (model, response, options) => {},
// Before fetching all values. // Before fetching all values.
// Fired before a `fetchAll` operation. // Fired before a `fetchAll` operation.
// beforeFetchAll: async (model, columns, options) => {}, // beforeFetchAll: async (model, columns, options) => {},
// After fetching all values. // After fetching all values.
// Fired after a `fetchAll` operation. // Fired after a `fetchAll` operation.
// afterFetchAll: async (model, response, options) => {}, // afterFetchAll: async (model, response, options) => {},
// Before creating a value. // Before creating a value.
// Fired before an `insert` query. // Fired before an `insert` query.
// beforeCreate: async (model, attrs, options) => {}, // beforeCreate: async (model, attrs, options) => {},
// After creating a value. // After creating a value.
// Fired after an `insert` query. // Fired after an `insert` query.
// afterCreate: async (model, attrs, options) => {}, // afterCreate: async (model, attrs, options) => {},
// Before updating a value. // Before updating a value.
// Fired before an `update` query. // Fired before an `update` query.
// beforeUpdate: async (model, attrs, options) => {}, // beforeUpdate: async (model, attrs, options) => {},
// After updating a value. // After updating a value.
// Fired after an `update` query. // Fired after an `update` query.
// afterUpdate: async (model, attrs, options) => {}, // afterUpdate: async (model, attrs, options) => {},
// Before destroying a value. // Before destroying a value.
// Fired before a `delete` query. // Fired before a `delete` query.
// beforeDestroy: async (model, attrs, options) => {}, // beforeDestroy: async (model, attrs, options) => {},
// After destroying a value. // After destroying a value.
// Fired after a `delete` query. // Fired after a `delete` query.
// afterDestroy: async (model, attrs, options) => {} // afterDestroy: async (model, attrs, options) => {}

View File

@ -26,6 +26,15 @@
"number": { "number": {
"type": "integer" "type": "integer"
}, },
"big_number": {
"type": "biginteger"
},
"float_number": {
"type": "float"
},
"decimal_number": {
"type": "decimal"
},
"date": { "date": {
"type": "date" "type": "date"
}, },
@ -58,7 +67,6 @@
"dominant": true "dominant": true
}, },
"fb_cta": { "fb_cta": {
"required": true,
"type": "group", "type": "group",
"group": "facebook_cta" "group": "facebook_cta"
}, },

View File

@ -113,7 +113,7 @@
"components.Input.error.validation.regex": "値が正規表現と一致しません", "components.Input.error.validation.regex": "値が正規表現と一致しません",
"components.Input.error.validation.required": "値は必須項目です", "components.Input.error.validation.required": "値は必須項目です",
"components.ListRow.empty": "表示するデータがありません", "components.ListRow.empty": "表示するデータがありません",
"components.OverlayBlocker.description": "サーバーのスタートが必要な機能を使用しています。サーバーが起動するまでお待ち下さい", "components.OverlayBlocker.description": "サーバーのスタートが必要な機能を使用しています。サーバーが起動するまでお待ち下さい",
"components.OverlayBlocker.title": "リスタートを待っています...", "components.OverlayBlocker.title": "リスタートを待っています...",
"components.PageFooter.select": "ページ毎に表示する投稿数", "components.PageFooter.select": "ページ毎に表示する投稿数",
"components.ProductionBlocker.description": "このプラグインは、安全のため、他の環境では無効する必要があります", "components.ProductionBlocker.description": "このプラグインは、安全のため、他の環境では無効する必要があります",

View File

@ -15,9 +15,9 @@ const createGroupModels = async ({ model, definition, ORM, GLOBALS }) => {
const joinColumn = `${pluralize.singular(collectionName)}_${primaryKey}`; const joinColumn = `${pluralize.singular(collectionName)}_${primaryKey}`;
const joinModel = ORM.Model.extend({ const joinModel = ORM.Model.extend({
tableName: joinTable, tableName: joinTable,
slice() { group() {
return this.morphTo( return this.morphTo(
'slice', 'group',
...groupAttributes.map(key => { ...groupAttributes.map(key => {
const groupKey = definition.attributes[key].group; const groupKey = definition.attributes[key].group;
return GLOBALS[strapi.groups[groupKey].globalId]; return GLOBALS[strapi.groups[groupKey].globalId];
@ -59,8 +59,8 @@ const createGroupJoinTables = async ({ definition, ORM }) => {
.integer('order') .integer('order')
.unsigned() .unsigned()
.notNullable(); .notNullable();
table.string('slice_type').notNullable(); table.string('group_type').notNullable();
table.integer('slice_id').notNullable(); table.integer('group_id').notNullable();
table.integer(joinColumn).notNullable(); table.integer(joinColumn).notNullable();
table table

View File

@ -436,6 +436,15 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
}); });
}; };
// Extract association except polymorphic.
const associations = definition.associations.filter(
association => association.nature.toLowerCase().indexOf('morph') === -1
);
// Extract polymorphic association.
const polymorphicAssociations = definition.associations.filter(
association => association.nature.toLowerCase().indexOf('morph') !== -1
);
// Update serialize to reformat data for polymorphic associations. // Update serialize to reformat data for polymorphic associations.
loadedModel.serialize = function(options) { loadedModel.serialize = function(options) {
const attrs = _.clone(this.attributes); const attrs = _.clone(this.attributes);
@ -447,27 +456,14 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
const relations = this.relations; const relations = this.relations;
groupAttributes.forEach(key => { groupAttributes.forEach(key => {
const { repeatable } = definition.attributes[key];
if (relations[key]) { if (relations[key]) {
const groups = relations[key].toJSON().map(el => el.slice); const groups = relations[key].toJSON().map(el => el.group);
attrs[key] = attrs[key] = repeatable === true ? groups : _.first(groups) || null;
definition.attributes[key].repeatable === true
? groups
: _.first(groups) || null;
} }
}); });
// Extract association except polymorphic.
const associations = definition.associations.filter(
association =>
association.nature.toLowerCase().indexOf('morph') === -1
);
// Extract polymorphic association.
const polymorphicAssociations = definition.associations.filter(
association =>
association.nature.toLowerCase().indexOf('morph') !== -1
);
polymorphicAssociations.map(association => { polymorphicAssociations.map(association => {
// Retrieve relation Bookshelf object. // Retrieve relation Bookshelf object.
const relation = relations[association.alias]; const relation = relations[association.alias];
@ -489,7 +485,7 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
switch (association.nature) { switch (association.nature) {
case 'oneToManyMorph': case 'oneToManyMorph':
attrs[association.alias] = attrs[association.alias] =
attrs[association.alias][model.collectionName]; attrs[association.alias][model.collectionName] || null;
break; break;
case 'manyToManyMorph': case 'manyToManyMorph':
attrs[association.alias] = attrs[association.alias].map( attrs[association.alias] = attrs[association.alias].map(
@ -497,7 +493,8 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
); );
break; break;
case 'oneMorphToOne': case 'oneMorphToOne':
attrs[association.alias] = attrs[association.alias].related; attrs[association.alias] =
attrs[association.alias].related || null;
break; break;
case 'manyMorphToOne': case 'manyMorphToOne':
case 'manyMorphToMany': case 'manyMorphToMany':
@ -524,6 +521,176 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
return attrs; return attrs;
}; };
const findModelByAssoc = ({ assoc }) => {
const target = assoc.collection || assoc.model;
return assoc.plugin === 'admin'
? strapi.admin.models[target]
: assoc.plugin
? strapi.plugins[assoc.plugin].models[target]
: strapi.models[target];
};
const isPolymorphic = ({ assoc }) => {
return assoc.nature.toLowerCase().indexOf('morph') !== -1;
};
const formatPolymorphicPopulate = ({ assoc, path, prefix = '' }) => {
if (_.isString(path) && path === assoc.via) {
return { [`related.${assoc.via}`]: () => {} };
} else if (_.isString(path) && path === assoc.alias) {
// MorphTo side.
if (assoc.related) {
return { [`${prefix}${assoc.alias}.related`]: () => {} };
}
// oneToMorph or manyToMorph side.
// Retrieve collection name because we are using it to build our hidden model.
const model = findModelByAssoc({ assoc });
return {
[`${prefix}${assoc.alias}.${model.collectionName}`]: function(
query
) {
query.orderBy('created_at', 'desc');
},
};
}
};
const createAssociationPopulate = () => {
return definition.associations
.filter(ast => ast.autoPopulate !== false)
.map(assoc => {
if (isPolymorphic({ assoc })) {
return formatPolymorphicPopulate({
assoc,
path: assoc.alias,
});
}
let path = assoc.alias;
let extraAssocs = [];
if (assoc) {
const assocModel = findModelByAssoc({ assoc });
extraAssocs = assocModel.associations
.filter(assoc => isPolymorphic({ assoc }))
.map(assoc =>
formatPolymorphicPopulate({
assoc,
path: assoc.alias,
prefix: `${path}.`,
})
);
}
return [assoc.alias, ...extraAssocs];
})
.reduce((acc, val) => acc.concat(val), []);
};
const populateGroup = key => {
let paths = [];
const group = strapi.groups[definition.attributes[key].group];
const assocs = (group.associations || []).filter(
assoc => assoc.autoPopulate === true
);
// paths.push(`${key}.group`);
assocs.forEach(assoc => {
if (isPolymorphic({ assoc })) {
const rel = formatPolymorphicPopulate({
assoc,
path: assoc.alias,
prefix: `${key}.group.`,
});
paths.push(rel);
} else {
paths.push(`${key}.group.${assoc.alias}`);
}
});
return paths;
};
const createGroupsPopulate = () => {
const groupsToPopulate = groupAttributes.reduce((acc, key) => {
const attribute = definition.attributes[key];
const autoPopulate = _.get(attribute, ['autoPopulate'], true);
if (autoPopulate === true) {
return acc.concat(populateGroup(key));
}
return acc;
}, []);
return groupsToPopulate;
};
const isGroup = (def, key) =>
_.get(def, ['attributes', key, 'type']) === 'group';
const formatPopulateOptions = withRelated => {
if (!Array.isArray(withRelated)) withRelated = [withRelated];
const obj = withRelated.reduce((acc, key) => {
if (_.isString(key)) {
acc[key] = () => {};
return acc;
}
return _.extend(acc, key);
}, {});
// if groups are no
const finalObj = Object.keys(obj).reduce((acc, key) => {
// check the key path and update it if necessary nothing more
const parts = key.split('.');
let newKey;
let prefix = '';
let tmpModel = definition;
for (let part of parts) {
if (isGroup(tmpModel, part)) {
tmpModel = strapi.groups[tmpModel.attributes[part].group];
// add group path and there relations / images
const path = `${prefix}${part}.group`;
newKey = path;
prefix = `${path}.`;
continue;
}
const assoc = tmpModel.associations.find(
association => association.alias === part
);
if (!assoc) return acc;
tmpModel = findModelByAssoc({ assoc });
if (isPolymorphic({ assoc })) {
const path = formatPolymorphicPopulate({
assoc,
path: assoc.alias,
prefix,
});
return _.extend(acc, path);
}
newKey = `${prefix}${part}`;
prefix = `${newKey}.`;
}
acc[newKey] = obj[key];
return acc;
}, {});
return [finalObj];
};
// Initialize lifecycle callbacks. // Initialize lifecycle callbacks.
loadedModel.initialize = function() { loadedModel.initialize = function() {
const lifecycle = { const lifecycle = {
@ -547,84 +714,19 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
} }
}); });
const findModelByAssoc = ({ assoc }) => {
return assoc.plugin
? strapi.plugins[assoc.plugin].models[
assoc.collection || assoc.model
]
: strapi.models[assoc.collection || assoc.model];
};
const isPolymorphic = ({ assoc }) => {
return assoc.nature.toLowerCase().indexOf('morph') !== -1;
};
const formatPolymorphicPopulate = ({ assoc, path, prefix = '' }) => {
if (_.isString(path) && path === assoc.via) {
return `related.${assoc.via}`;
} else if (_.isString(path) && path === assoc.alias) {
// MorphTo side.
if (assoc.related) {
return `${prefix}${assoc.alias}.related`;
}
// oneToMorph or manyToMorph side.
// Retrieve collection name because we are using it to build our hidden model.
const model = findModelByAssoc({ assoc });
return {
[`${prefix}${assoc.alias}.${model.collectionName}`]: function(
query
) {
query.orderBy('created_at', 'desc');
},
};
}
};
const addPolymorphicRelated = path => {
const assoc = definition.associations.find(
assoc => assoc.alias === path || assoc.via === path
);
if (assoc && isPolymorphic({ assoc })) {
return formatPolymorphicPopulate({
assoc,
path,
});
}
let extraAssocs = [];
if (assoc) {
const assocModel = findModelByAssoc({ assoc });
extraAssocs = assocModel.associations
.filter(assoc => isPolymorphic({ assoc }))
.map(assoc =>
formatPolymorphicPopulate({
assoc,
path: assoc.alias,
prefix: `${path}.`,
})
);
}
return [path, ...extraAssocs];
};
// Update withRelated level to bypass many-to-many association for polymorphic relationshiips. // Update withRelated level to bypass many-to-many association for polymorphic relationshiips.
// Apply only during fetching. // Apply only during fetching.
this.on('fetching fetching:collection', (instance, attrs, options) => { this.on('fetching fetching:collection', (instance, attrs, options) => {
if (_.isArray(options.withRelated)) { // do not populate anything
options.withRelated = options.withRelated if (options.withRelated === false) return;
.concat(groupAttributes.map(key => `${key}.slice`)) if (options.isEager === true) return;
.map(addPolymorphicRelated)
.reduce((acc, paths) => acc.concat(paths), []); if (_.isNil(options.withRelated)) {
options.withRelated = []
.concat(createGroupsPopulate())
.concat(createAssociationPopulate());
} else { } else {
options.withRelated = groupAttributes options.withRelated = formatPopulateOptions(options.withRelated);
.map(key => `${key}.slice`)
.map(addPolymorphicRelated)
.reduce((acc, paths) => acc.concat(paths), []);
} }
return _.isFunction(target[model.toLowerCase()]['beforeFetchAll']) return _.isFunction(target[model.toLowerCase()]['beforeFetchAll'])

View File

@ -19,11 +19,6 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
return model.attributes[key].type === 'group'; return model.attributes[key].type === 'group';
}); });
// default relations to populate
const defaultPopulate = model.associations
.filter(ast => ast.autoPopulate !== false)
.map(ast => ast.alias);
// Returns an object with relation keys only to create relations in DB // Returns an object with relation keys only to create relations in DB
const pickRelations = values => { const pickRelations = values => {
return _.pick(values, assocKeys); return _.pick(values, assocKeys);
@ -58,7 +53,7 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
} }
const entry = await model.forge(params).fetch({ const entry = await model.forge(params).fetch({
withRelated: populate || defaultPopulate, withRelated: populate,
}); });
return entry ? entry.toJSON() : null; return entry ? entry.toJSON() : null;
@ -72,7 +67,10 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
return model return model
.query(buildQuery({ model, filters })) .query(buildQuery({ model, filters }))
.fetchAll({ withRelated: populate || defaultPopulate, transacting }) .fetchAll({
withRelated: populate,
transacting,
})
.then(results => results.toJSON()); .then(results => results.toJSON());
} }
@ -196,9 +194,6 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
// Convert `params` object to filters compatible with Bookshelf. // Convert `params` object to filters compatible with Bookshelf.
const filters = modelUtils.convertParams(modelKey, params); const filters = modelUtils.convertParams(modelKey, params);
// Select field to populate.
const withRelated = populate || defaultPopulate;
return model return model
.query(qb => { .query(qb => {
buildSearchQuery(qb, model, params); buildSearchQuery(qb, model, params);
@ -216,7 +211,7 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
} }
}) })
.fetchAll({ .fetchAll({
withRelated, withRelated: populate,
}); });
} }
@ -248,8 +243,8 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
return joinModel.forge().save( return joinModel.forge().save(
{ {
[foreignKey]: entry.id, [foreignKey]: entry.id,
slice_type: groupModel.collectionName, group_type: groupModel.collectionName,
slice_id: group.id, group_id: group.id,
field: key, field: key,
order, order,
}, },
@ -317,8 +312,8 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
.query({ .query({
where: { where: {
[foreignKey]: entry.id, [foreignKey]: entry.id,
slice_type: groupModel.collectionName, group_type: groupModel.collectionName,
slice_id: group.id, group_id: group.id,
field: key, field: key,
}, },
}) })
@ -338,8 +333,8 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
return joinModel.forge().save( return joinModel.forge().save(
{ {
[foreignKey]: entry.id, [foreignKey]: entry.id,
slice_type: groupModel.collectionName, group_type: groupModel.collectionName,
slice_id: group.id, group_id: group.id,
field: key, field: key,
order, order,
}, },
@ -396,7 +391,7 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
qb.where(joinModel.foreignKey, entry.id).andWhere('field', key); qb.where(joinModel.foreignKey, entry.id).andWhere('field', key);
}) })
.fetchAll({ transacting }) .fetchAll({ transacting })
.map(el => el.get('slice_id').toString()); .map(el => el.get('group_id').toString());
// verify the provided ids are realted to this entity. // verify the provided ids are realted to this entity.
idsToKeep.forEach(id => { idsToKeep.forEach(id => {
@ -413,7 +408,7 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
if (idsToDelete.length > 0) { if (idsToDelete.length > 0) {
await joinModel await joinModel
.forge() .forge()
.query(qb => qb.whereIn('slice_id', idsToDelete)) .query(qb => qb.whereIn('group_id', idsToDelete))
.destroy({ transacting, require: false }); .destroy({ transacting, require: false });
await strapi await strapi
@ -442,12 +437,12 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
.query({ .query({
where: { where: {
[foreignKey]: entry.id, [foreignKey]: entry.id,
slice_type: groupModel.collectionName, group_type: groupModel.collectionName,
field: key, field: key,
}, },
}) })
.fetchAll({ transacting }) .fetchAll({ transacting })
.map(el => el.get('slice_id')); .map(el => el.get('group_id'));
await strapi await strapi
.query(groupModel.uid) .query(groupModel.uid)
@ -458,7 +453,7 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
.query({ .query({
where: { where: {
[foreignKey]: entry.id, [foreignKey]: entry.id,
slice_type: groupModel.collectionName, group_type: groupModel.collectionName,
field: key, field: key,
}, },
}) })

View File

@ -51,7 +51,7 @@ module.exports = {
[this.primaryKey]: getValuePrimaryKey(params, this.primaryKey), [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey),
}).fetch({ }).fetch({
transacting, transacting,
withRelated: populate || this.associations.map(x => x.alias), withRelated: populate,
}); });
const data = record ? record.toJSON() : record; const data = record ? record.toJSON() : record;
@ -395,7 +395,6 @@ module.exports = {
[this.primaryKey]: getValuePrimaryKey(params, this.primaryKey), [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey),
}).fetch({ }).fetch({
transacting, transacting,
withRelated: this.associations.map(x => x.alias),
}); });
}, },
@ -410,7 +409,6 @@ module.exports = {
}) })
.fetch({ .fetch({
transacting, transacting,
withRelated: this.associations.map(x => x.alias),
}); });
const entry = record ? record.toJSON() : record; const entry = record ? record.toJSON() : record;

View File

@ -24,59 +24,10 @@ module.exports = async (ctx, next) => {
'actions', 'actions',
ctx.request.route.action, ctx.request.route.action,
], ],
[], []
).split('.'); ).split('.');
if (controller && action) { if (controller && action) {
// Redirect to specific controller.
if (
ctx.request.body.hasOwnProperty('fields') &&
ctx.request.body.hasOwnProperty('files')
) {
let { files, fields } = ctx.request.body;
const parser = value => {
try {
value = JSON.parse(value);
} catch (e) {
// Silent.
}
return _.isArray(value) ? value.map(obj => parser(obj)) : value;
};
fields = Object.keys(fields).reduce((acc, current) => {
acc[current] = parser(fields[current]);
return acc;
}, {});
ctx.request.body = fields;
await target.controllers[controller.toLowerCase()][action](ctx);
const resBody = ctx.body;
await Promise.all(
Object.keys(files).map(async field => {
ctx.request.body = {
files: {
files: files[field],
},
fields: {
refId: resBody.id || resBody._id,
ref: ctx.params.model,
source,
field,
},
};
return strapi.plugins.upload.controllers.upload.upload(ctx);
}),
);
return ctx.send(resBody);
}
return await target.controllers[controller.toLowerCase()][action](ctx); return await target.controllers[controller.toLowerCase()][action](ctx);
} }
} }

View File

@ -64,6 +64,14 @@
"policies": [] "policies": []
} }
}, },
{
"method": "POST",
"path": "/explorer/upload",
"handler": "ContentManager.uploadFile",
"config": {
"policies": []
}
},
{ {
"method": "GET", "method": "GET",
"path": "/explorer/:model", "path": "/explorer/:model",

View File

@ -144,4 +144,15 @@ module.exports = {
ctx.request.query ctx.request.query
); );
}, },
/**
* Handle uploads in the explorer
*/
async uploadFile(ctx) {
if (!strapi.plugins.upload) {
ctx.send({ error: 'uploadPlugin.notInstalled' }, 400);
}
return strapi.plugins.upload.controllers.upload.upload(ctx);
},
}; };

View File

@ -5,27 +5,6 @@ const _ = require('lodash');
/** /**
* A set of functions called "actions" for `ContentManager` * A set of functions called "actions" for `ContentManager`
*/ */
const parseFormInput = value => {
try {
const parsed = JSON.parse(value);
// do not modify initial value if it is string except 'null'
if (typeof parsed !== 'string') {
value = parsed;
}
} catch (e) {
// Silent.
}
return _.isArray(value) ? value.map(parseFormInput) : value;
};
const parseFormData = fields =>
Object.keys(fields).reduce((acc, current) => {
acc[current] = parseFormInput(fields[current]);
return acc;
}, {});
module.exports = { module.exports = {
fetch(params, source, populate) { fetch(params, source, populate) {
return strapi return strapi
@ -53,61 +32,10 @@ module.exports = {
}, },
async add(params, values, source) { async add(params, values, source) {
// Multipart/form-data.
if (values.hasOwnProperty('fields') && values.hasOwnProperty('files')) {
const data = parseFormData(values.fields);
const entry = await strapi.query(params.model, source).create(data);
// Then, request plugin upload.
if (strapi.plugins.upload && Object.keys(values.files).length > 0) {
// Upload new files and attach them to this entity.
await strapi.plugins.upload.services.upload.uploadToEntity(
{
id: entry.id || entry._id,
model: params.model,
},
values.files,
source
);
}
return entry;
}
// Create an entry using `queries` system
return await strapi.query(params.model, source).create(values); return await strapi.query(params.model, source).create(values);
}, },
async edit(params, values, source) { async edit(params, values, source) {
// Multipart/form-data.
if (values.hasOwnProperty('fields') && values.hasOwnProperty('files')) {
// set empty attributes if old values was cleared
_.difference(
Object.keys(values.files),
Object.keys(values.fields)
).forEach(attr => {
values.fields[attr] = [];
});
const data = parseFormData(values.fields);
const updatedEntity = await strapi
.query(params.model, source)
.update({ id: params.id }, data);
// Then, request plugin upload.
if (strapi.plugins.upload) {
// Upload new files and attach them to this entity.
await strapi.plugins.upload.services.upload.uploadToEntity(
params,
values.files,
source
);
}
return updatedEntity;
}
// Raw JSON.
return strapi.query(params.model, source).update({ id: params.id }, values); return strapi.query(params.model, source).update({ id: params.id }, values);
}, },

View File

@ -12,6 +12,8 @@ const isSortable = (schema, name) => {
return false; return false;
} }
if (schema.modelType === 'group' && name === 'id') return false;
const attribute = schema.attributes[name]; const attribute = schema.attributes[name];
if (NON_SORTABLES.includes(attribute.type)) { if (NON_SORTABLES.includes(attribute.type)) {
return false; return false;

View File

@ -9,7 +9,7 @@ async function createDefaultConfiguration(model) {
const schema = formatContentTypeSchema(model); const schema = formatContentTypeSchema(model);
return { return {
settings: await createDefaultSettings(), settings: await createDefaultSettings(schema),
metadatas: await createDefaultMetadatas(schema), metadatas: await createDefaultMetadatas(schema),
layouts: await createDefaultLayouts(schema), layouts: await createDefaultLayouts(schema),
}; };

View File

@ -3,18 +3,32 @@
const _ = require('lodash'); const _ = require('lodash');
const { isSortable } = require('./attributes'); const { isSortable } = require('./attributes');
const getDefaultMainField = schema => {
if (schema.modelType == 'group') {
// find first group attribute that is sortable
return (
Object.keys(schema.attributes).find(key => isSortable(schema, key)) ||
'id'
);
}
return 'id';
};
/** /**
* Retunrs a configuration default settings * Retunrs a configuration default settings
*/ */
async function createDefaultSettings() { async function createDefaultSettings(schema) {
const generalSettings = await strapi.plugins[ const generalSettings = await strapi.plugins[
'content-manager' 'content-manager'
].services.generalsettings.getGeneralSettings(); ].services.generalsettings.getGeneralSettings();
let defaultField = getDefaultMainField(schema);
return { return {
...generalSettings, ...generalSettings,
mainField: 'id', mainField: defaultField,
defaultSortBy: 'id', defaultSortBy: defaultField,
defaultSortOrder: 'ASC', defaultSortOrder: 'ASC',
}; };
} }
@ -24,13 +38,17 @@ async function createDefaultSettings() {
async function syncSettings(configuration, schema) { async function syncSettings(configuration, schema) {
if (_.isEmpty(configuration.settings)) return createDefaultSettings(schema); if (_.isEmpty(configuration.settings)) return createDefaultSettings(schema);
const { mainField = 'id', defaultSortBy = 'id' } = let defaultField = getDefaultMainField(schema);
const { mainField = defaultField, defaultSortBy = defaultField } =
configuration.settings || {}; configuration.settings || {};
return { return {
...configuration.settings, ...configuration.settings,
mainField: isSortable(schema, mainField) ? mainField : 'id', mainField: isSortable(schema, mainField) ? mainField : defaultField,
defaultSortBy: isSortable(schema, defaultSortBy) ? defaultSortBy : 'id', defaultSortBy: isSortable(schema, defaultSortBy)
? defaultSortBy
: defaultField,
}; };
} }

View File

@ -57,7 +57,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: '/content-manager/explorer/tag/?source=content-manager', url: '/content-manager/explorer/tag/?source=content-manager',
method: 'POST', method: 'POST',
formData: { body: {
name: 'tag1', name: 'tag1',
}, },
}); });
@ -73,7 +73,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: '/content-manager/explorer/tag/?source=content-manager', url: '/content-manager/explorer/tag/?source=content-manager',
method: 'POST', method: 'POST',
formData: { body: {
name: 'tag2', name: 'tag2',
}, },
}); });
@ -89,7 +89,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: '/content-manager/explorer/tag/?source=content-manager', url: '/content-manager/explorer/tag/?source=content-manager',
method: 'POST', method: 'POST',
formData: { body: {
name: 'tag3', name: 'tag3',
}, },
}); });
@ -110,7 +110,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: '/content-manager/explorer/article/?source=content-manager', url: '/content-manager/explorer/article/?source=content-manager',
method: 'POST', method: 'POST',
formData: entry, body: entry,
}); });
data.articles.push(body); data.articles.push(body);
@ -132,7 +132,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: '/content-manager/explorer/article/?source=content-manager', url: '/content-manager/explorer/article/?source=content-manager',
method: 'POST', method: 'POST',
formData: entry, body: entry,
}); });
data.articles.push(body); data.articles.push(body);
@ -155,7 +155,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: `/content-manager/explorer/article/${entry.id}?source=content-manager`, url: `/content-manager/explorer/article/${entry.id}?source=content-manager`,
method: 'PUT', method: 'PUT',
formData: entry, body: entry,
}); });
data.articles[0] = body; data.articles[0] = body;
@ -178,7 +178,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: `/content-manager/explorer/article/${entry.id}?source=content-manager`, url: `/content-manager/explorer/article/${entry.id}?source=content-manager`,
method: 'PUT', method: 'PUT',
formData: entry, body: entry,
}); });
data.articles[0] = body; data.articles[0] = body;
@ -199,7 +199,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: `/content-manager/explorer/article/${entry.id}?source=content-manager`, url: `/content-manager/explorer/article/${entry.id}?source=content-manager`,
method: 'PUT', method: 'PUT',
formData: entry, body: entry,
}); });
data.articles[0] = body; data.articles[0] = body;
@ -221,7 +221,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: `/content-manager/explorer/article/${entry.id}?source=content-manager`, url: `/content-manager/explorer/article/${entry.id}?source=content-manager`,
method: 'PUT', method: 'PUT',
formData: entry, body: entry,
}); });
data.articles[0] = body; data.articles[0] = body;
@ -237,7 +237,7 @@ describe('Content Manager End to End', () => {
const { body: createdTag } = await rq({ const { body: createdTag } = await rq({
url: '/content-manager/explorer/tag/?source=content-manager', url: '/content-manager/explorer/tag/?source=content-manager',
method: 'POST', method: 'POST',
formData: { body: {
name: 'tag11', name: 'tag11',
}, },
}); });
@ -245,7 +245,7 @@ describe('Content Manager End to End', () => {
const { body: article12 } = await rq({ const { body: article12 } = await rq({
url: '/content-manager/explorer/article/?source=content-manager', url: '/content-manager/explorer/article/?source=content-manager',
method: 'POST', method: 'POST',
formData: { body: {
title: 'article12', title: 'article12',
content: 'Content', content: 'Content',
tags: [createdTag], tags: [createdTag],
@ -260,7 +260,7 @@ describe('Content Manager End to End', () => {
const { body: article13 } = await rq({ const { body: article13 } = await rq({
url: '/content-manager/explorer/article/?source=content-manager', url: '/content-manager/explorer/article/?source=content-manager',
method: 'POST', method: 'POST',
formData: { body: {
title: 'article13', title: 'article13',
content: 'Content', content: 'Content',
tags: [updatedTag], tags: [updatedTag],
@ -308,7 +308,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: '/content-manager/explorer/articlewithtag/?source=content-manager', url: '/content-manager/explorer/articlewithtag/?source=content-manager',
method: 'POST', method: 'POST',
formData: entry, body: entry,
}); });
expect(body.id); expect(body.id);
@ -330,7 +330,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: '/content-manager/explorer/category/?source=content-manager', url: '/content-manager/explorer/category/?source=content-manager',
method: 'POST', method: 'POST',
formData: { body: {
name: 'cat1', name: 'cat1',
}, },
}); });
@ -346,7 +346,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: '/content-manager/explorer/category/?source=content-manager', url: '/content-manager/explorer/category/?source=content-manager',
method: 'POST', method: 'POST',
formData: { body: {
name: 'cat2', name: 'cat2',
}, },
}); });
@ -368,7 +368,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: '/content-manager/explorer/article/?source=content-manager', url: '/content-manager/explorer/article/?source=content-manager',
method: 'POST', method: 'POST',
formData: entry, body: entry,
}); });
data.articles.push(body); data.articles.push(body);
@ -390,7 +390,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: `/content-manager/explorer/article/${entry.id}?source=content-manager`, url: `/content-manager/explorer/article/${entry.id}?source=content-manager`,
method: 'PUT', method: 'PUT',
formData: entry, body: entry,
}); });
data.articles[0] = body; data.articles[0] = body;
@ -411,7 +411,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: '/content-manager/explorer/article?source=content-manager', url: '/content-manager/explorer/article?source=content-manager',
method: 'POST', method: 'POST',
formData: entry, body: entry,
}); });
data.articles.push(body); data.articles.push(body);
@ -432,7 +432,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: `/content-manager/explorer/article/${entry.id}?source=content-manager`, url: `/content-manager/explorer/article/${entry.id}?source=content-manager`,
method: 'PUT', method: 'PUT',
formData: entry, body: entry,
}); });
data.articles[1] = body; data.articles[1] = body;
@ -453,7 +453,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: `/content-manager/explorer/category/${entry.id}?source=content-manager`, url: `/content-manager/explorer/category/${entry.id}?source=content-manager`,
method: 'PUT', method: 'PUT',
formData: entry, body: entry,
}); });
data.categories[0] = body; data.categories[0] = body;
@ -473,7 +473,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: '/content-manager/explorer/category/?source=content-manager', url: '/content-manager/explorer/category/?source=content-manager',
method: 'POST', method: 'POST',
formData: entry, body: entry,
}); });
data.categories.push(body); data.categories.push(body);
@ -549,7 +549,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: '/content-manager/explorer/reference/?source=content-manager', url: '/content-manager/explorer/reference/?source=content-manager',
method: 'POST', method: 'POST',
formData: { body: {
name: 'ref1', name: 'ref1',
}, },
}); });
@ -569,7 +569,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: '/content-manager/explorer/article?source=content-manager', url: '/content-manager/explorer/article?source=content-manager',
method: 'POST', method: 'POST',
formData: entry, body: entry,
}); });
data.articles.push(body); data.articles.push(body);
@ -589,7 +589,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: `/content-manager/explorer/article/${entry.id}?source=content-manager`, url: `/content-manager/explorer/article/${entry.id}?source=content-manager`,
method: 'PUT', method: 'PUT',
formData: entry, body: entry,
}); });
data.articles[0] = body; data.articles[0] = body;
@ -610,7 +610,7 @@ describe('Content Manager End to End', () => {
let { body } = await rq({ let { body } = await rq({
url: '/content-manager/explorer/article?source=content-manager', url: '/content-manager/explorer/article?source=content-manager',
method: 'POST', method: 'POST',
formData: entry, body: entry,
}); });
data.articles.push(body); data.articles.push(body);
@ -627,7 +627,7 @@ describe('Content Manager End to End', () => {
const { body: tagToCreate } = await rq({ const { body: tagToCreate } = await rq({
url: '/content-manager/explorer/tag/?source=content-manager', url: '/content-manager/explorer/tag/?source=content-manager',
method: 'POST', method: 'POST',
formData: { body: {
name: 'tag111', name: 'tag111',
}, },
}); });
@ -635,7 +635,7 @@ describe('Content Manager End to End', () => {
const { body: referenceToCreate } = await rq({ const { body: referenceToCreate } = await rq({
url: '/content-manager/explorer/reference/?source=content-manager', url: '/content-manager/explorer/reference/?source=content-manager',
method: 'POST', method: 'POST',
formData: { body: {
name: 'cat111', name: 'cat111',
tag: tagToCreate, tag: tagToCreate,
}, },
@ -648,7 +648,7 @@ describe('Content Manager End to End', () => {
const { body: tagToCreate } = await rq({ const { body: tagToCreate } = await rq({
url: '/content-manager/explorer/tag/?source=content-manager', url: '/content-manager/explorer/tag/?source=content-manager',
method: 'POST', method: 'POST',
formData: { body: {
name: 'tag111', name: 'tag111',
}, },
}); });
@ -656,7 +656,7 @@ describe('Content Manager End to End', () => {
const { body: referenceToCreate } = await rq({ const { body: referenceToCreate } = await rq({
url: '/content-manager/explorer/reference/?source=content-manager', url: '/content-manager/explorer/reference/?source=content-manager',
method: 'POST', method: 'POST',
formData: { body: {
name: 'cat111', name: 'cat111',
tag: tagToCreate, tag: tagToCreate,
}, },
@ -667,7 +667,7 @@ describe('Content Manager End to End', () => {
const { body: referenceToUpdate } = await rq({ const { body: referenceToUpdate } = await rq({
url: `/content-manager/explorer/reference/${referenceToCreate.id}?source=content-manager`, url: `/content-manager/explorer/reference/${referenceToCreate.id}?source=content-manager`,
method: 'PUT', method: 'PUT',
formData: { body: {
tag: null, tag: null,
}, },
}); });
@ -679,7 +679,7 @@ describe('Content Manager End to End', () => {
const { body: tagToCreate } = await rq({ const { body: tagToCreate } = await rq({
url: '/content-manager/explorer/tag/?source=content-manager', url: '/content-manager/explorer/tag/?source=content-manager',
method: 'POST', method: 'POST',
formData: { body: {
name: 'tag111', name: 'tag111',
}, },
}); });
@ -687,7 +687,7 @@ describe('Content Manager End to End', () => {
const { body: referenceToCreate } = await rq({ const { body: referenceToCreate } = await rq({
url: '/content-manager/explorer/reference/?source=content-manager', url: '/content-manager/explorer/reference/?source=content-manager',
method: 'POST', method: 'POST',
formData: { body: {
name: 'cat111', name: 'cat111',
tag: tagToCreate, tag: tagToCreate,
}, },

View File

@ -82,6 +82,25 @@ class AttributeForm extends React.Component {
acc[current] = [{ id: `${pluginId}.error.validation.required` }]; acc[current] = [{ id: `${pluginId}.error.validation.required` }];
} }
if (
current === 'name' &&
!new RegExp('^[A-Za-z][_0-9A-Za-z]*$').test(value)
) {
acc[current] = [{ id: `${pluginId}.error.validation.regex.name` }];
}
if (current === 'enum' && !!value) {
const split = value.split('\n');
const hasEnumFormatError = split.filter(
v => !new RegExp('^[A-Za-z][_0-9A-Za-z]*$').test(v)
);
if (hasEnumFormatError.length > 0) {
acc[current] = [{ id: `${pluginId}.error.validation.regex.values` }];
}
}
return acc; return acc;
}, formErrors); }, formErrors);

View File

@ -104,6 +104,12 @@ class ModelForm extends React.Component {
formErrors = { name: [{ id: `${pluginId}.error.validation.required` }] }; formErrors = { name: [{ id: `${pluginId}.error.validation.required` }] };
} }
if (!new RegExp('^[A-Za-z][_0-9A-Za-z]*$').test(modifiedData.name)) {
formErrors = {
name: [{ id: `${pluginId}.error.validation.regex.name` }],
};
}
this.setState(prevState => ({ this.setState(prevState => ({
formErrors, formErrors,
didCheckErrors: !prevState.didCheckErrors, didCheckErrors: !prevState.didCheckErrors,

View File

@ -35,6 +35,8 @@
"error.validation.minLength": "The value is too short.", "error.validation.minLength": "The value is too short.",
"error.validation.minSupMax": "Can't be superior", "error.validation.minSupMax": "Can't be superior",
"error.validation.regex": "The value does not match the regex.", "error.validation.regex": "The value does not match the regex.",
"error.validation.regex.name": "The name should not start with a number or a special character.",
"error.validation.regex.values": "Values should not start with a number or a special character.",
"error.validation.required": "This value input is required.", "error.validation.required": "This value input is required.",
"form.attribute.info.no-space-allowed": "No space is allowed for the name of the attribute", "form.attribute.info.no-space-allowed": "No space is allowed for the name of the attribute",
"form.attribute.item.appearance.description": "Otherwise, the value will be editable through a basic textarea field", "form.attribute.item.appearance.description": "Otherwise, the value will be editable through a basic textarea field",

View File

@ -34,6 +34,8 @@
"error.validation.minLength": "La valeur est trop courte.", "error.validation.minLength": "La valeur est trop courte.",
"error.validation.minSupMax": "Ne peut pas être plus grand", "error.validation.minSupMax": "Ne peut pas être plus grand",
"error.validation.regex": "La valeur ne correspond pas au format attendu.", "error.validation.regex": "La valeur ne correspond pas au format attendu.",
"error.validation.regex.name": "Le nom ne peut pas commencer par un nombre ou un caractère spécial",
"error.validation.regex.values": "Les valeurs ne peuvent pas commencer par un nombre ou un caractère spécial",
"error.validation.required": "Ce champ est obligatoire.", "error.validation.required": "Ce champ est obligatoire.",
"form.attribute.info.no-space-allowed": "Les espaces ne sont pas autorisés pour les noms du champ", "form.attribute.info.no-space-allowed": "Les espaces ne sont pas autorisés pour les noms du champ",
"form.attribute.item.appearance.description": "Sinon, il sera editable à partir d'une simple zone de texte", "form.attribute.item.appearance.description": "Sinon, il sera editable à partir d'une simple zone de texte",

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,126 @@
'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(),
max: yup.number().integer(),
};
}
case 'float': {
return {
default: yup.number(),
required: validators.required,
unique: validators.unique,
min: yup.number(),
max: yup.number(),
};
}
case 'decimal': {
return {
default: yup.number(),
required: validators.required,
unique: validators.unique,
min: yup.number(),
max: yup.number(),
};
}
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',

View File

@ -10,6 +10,8 @@ const _ = require('lodash');
module.exports = { module.exports = {
async upload(ctx) { async upload(ctx) {
const uploadService = strapi.plugins.upload.services.upload;
// Retrieve provider configuration. // Retrieve provider configuration.
const config = await strapi const config = await strapi
.store({ .store({
@ -30,8 +32,8 @@ module.exports = {
} }
// Extract optional relational data. // Extract optional relational data.
const { refId, ref, source, field, path } = ctx.request.body.fields || {}; const { refId, ref, source, field, path } = ctx.request.body || {};
const { files = {} } = ctx.request.body.files || {}; const { files = {} } = ctx.request.files || {};
if (_.isEmpty(files)) { if (_.isEmpty(files)) {
return ctx.badRequest( return ctx.badRequest(
@ -43,9 +45,8 @@ module.exports = {
} }
// Transform stream files to buffer // Transform stream files to buffer
const buffers = await strapi.plugins.upload.services.upload.bufferize( const buffers = await uploadService.bufferize(files);
ctx.request.body.files.files
);
const enhancedFiles = buffers.map(file => { const enhancedFiles = buffers.map(file => {
if (file.size > config.sizeLimit) { if (file.size > config.sizeLimit) {
return ctx.badRequest( return ctx.badRequest(
@ -94,10 +95,7 @@ module.exports = {
return; return;
} }
const uploadedFiles = await strapi.plugins.upload.services.upload.upload( const uploadedFiles = await uploadService.upload(enhancedFiles, config);
enhancedFiles,
config
);
// Send 200 `ok` // Send 200 `ok`
ctx.send( ctx.send(

View File

@ -24,7 +24,7 @@
"inquirer": "^6.2.1", "inquirer": "^6.2.1",
"kcors": "^2.2.0", "kcors": "^2.2.0",
"koa": "^2.1.0", "koa": "^2.1.0",
"koa-body": "^2.5.0", "koa-body": "^4.1.0",
"koa-compose": "^4.0.0", "koa-compose": "^4.0.0",
"koa-compress": "^2.0.0", "koa-compress": "^2.0.0",
"koa-convert": "^1.2.0", "koa-convert": "^1.2.0",

View File

@ -1,25 +1,13 @@
const request = require('request-promise-native'); const request = require('request-promise-native');
const createRequest = (defaults = {}) => { const createRequest = (defaults = {}) => {
const client = request.defaults({ return request.defaults({
baseUrl: 'http://localhost:1337', baseUrl: 'http://localhost:1337',
json: true, json: true,
resolveWithFullResponse: true, resolveWithFullResponse: true,
simple: false, simple: false,
...defaults, ...defaults,
}); });
return async options => {
const params = JSON.parse(JSON.stringify(options));
for (let key in params.formData) {
if (typeof params.formData[key] === 'object') {
params.formData[key] = JSON.stringify(params.formData[key]);
}
}
return client(params);
};
}; };
const createAuthRequest = token => { const createAuthRequest = token => {

View File

@ -2235,6 +2235,14 @@
"@types/express-serve-static-core" "*" "@types/express-serve-static-core" "*"
"@types/serve-static" "*" "@types/serve-static" "*"
"@types/formidable@^1.0.31":
version "1.0.31"
resolved "https://registry.yarnpkg.com/@types/formidable/-/formidable-1.0.31.tgz#274f9dc2d0a1a9ce1feef48c24ca0859e7ec947b"
integrity sha512-dIhM5t8lRP0oWe2HF8MuPvdd1TpPTjhDMAqemcq6oIZQCBQTovhBAdTQ5L5veJB4pdQChadmHuxtB0YzqvfU3Q==
dependencies:
"@types/events" "*"
"@types/node" "*"
"@types/glob@^7.1.1": "@types/glob@^7.1.1":
version "7.1.1" version "7.1.1"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
@ -10490,11 +10498,12 @@ knex@^0.19.0:
uuid "^3.3.2" uuid "^3.3.2"
v8flags "^3.1.3" v8flags "^3.1.3"
koa-body@^2.5.0: koa-body@^4.1.0:
version "2.6.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/koa-body/-/koa-body-2.6.0.tgz#8ed7a192a64a38df610a986342d1801855641a1d" resolved "https://registry.yarnpkg.com/koa-body/-/koa-body-4.1.0.tgz#99295ee2e9543884e5730ae696780930b3821c44"
integrity sha512-8i9ti3TRxelsnPUct0xY8toTFj5gTzGWW45ePBkT8fnzZP75y5woisVpziIdqcnqtt1lMNBD30p+tkiSC+NfjQ== integrity sha512-rWkMfMaCjFmIAMohtjlrg4BqDzcotK5BdZhiwJu1ONuR1ceoFUjnH3wp0hEV39HuBlc9tI3eUUFMK4Cp6ccFtA==
dependencies: dependencies:
"@types/formidable" "^1.0.31"
co-body "^5.1.1" co-body "^5.1.1"
formidable "^1.1.1" formidable "^1.1.1"