diff --git a/lib/configuration/hooks/jsonapi/helpers/request.js b/lib/configuration/hooks/jsonapi/helpers/request.js index 6828cc4db5..bc6c3d7fc3 100644 --- a/lib/configuration/hooks/jsonapi/helpers/request.js +++ b/lib/configuration/hooks/jsonapi/helpers/request.js @@ -6,6 +6,7 @@ // Public node modules. const _ = require('lodash'); +const utils = require('../utils/utils'); /** * JSON API helper @@ -19,78 +20,127 @@ module.exports = { * Parse request */ - parse: function * (ctx, cb) { + parse: function * (ctx) { switch (ctx.method.toUpperCase()) { case 'GET': console.log('GET'); break; - case 'PUT': + case 'PATCH': case 'POST': - yield this.fetchSchema(ctx, function * (err) { - yield cb(err); - }); + try { + yield this.fetchSchema(ctx); + yield this.formatBody(ctx); + } catch (err) { + throw err; + } break; case 'DELETE': console.log('DELETE'); break; default: + throw { + status: 403, + body: 'Invalid HTTP method' + }; } }, + /** + * Format attributes for more simple API + */ + + formatBody: function * (ctx) { + const body = ctx.request.body; + const values = _.assign({}, body.data.attributes); + + _.forEach(body.data.relationships, function (relation, key) { + values[key] = _.isArray(relation.data) ? _.map(relation.data, 'id') : relation.data.id; + }); + + ctx.request.body = values; + }, + /** * Fetch attributes schema */ - fetchSchema: function * (ctx, cb) { - const attributes = ctx.request.body; + fetchSchema: function * (ctx) { + const body = ctx.request.body; - if (!attributes.hasOwnProperty('data')) { - return yield cb({ + if (!body.hasOwnProperty('data')) { + throw { status: 403, body: 'Missing `data` member' - }); - } else if (!attributes.data.hasOwnProperty('type')) { - return yield cb({ + }; + } else if (!utils.isRessourceObject(body.data) && ctx.method !== 'POST') { + throw { status: 403, - body: 'Missing `type` member' - }); - } else if (!strapi.models.hasOwnProperty(attributes.data.type)) { - return yield cb({ + body: 'Invalid ressource object' + }; + } else if (!body.data.hasOwnProperty('type') && ctx.method === 'POST') { + throw { status: 403, - body: 'Unknow `type` ' + attributes.data.type - }); + body: 'Invalid ressource object' + }; + } else if (!strapi.models.hasOwnProperty(body.data.type)) { + throw { + status: 403, + body: 'Unknow `type` ' + body.data.type + }; } // Extract required attributes - const requiredAttributes = _.omit(_.mapValues(strapi.models[attributes.data.type].attributes, function (attr) { - return attr.required ? attr : undefined; + const requiredAttributes = _.omit(_.mapValues(strapi.models[body.data.type].attributes, function (attr) { + return (attr.required && attr.type) ? attr : undefined; }), _.isUndefined); // Identify missing attributes - const missingAttributes = _.difference(_.keys(requiredAttributes), _.keys(attributes.data.attributes)); + const missingAttributes = body.data.hasOwnProperty('attributes') ? _.difference(_.keys(requiredAttributes), _.keys(body.data.attributes)) : null; if (!_.isEmpty(missingAttributes)) { - return yield cb({ + throw { status: 403, body: 'Missing required attributes (' + missingAttributes.toString() + ')' - }); + }; } - // Identify required relationships - const relationships = _.indexBy(strapi.models[attributes.data.type].associations, 'alias'); // Extract required relationships - const requiredRelationships = _.intersection(_.keys(requiredAttributes), _.keys(relationships)); + const requiredRelationships = _.omit(_.mapValues(strapi.models[body.data.type].attributes, function (attr) { + return (attr.required && (attr.model || attr.collection)) ? attr : undefined; + }), _.isUndefined); // Identify missing relationships - const missingRelationships = _.difference(_.keys(requiredRelationships), _.keys(attributes.data.relationships)); + const missingRelationships = body.data.hasOwnProperty('relationships') ? _.difference(_.keys(requiredRelationships), _.keys(body.data.relationships)) : null; if (!_.isEmpty(missingRelationships)) { - return yield cb({ + throw { status: 403, body: 'Missing required relationships (' + missingRelationships.toString() + ')' - }); + }; } - // Looks good - yield cb(); + // Build array of errors + if (_.size(requiredRelationships)) { + const errors = _.remove(_.flattenDeep(_.map(body.data.relationships, function (relation, key) { + if (!relation.hasOwnProperty('data')) { + return 'Missing `data` member for relationships ' + relation; + } else if (_.isArray(relation.data)) { + return _.map(relation.data, function (ressource) { + if (!utils.isRessourceObject(ressource)) { + return 'Invalid ressource object in relationships ' + key; + } + }); + } else if (!utils.isRessourceObject(relation.data)) { + return 'Invalid ressource object for relationships ' + key; + } + })), function (n) { + return !_.isUndefined(n); + }); + if (!_.isEmpty(errors)) { + throw { + status: 403, + body: errors.toString() + }; + } + } } }; diff --git a/lib/configuration/hooks/jsonapi/helpers/response.js b/lib/configuration/hooks/jsonapi/helpers/response.js index e437ac1536..40a252d738 100644 --- a/lib/configuration/hooks/jsonapi/helpers/response.js +++ b/lib/configuration/hooks/jsonapi/helpers/response.js @@ -178,7 +178,7 @@ module.exports = { switch (object) { case 'collection': - if (_.isArray(data) && _.size(data) > 1) { + if ((_.isArray(data) && _.size(data) > 1) || _.isObject(data)) { return data; } else if (_.isArray(data) && (_.size(data) === 1 || _.size(data) === 0)) { return _.isObject(_.first(data)) ? _.first(data[0]) : []; diff --git a/lib/configuration/hooks/jsonapi/index.js b/lib/configuration/hooks/jsonapi/index.js index 41fcfa7033..ef9feb2340 100644 --- a/lib/configuration/hooks/jsonapi/index.js +++ b/lib/configuration/hooks/jsonapi/index.js @@ -45,7 +45,7 @@ module.exports = function (strapi) { if (this.request.type !== 'application/vnd.api+json') { this.status = 406; this.body = ''; - } else if (this.request.method === 'GET') { + } else { // Intercept only GET request // Detect route @@ -87,15 +87,14 @@ module.exports = function (strapi) { this.response.status = 406; this.response.body = ''; } else { - yield request.parse(this, function * (err) { - if (err) { - return _.assign(self.response, err); - } - + try { + yield request.parse(this); yield next; - }); + } catch (err) { + _.assign(self.response, err); + } } - } + }; } }; diff --git a/lib/configuration/hooks/jsonapi/utils/utils.js b/lib/configuration/hooks/jsonapi/utils/utils.js new file mode 100644 index 0000000000..2294e9093d --- /dev/null +++ b/lib/configuration/hooks/jsonapi/utils/utils.js @@ -0,0 +1,24 @@ +'use strict'; + +/** + * Module dependencies + */ + +// Public node modules. +const _ = require('lodash'); + +/** + * JSON API utils + */ + +module.exports = { + + /** + * Verify ressource object + */ + + isRessourceObject: function (object) { + return _.isObject(object) && object.hasOwnProperty('id') && object.hasOwnProperty('type'); + } + +};