diff --git a/README.md b/README.md index 97555f7a..21859f55 100644 --- a/README.md +++ b/README.md @@ -348,12 +348,14 @@ The `options` param can have the following (optional) fields: ```javascript const options = { - sortBy: 'name:desc', - limit: 5, - page: 2, + sortBy: 'name:desc', // sort order + limit: 5, // maximum results per page + page: 2, // page number }; ``` +The plugin also supports sorting by multiple criteria (separated by a comma): `sortBy: name:desc,role:asc` + The `paginate` method returns a Promise, which fulfills with an object having the following properties: ```json diff --git a/src/models/plugins/paginate.plugin.js b/src/models/plugins/paginate.plugin.js index 073ec887..20f58d35 100644 --- a/src/models/plugins/paginate.plugin.js +++ b/src/models/plugins/paginate.plugin.js @@ -13,17 +13,22 @@ const paginate = (schema) => { * Query for documents with pagination * @param {Object} [filter] - Mongo filter * @param {Object} [options] - Query options - * @param {string} [options.sortBy] - Sort option in the format: sortField:(desc|asc) + * @param {string} [options.sortBy] - Sorting criteria using the format: sortField:(desc|asc). Multiple sorting criteria should be separated by commas (,) * @param {number} [options.limit] - Maximum number of results per page (default = 10) * @param {number} [options.page] - Current page (default = 1) * @returns {Promise} */ schema.statics.paginate = async function (filter, options) { - const sort = {}; + let sort = ''; if (options.sortBy) { - const parts = options.sortBy.split(':'); - sort[parts[0]] = parts[1] === 'desc' ? -1 : 1; + const sortingCriteria = []; + options.sortBy.split(',').forEach((sortOption) => { + const [key, order] = sortOption.split(':'); + sortingCriteria.push((order === 'desc' ? '-' : '') + key); + }); + sort = sortingCriteria.join(' '); } + const limit = options.limit && parseInt(options.limit, 10) > 0 ? parseInt(options.limit, 10) : 10; const page = options.page && parseInt(options.page, 10) > 0 ? parseInt(options.page, 10) : 1; const skip = (page - 1) * limit; diff --git a/tests/integration/user.test.js b/tests/integration/user.test.js index 6edfa07f..0e7c256e 100644 --- a/tests/integration/user.test.js +++ b/tests/integration/user.test.js @@ -219,7 +219,7 @@ describe('User routes', () => { expect(res.body.results[1].id).toBe(userTwo._id.toHexString()); }); - test('should correctly sort returned array if descending sort param is specified', async () => { + test('should correctly sort the returned array if descending sort param is specified', async () => { await insertUsers([userOne, userTwo, admin]); const res = await request(app) @@ -242,7 +242,7 @@ describe('User routes', () => { expect(res.body.results[2].id).toBe(admin._id.toHexString()); }); - test('should correctly sort returned array if ascending sort param is specified', async () => { + test('should correctly sort the returned array if ascending sort param is specified', async () => { await insertUsers([userOne, userTwo, admin]); const res = await request(app) @@ -265,6 +265,40 @@ describe('User routes', () => { expect(res.body.results[2].id).toBe(userTwo._id.toHexString()); }); + test('should correctly sort the returned array if multiple sorting criteria are specified', async () => { + await insertUsers([userOne, userTwo, admin]); + + const res = await request(app) + .get('/v1/users') + .set('Authorization', `Bearer ${adminAccessToken}`) + .query({ sortBy: 'role:desc,name:asc' }) + .send() + .expect(httpStatus.OK); + + expect(res.body).toEqual({ + results: expect.any(Array), + page: 1, + limit: 10, + totalPages: 1, + totalResults: 3, + }); + expect(res.body.results).toHaveLength(3); + + const expectedOrder = [userOne, userTwo, admin].sort((a, b) => { + if (a.role < b.role) { + return 1; + } + if (a.role > b.role) { + return -1; + } + return a.name < b.name ? -1 : 1; + }); + + expectedOrder.forEach((user, index) => { + expect(res.body.results[index].id).toBe(user._id.toHexString()); + }); + }); + test('should limit returned array if limit param is specified', async () => { await insertUsers([userOne, userTwo, admin]);