Skip to content

Commit 92c43a2

Browse files
author
Deepak
committed
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
1 parent e443142 commit 92c43a2

File tree

7 files changed

+223
-137
lines changed

7 files changed

+223
-137
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: 42 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,49 @@ 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 memberFields = _.keys(models.ProjectMemberInvite.attributes);
27+
const invite = await models.ProjectMemberInvite.getPendingInviteByEmailOrUserId(
28+
projectId, req.authUser.email, currentUserId,
29+
);
30+
if (!invite) {
31+
// check there is an existing invite for the user with status PENDING
32+
// handle 404
33+
const err = new Error(
34+
'invite not found for project id ' +
35+
`${projectId}, userId ${currentUserId}, email ${req.authUser.email}`,
36+
);
37+
err.status = 404;
38+
throw err;
39+
}
40+
41+
let fields = null;
42+
if (req.query.fields) {
43+
fields = req.query.fields.split(',');
44+
}
45+
const opts = {
46+
logger: req.log,
47+
requestId: req.id,
48+
memberFields,
49+
};
50+
const inviteWithDetails = await util.getObjectsWithMemberDetails(invite, fields, opts);
51+
52+
return res.json(util.wrapResponse(req.id, inviteWithDetails));
53+
} catch (err) {
54+
return next(err);
55+
}
3456
},
3557
];
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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 memberFields = _.keys(models.ProjectMemberInvite.attributes);
30+
const projectId = _.parseInt(req.params.projectId);
31+
const invites = await models.ProjectMemberInvite.findAll({
32+
where: { projectId },
33+
raw: true,
34+
});
35+
const opts = {
36+
logger: req.log,
37+
requestId: req.id,
38+
memberFields,
39+
};
40+
const invitesWithDetails = await util.getObjectsWithMemberDetails(invites, fields, opts);
41+
return res.json(util.wrapResponse(req.id, invitesWithDetails));
42+
} catch (err) {
43+
return next(err);
44+
}
45+
},
46+
];

src/routes/projectMembers/get.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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 models from '../../models';
8+
import util from '../../util';
9+
10+
/**
11+
* API to get a project member in a project.
12+
*/
13+
const permissions = tcMiddleware.permissions;
14+
15+
const schema = {
16+
query: {
17+
fields: Joi.string().optional(),
18+
},
19+
};
20+
21+
module.exports = [
22+
validate(schema),
23+
permissions('project.getMember'),
24+
async (req, res, next) => {
25+
try {
26+
let fields = null;
27+
if (req.query.fields) {
28+
fields = req.query.fields.split(',');
29+
}
30+
const memberFields = _.keys(models.ProjectMember.attributes);
31+
const memberId = _.parseInt(req.params.id);
32+
const members = [_.find(req.context.currentProjectMembers, user => user.id === memberId)];
33+
const [member] = await util.getObjectsWithMemberDetails(
34+
members, fields, {
35+
logger: req.log,
36+
requestId: req.id,
37+
memberFields,
38+
},
39+
);
40+
return res.json(util.wrapResponse(req.id, member));
41+
} catch (err) {
42+
return next(err);
43+
}
44+
},
45+
];

src/routes/projectMembers/list.js

Lines changed: 30 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,124 +1,42 @@
1-
/**
2-
* Endpoint to list project members.
3-
*/
41
import _ from 'lodash';
2+
import Joi from 'joi';
3+
import validate from 'express-validation';
54
import { middleware as tcMiddleware } from 'tc-core-library-js';
5+
import models from '../../models';
66
import util from '../../util';
77

8+
/**
9+
* API to list all project members.
10+
*
11+
*/
812
const permissions = tcMiddleware.permissions;
913

14+
const schema = {
15+
query: {
16+
fields: Joi.string().optional(),
17+
},
18+
};
19+
1020
module.exports = [
21+
validate(schema),
1122
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-
}
23+
async (req, res, next) => {
24+
let fields = null;
25+
if (req.query.fields) {
26+
fields = req.query.fields.split(',');
27+
}
28+
try {
29+
const memberFields = _.keys(models.ProjectMember.attributes);
30+
const members = await util.getObjectsWithMemberDetails(
31+
req.context.currentProjectMembers, fields, {
32+
logger: req.log,
33+
requestId: req.id,
34+
memberFields,
35+
},
36+
);
11937
return res.json(util.wrapResponse(req.id, members));
38+
} catch (err) {
39+
return next(err);
12040
}
121-
122-
return res.json(util.wrapResponse(req.id, req.context.currentProjectMembers));
12341
},
12442
];

0 commit comments

Comments
 (0)