mirror of
https://github.com/strapi/strapi.git
synced 2025-09-03 22:03:08 +00:00
merge develop and regex validation
This commit is contained in:
commit
e6589ce6c6
@ -8,47 +8,36 @@ module.exports = {
|
||||
// Before saving a value.
|
||||
// Fired before an `insert` or `update` query.
|
||||
// beforeSave: async (model, attrs, options) => {},
|
||||
|
||||
// After saving a value.
|
||||
// Fired after an `insert` or `update` query.
|
||||
// afterSave: async (model, response, options) => {},
|
||||
|
||||
// Before fetching a value.
|
||||
// Fired before a `fetch` operation.
|
||||
// beforeFetch: async (model, columns, options) => {},
|
||||
|
||||
// After fetching a value.
|
||||
// Fired after a `fetch` operation.
|
||||
// afterFetch: async (model, response, options) => {},
|
||||
|
||||
// Before fetching all values.
|
||||
// Fired before a `fetchAll` operation.
|
||||
// beforeFetchAll: async (model, columns, options) => {},
|
||||
|
||||
// After fetching all values.
|
||||
// Fired after a `fetchAll` operation.
|
||||
// afterFetchAll: async (model, response, options) => {},
|
||||
|
||||
// Before creating a value.
|
||||
// Fired before an `insert` query.
|
||||
// beforeCreate: async (model, attrs, options) => {},
|
||||
|
||||
// After creating a value.
|
||||
// Fired after an `insert` query.
|
||||
// afterCreate: async (model, attrs, options) => {},
|
||||
|
||||
// Before updating a value.
|
||||
// Fired before an `update` query.
|
||||
// beforeUpdate: async (model, attrs, options) => {},
|
||||
|
||||
// After updating a value.
|
||||
// Fired after an `update` query.
|
||||
// afterUpdate: async (model, attrs, options) => {},
|
||||
|
||||
// Before destroying a value.
|
||||
// Fired before a `delete` query.
|
||||
// beforeDestroy: async (model, attrs, options) => {},
|
||||
|
||||
// After destroying a value.
|
||||
// Fired after a `delete` query.
|
||||
// afterDestroy: async (model, attrs, options) => {}
|
||||
|
@ -26,6 +26,15 @@
|
||||
"number": {
|
||||
"type": "integer"
|
||||
},
|
||||
"big_number": {
|
||||
"type": "biginteger"
|
||||
},
|
||||
"float_number": {
|
||||
"type": "float"
|
||||
},
|
||||
"decimal_number": {
|
||||
"type": "decimal"
|
||||
},
|
||||
"date": {
|
||||
"type": "date"
|
||||
},
|
||||
@ -58,7 +67,6 @@
|
||||
"dominant": true
|
||||
},
|
||||
"fb_cta": {
|
||||
"required": true,
|
||||
"type": "group",
|
||||
"group": "facebook_cta"
|
||||
},
|
||||
|
@ -113,7 +113,7 @@
|
||||
"components.Input.error.validation.regex": "値が正規表現と一致しません",
|
||||
"components.Input.error.validation.required": "値は必須項目です",
|
||||
"components.ListRow.empty": "表示するデータがありません",
|
||||
"components.OverlayBlocker.description": "サーバーのりスタートが必要な機能を使用しています。サーバーが起動するまでお待ち下さい",
|
||||
"components.OverlayBlocker.description": "サーバーのリスタートが必要な機能を使用しています。サーバーが起動するまでお待ち下さい",
|
||||
"components.OverlayBlocker.title": "リスタートを待っています...",
|
||||
"components.PageFooter.select": "ページ毎に表示する投稿数",
|
||||
"components.ProductionBlocker.description": "このプラグインは、安全のため、他の環境では無効する必要があります",
|
||||
|
@ -15,9 +15,9 @@ const createGroupModels = async ({ model, definition, ORM, GLOBALS }) => {
|
||||
const joinColumn = `${pluralize.singular(collectionName)}_${primaryKey}`;
|
||||
const joinModel = ORM.Model.extend({
|
||||
tableName: joinTable,
|
||||
slice() {
|
||||
group() {
|
||||
return this.morphTo(
|
||||
'slice',
|
||||
'group',
|
||||
...groupAttributes.map(key => {
|
||||
const groupKey = definition.attributes[key].group;
|
||||
return GLOBALS[strapi.groups[groupKey].globalId];
|
||||
@ -59,8 +59,8 @@ const createGroupJoinTables = async ({ definition, ORM }) => {
|
||||
.integer('order')
|
||||
.unsigned()
|
||||
.notNullable();
|
||||
table.string('slice_type').notNullable();
|
||||
table.integer('slice_id').notNullable();
|
||||
table.string('group_type').notNullable();
|
||||
table.integer('group_id').notNullable();
|
||||
table.integer(joinColumn).notNullable();
|
||||
|
||||
table
|
||||
|
@ -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.
|
||||
loadedModel.serialize = function(options) {
|
||||
const attrs = _.clone(this.attributes);
|
||||
@ -447,27 +456,14 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
|
||||
const relations = this.relations;
|
||||
|
||||
groupAttributes.forEach(key => {
|
||||
const { repeatable } = definition.attributes[key];
|
||||
if (relations[key]) {
|
||||
const groups = relations[key].toJSON().map(el => el.slice);
|
||||
const groups = relations[key].toJSON().map(el => el.group);
|
||||
|
||||
attrs[key] =
|
||||
definition.attributes[key].repeatable === true
|
||||
? groups
|
||||
: _.first(groups) || null;
|
||||
attrs[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 => {
|
||||
// Retrieve relation Bookshelf object.
|
||||
const relation = relations[association.alias];
|
||||
@ -489,7 +485,7 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
|
||||
switch (association.nature) {
|
||||
case 'oneToManyMorph':
|
||||
attrs[association.alias] =
|
||||
attrs[association.alias][model.collectionName];
|
||||
attrs[association.alias][model.collectionName] || null;
|
||||
break;
|
||||
case 'manyToManyMorph':
|
||||
attrs[association.alias] = attrs[association.alias].map(
|
||||
@ -497,7 +493,8 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
|
||||
);
|
||||
break;
|
||||
case 'oneMorphToOne':
|
||||
attrs[association.alias] = attrs[association.alias].related;
|
||||
attrs[association.alias] =
|
||||
attrs[association.alias].related || null;
|
||||
break;
|
||||
case 'manyMorphToOne':
|
||||
case 'manyMorphToMany':
|
||||
@ -524,6 +521,176 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
|
||||
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.
|
||||
loadedModel.initialize = function() {
|
||||
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.
|
||||
// Apply only during fetching.
|
||||
this.on('fetching fetching:collection', (instance, attrs, options) => {
|
||||
if (_.isArray(options.withRelated)) {
|
||||
options.withRelated = options.withRelated
|
||||
.concat(groupAttributes.map(key => `${key}.slice`))
|
||||
.map(addPolymorphicRelated)
|
||||
.reduce((acc, paths) => acc.concat(paths), []);
|
||||
// do not populate anything
|
||||
if (options.withRelated === false) return;
|
||||
if (options.isEager === true) return;
|
||||
|
||||
if (_.isNil(options.withRelated)) {
|
||||
options.withRelated = []
|
||||
.concat(createGroupsPopulate())
|
||||
.concat(createAssociationPopulate());
|
||||
} else {
|
||||
options.withRelated = groupAttributes
|
||||
.map(key => `${key}.slice`)
|
||||
.map(addPolymorphicRelated)
|
||||
.reduce((acc, paths) => acc.concat(paths), []);
|
||||
options.withRelated = formatPopulateOptions(options.withRelated);
|
||||
}
|
||||
|
||||
return _.isFunction(target[model.toLowerCase()]['beforeFetchAll'])
|
||||
|
@ -19,11 +19,6 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
|
||||
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
|
||||
const pickRelations = values => {
|
||||
return _.pick(values, assocKeys);
|
||||
@ -58,7 +53,7 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
|
||||
}
|
||||
|
||||
const entry = await model.forge(params).fetch({
|
||||
withRelated: populate || defaultPopulate,
|
||||
withRelated: populate,
|
||||
});
|
||||
|
||||
return entry ? entry.toJSON() : null;
|
||||
@ -72,7 +67,10 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
|
||||
|
||||
return model
|
||||
.query(buildQuery({ model, filters }))
|
||||
.fetchAll({ withRelated: populate || defaultPopulate, transacting })
|
||||
.fetchAll({
|
||||
withRelated: populate,
|
||||
transacting,
|
||||
})
|
||||
.then(results => results.toJSON());
|
||||
}
|
||||
|
||||
@ -196,9 +194,6 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
|
||||
// Convert `params` object to filters compatible with Bookshelf.
|
||||
const filters = modelUtils.convertParams(modelKey, params);
|
||||
|
||||
// Select field to populate.
|
||||
const withRelated = populate || defaultPopulate;
|
||||
|
||||
return model
|
||||
.query(qb => {
|
||||
buildSearchQuery(qb, model, params);
|
||||
@ -216,7 +211,7 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
|
||||
}
|
||||
})
|
||||
.fetchAll({
|
||||
withRelated,
|
||||
withRelated: populate,
|
||||
});
|
||||
}
|
||||
|
||||
@ -248,8 +243,8 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
|
||||
return joinModel.forge().save(
|
||||
{
|
||||
[foreignKey]: entry.id,
|
||||
slice_type: groupModel.collectionName,
|
||||
slice_id: group.id,
|
||||
group_type: groupModel.collectionName,
|
||||
group_id: group.id,
|
||||
field: key,
|
||||
order,
|
||||
},
|
||||
@ -317,8 +312,8 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
|
||||
.query({
|
||||
where: {
|
||||
[foreignKey]: entry.id,
|
||||
slice_type: groupModel.collectionName,
|
||||
slice_id: group.id,
|
||||
group_type: groupModel.collectionName,
|
||||
group_id: group.id,
|
||||
field: key,
|
||||
},
|
||||
})
|
||||
@ -338,8 +333,8 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
|
||||
return joinModel.forge().save(
|
||||
{
|
||||
[foreignKey]: entry.id,
|
||||
slice_type: groupModel.collectionName,
|
||||
slice_id: group.id,
|
||||
group_type: groupModel.collectionName,
|
||||
group_id: group.id,
|
||||
field: key,
|
||||
order,
|
||||
},
|
||||
@ -396,7 +391,7 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
|
||||
qb.where(joinModel.foreignKey, entry.id).andWhere('field', key);
|
||||
})
|
||||
.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.
|
||||
idsToKeep.forEach(id => {
|
||||
@ -413,7 +408,7 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
|
||||
if (idsToDelete.length > 0) {
|
||||
await joinModel
|
||||
.forge()
|
||||
.query(qb => qb.whereIn('slice_id', idsToDelete))
|
||||
.query(qb => qb.whereIn('group_id', idsToDelete))
|
||||
.destroy({ transacting, require: false });
|
||||
|
||||
await strapi
|
||||
@ -442,12 +437,12 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
|
||||
.query({
|
||||
where: {
|
||||
[foreignKey]: entry.id,
|
||||
slice_type: groupModel.collectionName,
|
||||
group_type: groupModel.collectionName,
|
||||
field: key,
|
||||
},
|
||||
})
|
||||
.fetchAll({ transacting })
|
||||
.map(el => el.get('slice_id'));
|
||||
.map(el => el.get('group_id'));
|
||||
|
||||
await strapi
|
||||
.query(groupModel.uid)
|
||||
@ -458,7 +453,7 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
|
||||
.query({
|
||||
where: {
|
||||
[foreignKey]: entry.id,
|
||||
slice_type: groupModel.collectionName,
|
||||
group_type: groupModel.collectionName,
|
||||
field: key,
|
||||
},
|
||||
})
|
||||
|
@ -51,7 +51,7 @@ module.exports = {
|
||||
[this.primaryKey]: getValuePrimaryKey(params, this.primaryKey),
|
||||
}).fetch({
|
||||
transacting,
|
||||
withRelated: populate || this.associations.map(x => x.alias),
|
||||
withRelated: populate,
|
||||
});
|
||||
|
||||
const data = record ? record.toJSON() : record;
|
||||
@ -395,7 +395,6 @@ module.exports = {
|
||||
[this.primaryKey]: getValuePrimaryKey(params, this.primaryKey),
|
||||
}).fetch({
|
||||
transacting,
|
||||
withRelated: this.associations.map(x => x.alias),
|
||||
});
|
||||
},
|
||||
|
||||
@ -410,7 +409,6 @@ module.exports = {
|
||||
})
|
||||
.fetch({
|
||||
transacting,
|
||||
withRelated: this.associations.map(x => x.alias),
|
||||
});
|
||||
|
||||
const entry = record ? record.toJSON() : record;
|
||||
|
@ -24,59 +24,10 @@ module.exports = async (ctx, next) => {
|
||||
'actions',
|
||||
ctx.request.route.action,
|
||||
],
|
||||
[],
|
||||
[]
|
||||
).split('.');
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +64,14 @@
|
||||
"policies": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/explorer/upload",
|
||||
"handler": "ContentManager.uploadFile",
|
||||
"config": {
|
||||
"policies": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/explorer/:model",
|
||||
|
@ -144,4 +144,15 @@ module.exports = {
|
||||
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);
|
||||
},
|
||||
};
|
||||
|
@ -5,27 +5,6 @@ const _ = require('lodash');
|
||||
/**
|
||||
* 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 = {
|
||||
fetch(params, source, populate) {
|
||||
return strapi
|
||||
@ -53,61 +32,10 @@ module.exports = {
|
||||
},
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
|
@ -12,6 +12,8 @@ const isSortable = (schema, name) => {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (schema.modelType === 'group' && name === 'id') return false;
|
||||
|
||||
const attribute = schema.attributes[name];
|
||||
if (NON_SORTABLES.includes(attribute.type)) {
|
||||
return false;
|
||||
|
@ -9,7 +9,7 @@ async function createDefaultConfiguration(model) {
|
||||
const schema = formatContentTypeSchema(model);
|
||||
|
||||
return {
|
||||
settings: await createDefaultSettings(),
|
||||
settings: await createDefaultSettings(schema),
|
||||
metadatas: await createDefaultMetadatas(schema),
|
||||
layouts: await createDefaultLayouts(schema),
|
||||
};
|
||||
|
@ -3,18 +3,32 @@
|
||||
const _ = require('lodash');
|
||||
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
|
||||
*/
|
||||
async function createDefaultSettings() {
|
||||
async function createDefaultSettings(schema) {
|
||||
const generalSettings = await strapi.plugins[
|
||||
'content-manager'
|
||||
].services.generalsettings.getGeneralSettings();
|
||||
|
||||
let defaultField = getDefaultMainField(schema);
|
||||
|
||||
return {
|
||||
...generalSettings,
|
||||
mainField: 'id',
|
||||
defaultSortBy: 'id',
|
||||
mainField: defaultField,
|
||||
defaultSortBy: defaultField,
|
||||
defaultSortOrder: 'ASC',
|
||||
};
|
||||
}
|
||||
@ -24,13 +38,17 @@ async function createDefaultSettings() {
|
||||
async function syncSettings(configuration, schema) {
|
||||
if (_.isEmpty(configuration.settings)) return createDefaultSettings(schema);
|
||||
|
||||
const { mainField = 'id', defaultSortBy = 'id' } =
|
||||
let defaultField = getDefaultMainField(schema);
|
||||
|
||||
const { mainField = defaultField, defaultSortBy = defaultField } =
|
||||
configuration.settings || {};
|
||||
|
||||
return {
|
||||
...configuration.settings,
|
||||
mainField: isSortable(schema, mainField) ? mainField : 'id',
|
||||
defaultSortBy: isSortable(schema, defaultSortBy) ? defaultSortBy : 'id',
|
||||
mainField: isSortable(schema, mainField) ? mainField : defaultField,
|
||||
defaultSortBy: isSortable(schema, defaultSortBy)
|
||||
? defaultSortBy
|
||||
: defaultField,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: '/content-manager/explorer/tag/?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: {
|
||||
body: {
|
||||
name: 'tag1',
|
||||
},
|
||||
});
|
||||
@ -73,7 +73,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: '/content-manager/explorer/tag/?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: {
|
||||
body: {
|
||||
name: 'tag2',
|
||||
},
|
||||
});
|
||||
@ -89,7 +89,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: '/content-manager/explorer/tag/?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: {
|
||||
body: {
|
||||
name: 'tag3',
|
||||
},
|
||||
});
|
||||
@ -110,7 +110,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: '/content-manager/explorer/article/?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: entry,
|
||||
body: entry,
|
||||
});
|
||||
|
||||
data.articles.push(body);
|
||||
@ -132,7 +132,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: '/content-manager/explorer/article/?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: entry,
|
||||
body: entry,
|
||||
});
|
||||
|
||||
data.articles.push(body);
|
||||
@ -155,7 +155,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: `/content-manager/explorer/article/${entry.id}?source=content-manager`,
|
||||
method: 'PUT',
|
||||
formData: entry,
|
||||
body: entry,
|
||||
});
|
||||
|
||||
data.articles[0] = body;
|
||||
@ -178,7 +178,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: `/content-manager/explorer/article/${entry.id}?source=content-manager`,
|
||||
method: 'PUT',
|
||||
formData: entry,
|
||||
body: entry,
|
||||
});
|
||||
|
||||
data.articles[0] = body;
|
||||
@ -199,7 +199,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: `/content-manager/explorer/article/${entry.id}?source=content-manager`,
|
||||
method: 'PUT',
|
||||
formData: entry,
|
||||
body: entry,
|
||||
});
|
||||
|
||||
data.articles[0] = body;
|
||||
@ -221,7 +221,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: `/content-manager/explorer/article/${entry.id}?source=content-manager`,
|
||||
method: 'PUT',
|
||||
formData: entry,
|
||||
body: entry,
|
||||
});
|
||||
|
||||
data.articles[0] = body;
|
||||
@ -237,7 +237,7 @@ describe('Content Manager End to End', () => {
|
||||
const { body: createdTag } = await rq({
|
||||
url: '/content-manager/explorer/tag/?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: {
|
||||
body: {
|
||||
name: 'tag11',
|
||||
},
|
||||
});
|
||||
@ -245,7 +245,7 @@ describe('Content Manager End to End', () => {
|
||||
const { body: article12 } = await rq({
|
||||
url: '/content-manager/explorer/article/?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: {
|
||||
body: {
|
||||
title: 'article12',
|
||||
content: 'Content',
|
||||
tags: [createdTag],
|
||||
@ -260,7 +260,7 @@ describe('Content Manager End to End', () => {
|
||||
const { body: article13 } = await rq({
|
||||
url: '/content-manager/explorer/article/?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: {
|
||||
body: {
|
||||
title: 'article13',
|
||||
content: 'Content',
|
||||
tags: [updatedTag],
|
||||
@ -308,7 +308,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: '/content-manager/explorer/articlewithtag/?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: entry,
|
||||
body: entry,
|
||||
});
|
||||
|
||||
expect(body.id);
|
||||
@ -330,7 +330,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: '/content-manager/explorer/category/?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: {
|
||||
body: {
|
||||
name: 'cat1',
|
||||
},
|
||||
});
|
||||
@ -346,7 +346,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: '/content-manager/explorer/category/?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: {
|
||||
body: {
|
||||
name: 'cat2',
|
||||
},
|
||||
});
|
||||
@ -368,7 +368,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: '/content-manager/explorer/article/?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: entry,
|
||||
body: entry,
|
||||
});
|
||||
|
||||
data.articles.push(body);
|
||||
@ -390,7 +390,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: `/content-manager/explorer/article/${entry.id}?source=content-manager`,
|
||||
method: 'PUT',
|
||||
formData: entry,
|
||||
body: entry,
|
||||
});
|
||||
|
||||
data.articles[0] = body;
|
||||
@ -411,7 +411,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: '/content-manager/explorer/article?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: entry,
|
||||
body: entry,
|
||||
});
|
||||
|
||||
data.articles.push(body);
|
||||
@ -432,7 +432,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: `/content-manager/explorer/article/${entry.id}?source=content-manager`,
|
||||
method: 'PUT',
|
||||
formData: entry,
|
||||
body: entry,
|
||||
});
|
||||
|
||||
data.articles[1] = body;
|
||||
@ -453,7 +453,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: `/content-manager/explorer/category/${entry.id}?source=content-manager`,
|
||||
method: 'PUT',
|
||||
formData: entry,
|
||||
body: entry,
|
||||
});
|
||||
|
||||
data.categories[0] = body;
|
||||
@ -473,7 +473,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: '/content-manager/explorer/category/?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: entry,
|
||||
body: entry,
|
||||
});
|
||||
|
||||
data.categories.push(body);
|
||||
@ -549,7 +549,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: '/content-manager/explorer/reference/?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: {
|
||||
body: {
|
||||
name: 'ref1',
|
||||
},
|
||||
});
|
||||
@ -569,7 +569,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: '/content-manager/explorer/article?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: entry,
|
||||
body: entry,
|
||||
});
|
||||
|
||||
data.articles.push(body);
|
||||
@ -589,7 +589,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: `/content-manager/explorer/article/${entry.id}?source=content-manager`,
|
||||
method: 'PUT',
|
||||
formData: entry,
|
||||
body: entry,
|
||||
});
|
||||
|
||||
data.articles[0] = body;
|
||||
@ -610,7 +610,7 @@ describe('Content Manager End to End', () => {
|
||||
let { body } = await rq({
|
||||
url: '/content-manager/explorer/article?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: entry,
|
||||
body: entry,
|
||||
});
|
||||
|
||||
data.articles.push(body);
|
||||
@ -627,7 +627,7 @@ describe('Content Manager End to End', () => {
|
||||
const { body: tagToCreate } = await rq({
|
||||
url: '/content-manager/explorer/tag/?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: {
|
||||
body: {
|
||||
name: 'tag111',
|
||||
},
|
||||
});
|
||||
@ -635,7 +635,7 @@ describe('Content Manager End to End', () => {
|
||||
const { body: referenceToCreate } = await rq({
|
||||
url: '/content-manager/explorer/reference/?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: {
|
||||
body: {
|
||||
name: 'cat111',
|
||||
tag: tagToCreate,
|
||||
},
|
||||
@ -648,7 +648,7 @@ describe('Content Manager End to End', () => {
|
||||
const { body: tagToCreate } = await rq({
|
||||
url: '/content-manager/explorer/tag/?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: {
|
||||
body: {
|
||||
name: 'tag111',
|
||||
},
|
||||
});
|
||||
@ -656,7 +656,7 @@ describe('Content Manager End to End', () => {
|
||||
const { body: referenceToCreate } = await rq({
|
||||
url: '/content-manager/explorer/reference/?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: {
|
||||
body: {
|
||||
name: 'cat111',
|
||||
tag: tagToCreate,
|
||||
},
|
||||
@ -667,7 +667,7 @@ describe('Content Manager End to End', () => {
|
||||
const { body: referenceToUpdate } = await rq({
|
||||
url: `/content-manager/explorer/reference/${referenceToCreate.id}?source=content-manager`,
|
||||
method: 'PUT',
|
||||
formData: {
|
||||
body: {
|
||||
tag: null,
|
||||
},
|
||||
});
|
||||
@ -679,7 +679,7 @@ describe('Content Manager End to End', () => {
|
||||
const { body: tagToCreate } = await rq({
|
||||
url: '/content-manager/explorer/tag/?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: {
|
||||
body: {
|
||||
name: 'tag111',
|
||||
},
|
||||
});
|
||||
@ -687,7 +687,7 @@ describe('Content Manager End to End', () => {
|
||||
const { body: referenceToCreate } = await rq({
|
||||
url: '/content-manager/explorer/reference/?source=content-manager',
|
||||
method: 'POST',
|
||||
formData: {
|
||||
body: {
|
||||
name: 'cat111',
|
||||
tag: tagToCreate,
|
||||
},
|
||||
|
@ -82,6 +82,25 @@ class AttributeForm extends React.Component {
|
||||
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;
|
||||
}, formErrors);
|
||||
|
||||
|
@ -104,6 +104,12 @@ class ModelForm extends React.Component {
|
||||
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 => ({
|
||||
formErrors,
|
||||
didCheckErrors: !prevState.didCheckErrors,
|
||||
|
@ -35,6 +35,8 @@
|
||||
"error.validation.minLength": "The value is too short.",
|
||||
"error.validation.minSupMax": "Can't be superior",
|
||||
"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.",
|
||||
"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",
|
||||
|
@ -34,6 +34,8 @@
|
||||
"error.validation.minLength": "La valeur est trop courte.",
|
||||
"error.validation.minSupMax": "Ne peut pas être plus grand",
|
||||
"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.",
|
||||
"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",
|
||||
|
@ -1,26 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const yup = require('yup');
|
||||
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)));
|
||||
|
||||
const validateGroupInput = require('./validation/group');
|
||||
/**
|
||||
* Groups controller
|
||||
*/
|
||||
|
@ -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,
|
||||
};
|
@ -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();
|
@ -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(),
|
||||
};
|
||||
};
|
@ -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 {};
|
||||
}
|
||||
}
|
||||
};
|
@ -201,9 +201,9 @@ const convertAttributes = attributes => {
|
||||
}
|
||||
|
||||
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)) {
|
||||
return acc;
|
||||
}
|
||||
@ -211,7 +211,6 @@ const convertAttributes = attributes => {
|
||||
acc[key] = {
|
||||
[nature === 'oneWay' ? 'model' : 'collection']: target,
|
||||
plugin: plugin ? _.trim(plugin) : undefined,
|
||||
required: required === true ? true : undefined,
|
||||
unique: unique === true ? true : undefined,
|
||||
};
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ describe.only('Content Type Builder - Groups', () => {
|
||||
method: 'POST',
|
||||
url: '/content-type-builder/groups',
|
||||
body: {
|
||||
name: 'some-group',
|
||||
name: 'SomeGroup',
|
||||
attributes: {
|
||||
title: {
|
||||
type: 'string',
|
||||
@ -58,7 +58,7 @@ describe.only('Content Type Builder - Groups', () => {
|
||||
method: 'POST',
|
||||
url: '/content-type-builder/groups',
|
||||
body: {
|
||||
name: 'some-group',
|
||||
name: 'someGroup',
|
||||
attributes: {},
|
||||
},
|
||||
});
|
||||
@ -121,7 +121,7 @@ describe.only('Content Type Builder - Groups', () => {
|
||||
data: {
|
||||
uid: 'some_group',
|
||||
schema: {
|
||||
name: 'some-group',
|
||||
name: 'SomeGroup',
|
||||
description: '',
|
||||
connection: 'default',
|
||||
collectionName: 'groups_some_groups',
|
||||
@ -176,7 +176,7 @@ describe.only('Content Type Builder - Groups', () => {
|
||||
method: 'PUT',
|
||||
url: '/content-type-builder/groups/some_group',
|
||||
body: {
|
||||
name: 'New Group',
|
||||
name: 'NewGroup',
|
||||
attributes: {},
|
||||
},
|
||||
});
|
||||
|
@ -10,6 +10,8 @@ const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
async upload(ctx) {
|
||||
const uploadService = strapi.plugins.upload.services.upload;
|
||||
|
||||
// Retrieve provider configuration.
|
||||
const config = await strapi
|
||||
.store({
|
||||
@ -30,8 +32,8 @@ module.exports = {
|
||||
}
|
||||
|
||||
// Extract optional relational data.
|
||||
const { refId, ref, source, field, path } = ctx.request.body.fields || {};
|
||||
const { files = {} } = ctx.request.body.files || {};
|
||||
const { refId, ref, source, field, path } = ctx.request.body || {};
|
||||
const { files = {} } = ctx.request.files || {};
|
||||
|
||||
if (_.isEmpty(files)) {
|
||||
return ctx.badRequest(
|
||||
@ -43,9 +45,8 @@ module.exports = {
|
||||
}
|
||||
|
||||
// Transform stream files to buffer
|
||||
const buffers = await strapi.plugins.upload.services.upload.bufferize(
|
||||
ctx.request.body.files.files
|
||||
);
|
||||
const buffers = await uploadService.bufferize(files);
|
||||
|
||||
const enhancedFiles = buffers.map(file => {
|
||||
if (file.size > config.sizeLimit) {
|
||||
return ctx.badRequest(
|
||||
@ -94,10 +95,7 @@ module.exports = {
|
||||
return;
|
||||
}
|
||||
|
||||
const uploadedFiles = await strapi.plugins.upload.services.upload.upload(
|
||||
enhancedFiles,
|
||||
config
|
||||
);
|
||||
const uploadedFiles = await uploadService.upload(enhancedFiles, config);
|
||||
|
||||
// Send 200 `ok`
|
||||
ctx.send(
|
||||
|
@ -24,7 +24,7 @@
|
||||
"inquirer": "^6.2.1",
|
||||
"kcors": "^2.2.0",
|
||||
"koa": "^2.1.0",
|
||||
"koa-body": "^2.5.0",
|
||||
"koa-body": "^4.1.0",
|
||||
"koa-compose": "^4.0.0",
|
||||
"koa-compress": "^2.0.0",
|
||||
"koa-convert": "^1.2.0",
|
||||
|
@ -1,25 +1,13 @@
|
||||
const request = require('request-promise-native');
|
||||
|
||||
const createRequest = (defaults = {}) => {
|
||||
const client = request.defaults({
|
||||
return request.defaults({
|
||||
baseUrl: 'http://localhost:1337',
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false,
|
||||
...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 => {
|
||||
|
17
yarn.lock
17
yarn.lock
@ -2235,6 +2235,14 @@
|
||||
"@types/express-serve-static-core" "*"
|
||||
"@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":
|
||||
version "7.1.1"
|
||||
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"
|
||||
v8flags "^3.1.3"
|
||||
|
||||
koa-body@^2.5.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/koa-body/-/koa-body-2.6.0.tgz#8ed7a192a64a38df610a986342d1801855641a1d"
|
||||
integrity sha512-8i9ti3TRxelsnPUct0xY8toTFj5gTzGWW45ePBkT8fnzZP75y5woisVpziIdqcnqtt1lMNBD30p+tkiSC+NfjQ==
|
||||
koa-body@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/koa-body/-/koa-body-4.1.0.tgz#99295ee2e9543884e5730ae696780930b3821c44"
|
||||
integrity sha512-rWkMfMaCjFmIAMohtjlrg4BqDzcotK5BdZhiwJu1ONuR1ceoFUjnH3wp0hEV39HuBlc9tI3eUUFMK4Cp6ccFtA==
|
||||
dependencies:
|
||||
"@types/formidable" "^1.0.31"
|
||||
co-body "^5.1.1"
|
||||
formidable "^1.1.1"
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user