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-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-01-22 18:36:15 +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-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
|
|
|
|
const value = this.fetchValue(ctx, object);
|
2016-01-26 16:30:31 +01:00
|
|
|
|
2016-01-29 17:56:57 +01:00
|
|
|
if (!_.isEmpty(value)) {
|
|
|
|
ctx.response.body = this.serialize(ctx, type, object, value);
|
|
|
|
}
|
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-01-26 15:35:25 +01:00
|
|
|
serialize: function (ctx, type, object, value) {
|
2016-01-22 17:59:46 +01:00
|
|
|
const toSerialize = {
|
2016-01-25 16:48:15 +01:00
|
|
|
topLevelLinks: {self: ctx.request.origin + ctx.request.url},
|
|
|
|
keyForAttribute: 'camelCase',
|
|
|
|
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-01-28 16:21:25 +01:00
|
|
|
const PK = utils.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)) {
|
|
|
|
return ctx.request.origin + ctx.request.url + '/' + record[PK];
|
|
|
|
}
|
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-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-01 17:38:08 +01:00
|
|
|
return _.assign(new JSONAPI.Serializer(type, value, toSerialize), {
|
2016-02-01 14:38:55 +01:00
|
|
|
jsonapi: {
|
|
|
|
version: '1.0'
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-02-01 16:00:54 +01:00
|
|
|
return new JSONAPI.Serializer(type, value, toSerialize);
|
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-01-29 16:01:10 +01:00
|
|
|
const PK = utils.getPK(relation.model) || utils.getPK(relation.collection);
|
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
|
|
|
}
|
|
|
|
};
|