Skip to content

Commit 78b969f

Browse files
authored
Merge pull request #414 from dpkshrma/feature/project-invites-traits
feat: project member & invites endpoints with additional fields
2 parents e443142 + c44ce33 commit 78b969f

File tree

7 files changed

+215
-138
lines changed

7 files changed

+215
-138
lines changed

src/permissions/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ module.exports = () => {
2121
Authorizer.setPolicy('project.view', projectView);
2222
Authorizer.setPolicy('project.edit', projectEdit);
2323
Authorizer.setPolicy('project.delete', projectDelete);
24+
Authorizer.setPolicy('project.getMember', projectView);
2425
Authorizer.setPolicy('project.addMember', projectView);
2526
Authorizer.setPolicy('project.listMembers', projectView);
2627
Authorizer.setPolicy('project.removeMember', projectMemberDelete);
@@ -86,6 +87,7 @@ module.exports = () => {
8687
Authorizer.setPolicy('projectMemberInvite.create', projectView);
8788
Authorizer.setPolicy('projectMemberInvite.put', true);
8889
Authorizer.setPolicy('projectMemberInvite.get', true);
90+
Authorizer.setPolicy('projectMemberInvite.list', projectView);
8991

9092
Authorizer.setPolicy('form.create', projectAdmin);
9193
Authorizer.setPolicy('form.edit', projectAdmin);

src/routes/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ router.route('/v4/projects/:projectId(\\d+)/members')
123123
.post(require('./projectMembers/create'));
124124

125125
router.route('/v4/projects/:projectId(\\d+)/members/:id(\\d+)')
126+
.get(require('./projectMembers/get'))
126127
.delete(require('./projectMembers/delete'))
127128
.patch(require('./projectMembers/update'));
128129

@@ -230,6 +231,9 @@ router.route('/v4/timelines/metadata/milestoneTemplates/:milestoneTemplateId(\\d
230231
.patch(require('./milestoneTemplates/update'))
231232
.delete(require('./milestoneTemplates/delete'));
232233

234+
router.route('/v4/projects/:projectId(\\d+)/members/invites')
235+
.get(require('./projectMemberInvites/list'));
236+
233237
router.route('/v4/projects/:projectId(\\d+)/members/invite')
234238
.post(require('./projectMemberInvites/create'))
235239
.put(require('./projectMemberInvites/update'))
Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
2-
31
import _ from 'lodash';
2+
import Joi from 'joi';
3+
import validate from 'express-validation';
44
import { middleware as tcMiddleware } from 'tc-core-library-js';
55
import models from '../../models';
66
import util from '../../util';
@@ -9,27 +9,43 @@ import util from '../../util';
99
* API to update invite member to project.
1010
*
1111
*/
12+
const schema = {
13+
query: {
14+
fields: Joi.string().optional(),
15+
},
16+
};
1217
const permissions = tcMiddleware.permissions;
1318

1419
module.exports = [
15-
// handles request validations
20+
validate(schema),
1621
permissions('projectMemberInvite.get'),
17-
(req, res, next) => {
18-
const projectId = _.parseInt(req.params.projectId);
19-
const currentUserId = req.authUser.userId;
20-
let invite;
21-
return models.ProjectMemberInvite.getPendingInviteByEmailOrUserId(projectId, req.authUser.email, currentUserId)
22-
.then((_invite) => {
23-
invite = _invite;
24-
if (!invite) {
25-
// check there is an existing invite for the user with status PENDING
26-
// handle 404
27-
const err = new Error('invite not found for project id ' +
28-
`${projectId}, userId ${currentUserId}, email ${req.authUser.email}`);
29-
err.status = 404;
30-
return next(err);
31-
}
32-
return res.json(util.wrapResponse(req.id, invite));
33-
});
22+
async (req, res, next) => {
23+
try {
24+
const projectId = _.parseInt(req.params.projectId);
25+
const currentUserId = req.authUser.userId;
26+
const invite = await models.ProjectMemberInvite.getPendingInviteByEmailOrUserId(
27+
projectId, req.authUser.email, currentUserId,
28+
);
29+
if (!invite) {
30+
// check there is an existing invite for the user with status PENDING
31+
// handle 404
32+
const err = new Error(
33+
'invite not found for project id ' +
34+
`${projectId}, userId ${currentUserId}, email ${req.authUser.email}`,
35+
);
36+
err.status = 404;
37+
throw err;
38+
}
39+
40+
let fields = null;
41+
if (req.query.fields) {
42+
fields = req.query.fields.split(',');
43+
}
44+
const [inviteWithDetails] = await util.getObjectsWithMemberDetails([invite], fields, req);
45+
46+
return res.json(util.wrapResponse(req.id, inviteWithDetails));
47+
} catch (err) {
48+
return next(err);
49+
}
3450
},
3551
];
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import _ from 'lodash';
2+
import Joi from 'joi';
3+
import validate from 'express-validation';
4+
import { middleware as tcMiddleware } from 'tc-core-library-js';
5+
import models from '../../models';
6+
import util from '../../util';
7+
8+
/**
9+
* API to list all project member invites.
10+
*
11+
*/
12+
const permissions = tcMiddleware.permissions;
13+
14+
const schema = {
15+
query: {
16+
fields: Joi.string().optional(),
17+
},
18+
};
19+
20+
module.exports = [
21+
validate(schema),
22+
permissions('projectMemberInvite.list'),
23+
async (req, res, next) => {
24+
try {
25+
let fields = null;
26+
if (req.query.fields) {
27+
fields = req.query.fields.split(',');
28+
}
29+
const projectId = _.parseInt(req.params.projectId);
30+
const invites = await models.ProjectMemberInvite.getPendingInvitesForProject(projectId);
31+
const invitesWithDetails = await util.getObjectsWithMemberDetails(invites, fields, req);
32+
return res.json(util.wrapResponse(req.id, invitesWithDetails));
33+
} catch (err) {
34+
return next(err);
35+
}
36+
},
37+
];

src/routes/projectMembers/get.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
2+
3+
import _ from 'lodash';
4+
import Joi from 'joi';
5+
import validate from 'express-validation';
6+
import { middleware as tcMiddleware } from 'tc-core-library-js';
7+
import util from '../../util';
8+
9+
/**
10+
* API to get a project member in a project.
11+
*/
12+
const permissions = tcMiddleware.permissions;
13+
14+
const schema = {
15+
query: {
16+
fields: Joi.string().optional(),
17+
},
18+
};
19+
20+
module.exports = [
21+
validate(schema),
22+
permissions('project.getMember'),
23+
async (req, res, next) => {
24+
try {
25+
const projectId = _.parseInt(req.params.projectId);
26+
let fields = null;
27+
if (req.query.fields) {
28+
fields = req.query.fields.split(',');
29+
}
30+
const memberId = _.parseInt(req.params.id);
31+
let member = _.find(req.context.currentProjectMembers, user => user.id === memberId);
32+
if (!member) {
33+
const err = new Error(
34+
`member not found for project id ${projectId}, userId ${memberId}`,
35+
);
36+
err.status = 404;
37+
throw err;
38+
}
39+
const memberDetails = await util.getObjectsWithMemberDetails([member], fields, req);
40+
member = _.first(memberDetails);
41+
return res.json(util.wrapResponse(req.id, member));
42+
} catch (err) {
43+
return next(err);
44+
}
45+
},
46+
];

src/routes/projectMembers/list.js

Lines changed: 22 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,124 +1,33 @@
1-
/**
2-
* Endpoint to list project members.
3-
*/
4-
import _ from 'lodash';
1+
import Joi from 'joi';
2+
import validate from 'express-validation';
53
import { middleware as tcMiddleware } from 'tc-core-library-js';
64
import util from '../../util';
75

6+
/**
7+
* API to list all project members.
8+
*
9+
*/
810
const permissions = tcMiddleware.permissions;
911

12+
const schema = {
13+
query: {
14+
fields: Joi.string().optional(),
15+
},
16+
};
17+
1018
module.exports = [
19+
validate(schema),
1120
permissions('project.listMembers'),
12-
async (req, res) => {
13-
let members = req.context.currentProjectMembers;
14-
15-
16-
if (members.length && _.get(req, 'query.fields')) {
17-
const fields = req.query.fields.split(',');
18-
19-
const ModelFields = [
20-
'id',
21-
'userId',
22-
'role',
23-
'isPrimary',
24-
'deletedAt',
25-
'createdAt',
26-
'updatedAt',
27-
'deletedBy',
28-
'createdBy',
29-
'updatedBy',
30-
];
31-
32-
const modelFields = _.intersection(ModelFields, fields);
33-
const hasUserIdField = _.indexOf(fields, 'userId') !== -1;
34-
if (hasUserIdField === false) {
35-
modelFields.push('userId');
36-
}
37-
// merge model fields
38-
members = _.map(members, m => _.pick(m, modelFields));
39-
40-
const MemberDetailFields = ['handle', 'firstName', 'lastName'];
41-
const memberDetailFields = _.intersection(MemberDetailFields, fields);
42-
43-
// has handleField
44-
const hasHandleField = _.indexOf(memberDetailFields, 'handle') !== -1;
45-
46-
if (hasHandleField === false) {
47-
memberDetailFields.push('handle');
48-
}
49-
50-
if (memberDetailFields.length) {
51-
const userIds = _.map(members, m => `userId:${m.userId}`) || [];
52-
53-
const logger = req.log;
54-
try {
55-
const memberDetails = await util.getMemberDetailsByUserIds(userIds, logger, req.id);
56-
57-
// merge detail fields
58-
_.forEach(members, (m) => {
59-
const detail = _.find(memberDetails, mt => mt.userId === m.userId);
60-
if (detail) {
61-
_.assign(m, _.pick(detail, memberDetailFields));
62-
}
63-
});
64-
65-
66-
const TraitFields = ['photoURL', 'workingHourStart', 'workingHourEnd', 'timeZone'];
67-
const traitFields = _.intersection(TraitFields, fields);
68-
if (traitFields.length) {
69-
const promises = [];
70-
_.forEach(members, (m) => {
71-
if (m.handle) {
72-
promises.push(util.getMemberTratisByHandle(m.handle, req.log, req.id));
73-
}
74-
});
75-
76-
const traits = await Promise.all(promises);
77-
78-
// remove photoURL, because connect_info also has photoURL
79-
const ConnectInfoFields = ['workingHourStart', 'workingHourEnd', 'timeZone'];
80-
const connectInfoFields = _.intersection(ConnectInfoFields, fields);
81-
82-
// merge traits
83-
_.forEach(members, (m) => {
84-
const traitsArr = _.find(traits, t => t[0].userId === m.userId);
85-
if (traitsArr) {
86-
if (traitFields[0] === 'photoURL') {
87-
_.assign(m, {
88-
photoURL: _.get(_.find(traitsArr, { traitId: 'basic_info' }), 'traits.data[0].photoURL'),
89-
});
90-
if (traitFields.length > 1) {
91-
const traitInfo = _.get(_.find(traitsArr, { traitId: 'connect_info' }), 'traits.data[0]', {});
92-
_.assign(m, _.pick(traitInfo, connectInfoFields));
93-
}
94-
} else {
95-
const traitInfo = _.get(_.find(traitsArr, { traitId: 'connect_info' }), 'traits.data[0]', {});
96-
_.assign(m, _.pick(traitInfo, connectInfoFields));
97-
}
98-
}
99-
});
100-
}
101-
} catch (e) {
102-
logger.error('Error getting member details', e);
103-
if (hasUserIdField === false) {
104-
members = _.map(members, m => _.omit(m, ['userId']));
105-
}
106-
if (hasHandleField === false) {
107-
members = _.map(members, m => _.omit(m, ['handle']));
108-
}
109-
return res.json(util.wrapResponse(req.id, members));
110-
}
111-
}
112-
113-
if (hasUserIdField === false) {
114-
members = _.map(members, m => _.omit(m, ['userId']));
115-
}
116-
if (hasHandleField === false) {
117-
members = _.map(members, m => _.omit(m, ['handle']));
118-
}
21+
async (req, res, next) => {
22+
let fields = null;
23+
if (req.query.fields) {
24+
fields = req.query.fields.split(',');
25+
}
26+
try {
27+
const members = await util.getObjectsWithMemberDetails(req.context.currentProjectMembers, fields, req);
11928
return res.json(util.wrapResponse(req.id, members));
29+
} catch (err) {
30+
return next(err);
12031
}
121-
122-
return res.json(util.wrapResponse(req.id, req.context.currentProjectMembers));
12332
},
12433
];

0 commit comments

Comments
 (0)