diff --git a/lib/configuration/hooks/jsonapi/helpers/request.js b/lib/configuration/hooks/jsonapi/helpers/request.js index dd1903859b..dcb2c50f93 100644 --- a/lib/configuration/hooks/jsonapi/helpers/request.js +++ b/lib/configuration/hooks/jsonapi/helpers/request.js @@ -41,7 +41,9 @@ module.exports = { default: throw { status: 403, - body: 'Invalid HTTP method' + body: { + message: 'Invalid HTTP method' + } }; } }, @@ -69,22 +71,30 @@ module.exports = { if (!body.hasOwnProperty('data')) { throw { status: 403, - body: 'Missing `data` member' + body: { + message: 'Missing `data` member' + } }; } else if (!utils.isRessourceObject(body.data) && ctx.method !== 'POST') { throw { status: 403, - body: 'Invalid ressource object' + body: { + message: 'Invalid ressource object' + } }; } else if (!body.data.hasOwnProperty('type') && ctx.method === 'POST') { throw { status: 403, - body: 'Invalid ressource object' + body: { + message: 'Invalid ressource object' + } }; } else if (!strapi.models.hasOwnProperty(body.data.type)) { throw { status: 403, - body: 'Unknow `type` ' + body.data.type + body: { + message: 'Unknow `type` ' + body.data.type + } }; } @@ -98,7 +108,9 @@ module.exports = { if (!_.isEmpty(missingAttributes)) { throw { status: 403, - body: 'Missing required attributes (' + missingAttributes.toString() + ')' + body: { + message: 'Missing required attributes (' + missingAttributes.toString() + ')' + } }; } @@ -112,7 +124,9 @@ module.exports = { if (!_.isEmpty(missingRelationships)) { throw { status: 403, - body: 'Missing required relationships (' + missingRelationships.toString() + ')' + body: { + message: 'Missing required relationships (' + missingRelationships.toString() + ')' + } }; } @@ -120,15 +134,22 @@ module.exports = { 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; + return { + message: 'Missing `data` member for relationships ' + relation + }; } else if (_.isArray(relation.data)) { - return _.map(relation.data, function (ressource) { + return _.map(relation.data, function (ressource, position) { if (!utils.isRessourceObject(ressource)) { - return 'Invalid ressource object in relationships ' + key; + return { + position: position, + message: 'Invalid ressource object in relationships ' + key + }; } }); } else if (!utils.isRessourceObject(relation.data)) { - return 'Invalid ressource object for relationships ' + key; + return { + message: 'Invalid ressource object for relationships ' + key + }; } })), function (n) { return !_.isUndefined(n); @@ -137,7 +158,7 @@ module.exports = { if (!_.isEmpty(errors)) { throw { status: 403, - body: errors.toString() + body: errors }; } } diff --git a/lib/configuration/hooks/jsonapi/helpers/response.js b/lib/configuration/hooks/jsonapi/helpers/response.js index 5428b4a887..630b6fa5f5 100644 --- a/lib/configuration/hooks/jsonapi/helpers/response.js +++ b/lib/configuration/hooks/jsonapi/helpers/response.js @@ -31,18 +31,20 @@ module.exports = { ctx.response.status = 404; ctx.response.body = ''; - return false; + return; } else if (ctx.method === 'DELETE') { // Request successful and responds with only top-level meta data or nothing. ctx.response.body = ''; - return false; + return; } // Fetch and format value const value = this.fetchValue(ctx, object); - ctx.response.body = this.serialize(ctx, type, object, value); + if (!_.isEmpty(value)) { + ctx.response.body = this.serialize(ctx, type, object, value); + } }, /** @@ -132,6 +134,11 @@ module.exports = { if (strapi.models.hasOwnProperty(type)) { _.forEach(strapi.models[type].associations, function (relation) { const PK = utils.getPK(relation.model) || utils.getPK(relation.collection); + const availableRoutes = { + relSlSelf: utils.isRoute('GET /' + type + '/:' + PK + '/relationships/:relation'), + relSlRelated: utils.isRoute('GET /' + type + '/:' + PK), + incSelf: relation.model ? utils.isRoute('GET /' + relation.model + '/:' + PK) : utils.isRoute('GET /' + relation.collection + '/:' + PK) + }; switch (relation.nature) { case 'oneToOne': @@ -139,24 +146,30 @@ module.exports = { // Object toSerialize[relation.alias] = { ref: PK, - attributes: _.keys(strapi.models[type].attributes), + attributes: _.keys(_.omit(strapi.models[type].attributes, _.isFunction)), relationshipLinks: { self: function (record) { - if (record.hasOwnProperty(PK)) { + if (record.hasOwnProperty(PK) && availableRoutes.relSlSelf) { return ctx.request.origin + '/' + type + '/' + record[PK] + '/relationships/' + relation.alias; } + + return undefined; }, related: function (record) { - if (record.hasOwnProperty(PK)) { + if (record.hasOwnProperty(PK) && availableRoutes.relSlRelated) { return ctx.request.origin + '/' + type + '/' + record[PK]; } + + return undefined; } }, includedLinks: { self: function (data, record) { - if (!_.isUndefined(record) && record.hasOwnProperty(PK)) { + if (!_.isUndefined(record) && record.hasOwnProperty(PK) && availableRoutes.incSelf) { return ctx.request.origin + '/' + relation.model + '/' + record[PK]; } + + return undefined; } } }; @@ -167,24 +180,30 @@ module.exports = { toSerialize[relation.alias] = { ref: PK, typeForAttribute: relation.collection, - attributes: _.keys(strapi.models[type].attributes), + attributes: _.keys(_.omit(strapi.models[type].attributes, _.isFunction)), relationshipLinks: { self: function (record) { - if (record.hasOwnProperty(PK)) { + if (record.hasOwnProperty(PK) && availableRoutes.relSlSelf) { return ctx.request.origin + '/' + type + '/' + record[PK] + '/relationships/' + relation.alias; } + + return undefined; }, related: function (record) { - if (record.hasOwnProperty(PK)) { + if (record.hasOwnProperty(PK) && availableRoutes.relSlRelated) { return ctx.request.origin + '/' + type + '/' + record[PK]; } + + return undefined; } }, includedLinks: { self: function (data, record) { - if (record.hasOwnProperty(PK)) { + if (record.hasOwnProperty(PK) && availableRoutes.incSelf) { return ctx.request.origin + '/' + relation.collection + '/' + record[PK]; } + + return undefined; } } }; diff --git a/lib/configuration/hooks/jsonapi/index.js b/lib/configuration/hooks/jsonapi/index.js index ef48b7a5df..a9354fc7da 100644 --- a/lib/configuration/hooks/jsonapi/index.js +++ b/lib/configuration/hooks/jsonapi/index.js @@ -31,6 +31,9 @@ module.exports = function (strapi) { // Exclude administration routes if (this.request.url.indexOf('admin') === -1) { + // Set the required header + this.response.type = 'application/vnd.api+json'; + // Verify Content-Type header if (this.request.type !== 'application/vnd.api+json') { this.status = 406; @@ -56,7 +59,7 @@ module.exports = function (strapi) { } else { // Intercept error requests this.body = { - error: this.body + errors: this.body }; } } diff --git a/lib/configuration/hooks/jsonapi/utils/utils.js b/lib/configuration/hooks/jsonapi/utils/utils.js index 40ac201b3d..df5a6267ce 100644 --- a/lib/configuration/hooks/jsonapi/utils/utils.js +++ b/lib/configuration/hooks/jsonapi/utils/utils.js @@ -21,6 +21,14 @@ module.exports = { return _.isObject(object) && object.hasOwnProperty('id') && object.hasOwnProperty('type'); }, + /** + * Verify if the route exists + */ + + isRoute: function (link) { + return strapi.config.routes.hasOwnProperty(link); + }, + /** * Find data object */