diff --git a/examples/getstarted/api/article/models/Article.js b/examples/getstarted/api/article/models/Article.js index 9a690533ea..11b47b89e9 100644 --- a/examples/getstarted/api/article/models/Article.js +++ b/examples/getstarted/api/article/models/Article.js @@ -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) => {} diff --git a/examples/getstarted/api/article/models/Article.settings.json b/examples/getstarted/api/article/models/Article.settings.json index 2579e3dfe9..9f7c6a19dd 100644 --- a/examples/getstarted/api/article/models/Article.settings.json +++ b/examples/getstarted/api/article/models/Article.settings.json @@ -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" }, diff --git a/packages/strapi-admin/admin/src/translations/ja.json b/packages/strapi-admin/admin/src/translations/ja.json index b771603e25..d8b323400c 100644 --- a/packages/strapi-admin/admin/src/translations/ja.json +++ b/packages/strapi-admin/admin/src/translations/ja.json @@ -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": "このプラグインは、安全のため、他の環境では無効する必要があります", diff --git a/packages/strapi-hook-bookshelf/lib/generate-group-relations.js b/packages/strapi-hook-bookshelf/lib/generate-group-relations.js index 3517446cdf..e249e1eb6c 100644 --- a/packages/strapi-hook-bookshelf/lib/generate-group-relations.js +++ b/packages/strapi-hook-bookshelf/lib/generate-group-relations.js @@ -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 diff --git a/packages/strapi-hook-bookshelf/lib/mount-models.js b/packages/strapi-hook-bookshelf/lib/mount-models.js index 2cce0a026b..1b7a21052b 100644 --- a/packages/strapi-hook-bookshelf/lib/mount-models.js +++ b/packages/strapi-hook-bookshelf/lib/mount-models.js @@ -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']) diff --git a/packages/strapi-hook-bookshelf/lib/queries.js b/packages/strapi-hook-bookshelf/lib/queries.js index e08e69959d..633480d97b 100644 --- a/packages/strapi-hook-bookshelf/lib/queries.js +++ b/packages/strapi-hook-bookshelf/lib/queries.js @@ -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, }, }) diff --git a/packages/strapi-hook-bookshelf/lib/relations.js b/packages/strapi-hook-bookshelf/lib/relations.js index cc125ce666..7746a927fb 100644 --- a/packages/strapi-hook-bookshelf/lib/relations.js +++ b/packages/strapi-hook-bookshelf/lib/relations.js @@ -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; diff --git a/packages/strapi-plugin-content-manager/config/policies/routing.js b/packages/strapi-plugin-content-manager/config/policies/routing.js index 0b03c6cb5b..13279b19c6 100644 --- a/packages/strapi-plugin-content-manager/config/policies/routing.js +++ b/packages/strapi-plugin-content-manager/config/policies/routing.js @@ -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); } } diff --git a/packages/strapi-plugin-content-manager/config/routes.json b/packages/strapi-plugin-content-manager/config/routes.json index 4c47e943ea..9c01c3971e 100644 --- a/packages/strapi-plugin-content-manager/config/routes.json +++ b/packages/strapi-plugin-content-manager/config/routes.json @@ -64,6 +64,14 @@ "policies": [] } }, + { + "method": "POST", + "path": "/explorer/upload", + "handler": "ContentManager.uploadFile", + "config": { + "policies": [] + } + }, { "method": "GET", "path": "/explorer/:model", diff --git a/packages/strapi-plugin-content-manager/controllers/ContentManager.js b/packages/strapi-plugin-content-manager/controllers/ContentManager.js index a3eec8f0ad..c186577844 100644 --- a/packages/strapi-plugin-content-manager/controllers/ContentManager.js +++ b/packages/strapi-plugin-content-manager/controllers/ContentManager.js @@ -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); + }, }; diff --git a/packages/strapi-plugin-content-manager/services/ContentManager.js b/packages/strapi-plugin-content-manager/services/ContentManager.js index 1060360aea..f7c057ceb6 100644 --- a/packages/strapi-plugin-content-manager/services/ContentManager.js +++ b/packages/strapi-plugin-content-manager/services/ContentManager.js @@ -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); }, diff --git a/packages/strapi-plugin-content-manager/services/utils/configuration/attributes.js b/packages/strapi-plugin-content-manager/services/utils/configuration/attributes.js index dc33f1e5a9..37cc0ed02c 100644 --- a/packages/strapi-plugin-content-manager/services/utils/configuration/attributes.js +++ b/packages/strapi-plugin-content-manager/services/utils/configuration/attributes.js @@ -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; diff --git a/packages/strapi-plugin-content-manager/services/utils/configuration/index.js b/packages/strapi-plugin-content-manager/services/utils/configuration/index.js index 0084fb871c..8bf16a62ac 100644 --- a/packages/strapi-plugin-content-manager/services/utils/configuration/index.js +++ b/packages/strapi-plugin-content-manager/services/utils/configuration/index.js @@ -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), }; diff --git a/packages/strapi-plugin-content-manager/services/utils/configuration/settings.js b/packages/strapi-plugin-content-manager/services/utils/configuration/settings.js index 409a22bfac..8edec259d8 100644 --- a/packages/strapi-plugin-content-manager/services/utils/configuration/settings.js +++ b/packages/strapi-plugin-content-manager/services/utils/configuration/settings.js @@ -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, }; } diff --git a/packages/strapi-plugin-content-manager/test/index.test.e2e.js b/packages/strapi-plugin-content-manager/test/index.test.e2e.js index 1dad282dda..0be7ee5a46 100644 --- a/packages/strapi-plugin-content-manager/test/index.test.e2e.js +++ b/packages/strapi-plugin-content-manager/test/index.test.e2e.js @@ -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, }, diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/AttributeForm/index.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/AttributeForm/index.js index fa1c07c005..2911d15e94 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/AttributeForm/index.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/AttributeForm/index.js @@ -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); diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/ModelForm/index.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/ModelForm/index.js index 86a8cac484..6c0642f11e 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/ModelForm/index.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/ModelForm/index.js @@ -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, diff --git a/packages/strapi-plugin-content-type-builder/admin/src/translations/en.json b/packages/strapi-plugin-content-type-builder/admin/src/translations/en.json index c03d62ca82..a9d8ad77cb 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/translations/en.json +++ b/packages/strapi-plugin-content-type-builder/admin/src/translations/en.json @@ -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", diff --git a/packages/strapi-plugin-content-type-builder/admin/src/translations/fr.json b/packages/strapi-plugin-content-type-builder/admin/src/translations/fr.json index 277e710807..c55e3ae8d1 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/translations/fr.json +++ b/packages/strapi-plugin-content-type-builder/admin/src/translations/fr.json @@ -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", diff --git a/packages/strapi-plugin-content-type-builder/controllers/Groups.js b/packages/strapi-plugin-content-type-builder/controllers/Groups.js index a6eb5d6cae..2033ca73ac 100644 --- a/packages/strapi-plugin-content-type-builder/controllers/Groups.js +++ b/packages/strapi-plugin-content-type-builder/controllers/Groups.js @@ -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 */ diff --git a/packages/strapi-plugin-content-type-builder/controllers/utils/__tests__/yup-formatter.test.js b/packages/strapi-plugin-content-type-builder/controllers/validation/__tests__/yup-formatter.test.js similarity index 100% rename from packages/strapi-plugin-content-type-builder/controllers/utils/__tests__/yup-formatter.test.js rename to packages/strapi-plugin-content-type-builder/controllers/validation/__tests__/yup-formatter.test.js diff --git a/packages/strapi-plugin-content-type-builder/controllers/validation/common.js b/packages/strapi-plugin-content-type-builder/controllers/validation/common.js new file mode 100644 index 0000000000..3fa218bcfb --- /dev/null +++ b/packages/strapi-plugin-content-type-builder/controllers/validation/common.js @@ -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, +}; diff --git a/packages/strapi-plugin-content-type-builder/controllers/validation/group.js b/packages/strapi-plugin-content-type-builder/controllers/validation/group.js new file mode 100644 index 0000000000..b597f8d350 --- /dev/null +++ b/packages/strapi-plugin-content-type-builder/controllers/validation/group.js @@ -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(); diff --git a/packages/strapi-plugin-content-type-builder/controllers/validation/relations.js b/packages/strapi-plugin-content-type-builder/controllers/validation/relations.js new file mode 100644 index 0000000000..418470bcdd --- /dev/null +++ b/packages/strapi-plugin-content-type-builder/controllers/validation/relations.js @@ -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(), + }; +}; diff --git a/packages/strapi-plugin-content-type-builder/controllers/validation/types.js b/packages/strapi-plugin-content-type-builder/controllers/validation/types.js new file mode 100644 index 0000000000..dafc32feb6 --- /dev/null +++ b/packages/strapi-plugin-content-type-builder/controllers/validation/types.js @@ -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 {}; + } + } +}; diff --git a/packages/strapi-plugin-content-type-builder/controllers/utils/yup-formatter.js b/packages/strapi-plugin-content-type-builder/controllers/validation/yup-formatter.js similarity index 100% rename from packages/strapi-plugin-content-type-builder/controllers/utils/yup-formatter.js rename to packages/strapi-plugin-content-type-builder/controllers/validation/yup-formatter.js diff --git a/packages/strapi-plugin-content-type-builder/services/Groups.js b/packages/strapi-plugin-content-type-builder/services/Groups.js index 998961bd21..7eda30b306 100644 --- a/packages/strapi-plugin-content-type-builder/services/Groups.js +++ b/packages/strapi-plugin-content-type-builder/services/Groups.js @@ -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, }; } diff --git a/packages/strapi-plugin-content-type-builder/test/groups.test.e2e.js b/packages/strapi-plugin-content-type-builder/test/groups.test.e2e.js index b1e65eb5c6..58537fff11 100644 --- a/packages/strapi-plugin-content-type-builder/test/groups.test.e2e.js +++ b/packages/strapi-plugin-content-type-builder/test/groups.test.e2e.js @@ -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: {}, }, }); diff --git a/packages/strapi-plugin-upload/controllers/Upload.js b/packages/strapi-plugin-upload/controllers/Upload.js index 87e5c75cd3..19e7144084 100644 --- a/packages/strapi-plugin-upload/controllers/Upload.js +++ b/packages/strapi-plugin-upload/controllers/Upload.js @@ -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( diff --git a/packages/strapi/package.json b/packages/strapi/package.json index 294b304001..66d9d3b054 100644 --- a/packages/strapi/package.json +++ b/packages/strapi/package.json @@ -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", diff --git a/test/helpers/request.js b/test/helpers/request.js index 7d88c36a22..961fb79184 100644 --- a/test/helpers/request.js +++ b/test/helpers/request.js @@ -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 => { diff --git a/yarn.lock b/yarn.lock index c1cece4417..917306f114 100644 --- a/yarn.lock +++ b/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"