mirror of
https://github.com/strapi/strapi.git
synced 2025-11-01 18:33:55 +00:00
Support pagination with JSON API
This commit is contained in:
parent
f08409d336
commit
93572c4e02
@ -150,8 +150,11 @@ module.exports = {
|
||||
fields: {}
|
||||
};
|
||||
|
||||
ctx.state.url = ctx.request.url.replace(ctx.request.search, '');
|
||||
ctx.state.query = ctx.request.query;
|
||||
|
||||
_.forEach(ctx.query, function (value, key) {
|
||||
if (_.includes(['include', 'sort', 'page', 'filter'], key)) {
|
||||
if (_.includes(['include', 'sort', 'filter'], key)) {
|
||||
throw {
|
||||
status: 400,
|
||||
body: {
|
||||
@ -172,6 +175,12 @@ module.exports = {
|
||||
}
|
||||
|
||||
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.
|
||||
const utils = require('../utils/utils');
|
||||
let utilsORM;
|
||||
|
||||
/**
|
||||
* JSON API helper
|
||||
@ -21,10 +22,13 @@ module.exports = {
|
||||
* Set response
|
||||
*/
|
||||
|
||||
set: function (ctx, matchedRoute, actionRoute) {
|
||||
set: function * (ctx, matchedRoute, actionRoute) {
|
||||
const object = utils.getObject(matchedRoute);
|
||||
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
|
||||
// Reject related request with `include` parameter
|
||||
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);
|
||||
|
||||
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: function (ctx, type, object, value, matchedRoute) {
|
||||
serialize: function * (ctx, type, object, value) {
|
||||
const toSerialize = {
|
||||
topLevelLinks: {self: ctx.request.origin + ctx.request.url},
|
||||
topLevelLinks: {self: ctx.request.origin + ctx.request.originalUrl},
|
||||
keyForAttribute: 'dash-case',
|
||||
pluralizeType: false,
|
||||
included: true,
|
||||
@ -71,7 +75,7 @@ module.exports = {
|
||||
_.assign(toSerialize, _.pick(strapi.config.jsonapi, 'keyForAttribute'));
|
||||
}
|
||||
|
||||
const PK = utils.getPK(type);
|
||||
const PK = utilsORM.getPK(type);
|
||||
|
||||
if (_.isArray(value) && !_.isEmpty(value)) {
|
||||
// Array
|
||||
@ -86,7 +90,7 @@ module.exports = {
|
||||
toSerialize.dataLinks = {
|
||||
self: function (record) {
|
||||
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
|
||||
// TODO:
|
||||
// - Only enabled this feature for BookShelf ORM.
|
||||
if (_.isPlainObject(strapi.config.jsonapi) && strapi.config.jsonapi.hasOwnProperty('pagination') && strapi.config.jsonapi.pagination === true) {
|
||||
this.includePagination(ctx, toSerialize, object, type, matchedRoute);
|
||||
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);
|
||||
}
|
||||
|
||||
console.log(toSerialize);
|
||||
console.log(value);
|
||||
|
||||
const serialized = new JSONAPI.Serializer(type, value, toSerialize);
|
||||
|
||||
// Display JSON API version support
|
||||
@ -151,32 +150,52 @@ module.exports = {
|
||||
* Include pagination links to the object
|
||||
*/
|
||||
|
||||
includePagination: function (ctx, toSerialize, object, type, matchedRoute) {
|
||||
const links = {
|
||||
first: null,
|
||||
last: null,
|
||||
prev: null,
|
||||
next: null
|
||||
};
|
||||
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);
|
||||
|
||||
let index = 1;
|
||||
const currentParameters = ctx.request.url.match(matchedRoute.regexp);
|
||||
const data = _.mapValues(_.indexBy(matchedRoute.paramNames, 'name'), function () {
|
||||
return currentParameters[index++];
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
_.assign(toSerialize.topLevelLinks, _.omit(links, _.isNull));
|
||||
|
||||
resolve();
|
||||
})
|
||||
.catch(function (err) {
|
||||
reject(err);
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
// TODO:
|
||||
// - Call request to get first, latest, previous and next record
|
||||
|
||||
switch (object) {
|
||||
default:
|
||||
_.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;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -186,7 +205,7 @@ module.exports = {
|
||||
includedRelationShips: function (ctx, toSerialize, type) {
|
||||
if (strapi.models.hasOwnProperty(type)) {
|
||||
_.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:
|
||||
// - Use matched route
|
||||
const availableRoutes = {
|
||||
|
||||
@ -48,7 +48,7 @@ module.exports = function (strapi) {
|
||||
const actionRoute = strapi.config.routes[this.request.method.toUpperCase() + ' ' + matchedRoute.path];
|
||||
|
||||
if (!_.isUndefined(actionRoute)) {
|
||||
response.set(this, matchedRoute, actionRoute);
|
||||
yield response.set(this, matchedRoute, actionRoute);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -19,6 +19,15 @@ module.exports = {
|
||||
|
||||
getPK: function (type) {
|
||||
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.
|
||||
const _ = require('lodash');
|
||||
const utilsBookShelf = require('./bookshelf');
|
||||
const utilsWaterline = require('./waterline');
|
||||
|
||||
/**
|
||||
* JSON API utils
|
||||
@ -78,32 +76,13 @@ module.exports = {
|
||||
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
|
||||
*/
|
||||
|
||||
matchedRoute: function (ctx) {
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
@ -39,6 +39,8 @@ module.exports = function (strapi) {
|
||||
// Set up config defaults.
|
||||
return {
|
||||
|
||||
collections: {},
|
||||
|
||||
// Core (default) hooks.
|
||||
hooks: _.reduce(DEFAULT_HOOKS, function (memo, hookBundled, hookIdentity) {
|
||||
memo[hookIdentity] = require('./hooks/' + hookIdentity);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user