From 92c43a2f094cca2ea374d0d36f477a427ebfe8af Mon Sep 17 00:00:00 2001 From: Deepak Date: Thu, 28 Nov 2019 16:41:30 +0530 Subject: [PATCH 1/3] feat: project member & invites endpoint with additional fields Added/updated following endpoints to get user information with additional data: 1. project member by id 2. list of all invites inside the project 3. authenticated user invite details --- src/permissions/index.js | 2 + src/routes/index.js | 4 + src/routes/projectMemberInvites/get.js | 62 +++++++---- src/routes/projectMemberInvites/list.js | 46 ++++++++ src/routes/projectMembers/get.js | 45 ++++++++ src/routes/projectMembers/list.js | 142 +++++------------------- src/util.js | 59 +++++++++- 7 files changed, 223 insertions(+), 137 deletions(-) create mode 100644 src/routes/projectMemberInvites/list.js create mode 100644 src/routes/projectMembers/get.js diff --git a/src/permissions/index.js b/src/permissions/index.js index 2aa52d77..9af747b9 100644 --- a/src/permissions/index.js +++ b/src/permissions/index.js @@ -21,6 +21,7 @@ module.exports = () => { Authorizer.setPolicy('project.view', projectView); Authorizer.setPolicy('project.edit', projectEdit); Authorizer.setPolicy('project.delete', projectDelete); + Authorizer.setPolicy('project.getMember', projectView); Authorizer.setPolicy('project.addMember', projectView); Authorizer.setPolicy('project.listMembers', projectView); Authorizer.setPolicy('project.removeMember', projectMemberDelete); @@ -86,6 +87,7 @@ module.exports = () => { Authorizer.setPolicy('projectMemberInvite.create', projectView); Authorizer.setPolicy('projectMemberInvite.put', true); Authorizer.setPolicy('projectMemberInvite.get', true); + Authorizer.setPolicy('projectMemberInvite.list', projectView); Authorizer.setPolicy('form.create', projectAdmin); Authorizer.setPolicy('form.edit', projectAdmin); diff --git a/src/routes/index.js b/src/routes/index.js index b91ea0e3..cd578fd1 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -123,6 +123,7 @@ router.route('/v4/projects/:projectId(\\d+)/members') .post(require('./projectMembers/create')); router.route('/v4/projects/:projectId(\\d+)/members/:id(\\d+)') + .get(require('./projectMembers/get')) .delete(require('./projectMembers/delete')) .patch(require('./projectMembers/update')); @@ -230,6 +231,9 @@ router.route('/v4/timelines/metadata/milestoneTemplates/:milestoneTemplateId(\\d .patch(require('./milestoneTemplates/update')) .delete(require('./milestoneTemplates/delete')); +router.route('/v4/projects/:projectId(\\d+)/members/invites') + .get(require('./projectMemberInvites/list')); + router.route('/v4/projects/:projectId(\\d+)/members/invite') .post(require('./projectMemberInvites/create')) .put(require('./projectMemberInvites/update')) diff --git a/src/routes/projectMemberInvites/get.js b/src/routes/projectMemberInvites/get.js index 8de0fd5a..dafa5993 100644 --- a/src/routes/projectMemberInvites/get.js +++ b/src/routes/projectMemberInvites/get.js @@ -1,6 +1,6 @@ - - import _ from 'lodash'; +import Joi from 'joi'; +import validate from 'express-validation'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; @@ -9,27 +9,49 @@ import util from '../../util'; * API to update invite member to project. * */ +const schema = { + query: { + fields: Joi.string().optional(), + }, +}; const permissions = tcMiddleware.permissions; module.exports = [ - // handles request validations + validate(schema), permissions('projectMemberInvite.get'), - (req, res, next) => { - const projectId = _.parseInt(req.params.projectId); - const currentUserId = req.authUser.userId; - let invite; - return models.ProjectMemberInvite.getPendingInviteByEmailOrUserId(projectId, req.authUser.email, currentUserId) - .then((_invite) => { - invite = _invite; - if (!invite) { - // check there is an existing invite for the user with status PENDING - // handle 404 - const err = new Error('invite not found for project id ' + - `${projectId}, userId ${currentUserId}, email ${req.authUser.email}`); - err.status = 404; - return next(err); - } - return res.json(util.wrapResponse(req.id, invite)); - }); + async (req, res, next) => { + try { + const projectId = _.parseInt(req.params.projectId); + const currentUserId = req.authUser.userId; + const memberFields = _.keys(models.ProjectMemberInvite.attributes); + const invite = await models.ProjectMemberInvite.getPendingInviteByEmailOrUserId( + projectId, req.authUser.email, currentUserId, + ); + if (!invite) { + // check there is an existing invite for the user with status PENDING + // handle 404 + const err = new Error( + 'invite not found for project id ' + + `${projectId}, userId ${currentUserId}, email ${req.authUser.email}`, + ); + err.status = 404; + throw err; + } + + let fields = null; + if (req.query.fields) { + fields = req.query.fields.split(','); + } + const opts = { + logger: req.log, + requestId: req.id, + memberFields, + }; + const inviteWithDetails = await util.getObjectsWithMemberDetails(invite, fields, opts); + + return res.json(util.wrapResponse(req.id, inviteWithDetails)); + } catch (err) { + return next(err); + } }, ]; diff --git a/src/routes/projectMemberInvites/list.js b/src/routes/projectMemberInvites/list.js new file mode 100644 index 00000000..cb4e949f --- /dev/null +++ b/src/routes/projectMemberInvites/list.js @@ -0,0 +1,46 @@ +import _ from 'lodash'; +import Joi from 'joi'; +import validate from 'express-validation'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; +import util from '../../util'; + +/** + * API to list all project member invites. + * + */ +const permissions = tcMiddleware.permissions; + +const schema = { + query: { + fields: Joi.string().optional(), + }, +}; + +module.exports = [ + validate(schema), + permissions('projectMemberInvite.list'), + async (req, res, next) => { + try { + let fields = null; + if (req.query.fields) { + fields = req.query.fields.split(','); + } + const memberFields = _.keys(models.ProjectMemberInvite.attributes); + const projectId = _.parseInt(req.params.projectId); + const invites = await models.ProjectMemberInvite.findAll({ + where: { projectId }, + raw: true, + }); + const opts = { + logger: req.log, + requestId: req.id, + memberFields, + }; + const invitesWithDetails = await util.getObjectsWithMemberDetails(invites, fields, opts); + return res.json(util.wrapResponse(req.id, invitesWithDetails)); + } catch (err) { + return next(err); + } + }, +]; diff --git a/src/routes/projectMembers/get.js b/src/routes/projectMembers/get.js new file mode 100644 index 00000000..07a9eca5 --- /dev/null +++ b/src/routes/projectMembers/get.js @@ -0,0 +1,45 @@ + + +import _ from 'lodash'; +import Joi from 'joi'; +import validate from 'express-validation'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; +import util from '../../util'; + +/** + * API to get a project member in a project. + */ +const permissions = tcMiddleware.permissions; + +const schema = { + query: { + fields: Joi.string().optional(), + }, +}; + +module.exports = [ + validate(schema), + permissions('project.getMember'), + async (req, res, next) => { + try { + let fields = null; + if (req.query.fields) { + fields = req.query.fields.split(','); + } + const memberFields = _.keys(models.ProjectMember.attributes); + const memberId = _.parseInt(req.params.id); + const members = [_.find(req.context.currentProjectMembers, user => user.id === memberId)]; + const [member] = await util.getObjectsWithMemberDetails( + members, fields, { + logger: req.log, + requestId: req.id, + memberFields, + }, + ); + return res.json(util.wrapResponse(req.id, member)); + } catch (err) { + return next(err); + } + }, +]; diff --git a/src/routes/projectMembers/list.js b/src/routes/projectMembers/list.js index bb643b08..8a87a1c6 100644 --- a/src/routes/projectMembers/list.js +++ b/src/routes/projectMembers/list.js @@ -1,124 +1,42 @@ -/** - * Endpoint to list project members. - */ import _ from 'lodash'; +import Joi from 'joi'; +import validate from 'express-validation'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; import util from '../../util'; +/** + * API to list all project members. + * + */ const permissions = tcMiddleware.permissions; +const schema = { + query: { + fields: Joi.string().optional(), + }, +}; + module.exports = [ + validate(schema), permissions('project.listMembers'), - async (req, res) => { - let members = req.context.currentProjectMembers; - - - if (members.length && _.get(req, 'query.fields')) { - const fields = req.query.fields.split(','); - - const ModelFields = [ - 'id', - 'userId', - 'role', - 'isPrimary', - 'deletedAt', - 'createdAt', - 'updatedAt', - 'deletedBy', - 'createdBy', - 'updatedBy', - ]; - - const modelFields = _.intersection(ModelFields, fields); - const hasUserIdField = _.indexOf(fields, 'userId') !== -1; - if (hasUserIdField === false) { - modelFields.push('userId'); - } - // merge model fields - members = _.map(members, m => _.pick(m, modelFields)); - - const MemberDetailFields = ['handle', 'firstName', 'lastName']; - const memberDetailFields = _.intersection(MemberDetailFields, fields); - - // has handleField - const hasHandleField = _.indexOf(memberDetailFields, 'handle') !== -1; - - if (hasHandleField === false) { - memberDetailFields.push('handle'); - } - - if (memberDetailFields.length) { - const userIds = _.map(members, m => `userId:${m.userId}`) || []; - - const logger = req.log; - try { - const memberDetails = await util.getMemberDetailsByUserIds(userIds, logger, req.id); - - // merge detail fields - _.forEach(members, (m) => { - const detail = _.find(memberDetails, mt => mt.userId === m.userId); - if (detail) { - _.assign(m, _.pick(detail, memberDetailFields)); - } - }); - - - const TraitFields = ['photoURL', 'workingHourStart', 'workingHourEnd', 'timeZone']; - const traitFields = _.intersection(TraitFields, fields); - if (traitFields.length) { - const promises = []; - _.forEach(members, (m) => { - if (m.handle) { - promises.push(util.getMemberTratisByHandle(m.handle, req.log, req.id)); - } - }); - - const traits = await Promise.all(promises); - - // remove photoURL, because connect_info also has photoURL - const ConnectInfoFields = ['workingHourStart', 'workingHourEnd', 'timeZone']; - const connectInfoFields = _.intersection(ConnectInfoFields, fields); - - // merge traits - _.forEach(members, (m) => { - const traitsArr = _.find(traits, t => t[0].userId === m.userId); - if (traitsArr) { - if (traitFields[0] === 'photoURL') { - _.assign(m, { - photoURL: _.get(_.find(traitsArr, { traitId: 'basic_info' }), 'traits.data[0].photoURL'), - }); - if (traitFields.length > 1) { - const traitInfo = _.get(_.find(traitsArr, { traitId: 'connect_info' }), 'traits.data[0]', {}); - _.assign(m, _.pick(traitInfo, connectInfoFields)); - } - } else { - const traitInfo = _.get(_.find(traitsArr, { traitId: 'connect_info' }), 'traits.data[0]', {}); - _.assign(m, _.pick(traitInfo, connectInfoFields)); - } - } - }); - } - } catch (e) { - logger.error('Error getting member details', e); - if (hasUserIdField === false) { - members = _.map(members, m => _.omit(m, ['userId'])); - } - if (hasHandleField === false) { - members = _.map(members, m => _.omit(m, ['handle'])); - } - return res.json(util.wrapResponse(req.id, members)); - } - } - - if (hasUserIdField === false) { - members = _.map(members, m => _.omit(m, ['userId'])); - } - if (hasHandleField === false) { - members = _.map(members, m => _.omit(m, ['handle'])); - } + async (req, res, next) => { + let fields = null; + if (req.query.fields) { + fields = req.query.fields.split(','); + } + try { + const memberFields = _.keys(models.ProjectMember.attributes); + const members = await util.getObjectsWithMemberDetails( + req.context.currentProjectMembers, fields, { + logger: req.log, + requestId: req.id, + memberFields, + }, + ); return res.json(util.wrapResponse(req.id, members)); + } catch (err) { + return next(err); } - - return res.json(util.wrapResponse(req.id, req.context.currentProjectMembers)); }, ]; diff --git a/src/util.js b/src/util.js index 11e139c6..514b7ae7 100644 --- a/src/util.js +++ b/src/util.js @@ -393,12 +393,9 @@ _.assignIn(util, { }, /** - * Retrieve member traitbyhandle - * @param {string} handle - * @param {Object} logger req.logger - * @return {string} requestId + * Retrieve member traits from user handle */ - getMemberTratisByHandle: Promise.coroutine(function* (handle, logger, requestId) { // eslint-disable-line func-names + getMemberTraitsByHandle: Promise.coroutine(function* (handle, logger, requestId) { // eslint-disable-line func-names try { const token = yield this.getM2MToken(); const httpClient = this.getHttpClient({ id: requestId, log: logger }); @@ -443,6 +440,58 @@ _.assignIn(util, { } }), + /** + * Filter member details by input fields + * + * @param {Array} members Array of member detail objects + * @param {Array} fields Array of fields to be used to filter member objects + * @param {Object} opts logger & request id + * + * @return {Array} Filtered array of member detail objects + */ + getObjectsWithMemberDetails: async (members, fields, opts) => { + if (!fields || _.isEmpty(fields)) { + return members; + } + const { logger, requestId, memberFields } = opts; + const requestedNonMemberFields = _.filter(fields, field => !_.includes(memberFields, field)); + const otherMemberFields = ['photoURL', 'workingHoursStart', 'workingHoursEnd', 'timeZone']; + + let allMemberDetails = []; + if (requestedNonMemberFields.length > 0) { + const userIds = _.map(members, 'userId'); + allMemberDetails = await util.getMemberDetailsByUserIds(userIds, logger, requestId); + + if (_.intersection(requestedNonMemberFields, otherMemberFields).length > 0) { + const promises = _.map( + allMemberDetails, + member => util.getMemberTraitsByHandle(member.handle, logger, requestId), + ); + const traits = await Promise.all(promises); + for (let i = 0; i < allMemberDetails.length; i += 1) { + const member = allMemberDetails[i]; + const data = _.find(traits, trait => trait.handle === member.handle); + const basicInfo = _.find(data, infoItem => infoItem.traitId === 'basic_info'); + const connectInfo = _.find(data, infoItem => infoItem.traitId === 'connect_info'); + const photoUrl = _.get(basicInfo, 'traits.data[0].photoURL', null); + const memberTraits = _.assign({}, { photoUrl }, _.pick( + _.get(connectInfo, 'traits.data.0'), + 'workingHourStart', 'workingHourEnd', 'timeZone', + )); + allMemberDetails[i] = _.assign({}, member, memberTraits); + } + } + } + + const memberDefaults = _.assign(...fields.map(field => ({ [field]: null }))); + + return _.map(members, (member) => { + let memberDetails = _.find(allMemberDetails, ({ userId }) => userId === member.userId); + memberDetails = _.assign({}, member, memberDetails); + return _(memberDetails).pick(fields).defaults(memberDefaults).value(); + }); + }, + /** * Retrieve member details from userIds */ From 9747253e36baf815b1a4b1a7c39c0fb6a9c41fe9 Mon Sep 17 00:00:00 2001 From: Deepak Date: Sun, 1 Dec 2019 22:56:07 +0530 Subject: [PATCH 2/3] Fix additional member field values --- src/routes/projectMemberInvites/get.js | 8 +--- src/routes/projectMemberInvites/list.js | 8 +--- src/routes/projectMembers/get.js | 10 +---- src/routes/projectMembers/list.js | 11 +---- src/util.js | 54 ++++++++++++++++--------- 5 files changed, 38 insertions(+), 53 deletions(-) diff --git a/src/routes/projectMemberInvites/get.js b/src/routes/projectMemberInvites/get.js index dafa5993..af3f7efd 100644 --- a/src/routes/projectMemberInvites/get.js +++ b/src/routes/projectMemberInvites/get.js @@ -23,7 +23,6 @@ module.exports = [ try { const projectId = _.parseInt(req.params.projectId); const currentUserId = req.authUser.userId; - const memberFields = _.keys(models.ProjectMemberInvite.attributes); const invite = await models.ProjectMemberInvite.getPendingInviteByEmailOrUserId( projectId, req.authUser.email, currentUserId, ); @@ -42,12 +41,7 @@ module.exports = [ if (req.query.fields) { fields = req.query.fields.split(','); } - const opts = { - logger: req.log, - requestId: req.id, - memberFields, - }; - const inviteWithDetails = await util.getObjectsWithMemberDetails(invite, fields, opts); + const inviteWithDetails = await util.getObjectsWithMemberDetails([invite], fields, req); return res.json(util.wrapResponse(req.id, inviteWithDetails)); } catch (err) { diff --git a/src/routes/projectMemberInvites/list.js b/src/routes/projectMemberInvites/list.js index cb4e949f..582bea6c 100644 --- a/src/routes/projectMemberInvites/list.js +++ b/src/routes/projectMemberInvites/list.js @@ -26,18 +26,12 @@ module.exports = [ if (req.query.fields) { fields = req.query.fields.split(','); } - const memberFields = _.keys(models.ProjectMemberInvite.attributes); const projectId = _.parseInt(req.params.projectId); const invites = await models.ProjectMemberInvite.findAll({ where: { projectId }, raw: true, }); - const opts = { - logger: req.log, - requestId: req.id, - memberFields, - }; - const invitesWithDetails = await util.getObjectsWithMemberDetails(invites, fields, opts); + const invitesWithDetails = await util.getObjectsWithMemberDetails(invites, fields, req); return res.json(util.wrapResponse(req.id, invitesWithDetails)); } catch (err) { return next(err); diff --git a/src/routes/projectMembers/get.js b/src/routes/projectMembers/get.js index 07a9eca5..d935771d 100644 --- a/src/routes/projectMembers/get.js +++ b/src/routes/projectMembers/get.js @@ -4,7 +4,6 @@ import _ from 'lodash'; import Joi from 'joi'; import validate from 'express-validation'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import models from '../../models'; import util from '../../util'; /** @@ -27,16 +26,9 @@ module.exports = [ if (req.query.fields) { fields = req.query.fields.split(','); } - const memberFields = _.keys(models.ProjectMember.attributes); const memberId = _.parseInt(req.params.id); const members = [_.find(req.context.currentProjectMembers, user => user.id === memberId)]; - const [member] = await util.getObjectsWithMemberDetails( - members, fields, { - logger: req.log, - requestId: req.id, - memberFields, - }, - ); + const [member] = await util.getObjectsWithMemberDetails(members, fields, req); return res.json(util.wrapResponse(req.id, member)); } catch (err) { return next(err); diff --git a/src/routes/projectMembers/list.js b/src/routes/projectMembers/list.js index 8a87a1c6..10f4a21c 100644 --- a/src/routes/projectMembers/list.js +++ b/src/routes/projectMembers/list.js @@ -1,8 +1,6 @@ -import _ from 'lodash'; import Joi from 'joi'; import validate from 'express-validation'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import models from '../../models'; import util from '../../util'; /** @@ -26,14 +24,7 @@ module.exports = [ fields = req.query.fields.split(','); } try { - const memberFields = _.keys(models.ProjectMember.attributes); - const members = await util.getObjectsWithMemberDetails( - req.context.currentProjectMembers, fields, { - logger: req.log, - requestId: req.id, - memberFields, - }, - ); + const members = await util.getObjectsWithMemberDetails(req.context.currentProjectMembers, fields, req); return res.json(util.wrapResponse(req.id, members)); } catch (err) { return next(err); diff --git a/src/util.js b/src/util.js index 514b7ae7..4033e05e 100644 --- a/src/util.js +++ b/src/util.js @@ -445,46 +445,60 @@ _.assignIn(util, { * * @param {Array} members Array of member detail objects * @param {Array} fields Array of fields to be used to filter member objects - * @param {Object} opts logger & request id + * @param {Object} req The request object * * @return {Array} Filtered array of member detail objects */ - getObjectsWithMemberDetails: async (members, fields, opts) => { + getObjectsWithMemberDetails: async (members, fields, req) => { if (!fields || _.isEmpty(fields)) { return members; } - const { logger, requestId, memberFields } = opts; - const requestedNonMemberFields = _.filter(fields, field => !_.includes(memberFields, field)); - const otherMemberFields = ['photoURL', 'workingHoursStart', 'workingHoursEnd', 'timeZone']; + const memberTraitFields = ['photoURL', 'workingHourStart', 'workingHourEnd', 'timeZone']; + const memberDetailFields = ['handle', 'firstName', 'lastName']; let allMemberDetails = []; - if (requestedNonMemberFields.length > 0) { + if (_.intersection(fields, _.union(memberDetailFields, memberTraitFields)).length > 0) { const userIds = _.map(members, 'userId'); - allMemberDetails = await util.getMemberDetailsByUserIds(userIds, logger, requestId); + allMemberDetails = await util.getMemberDetailsByUserIds(userIds, req.log, req.id); - if (_.intersection(requestedNonMemberFields, otherMemberFields).length > 0) { + if (_.intersection(fields, memberTraitFields).length > 0) { const promises = _.map( allMemberDetails, - member => util.getMemberTraitsByHandle(member.handle, logger, requestId), + member => util.getMemberTraitsByHandle(member.handle, req.log, req.id), ); const traits = await Promise.all(promises); - for (let i = 0; i < allMemberDetails.length; i += 1) { - const member = allMemberDetails[i]; - const data = _.find(traits, trait => trait.handle === member.handle); - const basicInfo = _.find(data, infoItem => infoItem.traitId === 'basic_info'); - const connectInfo = _.find(data, infoItem => infoItem.traitId === 'connect_info'); - const photoUrl = _.get(basicInfo, 'traits.data[0].photoURL', null); - const memberTraits = _.assign({}, { photoUrl }, _.pick( + _.each(traits, (memberTraits) => { + const basicInfo = _.find(memberTraits, trait => trait.traitId === 'basic_info'); + const connectInfo = _.find(memberTraits, trait => trait.traitId === 'connect_info'); + const memberIndex = _.findIndex( + allMemberDetails, + member => member.userId === _.get(basicInfo, 'traits.data[0].userId'), + ); + const basicDetails = { + photoURL: _.get(basicInfo, 'traits.data[0].photoURL', null), + }; + const connectDetails = _.pick( _.get(connectInfo, 'traits.data.0'), 'workingHourStart', 'workingHourEnd', 'timeZone', - )); - allMemberDetails[i] = _.assign({}, member, memberTraits); - } + ); + allMemberDetails.splice( + memberIndex, 1, + _.assign({}, allMemberDetails[memberIndex], basicDetails, connectDetails), + ); + }); } } - const memberDefaults = _.assign(...fields.map(field => ({ [field]: null }))); + // set default null value for all valid fields + const memberDefaults = _.reduce(fields, (acc, field) => { + const isValidField = _.includes(_.union(memberDetailFields, memberTraitFields), field); + if (isValidField) { + acc[field] = null; + } + return acc; + }, {}); + // pick valid fields from fetched member details return _.map(members, (member) => { let memberDetails = _.find(allMemberDetails, ({ userId }) => userId === member.userId); memberDetails = _.assign({}, member, memberDetails); From c44ce3360f432128c27b516233b576564365e74c Mon Sep 17 00:00:00 2001 From: Deepak Date: Mon, 2 Dec 2019 16:21:32 +0530 Subject: [PATCH 3/3] Project Member & Invite endpoint fixes 1. Return only single object for invite endpoint 2. Handle 404 error for get project member by id endpoint 3. Return only pending project member invites --- src/routes/projectMemberInvites/get.js | 2 +- src/routes/projectMemberInvites/list.js | 5 +---- src/routes/projectMembers/get.js | 13 +++++++++++-- src/util.js | 4 ++-- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/routes/projectMemberInvites/get.js b/src/routes/projectMemberInvites/get.js index af3f7efd..2ad7adde 100644 --- a/src/routes/projectMemberInvites/get.js +++ b/src/routes/projectMemberInvites/get.js @@ -41,7 +41,7 @@ module.exports = [ if (req.query.fields) { fields = req.query.fields.split(','); } - const inviteWithDetails = await util.getObjectsWithMemberDetails([invite], fields, req); + const [inviteWithDetails] = await util.getObjectsWithMemberDetails([invite], fields, req); return res.json(util.wrapResponse(req.id, inviteWithDetails)); } catch (err) { diff --git a/src/routes/projectMemberInvites/list.js b/src/routes/projectMemberInvites/list.js index 582bea6c..2f23cac9 100644 --- a/src/routes/projectMemberInvites/list.js +++ b/src/routes/projectMemberInvites/list.js @@ -27,10 +27,7 @@ module.exports = [ fields = req.query.fields.split(','); } const projectId = _.parseInt(req.params.projectId); - const invites = await models.ProjectMemberInvite.findAll({ - where: { projectId }, - raw: true, - }); + const invites = await models.ProjectMemberInvite.getPendingInvitesForProject(projectId); const invitesWithDetails = await util.getObjectsWithMemberDetails(invites, fields, req); return res.json(util.wrapResponse(req.id, invitesWithDetails)); } catch (err) { diff --git a/src/routes/projectMembers/get.js b/src/routes/projectMembers/get.js index d935771d..48e17b09 100644 --- a/src/routes/projectMembers/get.js +++ b/src/routes/projectMembers/get.js @@ -22,13 +22,22 @@ module.exports = [ permissions('project.getMember'), async (req, res, next) => { try { + const projectId = _.parseInt(req.params.projectId); let fields = null; if (req.query.fields) { fields = req.query.fields.split(','); } const memberId = _.parseInt(req.params.id); - const members = [_.find(req.context.currentProjectMembers, user => user.id === memberId)]; - const [member] = await util.getObjectsWithMemberDetails(members, fields, req); + let member = _.find(req.context.currentProjectMembers, user => user.id === memberId); + if (!member) { + const err = new Error( + `member not found for project id ${projectId}, userId ${memberId}`, + ); + err.status = 404; + throw err; + } + const memberDetails = await util.getObjectsWithMemberDetails([member], fields, req); + member = _.first(memberDetails); return res.json(util.wrapResponse(req.id, member)); } catch (err) { return next(err); diff --git a/src/util.js b/src/util.js index 4033e05e..c5402fac 100644 --- a/src/util.js +++ b/src/util.js @@ -445,12 +445,12 @@ _.assignIn(util, { * * @param {Array} members Array of member detail objects * @param {Array} fields Array of fields to be used to filter member objects - * @param {Object} req The request object + * @param {Object} req The request object * * @return {Array} Filtered array of member detail objects */ getObjectsWithMemberDetails: async (members, fields, req) => { - if (!fields || _.isEmpty(fields)) { + if (!fields || _.isEmpty(fields) || _.isEmpty(members)) { return members; } const memberTraitFields = ['photoURL', 'workingHourStart', 'workingHourEnd', 'timeZone'];