2016-01-22 15:40:43 +01:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Module dependencies
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Public node modules.
|
|
|
|
const _ = require('lodash');
|
2016-02-01 16:00:54 +01:00
|
|
|
const JSONAPI = require('jsonapi-serializer');
|
2016-01-29 14:52:11 +01:00
|
|
|
|
|
|
|
// Local Strapi dependencies.
|
2016-01-28 16:21:25 +01:00
|
|
|
const utils = require('../utils/utils');
|
2016-03-02 16:20:36 +01:00
|
|
|
let utilsORM;
|
2016-01-22 15:40:43 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* JSON API helper
|
|
|
|
*/
|
|
|
|
|
|
|
|
module.exports = {
|
2016-01-22 17:59:46 +01:00
|
|
|
|
|
|
|
/**
|
2016-01-22 18:36:15 +01:00
|
|
|
* Set response
|
2016-01-22 17:59:46 +01:00
|
|
|
*/
|
|
|
|
|
2016-03-02 16:20:36 +01:00
|
|
|
set: function * (ctx, matchedRoute, actionRoute) {
|
2016-01-28 16:21:25 +01:00
|
|
|
const object = utils.getObject(matchedRoute);
|
|
|
|
const type = utils.getType(ctx, actionRoute.controller);
|
2016-01-22 17:59:46 +01:00
|
|
|
|
2016-03-02 16:20:36 +01:00
|
|
|
// Load right ORM utils
|
|
|
|
utilsORM = require('../utils/' + strapi.models[type].orm);
|
|
|
|
|
2016-01-26 16:24:43 +01:00
|
|
|
// Fetch a relationship that does not exist
|
2016-01-26 16:30:31 +01:00
|
|
|
// Reject related request with `include` parameter
|
|
|
|
if (_.isUndefined(type) || (type === 'related' && ctx.params.hasOwnProperty('include'))) {
|
2016-01-26 16:24:43 +01:00
|
|
|
ctx.response.status = 404;
|
|
|
|
ctx.response.body = '';
|
|
|
|
|
2016-01-29 17:56:57 +01:00
|
|
|
return;
|
2016-01-29 13:17:38 +01:00
|
|
|
} else if (ctx.method === 'DELETE') {
|
|
|
|
// Request successful and responds with only top-level meta data or nothing.
|
|
|
|
ctx.response.body = '';
|
|
|
|
|
2016-01-29 17:56:57 +01:00
|
|
|
return;
|
2016-01-26 16:24:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch and format value
|
2016-02-22 17:36:58 +01:00
|
|
|
const value = this.fetchValue(ctx, object).toJSON() || this.fetchValue(ctx, object);
|
2016-01-26 16:30:31 +01:00
|
|
|
|
2016-01-29 17:56:57 +01:00
|
|
|
if (!_.isEmpty(value)) {
|
2016-03-02 16:20:36 +01:00
|
|
|
ctx.response.body = yield this.serialize(ctx, type, object, value, matchedRoute);
|
2016-01-29 17:56:57 +01:00
|
|
|
}
|
2016-01-22 17:59:46 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2016-01-22 18:36:15 +01:00
|
|
|
* Serialize response with JSON API specification
|
2016-01-22 17:59:46 +01:00
|
|
|
*/
|
|
|
|
|
2016-03-02 16:20:36 +01:00
|
|
|
serialize: function * (ctx, type, object, value) {
|
2016-01-22 17:59:46 +01:00
|
|
|
const toSerialize = {
|
2016-03-02 16:20:36 +01:00
|
|
|
topLevelLinks: {self: ctx.request.origin + ctx.request.originalUrl},
|
2016-02-22 17:36:58 +01:00
|
|
|
keyForAttribute: 'dash-case',
|
2016-01-25 16:48:15 +01:00
|
|
|
pluralizeType: false,
|
2016-01-29 16:01:10 +01:00
|
|
|
included: true,
|
2016-01-25 16:48:15 +01:00
|
|
|
typeForAttribute: function (currentType) {
|
|
|
|
if (strapi.models.hasOwnProperty(type)) {
|
2016-01-26 15:35:25 +01:00
|
|
|
return _.first(_.reject(_.map(strapi.models[type].associations, function (relation) {
|
2016-01-25 16:48:15 +01:00
|
|
|
return (relation.alias === currentType) ? relation.model || relation.collection : undefined;
|
2016-01-26 15:35:25 +01:00
|
|
|
}), _.isUndefined)) || currentType;
|
2016-01-25 16:48:15 +01:00
|
|
|
}
|
|
|
|
}
|
2016-01-22 17:59:46 +01:00
|
|
|
};
|
|
|
|
|
2016-01-29 16:01:10 +01:00
|
|
|
// Assign custom configurations
|
|
|
|
if (_.isPlainObject(strapi.config.jsonapi) && !_.isEmpty(strapi.config.jsonapi)) {
|
2016-02-01 16:00:54 +01:00
|
|
|
_.assign(toSerialize, _.pick(strapi.config.jsonapi, 'keyForAttribute'));
|
2016-01-29 16:01:10 +01:00
|
|
|
}
|
|
|
|
|
2016-03-02 16:20:36 +01:00
|
|
|
const PK = utilsORM.getPK(type);
|
2016-01-25 16:48:15 +01:00
|
|
|
|
|
|
|
if (_.isArray(value) && !_.isEmpty(value)) {
|
|
|
|
// Array
|
|
|
|
if (!_.isNull(PK)) {
|
|
|
|
_.forEach(value, function (record) {
|
2016-01-29 16:01:10 +01:00
|
|
|
if (record.hasOwnProperty(PK)) {
|
|
|
|
record[PK] = record[PK].toString();
|
|
|
|
}
|
2016-01-25 16:48:15 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
toSerialize.dataLinks = {
|
|
|
|
self: function (record) {
|
2016-01-29 13:17:38 +01:00
|
|
|
if (record.hasOwnProperty(PK)) {
|
2016-03-02 16:20:36 +01:00
|
|
|
return ctx.request.origin + ctx.state.url + '/' + record[PK];
|
2016-01-29 13:17:38 +01:00
|
|
|
}
|
2016-01-22 17:59:46 +01:00
|
|
|
}
|
2016-01-25 16:48:15 +01:00
|
|
|
};
|
2016-01-22 17:59:46 +01:00
|
|
|
|
2016-02-01 17:38:08 +01:00
|
|
|
toSerialize.attributes = ctx.state.filter.fields[type] || _.keys(_.last(value));
|
2016-01-25 16:48:15 +01:00
|
|
|
} else if (_.isObject(value) && !_.isEmpty(value)) {
|
|
|
|
// Object
|
2016-01-29 13:17:38 +01:00
|
|
|
if (!_.isNull(PK) && value.hasOwnProperty(PK)) {
|
2016-01-25 16:48:15 +01:00
|
|
|
value[PK] = value[PK].toString();
|
|
|
|
}
|
2016-01-22 18:36:15 +01:00
|
|
|
|
2016-02-01 17:38:08 +01:00
|
|
|
toSerialize.attributes = ctx.state.filter.fields[type] || _.keys(value);
|
2016-01-25 16:48:15 +01:00
|
|
|
}
|
2016-01-22 17:59:46 +01:00
|
|
|
|
2016-01-26 15:35:25 +01:00
|
|
|
switch (object) {
|
|
|
|
case 'collection':
|
|
|
|
this.includedRelationShips(ctx, toSerialize, type);
|
|
|
|
break;
|
|
|
|
case 'ressource':
|
|
|
|
this.includedRelationShips(ctx, toSerialize, type);
|
|
|
|
break;
|
|
|
|
case 'relationships':
|
|
|
|
// Remove data key
|
|
|
|
delete toSerialize.dataLinks;
|
|
|
|
delete toSerialize.attributes;
|
|
|
|
|
|
|
|
// Dirty way to set related URL
|
|
|
|
toSerialize.topLevelLinks.related = toSerialize.topLevelLinks.self.replace('relationships/', '');
|
|
|
|
break;
|
|
|
|
case 'related':
|
2016-01-26 16:24:43 +01:00
|
|
|
this.includedRelationShips(ctx, toSerialize, type);
|
2016-01-26 15:35:25 +01:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2016-01-22 17:59:46 +01:00
|
|
|
|
2016-02-03 16:53:30 +01:00
|
|
|
// Display JSON API pagination
|
2016-03-02 16:20:36 +01:00
|
|
|
if (_.isPlainObject(strapi.config.jsonapi) && strapi.config.jsonapi.hasOwnProperty('paginate') && strapi.config.jsonapi.paginate === parseInt(strapi.config.jsonapi.paginate, 10) && object === 'collection') {
|
|
|
|
yield this.includePagination(ctx, toSerialize, object, type);
|
2016-02-03 16:53:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const serialized = new JSONAPI.Serializer(type, value, toSerialize);
|
|
|
|
|
2016-02-01 14:38:55 +01:00
|
|
|
// Display JSON API version support
|
|
|
|
if (_.isPlainObject(strapi.config.jsonapi) && strapi.config.jsonapi.hasOwnProperty('showVersion') && strapi.config.jsonapi.showVersion === true) {
|
2016-02-03 16:53:30 +01:00
|
|
|
_.assign(serialized, {
|
2016-02-01 14:38:55 +01:00
|
|
|
jsonapi: {
|
|
|
|
version: '1.0'
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-02-03 16:53:30 +01:00
|
|
|
return serialized;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Include pagination links to the object
|
|
|
|
*/
|
|
|
|
|
2016-03-02 16:20:36 +01:00
|
|
|
includePagination: function * (ctx, toSerialize, object, type) {
|
|
|
|
return new Promise(function (resolve, reject) {
|
|
|
|
if (strapi.models.hasOwnProperty(type) && strapi.hasOwnProperty(strapi.models[type].orm) && strapi[strapi.models[type].orm].hasOwnProperty('collections')) {
|
|
|
|
// We force page-based strategy for now.
|
|
|
|
utilsORM.getCount(type).then(function (count) {
|
|
|
|
const links = {};
|
|
|
|
const pageNumber = Math.ceil(count / strapi.config.jsonapi.paginate);
|
|
|
|
|
|
|
|
// Get current page number
|
|
|
|
const value = _.first(_.values(_.pick(ctx.state.query, 'page[number]')));
|
|
|
|
const currentPage = _.isEmpty(value) ? 1 : value;
|
|
|
|
|
|
|
|
// Verify integer
|
|
|
|
if (currentPage.toString() === parseInt(currentPage, 10).toString()) {
|
|
|
|
links.first = ctx.request.origin + ctx.state.url;
|
|
|
|
links.prev = ctx.request.origin + ctx.state.url + '?page[number]=' + (parseInt(currentPage, 10) - 1);
|
|
|
|
links.next = ctx.request.origin + ctx.state.url + '?page[number]=' + (parseInt(currentPage, 10) + 1);
|
|
|
|
links.last = ctx.request.origin + ctx.state.url + '?page[number]=' + pageNumber;
|
|
|
|
|
|
|
|
if ((parseInt(currentPage, 10) - 1) === 0) {
|
|
|
|
links.prev = links.first;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Last page
|
|
|
|
if (ctx.request.url === ctx.state.url + '?page[number]=' + pageNumber) {
|
|
|
|
// Don't display useless
|
|
|
|
links.last = null;
|
|
|
|
links.next = null;
|
|
|
|
} else if (ctx.request.url === ctx.state.url) {
|
|
|
|
// First page
|
|
|
|
links.first = null;
|
|
|
|
links.prev = null;
|
|
|
|
}
|
|
|
|
}
|
2016-02-03 16:53:30 +01:00
|
|
|
|
2016-03-02 16:20:36 +01:00
|
|
|
_.assign(toSerialize.topLevelLinks, _.omit(links, _.isNull));
|
2016-02-03 16:53:30 +01:00
|
|
|
|
2016-03-02 16:20:36 +01:00
|
|
|
resolve();
|
|
|
|
})
|
|
|
|
.catch(function (err) {
|
|
|
|
reject(err);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
resolve();
|
|
|
|
}
|
|
|
|
});
|
2016-01-25 16:48:15 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Include relationships values to the object
|
|
|
|
*/
|
|
|
|
|
|
|
|
includedRelationShips: function (ctx, toSerialize, type) {
|
|
|
|
if (strapi.models.hasOwnProperty(type)) {
|
|
|
|
_.forEach(strapi.models[type].associations, function (relation) {
|
2016-03-02 16:20:36 +01:00
|
|
|
const PK = utilsORM.getPK(relation.model) || utilsORM.getPK(relation.collection);
|
2016-02-03 16:53:30 +01:00
|
|
|
// TODO:
|
|
|
|
// - Use matched route
|
2016-01-29 17:56:57 +01:00
|
|
|
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)
|
|
|
|
};
|
2016-01-29 13:17:38 +01:00
|
|
|
|
2016-01-25 16:48:15 +01:00
|
|
|
switch (relation.nature) {
|
|
|
|
case 'oneToOne':
|
|
|
|
case 'manyToOne':
|
|
|
|
// Object
|
|
|
|
toSerialize[relation.alias] = {
|
2016-01-29 13:17:38 +01:00
|
|
|
ref: PK,
|
2016-02-01 16:00:54 +01:00
|
|
|
included: strapi.config.jsonapi.included || false,
|
|
|
|
ignoreRelationshipData: strapi.config.jsonapi.ignoreRelationshipData || false,
|
2016-02-01 17:38:08 +01:00
|
|
|
attributes: ctx.state.filter.fields[relation.model] || _.keys(_.omit(strapi.models[type].attributes, _.isFunction)),
|
2016-01-25 16:48:15 +01:00
|
|
|
relationshipLinks: {
|
2016-01-26 15:35:25 +01:00
|
|
|
self: function (record) {
|
2016-01-29 17:56:57 +01:00
|
|
|
if (record.hasOwnProperty(PK) && availableRoutes.relSlSelf) {
|
2016-01-29 13:17:38 +01:00
|
|
|
return ctx.request.origin + '/' + type + '/' + record[PK] + '/relationships/' + relation.alias;
|
|
|
|
}
|
2016-01-29 17:56:57 +01:00
|
|
|
|
|
|
|
return undefined;
|
2016-01-26 15:35:25 +01:00
|
|
|
},
|
|
|
|
related: function (record) {
|
2016-01-29 17:56:57 +01:00
|
|
|
if (record.hasOwnProperty(PK) && availableRoutes.relSlRelated) {
|
2016-01-29 13:17:38 +01:00
|
|
|
return ctx.request.origin + '/' + type + '/' + record[PK];
|
|
|
|
}
|
2016-01-29 17:56:57 +01:00
|
|
|
|
|
|
|
return undefined;
|
2016-01-26 15:35:25 +01:00
|
|
|
}
|
2016-01-25 16:48:15 +01:00
|
|
|
},
|
|
|
|
includedLinks: {
|
|
|
|
self: function (data, record) {
|
2016-01-29 17:56:57 +01:00
|
|
|
if (!_.isUndefined(record) && record.hasOwnProperty(PK) && availableRoutes.incSelf) {
|
2016-01-29 13:17:38 +01:00
|
|
|
return ctx.request.origin + '/' + relation.model + '/' + record[PK];
|
|
|
|
}
|
2016-01-29 17:56:57 +01:00
|
|
|
|
|
|
|
return undefined;
|
2016-01-25 16:48:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
case 'oneToMany':
|
|
|
|
case 'manyToMany':
|
|
|
|
// Array
|
|
|
|
toSerialize[relation.alias] = {
|
2016-01-29 13:17:38 +01:00
|
|
|
ref: PK,
|
2016-02-01 16:00:54 +01:00
|
|
|
included: strapi.config.jsonapi.included || false,
|
|
|
|
ignoreRelationshipData: strapi.config.jsonapi.ignoreRelationshipData || false,
|
2016-01-25 16:48:15 +01:00
|
|
|
typeForAttribute: relation.collection,
|
2016-02-01 17:38:08 +01:00
|
|
|
attributes: ctx.state.filter.fields[relation.collection] || _.keys(_.omit(strapi.models[type].attributes, _.isFunction)),
|
2016-01-25 16:48:15 +01:00
|
|
|
relationshipLinks: {
|
2016-01-26 15:35:25 +01:00
|
|
|
self: function (record) {
|
2016-01-29 17:56:57 +01:00
|
|
|
if (record.hasOwnProperty(PK) && availableRoutes.relSlSelf) {
|
2016-01-29 13:17:38 +01:00
|
|
|
return ctx.request.origin + '/' + type + '/' + record[PK] + '/relationships/' + relation.alias;
|
|
|
|
}
|
2016-01-29 17:56:57 +01:00
|
|
|
|
|
|
|
return undefined;
|
2016-01-26 15:35:25 +01:00
|
|
|
},
|
|
|
|
related: function (record) {
|
2016-01-29 17:56:57 +01:00
|
|
|
if (record.hasOwnProperty(PK) && availableRoutes.relSlRelated) {
|
2016-01-29 13:17:38 +01:00
|
|
|
return ctx.request.origin + '/' + type + '/' + record[PK];
|
|
|
|
}
|
2016-01-29 17:56:57 +01:00
|
|
|
|
|
|
|
return undefined;
|
2016-01-26 15:35:25 +01:00
|
|
|
}
|
2016-01-25 16:48:15 +01:00
|
|
|
},
|
|
|
|
includedLinks: {
|
|
|
|
self: function (data, record) {
|
2016-01-29 17:56:57 +01:00
|
|
|
if (record.hasOwnProperty(PK) && availableRoutes.incSelf) {
|
2016-01-29 13:17:38 +01:00
|
|
|
return ctx.request.origin + '/' + relation.collection + '/' + record[PK];
|
|
|
|
}
|
2016-01-29 17:56:57 +01:00
|
|
|
|
|
|
|
return undefined;
|
2016-01-25 16:48:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
});
|
2016-01-22 17:59:46 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2016-01-26 16:24:43 +01:00
|
|
|
* Fetch and format value
|
2016-01-22 17:59:46 +01:00
|
|
|
*/
|
|
|
|
|
2016-01-26 16:24:43 +01:00
|
|
|
fetchValue: function (ctx, object) {
|
2016-01-22 17:59:46 +01:00
|
|
|
const data = ctx.body;
|
|
|
|
|
2016-01-22 18:36:15 +01:00
|
|
|
switch (object) {
|
2016-01-22 17:59:46 +01:00
|
|
|
case 'collection':
|
2016-01-28 16:12:16 +01:00
|
|
|
if ((_.isArray(data) && _.size(data) > 1) || _.isObject(data)) {
|
2016-01-22 17:59:46 +01:00
|
|
|
return data;
|
|
|
|
} else if (_.isArray(data) && (_.size(data) === 1 || _.size(data) === 0)) {
|
2016-01-26 16:24:43 +01:00
|
|
|
return _.isObject(_.first(data)) ? _.first(data[0]) : [];
|
2016-01-22 17:59:46 +01:00
|
|
|
}
|
2016-01-26 16:24:43 +01:00
|
|
|
|
|
|
|
return null;
|
2016-01-22 17:59:46 +01:00
|
|
|
case 'ressource':
|
|
|
|
if (_.isObject(data)) {
|
|
|
|
return data;
|
|
|
|
}
|
2016-01-26 16:24:43 +01:00
|
|
|
|
|
|
|
return null;
|
|
|
|
case 'related':
|
2016-01-22 17:59:46 +01:00
|
|
|
case 'relationships':
|
2016-01-26 15:35:25 +01:00
|
|
|
if (_.isObject(data) || _.isArray(data) && data.hasOwnProperty(ctx.params.relation)) {
|
2016-01-26 16:24:43 +01:00
|
|
|
if (_.isArray(data[ctx.params.relation]) && _.size(data[ctx.params.relation]) > 1) {
|
|
|
|
return data[ctx.params.relation];
|
|
|
|
}
|
2016-01-22 17:59:46 +01:00
|
|
|
|
2016-01-26 16:24:43 +01:00
|
|
|
return _.first(data[ctx.params.relation]) || data[ctx.params.relation];
|
2016-01-22 17:59:46 +01:00
|
|
|
}
|
2016-01-26 16:24:43 +01:00
|
|
|
|
|
|
|
return null;
|
2016-01-22 17:59:46 +01:00
|
|
|
default:
|
2016-01-22 18:36:15 +01:00
|
|
|
return 'collection';
|
2016-01-22 17:59:46 +01:00
|
|
|
}
|
2016-01-22 15:40:43 +01:00
|
|
|
}
|
|
|
|
};
|