Skip to content

Commit cf04b21

Browse files
authored
Merge pull request #218 from maxceem/connect-issue-2741-invite-existent-user
fix connect app issue 2741 - [Team Management 2.0] Handle the case when email entered to be invited is already topcoder user
2 parents 1dd16df + c52e099 commit cf04b21

File tree

3 files changed

+120
-2
lines changed

3 files changed

+120
-2
lines changed

src/routes/projectMemberInvites/create.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,35 @@ module.exports = [
118118
data.userId = null;
119119

120120
if (invite.emails) {
121+
// if for some emails there are already existent users, we will invite them by userId,
122+
// to avoid sending them registration email
123+
const existentUsers = util.lookupUserEmails(req, invite.emails).map((user) => {
124+
const userWithNumberId = {};
125+
_.assign(userWithNumberId, user, {
126+
id: parseInt(user.id, 10),
127+
});
128+
return userWithNumberId;
129+
});
130+
// for existent users - invite by ids
131+
const existentUserIds = _.map(existentUsers, 'id');
132+
// the rest of email of non-existent users, so we will invite them by email
133+
const nonExistentUserEmails = invite.emails.filter(inviteEmail =>
134+
!_.find(existentUsers, { email: inviteEmail }),
135+
);
136+
137+
// remove invites for users that are invited already
138+
_.remove(existentUserIds, userId => _.some(invites, i => i.userId === userId));
139+
existentUserIds.forEach((userId) => {
140+
const dataNew = _.clone(data);
141+
_.assign(dataNew, {
142+
userId,
143+
});
144+
invitePromises.push(models.ProjectMemberInvite.create(dataNew));
145+
});
146+
121147
// remove invites for users that are invited already
122-
_.remove(invite.emails, u => _.some(invites, i => i.email === u));
123-
invite.emails.forEach((email) => {
148+
_.remove(nonExistentUserEmails, email => _.some(invites, i => i.email === email));
149+
nonExistentUserEmails.forEach((email) => {
124150
const dataNew = _.clone(data);
125151
_.assign(dataNew, {
126152
email,

src/routes/projectMemberInvites/create.spec.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ describe('Project Member Invite create', () => {
8686
server.services.pubsub.publish.restore();
8787
sinon.stub(server.services.pubsub, 'init', () => {});
8888
sinon.stub(server.services.pubsub, 'publish', () => {});
89+
// by default mock lookupUserEmails return nothing so all the cases are not broken
90+
sandbox.stub(util, 'lookupUserEmails', () => []);
8991
});
9092
afterEach(() => {
9193
sandbox.restore();
@@ -275,6 +277,58 @@ describe('Project Member Invite create', () => {
275277
});
276278
});
277279

280+
it('should return 201 and add new userId invite as customer for existent user when invite by email', (done) => {
281+
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
282+
get: () => Promise.resolve({
283+
status: 200,
284+
data: {
285+
id: 'requesterId',
286+
version: 'v3',
287+
result: {
288+
success: true,
289+
status: 200,
290+
content: [{
291+
roleName: USER_ROLE.COPILOT,
292+
}],
293+
},
294+
},
295+
}),
296+
});
297+
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
298+
util.lookupUserEmails.restore();
299+
sandbox.stub(util, 'lookupUserEmails', () => [{
300+
id: '12345',
301+
email: 'hello@world.com',
302+
}]);
303+
request(server)
304+
.post(`/v4/projects/${project2.id}/members/invite`)
305+
.set({
306+
Authorization: `Bearer ${testUtil.jwts.copilot}`,
307+
})
308+
.send({
309+
param: {
310+
emails: ['hello@world.com'],
311+
role: 'customer',
312+
},
313+
})
314+
.expect('Content-Type', /json/)
315+
.expect(201)
316+
.end((err, res) => {
317+
if (err) {
318+
done(err);
319+
} else {
320+
const resJson = res.body.result.content[0];
321+
should.exist(resJson);
322+
resJson.role.should.equal('customer');
323+
resJson.projectId.should.equal(project2.id);
324+
resJson.userId.should.equal(12345);
325+
should.not.exist(resJson.email);
326+
server.services.pubsub.publish.calledWith('project.member.invite.created').should.be.true;
327+
done();
328+
}
329+
});
330+
});
331+
278332
it('should return 201 and add new user invite as customer', (done) => {
279333
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
280334
get: () => Promise.resolve({

src/util.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,44 @@ _.assignIn(util, {
423423
return Promise.reject(err);
424424
});
425425
}),
426+
427+
/**
428+
* Lookup user handles from emails
429+
* @param {Object} req request
430+
* @param {Array} userEmails user emails
431+
* @param {Boolean} isPattern flag to indicate that pattern matching is required or not
432+
* @return {Promise} promise
433+
*/
434+
lookupUserEmails: (req, userEmails, isPattern = false) => {
435+
req.log.debug(`identityServiceEndpoint: ${config.get('identityServiceEndpoint')}`);
436+
let filter = _.map(userEmails, i => `email=${i}`).join(' OR ');
437+
if (isPattern) {
438+
filter += '&like=true';
439+
}
440+
req.log.trace('filter for users api call', filter);
441+
return util.getSystemUserToken(req.log)
442+
.then((token) => {
443+
req.log.debug(`Bearer ${token}`);
444+
const httpClient = this.getHttpClient({ id: req.id, log: req.log });
445+
return httpClient.get(`${config.get('identityServiceEndpoint')}users`, {
446+
headers: {
447+
Authorization: `Bearer ${token}`,
448+
Accept: 'application/json',
449+
'Content-Type': 'application/json',
450+
},
451+
params: {
452+
fields: 'handle,id,email',
453+
filter,
454+
},
455+
})
456+
.then((response) => {
457+
const data = _.get(response, 'data.result.content', null);
458+
if (!data) { throw new Error('Response does not have result.content'); }
459+
req.log.debug('UserHandle response', data);
460+
return data;
461+
});
462+
});
463+
},
426464
});
427465

428466
export default util;

0 commit comments

Comments
 (0)