mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
Merge pull request #5432 from ErisDS/api-pagination2
Refactor pagination count query
This commit is contained in:
commit
f79a4f336b
3 changed files with 275 additions and 254 deletions
|
@ -355,9 +355,35 @@ Post = ghostBookshelf.Model.extend({
|
|||
findPage: function findPage(options) {
|
||||
options = options || {};
|
||||
|
||||
var tagInstance = options.tag !== undefined ? ghostBookshelf.model('Tag').forge({slug: options.tag}) : false,
|
||||
// -- Part 0 --
|
||||
// Step 1: Setup filter models
|
||||
var self = this,
|
||||
tagInstance = options.tag !== undefined ? ghostBookshelf.model('Tag').forge({slug: options.tag}) : false,
|
||||
authorInstance = options.author !== undefined ? ghostBookshelf.model('User').forge({slug: options.author}) : false;
|
||||
|
||||
// Step 2: Setup filter model promises
|
||||
function fetchTagQuery() {
|
||||
if (tagInstance) {
|
||||
return tagInstance.fetch();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function fetchAuthorQuery() {
|
||||
if (authorInstance) {
|
||||
return authorInstance.fetch();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Prefetch filter models
|
||||
return Promise.join(fetchTagQuery(), fetchAuthorQuery()).then(function setupCollectionPromises() {
|
||||
// -- Part 1 --
|
||||
var postCollection = Posts.forge(),
|
||||
collectionPromise,
|
||||
countPromise;
|
||||
|
||||
// Step 1: Setup pagination options
|
||||
if (options.limit && options.limit !== 'all') {
|
||||
options.limit = parseInt(options.limit, 10) || 15;
|
||||
}
|
||||
|
@ -366,9 +392,10 @@ Post = ghostBookshelf.Model.extend({
|
|||
options.page = parseInt(options.page, 10) || 1;
|
||||
}
|
||||
|
||||
options = this.filterOptions(options, 'findPage');
|
||||
// Step 2: Filter options
|
||||
options = self.filterOptions(options, 'findPage');
|
||||
|
||||
// Set default settings for options
|
||||
// Step 3: Extend defaults
|
||||
options = _.extend({
|
||||
page: 1, // pagination page
|
||||
limit: 15,
|
||||
|
@ -377,6 +404,7 @@ Post = ghostBookshelf.Model.extend({
|
|||
where: {}
|
||||
}, options);
|
||||
|
||||
// Step 4: Setup filters (where clauses)
|
||||
if (options.staticPages !== 'all') {
|
||||
// convert string true/false to boolean
|
||||
if (!_.isBoolean(options.staticPages)) {
|
||||
|
@ -401,44 +429,12 @@ Post = ghostBookshelf.Model.extend({
|
|||
options.where.status = options.status;
|
||||
}
|
||||
|
||||
// Add related objects
|
||||
options.withRelated = _.union(options.withRelated, options.include);
|
||||
|
||||
// If a query param for a tag is attached
|
||||
// we need to fetch the tag model to find its id
|
||||
function fetchTagQuery() {
|
||||
if (tagInstance) {
|
||||
return tagInstance.fetch();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function fetchAuthorQuery() {
|
||||
if (authorInstance) {
|
||||
return authorInstance.fetch();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return Promise.join(fetchTagQuery(), fetchAuthorQuery())
|
||||
// Set the limit & offset for the query, fetching
|
||||
// with the opts (to specify any eager relations, etc.)
|
||||
// Omitting the `page`, `limit`, `where` just to be sure
|
||||
// aren't used for other purposes.
|
||||
.then(function then() {
|
||||
var postCollection = Posts.forge(),
|
||||
collectionPromise,
|
||||
countPromise,
|
||||
qb;
|
||||
|
||||
// If there are where conditionals specified, add those
|
||||
// to the query.
|
||||
// If there are where conditionals specified, add those to the query.
|
||||
if (options.where) {
|
||||
postCollection.query('where', options.where);
|
||||
}
|
||||
// If we have a tag instance we need to modify our query.
|
||||
// We need to ensure we only select posts that contain
|
||||
// the tag given in the query param.
|
||||
|
||||
// Step 5: Setup joins
|
||||
if (tagInstance) {
|
||||
postCollection
|
||||
.query('join', 'posts_tags', 'posts_tags.post_id', '=', 'posts.id')
|
||||
|
@ -450,42 +446,43 @@ Post = ghostBookshelf.Model.extend({
|
|||
.query('where', 'author_id', '=', authorInstance.id);
|
||||
}
|
||||
|
||||
// Step 6: Setup the counter to fetch the number of items in the set
|
||||
// @TODO abstract this out
|
||||
// tableName = _.result(postCollection, 'tableName'),
|
||||
// idAttribute = _.result(postCollection, 'idAttribute');
|
||||
countPromise = postCollection.query().clone().count('posts.id as aggregate');
|
||||
|
||||
// -- Part 2 --
|
||||
// Add limit, offset and other query changes which aren't required when performing a count
|
||||
|
||||
// Step 1: Add related objects
|
||||
options.withRelated = _.union(options.withRelated, options.include);
|
||||
|
||||
// Step 2: Add pagination options if needed
|
||||
if (_.isNumber(options.limit)) {
|
||||
postCollection
|
||||
.query('limit', options.limit)
|
||||
.query('offset', options.limit * (options.page - 1));
|
||||
}
|
||||
|
||||
collectionPromise = postCollection
|
||||
// Step 3: add order parameters
|
||||
postCollection
|
||||
.query('orderBy', 'status', 'ASC')
|
||||
.query('orderBy', 'published_at', 'DESC')
|
||||
.query('orderBy', 'updated_at', 'DESC')
|
||||
.query('orderBy', 'id', 'DESC')
|
||||
.fetch(_.omit(options, 'page', 'limit'));
|
||||
.query('orderBy', 'id', 'DESC');
|
||||
|
||||
// Find the total number of posts
|
||||
|
||||
qb = ghostBookshelf.knex('posts');
|
||||
|
||||
if (options.where) {
|
||||
qb.where(options.where);
|
||||
}
|
||||
|
||||
if (tagInstance) {
|
||||
qb.join('posts_tags', 'posts_tags.post_id', '=', 'posts.id');
|
||||
qb.where('posts_tags.tag_id', '=', tagInstance.id);
|
||||
}
|
||||
if (authorInstance) {
|
||||
qb.where('author_id', '=', authorInstance.id);
|
||||
}
|
||||
|
||||
countPromise = qb.count('posts.id as aggregate');
|
||||
// Step 4: Setup the promise
|
||||
collectionPromise = postCollection.fetch(_.omit(options, 'page', 'limit'));
|
||||
|
||||
// -- Part 3 --
|
||||
// Step 1: Fetch the data
|
||||
return Promise.join(collectionPromise, countPromise);
|
||||
}).then(function then(results) {
|
||||
}).then(function formatResponse(results) {
|
||||
var postCollection = results[0],
|
||||
data = {};
|
||||
|
||||
// Step 2: Format the data
|
||||
data.posts = postCollection.toJSON(options);
|
||||
data.meta = {pagination: paginateResponse(results[1][0].aggregate, options)};
|
||||
|
||||
|
@ -504,8 +501,7 @@ Post = ghostBookshelf.Model.extend({
|
|||
}
|
||||
|
||||
return data;
|
||||
})
|
||||
.catch(errors.logAndThrowError);
|
||||
}).catch(errors.logAndThrowError);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -110,10 +110,17 @@ Tag = ghostBookshelf.Model.extend({
|
|||
findPage: function findPage(options) {
|
||||
options = options || {};
|
||||
|
||||
// -- Part 0 --
|
||||
// Step 1: Setup filter models
|
||||
// Step 2: Setup filter model promises
|
||||
// Step 3: Prefetch filter models
|
||||
|
||||
// -- Part 1 --
|
||||
var tagCollection = Tags.forge(),
|
||||
collectionPromise,
|
||||
qb;
|
||||
countPromise;
|
||||
|
||||
// Step 1: Setup pagination options
|
||||
if (options.limit && options.limit !== 'all') {
|
||||
options.limit = parseInt(options.limit, 10) || 15;
|
||||
}
|
||||
|
@ -122,42 +129,60 @@ Tag = ghostBookshelf.Model.extend({
|
|||
options.page = parseInt(options.page, 10) || 1;
|
||||
}
|
||||
|
||||
// Step 2: Filter options
|
||||
options = this.filterOptions(options, 'findPage');
|
||||
// Set default settings for options
|
||||
|
||||
// Step 3: Extend defaults
|
||||
options = _.extend({
|
||||
page: 1, // pagination page
|
||||
limit: 15,
|
||||
where: {}
|
||||
}, options);
|
||||
|
||||
// only include a limit-query if a numeric limit is provided
|
||||
// Step 4: Setup filters (where clauses)
|
||||
// If there are where conditionals specified, add those to the query.
|
||||
if (options.where) {
|
||||
tagCollection.query('where', options.where);
|
||||
}
|
||||
|
||||
// Step 5: Setup joins
|
||||
|
||||
// Step 6: Setup the counter to fetch the number of items in the set
|
||||
// @TODO abstract this out
|
||||
// tableName = _.result(postCollection, 'tableName'),
|
||||
// idAttribute = _.result(postCollection, 'idAttribute');
|
||||
countPromise = tagCollection.query().clone().count('tags.id as aggregate');
|
||||
|
||||
// -- Part 2 --
|
||||
// Add limit, offset and other query changes which aren't required when performing a count
|
||||
|
||||
// Step 1: Add related objects
|
||||
addPostCount(options, tagCollection);
|
||||
options.withRelated = _.union(options.withRelated, options.include);
|
||||
|
||||
// Step 2: Add pagination options if needed
|
||||
if (_.isNumber(options.limit)) {
|
||||
tagCollection
|
||||
.query('limit', options.limit)
|
||||
.query('offset', options.limit * (options.page - 1));
|
||||
}
|
||||
|
||||
addPostCount(options, tagCollection);
|
||||
// Step 3: add order parameters
|
||||
|
||||
// Step 4: Setup the promise
|
||||
collectionPromise = tagCollection.fetch(_.omit(options, 'page', 'limit'));
|
||||
|
||||
// Find total number of tags
|
||||
qb = ghostBookshelf.knex('tags');
|
||||
|
||||
if (options.where) {
|
||||
qb.where(options.where);
|
||||
}
|
||||
|
||||
return Promise.join(collectionPromise, qb.count('tags.id as aggregate')).then(function then(results) {
|
||||
// -- Part 3 --
|
||||
// Step 1: Fetch the data
|
||||
return Promise.join(collectionPromise, countPromise).then(function formatResponse(results) {
|
||||
var tagCollection = results[0],
|
||||
data = {};
|
||||
|
||||
// Step 2: Format the data
|
||||
data.tags = tagCollection.toJSON(options);
|
||||
data.meta = {pagination: paginateResponse(results[1][0].aggregate, options)};
|
||||
|
||||
return data;
|
||||
})
|
||||
.catch(errors.logAndThrowError);
|
||||
}).catch(errors.logAndThrowError);
|
||||
},
|
||||
destroy: function destroy(options) {
|
||||
var id = options.id;
|
||||
|
|
|
@ -225,9 +225,27 @@ User = ghostBookshelf.Model.extend({
|
|||
findPage: function findPage(options) {
|
||||
options = options || {};
|
||||
|
||||
var userCollection = Users.forge(),
|
||||
// -- Part 0 --
|
||||
// Step 1: Setup filter models
|
||||
var self = this,
|
||||
roleInstance = options.role !== undefined ? ghostBookshelf.model('Role').forge({name: options.role}) : false;
|
||||
|
||||
// Step 2: Setup filter model promises
|
||||
function fetchRoleQuery() {
|
||||
if (roleInstance) {
|
||||
return roleInstance.fetch();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Prefetch filter models
|
||||
return Promise.resolve(fetchRoleQuery()).then(function setupCollectionPromises() {
|
||||
// -- Part 1 --
|
||||
var userCollection = Users.forge(),
|
||||
collectionPromise,
|
||||
countPromise;
|
||||
|
||||
// Step 1: Setup pagination options
|
||||
if (options.limit && options.limit !== 'all') {
|
||||
options.limit = parseInt(options.limit, 10) || 15;
|
||||
}
|
||||
|
@ -236,9 +254,10 @@ User = ghostBookshelf.Model.extend({
|
|||
options.page = parseInt(options.page, 10) || 1;
|
||||
}
|
||||
|
||||
options = this.filterOptions(options, 'findPage');
|
||||
// Step 2: Filter options
|
||||
options = self.filterOptions(options, 'findPage');
|
||||
|
||||
// Set default settings for options
|
||||
// Step 3: Extend defaults
|
||||
options = _.extend({
|
||||
page: 1, // pagination page
|
||||
limit: 15,
|
||||
|
@ -247,6 +266,7 @@ User = ghostBookshelf.Model.extend({
|
|||
whereIn: {}
|
||||
}, options);
|
||||
|
||||
// Step 4: Setup filters (where clauses)
|
||||
// TODO: there are multiple statuses that make a user "active" or "invited" - we a way to translate/map them:
|
||||
// TODO (cont'd from above): * valid "active" statuses: active, warn-1, warn-2, warn-3, warn-4, locked
|
||||
// TODO (cont'd from above): * valid "invited" statuses" invited, invited-pending
|
||||
|
@ -268,72 +288,54 @@ User = ghostBookshelf.Model.extend({
|
|||
options.where.status = options.status;
|
||||
}
|
||||
|
||||
// If there are where conditionals specified, add those
|
||||
// to the query.
|
||||
// If there are where conditionals specified, add those to the query.
|
||||
if (options.where) {
|
||||
userCollection.query('where', options.where);
|
||||
}
|
||||
|
||||
// Add related objects
|
||||
options.withRelated = _.union(options.withRelated, options.include);
|
||||
|
||||
// only include a limit-query if a numeric limit is provided
|
||||
if (_.isNumber(options.limit)) {
|
||||
userCollection
|
||||
.query('limit', options.limit)
|
||||
.query('offset', options.limit * (options.page - 1));
|
||||
}
|
||||
|
||||
function fetchRoleQuery() {
|
||||
if (roleInstance) {
|
||||
return roleInstance.fetch();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return Promise.resolve(fetchRoleQuery())
|
||||
.then(function then() {
|
||||
function fetchCollection() {
|
||||
// Step 5: Setup joins
|
||||
if (roleInstance) {
|
||||
userCollection
|
||||
.query('join', 'roles_users', 'roles_users.user_id', '=', 'users.id')
|
||||
.query('where', 'roles_users.role_id', '=', roleInstance.id);
|
||||
}
|
||||
|
||||
return userCollection
|
||||
// Step 6: Setup the counter to fetch the number of items in the set
|
||||
// @TODO abstract this out
|
||||
// tableName = _.result(userCollection, 'tableName'),
|
||||
// idAttribute = _.result(userCollection, 'idAttribute');
|
||||
countPromise = userCollection.query().clone().count('users.id as aggregate');
|
||||
|
||||
// -- Part 2 --
|
||||
// Add limit, offset and other query changes which aren't required when performing a count
|
||||
|
||||
// Step 1: Add related objects
|
||||
options.withRelated = _.union(options.withRelated, options.include);
|
||||
|
||||
// Step 2: Add pagination options if needed
|
||||
if (_.isNumber(options.limit)) {
|
||||
userCollection
|
||||
.query('limit', options.limit)
|
||||
.query('offset', options.limit * (options.page - 1));
|
||||
}
|
||||
|
||||
// Step 3: add order parameters
|
||||
userCollection
|
||||
.query('orderBy', 'last_login', 'DESC')
|
||||
.query('orderBy', 'name', 'ASC')
|
||||
.query('orderBy', 'created_at', 'DESC')
|
||||
.fetch(_.omit(options, 'page', 'limit'));
|
||||
}
|
||||
.query('orderBy', 'created_at', 'DESC');
|
||||
|
||||
function fetchPaginationData() {
|
||||
var qb,
|
||||
tableName = _.result(userCollection, 'tableName'),
|
||||
idAttribute = _.result(userCollection, 'idAttribute');
|
||||
// Step 4: Setup the promise
|
||||
collectionPromise = userCollection.fetch(_.omit(options, 'page', 'limit'));
|
||||
|
||||
// After we're done, we need to figure out what
|
||||
// the limits are for the pagination values.
|
||||
qb = ghostBookshelf.knex(tableName);
|
||||
|
||||
if (options.where) {
|
||||
qb.where(options.where);
|
||||
}
|
||||
|
||||
if (roleInstance) {
|
||||
qb.join('roles_users', 'roles_users.user_id', '=', 'users.id');
|
||||
qb.where('roles_users.role_id', '=', roleInstance.id);
|
||||
}
|
||||
|
||||
return qb.count(tableName + '.' + idAttribute + ' as aggregate');
|
||||
}
|
||||
|
||||
return Promise.join(fetchCollection(), fetchPaginationData());
|
||||
})
|
||||
// Format response of data
|
||||
.then(function then(results) {
|
||||
var data = {};
|
||||
// -- Part 3 --
|
||||
// Step 1: Fetch the data
|
||||
return Promise.join(collectionPromise, countPromise);
|
||||
}).then(function formatResponse(results) {
|
||||
var userCollection = results[0],
|
||||
data = {};
|
||||
|
||||
// Step 2: Format the data
|
||||
data.users = userCollection.toJSON(options);
|
||||
data.meta = {pagination: paginateResponse(results[1][0].aggregate, options)};
|
||||
|
||||
|
@ -345,8 +347,7 @@ User = ghostBookshelf.Model.extend({
|
|||
}
|
||||
|
||||
return data;
|
||||
})
|
||||
.catch(errors.logAndThrowError);
|
||||
}).catch(errors.logAndThrowError);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -370,6 +371,7 @@ User = ghostBookshelf.Model.extend({
|
|||
|
||||
options = options || {};
|
||||
options.withRelated = _.union(options.withRelated, options.include);
|
||||
data = this.filterData(data);
|
||||
|
||||
// Support finding by role
|
||||
if (lookupRole) {
|
||||
|
@ -386,8 +388,6 @@ User = ghostBookshelf.Model.extend({
|
|||
query = this.forge(data, {include: options.include});
|
||||
}
|
||||
|
||||
data = this.filterData(data);
|
||||
|
||||
if (status === 'active') {
|
||||
query.query('whereIn', 'status', activeStates);
|
||||
} else if (status === 'invited') {
|
||||
|
|
Loading…
Add table
Reference in a new issue