From 9cf953553556bc5060821dc630a2d2d5e12da37f Mon Sep 17 00:00:00 2001 From: Samkit Jain Date: Fri, 4 Dec 2020 10:56:11 +0530 Subject: [PATCH 1/3] Added support for populate data --- src/models/plugins/paginate.plugin.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/models/plugins/paginate.plugin.js b/src/models/plugins/paginate.plugin.js index 392ccd50..b42745c3 100644 --- a/src/models/plugins/paginate.plugin.js +++ b/src/models/plugins/paginate.plugin.js @@ -13,6 +13,7 @@ const paginate = (schema) => { * Query for documents with pagination * @param {Object} [filter] - Mongo filter * @param {Object} [options] - Query options + * @param {string} [options.populate] - populate data fields. Hierarchy of fields should be separated by (.). Multiple sorting criteria should be separated by commas (,) * @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) @@ -36,7 +37,20 @@ const paginate = (schema) => { const skip = (page - 1) * limit; const countPromise = this.countDocuments(filter).exec(); - const docsPromise = this.find(filter).sort(sort).skip(skip).limit(limit).exec(); + let docsPromise = this.find(filter).sort(sort).skip(skip).limit(limit); + + if (options.populate) { + options.populate.split(',').forEach((populateOption) => { + docsPromise = docsPromise.populate( + populateOption + .split('.') + .reverse() + .reduce((a, b) => ({ path: b, populate: a })) + ); + }); + } + + docsPromise = docsPromise.exec(); return Promise.all([countPromise, docsPromise]).then((values) => { const [totalResults, results] = values; From 18e8f79e75a083967e0bfb4a281f5d3fe300f0da Mon Sep 17 00:00:00 2001 From: Samkit Jain Date: Fri, 25 Dec 2020 11:24:19 +0530 Subject: [PATCH 2/3] Added test case to validate populate functionality --- README.md | 1 + src/controllers/user.controller.js | 2 +- src/models/user.model.js | 4 ++++ src/services/user.service.js | 1 + src/validations/user.validation.js | 1 + tests/integration/user.test.js | 18 ++++++++++++++++++ 6 files changed, 26 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8f24386d..e6467c76 100644 --- a/README.md +++ b/README.md @@ -348,6 +348,7 @@ The `options` param can have the following (optional) fields: ```javascript const options = { + populate: 'manager', //populate manager sortBy: 'name:desc', // sort order limit: 5, // maximum results per page page: 2, // page number diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js index 66b83fa7..0431ed4c 100644 --- a/src/controllers/user.controller.js +++ b/src/controllers/user.controller.js @@ -11,7 +11,7 @@ const createUser = catchAsync(async (req, res) => { const getUsers = catchAsync(async (req, res) => { const filter = pick(req.query, ['name', 'role']); - const options = pick(req.query, ['sortBy', 'limit', 'page']); + const options = pick(req.query, ['populate', 'sortBy', 'limit', 'page']); const result = await userService.queryUsers(filter, options); res.send(result); }); diff --git a/src/models/user.model.js b/src/models/user.model.js index df877935..7f008569 100644 --- a/src/models/user.model.js +++ b/src/models/user.model.js @@ -40,6 +40,10 @@ const userSchema = mongoose.Schema( enum: roles, default: 'user', }, + manager: { + type: mongoose.SchemaTypes.ObjectId, + ref: 'User', + }, }, { timestamps: true, diff --git a/src/services/user.service.js b/src/services/user.service.js index 3488a715..cc64b69b 100644 --- a/src/services/user.service.js +++ b/src/services/user.service.js @@ -19,6 +19,7 @@ const createUser = async (userBody) => { * Query for users * @param {Object} filter - Mongo filter * @param {Object} options - Query options + * @param {string} [options.populate] - Populate data fields. Hierarchy of fields should be separated by (.). Multiple sorting criteria should be separated by commas (,) * @param {string} [options.sortBy] - Sort option in the format: sortField:(desc|asc) * @param {number} [options.limit] - Maximum number of results per page (default = 10) * @param {number} [options.page] - Current page (default = 1) diff --git a/src/validations/user.validation.js b/src/validations/user.validation.js index 1be6ce41..1a938ccc 100644 --- a/src/validations/user.validation.js +++ b/src/validations/user.validation.js @@ -14,6 +14,7 @@ const getUsers = { query: Joi.object().keys({ name: Joi.string(), role: Joi.string(), + populate: Joi.string(), sortBy: Joi.string(), limit: Joi.number().integer(), page: Joi.number().integer(), diff --git a/tests/integration/user.test.js b/tests/integration/user.test.js index 0e7c256e..7b108cf5 100644 --- a/tests/integration/user.test.js +++ b/tests/integration/user.test.js @@ -343,6 +343,24 @@ describe('User routes', () => { }); }); + test('should return the populated managers', async () => { + await insertUsers([admin]); + await insertUsers([ + { ...userOne, manager: admin._id }, + { ...userTwo, manager: admin._id }, + ]); + + const res = await request(app) + .get('/v1/users') + .set('Authorization', `Bearer ${adminAccessToken}`) + .query({ populate: 'manager' }) + .send() + .expect(httpStatus.OK); + + expect(res.body.results[1].manager.id).toBe(admin._id.toHexString()); + expect(res.body.results[2].manager.id).toBe(admin._id.toHexString()); + }); + describe('GET /v1/users/:userId', () => { test('should return 200 and the user object if data is ok', async () => { await insertUsers([userOne]); From 7967febe2adfc891c3cd38737d627b0f13cbdafc Mon Sep 17 00:00:00 2001 From: Samkit Jain Date: Sat, 26 Dec 2020 17:26:40 +0530 Subject: [PATCH 3/3] Revert "Added test case to validate populate functionality" This reverts commit 18e8f79e --- README.md | 1 - src/controllers/user.controller.js | 2 +- src/models/user.model.js | 4 ---- src/services/user.service.js | 1 - src/validations/user.validation.js | 1 - tests/integration/user.test.js | 18 ------------------ 6 files changed, 1 insertion(+), 26 deletions(-) diff --git a/README.md b/README.md index e6467c76..8f24386d 100644 --- a/README.md +++ b/README.md @@ -348,7 +348,6 @@ The `options` param can have the following (optional) fields: ```javascript const options = { - populate: 'manager', //populate manager sortBy: 'name:desc', // sort order limit: 5, // maximum results per page page: 2, // page number diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js index 0431ed4c..66b83fa7 100644 --- a/src/controllers/user.controller.js +++ b/src/controllers/user.controller.js @@ -11,7 +11,7 @@ const createUser = catchAsync(async (req, res) => { const getUsers = catchAsync(async (req, res) => { const filter = pick(req.query, ['name', 'role']); - const options = pick(req.query, ['populate', 'sortBy', 'limit', 'page']); + const options = pick(req.query, ['sortBy', 'limit', 'page']); const result = await userService.queryUsers(filter, options); res.send(result); }); diff --git a/src/models/user.model.js b/src/models/user.model.js index 7f008569..df877935 100644 --- a/src/models/user.model.js +++ b/src/models/user.model.js @@ -40,10 +40,6 @@ const userSchema = mongoose.Schema( enum: roles, default: 'user', }, - manager: { - type: mongoose.SchemaTypes.ObjectId, - ref: 'User', - }, }, { timestamps: true, diff --git a/src/services/user.service.js b/src/services/user.service.js index cc64b69b..3488a715 100644 --- a/src/services/user.service.js +++ b/src/services/user.service.js @@ -19,7 +19,6 @@ const createUser = async (userBody) => { * Query for users * @param {Object} filter - Mongo filter * @param {Object} options - Query options - * @param {string} [options.populate] - Populate data fields. Hierarchy of fields should be separated by (.). Multiple sorting criteria should be separated by commas (,) * @param {string} [options.sortBy] - Sort option in the format: sortField:(desc|asc) * @param {number} [options.limit] - Maximum number of results per page (default = 10) * @param {number} [options.page] - Current page (default = 1) diff --git a/src/validations/user.validation.js b/src/validations/user.validation.js index 1a938ccc..1be6ce41 100644 --- a/src/validations/user.validation.js +++ b/src/validations/user.validation.js @@ -14,7 +14,6 @@ const getUsers = { query: Joi.object().keys({ name: Joi.string(), role: Joi.string(), - populate: Joi.string(), sortBy: Joi.string(), limit: Joi.number().integer(), page: Joi.number().integer(), diff --git a/tests/integration/user.test.js b/tests/integration/user.test.js index 7b108cf5..0e7c256e 100644 --- a/tests/integration/user.test.js +++ b/tests/integration/user.test.js @@ -343,24 +343,6 @@ describe('User routes', () => { }); }); - test('should return the populated managers', async () => { - await insertUsers([admin]); - await insertUsers([ - { ...userOne, manager: admin._id }, - { ...userTwo, manager: admin._id }, - ]); - - const res = await request(app) - .get('/v1/users') - .set('Authorization', `Bearer ${adminAccessToken}`) - .query({ populate: 'manager' }) - .send() - .expect(httpStatus.OK); - - expect(res.body.results[1].manager.id).toBe(admin._id.toHexString()); - expect(res.body.results[2].manager.id).toBe(admin._id.toHexString()); - }); - describe('GET /v1/users/:userId', () => { test('should return 200 and the user object if data is ok', async () => { await insertUsers([userOne]);