mirror of
https://github.com/strapi/strapi.git
synced 2025-11-14 17:19:01 +00:00
Support pagination with JSON API
This commit is contained in:
parent
f08409d336
commit
93572c4e02
@ -150,8 +150,11 @@ module.exports = {
|
|||||||
fields: {}
|
fields: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ctx.state.url = ctx.request.url.replace(ctx.request.search, '');
|
||||||
|
ctx.state.query = ctx.request.query;
|
||||||
|
|
||||||
_.forEach(ctx.query, function (value, key) {
|
_.forEach(ctx.query, function (value, key) {
|
||||||
if (_.includes(['include', 'sort', 'page', 'filter'], key)) {
|
if (_.includes(['include', 'sort', 'filter'], key)) {
|
||||||
throw {
|
throw {
|
||||||
status: 400,
|
status: 400,
|
||||||
body: {
|
body: {
|
||||||
@ -172,6 +175,12 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx.state.filter.fields[type] = value.split(',');
|
ctx.state.filter.fields[type] = value.split(',');
|
||||||
|
} else if (key === 'page[number]' && _.isPlainObject(strapi.config.jsonapi) && strapi.config.jsonapi.hasOwnProperty('paginate') && strapi.config.jsonapi.paginate === parseInt(strapi.config.jsonapi.paginate, 10)) {
|
||||||
|
_.set(ctx.request.query, 'limit', strapi.config.jsonapi.paginate);
|
||||||
|
_.set(ctx.request.query, 'offset', strapi.config.jsonapi.paginate * (value - 1));
|
||||||
|
|
||||||
|
// Remove JSON API page strategy
|
||||||
|
ctx.request.query = _.omit(ctx.request.query, 'page[number]');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@ -10,6 +10,7 @@ const JSONAPI = require('jsonapi-serializer');
|
|||||||
|
|
||||||
// Local Strapi dependencies.
|
// Local Strapi dependencies.
|
||||||
const utils = require('../utils/utils');
|
const utils = require('../utils/utils');
|
||||||
|
let utilsORM;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSON API helper
|
* JSON API helper
|
||||||
@ -21,10 +22,13 @@ module.exports = {
|
|||||||
* Set response
|
* Set response
|
||||||
*/
|
*/
|
||||||
|
|
||||||
set: function (ctx, matchedRoute, actionRoute) {
|
set: function * (ctx, matchedRoute, actionRoute) {
|
||||||
const object = utils.getObject(matchedRoute);
|
const object = utils.getObject(matchedRoute);
|
||||||
const type = utils.getType(ctx, actionRoute.controller);
|
const type = utils.getType(ctx, actionRoute.controller);
|
||||||
|
|
||||||
|
// Load right ORM utils
|
||||||
|
utilsORM = require('../utils/' + strapi.models[type].orm);
|
||||||
|
|
||||||
// Fetch a relationship that does not exist
|
// Fetch a relationship that does not exist
|
||||||
// Reject related request with `include` parameter
|
// Reject related request with `include` parameter
|
||||||
if (_.isUndefined(type) || (type === 'related' && ctx.params.hasOwnProperty('include'))) {
|
if (_.isUndefined(type) || (type === 'related' && ctx.params.hasOwnProperty('include'))) {
|
||||||
@ -43,7 +47,7 @@ module.exports = {
|
|||||||
const value = this.fetchValue(ctx, object).toJSON() || this.fetchValue(ctx, object);
|
const value = this.fetchValue(ctx, object).toJSON() || this.fetchValue(ctx, object);
|
||||||
|
|
||||||
if (!_.isEmpty(value)) {
|
if (!_.isEmpty(value)) {
|
||||||
ctx.response.body = this.serialize(ctx, type, object, value, matchedRoute);
|
ctx.response.body = yield this.serialize(ctx, type, object, value, matchedRoute);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -51,9 +55,9 @@ module.exports = {
|
|||||||
* Serialize response with JSON API specification
|
* Serialize response with JSON API specification
|
||||||
*/
|
*/
|
||||||
|
|
||||||
serialize: function (ctx, type, object, value, matchedRoute) {
|
serialize: function * (ctx, type, object, value) {
|
||||||
const toSerialize = {
|
const toSerialize = {
|
||||||
topLevelLinks: {self: ctx.request.origin + ctx.request.url},
|
topLevelLinks: {self: ctx.request.origin + ctx.request.originalUrl},
|
||||||
keyForAttribute: 'dash-case',
|
keyForAttribute: 'dash-case',
|
||||||
pluralizeType: false,
|
pluralizeType: false,
|
||||||
included: true,
|
included: true,
|
||||||
@ -71,7 +75,7 @@ module.exports = {
|
|||||||
_.assign(toSerialize, _.pick(strapi.config.jsonapi, 'keyForAttribute'));
|
_.assign(toSerialize, _.pick(strapi.config.jsonapi, 'keyForAttribute'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const PK = utils.getPK(type);
|
const PK = utilsORM.getPK(type);
|
||||||
|
|
||||||
if (_.isArray(value) && !_.isEmpty(value)) {
|
if (_.isArray(value) && !_.isEmpty(value)) {
|
||||||
// Array
|
// Array
|
||||||
@ -86,7 +90,7 @@ module.exports = {
|
|||||||
toSerialize.dataLinks = {
|
toSerialize.dataLinks = {
|
||||||
self: function (record) {
|
self: function (record) {
|
||||||
if (record.hasOwnProperty(PK)) {
|
if (record.hasOwnProperty(PK)) {
|
||||||
return ctx.request.origin + ctx.request.url + '/' + record[PK];
|
return ctx.request.origin + ctx.state.url + '/' + record[PK];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -124,15 +128,10 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Display JSON API pagination
|
// Display JSON API pagination
|
||||||
// TODO:
|
if (_.isPlainObject(strapi.config.jsonapi) && strapi.config.jsonapi.hasOwnProperty('paginate') && strapi.config.jsonapi.paginate === parseInt(strapi.config.jsonapi.paginate, 10) && object === 'collection') {
|
||||||
// - Only enabled this feature for BookShelf ORM.
|
yield this.includePagination(ctx, toSerialize, object, type);
|
||||||
if (_.isPlainObject(strapi.config.jsonapi) && strapi.config.jsonapi.hasOwnProperty('pagination') && strapi.config.jsonapi.pagination === true) {
|
|
||||||
this.includePagination(ctx, toSerialize, object, type, matchedRoute);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(toSerialize);
|
|
||||||
console.log(value);
|
|
||||||
|
|
||||||
const serialized = new JSONAPI.Serializer(type, value, toSerialize);
|
const serialized = new JSONAPI.Serializer(type, value, toSerialize);
|
||||||
|
|
||||||
// Display JSON API version support
|
// Display JSON API version support
|
||||||
@ -151,32 +150,52 @@ module.exports = {
|
|||||||
* Include pagination links to the object
|
* Include pagination links to the object
|
||||||
*/
|
*/
|
||||||
|
|
||||||
includePagination: function (ctx, toSerialize, object, type, matchedRoute) {
|
includePagination: function * (ctx, toSerialize, object, type) {
|
||||||
const links = {
|
return new Promise(function (resolve, reject) {
|
||||||
first: null,
|
if (strapi.models.hasOwnProperty(type) && strapi.hasOwnProperty(strapi.models[type].orm) && strapi[strapi.models[type].orm].hasOwnProperty('collections')) {
|
||||||
last: null,
|
// We force page-based strategy for now.
|
||||||
prev: null,
|
utilsORM.getCount(type).then(function (count) {
|
||||||
next: null
|
const links = {};
|
||||||
};
|
const pageNumber = Math.ceil(count / strapi.config.jsonapi.paginate);
|
||||||
|
|
||||||
let index = 1;
|
// Get current page number
|
||||||
const currentParameters = ctx.request.url.match(matchedRoute.regexp);
|
const value = _.first(_.values(_.pick(ctx.state.query, 'page[number]')));
|
||||||
const data = _.mapValues(_.indexBy(matchedRoute.paramNames, 'name'), function () {
|
const currentPage = _.isEmpty(value) ? 1 : value;
|
||||||
return currentParameters[index++];
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO:
|
// Verify integer
|
||||||
// - Call request to get first, latest, previous and next record
|
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;
|
||||||
|
|
||||||
switch (object) {
|
if ((parseInt(currentPage, 10) - 1) === 0) {
|
||||||
default:
|
links.prev = links.first;
|
||||||
_.assign(toSerialize.topLevelLinks, _.mapValues(links, function () {
|
|
||||||
return ctx.request.origin + matchedRoute.path.replace(/(:[a-z]+)/g, function (match, token) {
|
|
||||||
return data[token.substr(1)];
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_.assign(toSerialize.topLevelLinks, _.omit(links, _.isNull));
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -186,7 +205,7 @@ module.exports = {
|
|||||||
includedRelationShips: function (ctx, toSerialize, type) {
|
includedRelationShips: function (ctx, toSerialize, type) {
|
||||||
if (strapi.models.hasOwnProperty(type)) {
|
if (strapi.models.hasOwnProperty(type)) {
|
||||||
_.forEach(strapi.models[type].associations, function (relation) {
|
_.forEach(strapi.models[type].associations, function (relation) {
|
||||||
const PK = utils.getPK(relation.model) || utils.getPK(relation.collection);
|
const PK = utilsORM.getPK(relation.model) || utilsORM.getPK(relation.collection);
|
||||||
// TODO:
|
// TODO:
|
||||||
// - Use matched route
|
// - Use matched route
|
||||||
const availableRoutes = {
|
const availableRoutes = {
|
||||||
|
|||||||
@ -48,7 +48,7 @@ module.exports = function (strapi) {
|
|||||||
const actionRoute = strapi.config.routes[this.request.method.toUpperCase() + ' ' + matchedRoute.path];
|
const actionRoute = strapi.config.routes[this.request.method.toUpperCase() + ' ' + matchedRoute.path];
|
||||||
|
|
||||||
if (!_.isUndefined(actionRoute)) {
|
if (!_.isUndefined(actionRoute)) {
|
||||||
response.set(this, matchedRoute, actionRoute);
|
yield response.set(this, matchedRoute, actionRoute);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -19,6 +19,15 @@ module.exports = {
|
|||||||
|
|
||||||
getPK: function (type) {
|
getPK: function (type) {
|
||||||
return global[_.capitalize(type)].idAttribute || 'id';
|
return global[_.capitalize(type)].idAttribute || 'id';
|
||||||
}
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find primary key
|
||||||
|
*/
|
||||||
|
|
||||||
|
getCount: function (type) {
|
||||||
|
return strapi.bookshelf.collections[type].forge().count().then(function (count) {
|
||||||
|
return count;
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,8 +6,6 @@
|
|||||||
|
|
||||||
// Public node modules.
|
// Public node modules.
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const utilsBookShelf = require('./bookshelf');
|
|
||||||
const utilsWaterline = require('./waterline');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSON API utils
|
* JSON API utils
|
||||||
@ -78,32 +76,13 @@ module.exports = {
|
|||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Find primary key
|
|
||||||
*/
|
|
||||||
|
|
||||||
getPK: function (type) {
|
|
||||||
if (!strapi.models.hasOwnProperty(type)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (strapi.models[type].orm.toLowerCase()) {
|
|
||||||
case 'bookshelf':
|
|
||||||
return utilsBookShelf.getPK(type);
|
|
||||||
case 'waterline':
|
|
||||||
return utilsWaterline.getPK(type);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find router object for matched route
|
* Find router object for matched route
|
||||||
*/
|
*/
|
||||||
|
|
||||||
matchedRoute: function (ctx) {
|
matchedRoute: function (ctx) {
|
||||||
return _.find(strapi.router.stack, function (stack) {
|
return _.find(strapi.router.stack, function (stack) {
|
||||||
if (new RegExp(stack.regexp).test(ctx.request.url) && _.includes(stack.methods, ctx.request.method.toUpperCase())) {
|
if (new RegExp(stack.regexp).test(ctx.request.url.replace(ctx.request.search, '')) && _.includes(stack.methods, ctx.request.method.toUpperCase())) {
|
||||||
return stack;
|
return stack;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -39,6 +39,8 @@ module.exports = function (strapi) {
|
|||||||
// Set up config defaults.
|
// Set up config defaults.
|
||||||
return {
|
return {
|
||||||
|
|
||||||
|
collections: {},
|
||||||
|
|
||||||
// Core (default) hooks.
|
// Core (default) hooks.
|
||||||
hooks: _.reduce(DEFAULT_HOOKS, function (memo, hookBundled, hookIdentity) {
|
hooks: _.reduce(DEFAULT_HOOKS, function (memo, hookBundled, hookIdentity) {
|
||||||
memo[hookIdentity] = require('./hooks/' + hookIdentity);
|
memo[hookIdentity] = require('./hooks/' + hookIdentity);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user