Skip to content

Commit 3a66d1f

Browse files
committed
#488 Project List endpoint returns member email for some projects
1 parent dca4e66 commit 3a66d1f

File tree

5 files changed

+269
-8
lines changed

5 files changed

+269
-8
lines changed

src/routes/projects/get.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType');
2222
// var permissions = require('tc-core-library-js').middleware.permissions
2323
const permissions = tcMiddleware.permissions;
2424
const PROJECT_ATTRIBUTES = _.without(_.keys(models.Project.rawAttributes), 'utm', 'deletedAt');
25-
const PROJECT_MEMBER_ATTRIBUTES = _.without(_.keys(models.ProjectMember.rawAttributes), 'deletedAt');
25+
const PROJECT_MEMBER_ATTRIBUTES = _.concat(_.without(_.keys(models.ProjectMember.rawAttributes), 'deletedAt'),
26+
['firstName', 'lastName', 'handle', 'email']);
2627
const PROJECT_MEMBER_INVITE_ATTRIBUTES = _.without(_.keys(models.ProjectMemberInvite.rawAttributes), 'deletedAt');
2728
const PROJECT_ATTACHMENT_ATTRIBUTES = _.without(_.keys(models.ProjectAttachment.rawAttributes), 'deletedAt');
2829

@@ -58,7 +59,7 @@ const parseElasticSearchCriteria = (projectId, fields) => {
5859
}
5960

6061
if (sourceInclude) {
61-
searchCriteria._sourceInclude = sourceInclude; // eslint-disable-line no-underscore-dangle
62+
searchCriteria._sourceIncludes = sourceInclude; // eslint-disable-line no-underscore-dangle
6263
}
6364

6465

@@ -90,6 +91,9 @@ const retrieveProjectFromES = (projectId, req) => {
9091
attachments: PROJECT_ATTACHMENT_ATTRIBUTES,
9192
});
9293

94+
// if user is not admin, ignore email field for project_members
95+
fields = util.ignoreEmailField(req, fields);
96+
9397
const searchCriteria = parseElasticSearchCriteria(projectId, fields) || {};
9498
return new Promise((accept, reject) => {
9599
const es = util.getElasticSearchClient();
@@ -108,6 +112,7 @@ const retrieveProjectFromDB = (projectId, req) => {
108112
projects: PROJECT_ATTRIBUTES,
109113
project_members: PROJECT_MEMBER_ATTRIBUTES,
110114
});
115+
111116
return models.Project
112117
.findOne({
113118
where: { id: projectId },

src/routes/projects/get.spec.js

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ const data = [
2323
billingAccountId: 1,
2424
name: 'test1',
2525
description: 'es_project',
26-
status: 'active',
26+
cancelReason: 'price/cost',
27+
status: 'draft',
2728
details: {
2829
utm: {
2930
code: 'code1',
@@ -42,6 +43,7 @@ const data = [
4243
firstName: 'es_member_1_firstName',
4344
lastName: 'Lastname',
4445
handle: 'test_tourist_handle',
46+
email: 'test@test.com',
4547
isPrimary: true,
4648
createdBy: 1,
4749
updatedBy: 1,
@@ -80,6 +82,7 @@ describe('GET Project', () => {
8082
.then(() => testUtil.clearES())
8183
.then(() => {
8284
const p1 = models.Project.create({
85+
id: 5,
8386
type: 'generic',
8487
billingAccountId: 1,
8588
name: 'test1',
@@ -98,6 +101,10 @@ describe('GET Project', () => {
98101
projectId: project1.id,
99102
role: 'customer',
100103
isPrimary: true,
104+
firstName: 'Firstname',
105+
lastName: 'Lastname',
106+
handle: 'test_tourist_handle',
107+
email: 'test@test.com',
101108
createdBy: 1,
102109
updatedBy: 1,
103110
});
@@ -343,5 +350,115 @@ describe('GET Project', () => {
343350
});
344351
});
345352
});
353+
354+
describe('URL Query fields', () => {
355+
it('should not return "email" for project members when "fields" query param is not defined (to non-admin users)', (done) => {
356+
request(server)
357+
.get(`/v5/projects/${project1.id}?fields=members.handle`)
358+
.set({
359+
Authorization: `Bearer ${testUtil.jwts.member}`,
360+
})
361+
.expect('Content-Type', /json/)
362+
.expect(200)
363+
.end((err, res) => {
364+
if (err) {
365+
done(err);
366+
} else {
367+
const resJson = res.body;
368+
should.exist(resJson);
369+
resJson.members[0].should.have.property('handle');
370+
resJson.members[0].should.not.have.property('email');
371+
done();
372+
}
373+
});
374+
});
375+
376+
it('should not return "email" for project members even if it\'s defined in "fields" query param (to non-admin users)', (done) => {
377+
request(server)
378+
.get(`/v5/projects/${project1.id}?fields=members.email,members.handle`)
379+
.set({
380+
Authorization: `Bearer ${testUtil.jwts.member}`,
381+
})
382+
.expect('Content-Type', /json/)
383+
.expect(200)
384+
.end((err, res) => {
385+
if (err) {
386+
done(err);
387+
} else {
388+
const resJson = res.body;
389+
should.exist(resJson);
390+
resJson.members[0].should.have.property('handle');
391+
resJson.members[0].should.not.have.property('email');
392+
done();
393+
}
394+
});
395+
});
396+
397+
398+
it('should not return "cancelReason" if it is not listed in "fields" query param ', (done) => {
399+
request(server)
400+
.get(`/v5/projects/${project1.id}?fields=description`)
401+
.set({
402+
Authorization: `Bearer ${testUtil.jwts.member}`,
403+
})
404+
.expect('Content-Type', /json/)
405+
.expect(200)
406+
.end((err, res) => {
407+
if (err) {
408+
done(err);
409+
} else {
410+
const resJson = res.body;
411+
should.exist(resJson);
412+
resJson.should.have.property('description');
413+
resJson.description.should.be.eq('es_project');
414+
resJson.should.not.have.property('cancelReason');
415+
done();
416+
}
417+
});
418+
});
419+
420+
it('should not return "email" for project members when "fields" query param is not defined (to admin users)', (done) => {
421+
request(server)
422+
.get(`/v5/projects/${project1.id}?fields=description,members.id`)
423+
.set({
424+
Authorization: `Bearer ${testUtil.jwts.admin}`,
425+
})
426+
.expect('Content-Type', /json/)
427+
.expect(200)
428+
.end((err, res) => {
429+
if (err) {
430+
done(err);
431+
} else {
432+
const resJson = res.body;
433+
should.exist(resJson);
434+
resJson.members.should.have.lengthOf(2);
435+
resJson.members[0].should.not.have.property('email');
436+
done();
437+
}
438+
});
439+
});
440+
441+
it('should return "email" for project members if it\'s defined in "fields" query param (to admin users', (done) => {
442+
request(server)
443+
.get(`/v5/projects/${project1.id}?fields=description,members.id,members.email`)
444+
.set({
445+
Authorization: `Bearer ${testUtil.jwts.admin}`,
446+
})
447+
.expect('Content-Type', /json/)
448+
.expect(200)
449+
.end((err, res) => {
450+
if (err) {
451+
done(err);
452+
} else {
453+
const resJson = res.body;
454+
should.exist(resJson);
455+
resJson.members.should.have.lengthOf(2);
456+
resJson.members[0].should.have.property('email');
457+
resJson.members[0].email.should.be.eq('test@test.com');
458+
done();
459+
}
460+
});
461+
});
462+
});
346463
});
347464
});

src/routes/projects/list.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ const PROJECT_ATTRIBUTES = _.without(_.keys(models.Project.rawAttributes),
2626
'utm',
2727
'deletedAt',
2828
);
29-
const PROJECT_MEMBER_ATTRIBUTES = _.without(
29+
const PROJECT_MEMBER_ATTRIBUTES = _.concat(_.without(
3030
_.keys(models.ProjectMember.rawAttributes),
3131
'deletedAt',
32-
);
32+
), ['firstName', 'lastName', 'handle', 'email']);
3333
const PROJECT_MEMBER_INVITE_ATTRIBUTES = _.without(
3434
_.keys(models.ProjectMemberInvite.rawAttributes),
3535
'deletedAt',
@@ -301,7 +301,7 @@ const parseElasticSearchCriteria = (criteria, fields, order) => {
301301
}
302302

303303
if (sourceInclude) {
304-
searchCriteria._sourceInclude = sourceInclude; // eslint-disable-line no-underscore-dangle
304+
searchCriteria._sourceIncludes = sourceInclude; // eslint-disable-line no-underscore-dangle
305305
}
306306
// prepare the elasticsearch filter criteria
307307
const boolQuery = [];
@@ -462,6 +462,8 @@ const retrieveProjectsFromDB = (req, criteria, sort, ffields) => {
462462
projects: PROJECT_ATTRIBUTES,
463463
project_members: PROJECT_MEMBER_ATTRIBUTES,
464464
});
465+
466+
465467
// make sure project.id is part of fields
466468
if (_.indexOf(fields.projects, 'id') < 0) fields.projects.push('id');
467469
const retrieveAttachments = !req.query.fields || req.query.fields.indexOf('attachments') > -1;
@@ -529,6 +531,11 @@ const retrieveProjects = (req, criteria, sort, ffields) => {
529531
project_phases_products: PROJECT_PHASE_PRODUCTS_ATTRIBUTES,
530532
attachments: PROJECT_ATTACHMENT_ATTRIBUTES,
531533
});
534+
535+
536+
// if user is not admin, ignore email field for project_members
537+
fields = util.ignoreEmailField(req, fields);
538+
532539
// make sure project.id is part of fields
533540
if (_.indexOf(fields.projects, 'id') < 0) {
534541
fields.projects.push('id');

src/routes/projects/list.spec.js

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable no-unused-expressions */
22
/* eslint-disable max-len */
33
import chai from 'chai';
4+
import _ from 'lodash';
45
import request from 'supertest';
56
// import sleep from 'sleep';
67
import config from 'config';
@@ -29,6 +30,7 @@ const data = [
2930
},
3031
createdBy: 1,
3132
updatedBy: 1,
33+
cancelReason: 'price/cost',
3234
lastActivityAt: 1,
3335
lastActivityUserId: '1',
3436
members: [
@@ -40,6 +42,7 @@ const data = [
4042
firstName: 'Firstname',
4143
lastName: 'Lastname',
4244
handle: 'test_tourist_handle',
45+
email: 'test@test.com',
4346
isPrimary: true,
4447
createdBy: 1,
4548
updatedBy: 1,
@@ -396,7 +399,7 @@ describe('LIST Project', () => {
396399

397400
it('should return the project for administrator with field description and billingAccountId', (done) => {
398401
request(server)
399-
.get('/v5/projects/?fields=description,billingAccountId&sort=id asc')
402+
.get('/v5/projects/?fields=description,billingAccountId,attachments&sort=id asc')
400403
.set({
401404
Authorization: `Bearer ${testUtil.jwts.admin}`,
402405
})
@@ -893,5 +896,120 @@ describe('LIST Project', () => {
893896
});
894897
});
895898
});
899+
900+
describe('URL Query fields', () => {
901+
it('should not return "email" for project members when "fields" query param is not defined (to non-admin users)', (done) => {
902+
request(server)
903+
.get('/v5/projects/')
904+
.set({
905+
Authorization: `Bearer ${testUtil.jwts.member2}`,
906+
})
907+
.expect('Content-Type', /json/)
908+
.expect(200)
909+
.end((err, res) => {
910+
if (err) {
911+
done(err);
912+
} else {
913+
const resJson = res.body;
914+
should.exist(resJson);
915+
resJson.should.have.lengthOf(1);
916+
resJson[0].members[0].should.not.have.property('email');
917+
done();
918+
}
919+
});
920+
});
921+
922+
923+
it('should not return "email" for project members even if it\'s defined in "fields" query param (to non-admin users)', (done) => {
924+
request(server)
925+
.get('/v5/projects/?fields=members.email,members.id')
926+
.set({
927+
Authorization: `Bearer ${testUtil.jwts.member2}`,
928+
})
929+
.expect('Content-Type', /json/)
930+
.expect(200)
931+
.end((err, res) => {
932+
if (err) {
933+
done(err);
934+
} else {
935+
const resJson = res.body;
936+
should.exist(resJson);
937+
resJson.should.have.lengthOf(1);
938+
resJson[0].members[0].should.not.have.property('email');
939+
done();
940+
}
941+
});
942+
});
943+
944+
945+
it('should not return "cancelReason" if it is not listed in "fields" query param ', (done) => {
946+
request(server)
947+
.get('/v5/projects/?fields=description')
948+
.set({
949+
Authorization: `Bearer ${testUtil.jwts.member2}`,
950+
})
951+
.expect('Content-Type', /json/)
952+
.expect(200)
953+
.end((err, res) => {
954+
if (err) {
955+
done(err);
956+
} else {
957+
const resJson = res.body;
958+
should.exist(resJson);
959+
resJson.should.have.lengthOf(1);
960+
resJson[0].should.have.property('description');
961+
resJson[0].should.not.have.property('cancelReason');
962+
resJson[0].description.should.be.eq('test project1');
963+
done();
964+
}
965+
});
966+
});
967+
968+
it('should not return "email" for project members when "fields" query param is not defined (to admin users)', (done) => {
969+
request(server)
970+
.get('/v5/projects/?fields=description,members.id')
971+
.set({
972+
Authorization: `Bearer ${testUtil.jwts.admin}`,
973+
})
974+
.expect('Content-Type', /json/)
975+
.expect(200)
976+
.end((err, res) => {
977+
if (err) {
978+
done(err);
979+
} else {
980+
const resJson = res.body;
981+
should.exist(resJson);
982+
const project = _.find(resJson, p => p.id === 1);
983+
const member = _.find(project.members, m => m.id === 1);
984+
member.should.not.have.property('email');
985+
done();
986+
}
987+
});
988+
});
989+
990+
991+
it('should return "email" for project members if it\'s defined in "fields" query param (to admin users', (done) => {
992+
request(server)
993+
.get('/v5/projects/?fields=description,members.id,members.email')
994+
.set({
995+
Authorization: `Bearer ${testUtil.jwts.admin}`,
996+
})
997+
.expect('Content-Type', /json/)
998+
.expect(200)
999+
.end((err, res) => {
1000+
if (err) {
1001+
done(err);
1002+
} else {
1003+
const resJson = res.body;
1004+
should.exist(resJson);
1005+
const project = _.find(resJson, p => p.id === 1);
1006+
const member = _.find(project.members, m => m.id === 1);
1007+
member.should.have.property('email');
1008+
member.email.should.be.eq('test@test.com');
1009+
done();
1010+
}
1011+
});
1012+
});
1013+
});
8961014
});
8971015
});

0 commit comments

Comments
 (0)