Handle POST method with JSON API request and response

This commit is contained in:
Aurélien Georget 2016-01-28 16:12:16 +01:00
parent c7049253d6
commit 37b937a4de
4 changed files with 113 additions and 40 deletions

View File

@ -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()
};
}
}
}
};

View File

@ -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]) : [];

View File

@ -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);
}
}
}
};
}
};

View File

@ -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');
}
};