From 7afbc8df3654c3d585eeb443b3d620ff8332a44f Mon Sep 17 00:00:00 2001 From: chq-matteo Date: Sat, 6 Jan 2018 21:58:55 +0100 Subject: [PATCH 01/27] Add same regex as client to address validation --- src/routes/projects/create.js | 2 +- src/routes/projects/update.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/projects/create.js b/src/routes/projects/create.js index 9e84bf2c..3e194fa4 100644 --- a/src/routes/projects/create.js +++ b/src/routes/projects/create.js @@ -34,7 +34,7 @@ const createProjectValdiations = { }).allow(null), bookmarks: Joi.array().items(Joi.object().keys({ title: Joi.string(), - address: Joi.string(), + address: Joi.string().regex(/^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,15})+(\:[0-9]{2,5})?(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=]*)?$/), })).optional().allow(null), estimatedPrice: Joi.number().precision(2).positive().optional() .allow(null), diff --git a/src/routes/projects/update.js b/src/routes/projects/update.js index 985c7c82..6c1f2fb0 100644 --- a/src/routes/projects/update.js +++ b/src/routes/projects/update.js @@ -57,7 +57,7 @@ const updateProjectValdiations = { }).allow(null), bookmarks: Joi.array().items(Joi.object().keys({ title: Joi.string(), - address: Joi.string(), + address: Joi.string().regex(/^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,15})+(\:[0-9]{2,5})?(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=]*)?$/), })).optional().allow(null), type: Joi.any().valid(_.values(PROJECT_TYPE)), details: Joi.any(), From e727fd31d528f8895d37c3376188eb155403842f Mon Sep 17 00:00:00 2001 From: chq-matteo Date: Sat, 6 Jan 2018 23:40:10 +0100 Subject: [PATCH 02/27] Move address regex to constants.js --- src/constants.js | 4 ++++ src/routes/projects/create.js | 4 ++-- src/routes/projects/update.js | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/constants.js b/src/constants.js index 3e5a14c6..54eef261 100644 --- a/src/constants.js +++ b/src/constants.js @@ -71,3 +71,7 @@ export const BUS_API_EVENT = { PROJECT_FILE_UPLOADED: 'connect.project.fileUploaded', PROJECT_SPECIFICATION_MODIFIED: 'connect.project.specificationModified', }; + +export const REGEX = { + URL: /^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,15})+(\:[0-9]{2,5})?(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=]*)?$/, +}; \ No newline at end of file diff --git a/src/routes/projects/create.js b/src/routes/projects/create.js index 3e194fa4..684ef859 100644 --- a/src/routes/projects/create.js +++ b/src/routes/projects/create.js @@ -5,7 +5,7 @@ import _ from 'lodash'; import Joi from 'joi'; import models from '../../models'; -import { PROJECT_TYPE, PROJECT_MEMBER_ROLE, PROJECT_STATUS, USER_ROLE, EVENT } from '../../constants'; +import { PROJECT_TYPE, PROJECT_MEMBER_ROLE, PROJECT_STATUS, USER_ROLE, EVENT, REGEX } from '../../constants'; import util from '../../util'; import directProject from '../../services/directProject'; @@ -34,7 +34,7 @@ const createProjectValdiations = { }).allow(null), bookmarks: Joi.array().items(Joi.object().keys({ title: Joi.string(), - address: Joi.string().regex(/^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,15})+(\:[0-9]{2,5})?(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=]*)?$/), + address: Joi.string().regex(REGEX.URL), })).optional().allow(null), estimatedPrice: Joi.number().precision(2).positive().optional() .allow(null), diff --git a/src/routes/projects/update.js b/src/routes/projects/update.js index 6c1f2fb0..b02fc55d 100644 --- a/src/routes/projects/update.js +++ b/src/routes/projects/update.js @@ -11,6 +11,7 @@ import { PROJECT_MEMBER_ROLE, EVENT, USER_ROLE, + REGEX, } from '../../constants'; import util from '../../util'; import directProject from '../../services/directProject'; @@ -57,7 +58,7 @@ const updateProjectValdiations = { }).allow(null), bookmarks: Joi.array().items(Joi.object().keys({ title: Joi.string(), - address: Joi.string().regex(/^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,15})+(\:[0-9]{2,5})?(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=]*)?$/), + address: Joi.string().regex(REGEX.URL), })).optional().allow(null), type: Joi.any().valid(_.values(PROJECT_TYPE)), details: Joi.any(), From 29150dbb6682192c2b6c5ac517a71e592e3ca797 Mon Sep 17 00:00:00 2001 From: Samir Gondzetovic Date: Sun, 14 Jan 2018 14:33:55 +0000 Subject: [PATCH 03/27] add event initiator id --- src/constants.js | 4 ++-- src/events/busApi.js | 13 ++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/constants.js b/src/constants.js index 54eef261..e29d3fcb 100644 --- a/src/constants.js +++ b/src/constants.js @@ -73,5 +73,5 @@ export const BUS_API_EVENT = { }; export const REGEX = { - URL: /^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,15})+(\:[0-9]{2,5})?(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=]*)?$/, -}; \ No newline at end of file + URL: /^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,15})+(\:[0-9]{2,5})?(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=]*)?$/, // eslint-disable-line +}; diff --git a/src/events/busApi.js b/src/events/busApi.js index 79230b2b..b6ace085 100644 --- a/src/events/busApi.js +++ b/src/events/busApi.js @@ -27,7 +27,7 @@ module.exports = (app, logger) => { createEvent(BUS_API_EVENT.PROJECT_CREATED, { projectId: project.id, projectName: project.name, - userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, }); }); @@ -42,7 +42,7 @@ module.exports = (app, logger) => { createEvent(mapEventTypes[updated.status], { projectId: updated.id, projectName: updated.name, - userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, }); } else if ( !_.isEqual(original.details, updated.details) || @@ -52,14 +52,14 @@ module.exports = (app, logger) => { createEvent(BUS_API_EVENT.PROJECT_SPECIFICATION_MODIFIED, { projectId: updated.id, projectName: updated.name, - userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, }); } else if (!_.isEqual(original.bookmarks, updated.bookmarks)) { logger.debug('project bookmarks is updated'); createEvent(BUS_API_EVENT.PROJECT_LINK_CREATED, { projectId: updated.id, projectName: updated.name, - userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, }); } }); @@ -92,6 +92,7 @@ module.exports = (app, logger) => { projectId, projectName: project.name, userId: member.userId, + initiatorUserId: req.authUser.userId, }); }).catch(err => null); // eslint-disable-line no-unused-vars }); @@ -119,6 +120,7 @@ module.exports = (app, logger) => { projectId, projectName: project.name, userId: member.userId, + initiatorUserId: req.authUser.userId, }); } }).catch(err => null); // eslint-disable-line no-unused-vars @@ -141,6 +143,7 @@ module.exports = (app, logger) => { projectId, projectName: project.name, userId: updated.userId, + initiatorUserId: req.authUser.userId, }); } }).catch(err => null); // eslint-disable-line no-unused-vars @@ -163,7 +166,7 @@ module.exports = (app, logger) => { projectId, projectName: project.name, fileName: attachment.filePath.replace(/^.*[\\\/]/, ''), // eslint-disable-line - userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, }); }).catch(err => null); // eslint-disable-line no-unused-vars }); From 2357c75f0272b2debe9f325882cf2249d217ee89 Mon Sep 17 00:00:00 2001 From: gondzo Date: Mon, 15 Jan 2018 10:28:42 +0100 Subject: [PATCH 04/27] add search by ref code --- src/routes/projects/list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/projects/list.js b/src/routes/projects/list.js index f1cd2c9a..b6b3cbaa 100755 --- a/src/routes/projects/list.js +++ b/src/routes/projects/list.js @@ -118,7 +118,7 @@ const parseElasticSearchCriteria = (criteria, fields, order) => { query_string: { query: `*${keyword}*`, analyze_wildcard: true, - fields: ['name^3', 'description', 'type'], // boost name field + fields: ['name^3', 'description', 'type', 'details.utm.code'], // boost name field }, }, { From 65c0f1a1cbf586659925b90661d9282807e31452 Mon Sep 17 00:00:00 2001 From: gondzo Date: Mon, 15 Jan 2018 11:07:30 +0100 Subject: [PATCH 05/27] Update create.spec.js --- src/routes/projects/create.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/projects/create.spec.js b/src/routes/projects/create.spec.js index c41fa612..416db463 100644 --- a/src/routes/projects/create.spec.js +++ b/src/routes/projects/create.spec.js @@ -33,7 +33,7 @@ describe('Project create', () => { name: 'test project1', bookmarks: [{ title: 'title1', - address: 'address1', + address: 'http://www.address.com', }], }, }; From a5606d3c6583423a505dddd3f2dc389bde7baf9d Mon Sep 17 00:00:00 2001 From: gondzo Date: Mon, 15 Jan 2018 11:11:56 +0100 Subject: [PATCH 06/27] fix tests --- src/routes/projects/create.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/projects/create.spec.js b/src/routes/projects/create.spec.js index 416db463..9e9cb0dc 100644 --- a/src/routes/projects/create.spec.js +++ b/src/routes/projects/create.spec.js @@ -136,7 +136,7 @@ describe('Project create', () => { resJson.members[0].isPrimary.should.be.truthy; resJson.bookmarks.should.have.lengthOf(1); resJson.bookmarks[0].title.should.be.eql('title1'); - resJson.bookmarks[0].address.should.be.eql('address1'); + resJson.bookmarks[0].address.should.be.eql('http://www.address.com'); server.services.pubsub.publish.calledWith('project.draft-created').should.be.true; done(); } From 7605d1fd39ba18c8733f50707be442f688ca6ec6 Mon Sep 17 00:00:00 2001 From: Samir Gondzetovic Date: Mon, 15 Jan 2018 15:31:10 +0000 Subject: [PATCH 07/27] update es index for searching details.utm.code --- migrations/elasticsearch_sync.js | 4 ++++ src/routes/projects/list.js | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/migrations/elasticsearch_sync.js b/migrations/elasticsearch_sync.js index 6974f3d7..1fe0e30b 100644 --- a/migrations/elasticsearch_sync.js +++ b/migrations/elasticsearch_sync.js @@ -63,6 +63,10 @@ function getRequestBody(indexName) { details: { type: 'nested', properties: { summary: { type: 'string' }, + utm: { type: 'nested', + properties: { + code: { type: 'string' } + } }, TBD_usageDescription: { type: 'string' }, TBD_features: { type: 'nested', properties: { diff --git a/src/routes/projects/list.js b/src/routes/projects/list.js index b6b3cbaa..fc5ecd64 100755 --- a/src/routes/projects/list.js +++ b/src/routes/projects/list.js @@ -121,6 +121,23 @@ const parseElasticSearchCriteria = (criteria, fields, order) => { fields: ['name^3', 'description', 'type', 'details.utm.code'], // boost name field }, }, + { + nested: { + path: 'details', + query: { + nested: { + path: 'details.utm', + query: { + query_string: { + query: `*${keyword}*`, + analyze_wildcard: true, + fields: ['details.utm.code'], + }, + }, + } + }, + }, + }, { nested: { path: 'members', From 44190fa78801c05d769c44280270be123f3fded4 Mon Sep 17 00:00:00 2001 From: Samir Gondzetovic Date: Mon, 15 Jan 2018 15:37:58 +0000 Subject: [PATCH 08/27] lint fix --- src/routes/projects/list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/projects/list.js b/src/routes/projects/list.js index fc5ecd64..16e8c98a 100755 --- a/src/routes/projects/list.js +++ b/src/routes/projects/list.js @@ -134,7 +134,7 @@ const parseElasticSearchCriteria = (criteria, fields, order) => { fields: ['details.utm.code'], }, }, - } + }, }, }, }, From 187e3b29ce30d43889ea719e2a8e581c6f8d0b77 Mon Sep 17 00:00:00 2001 From: Samir Gondzetovic Date: Mon, 15 Jan 2018 15:38:57 +0000 Subject: [PATCH 09/27] lint fix --- migrations/elasticsearch_sync.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/elasticsearch_sync.js b/migrations/elasticsearch_sync.js index 1fe0e30b..14c283b7 100644 --- a/migrations/elasticsearch_sync.js +++ b/migrations/elasticsearch_sync.js @@ -65,7 +65,7 @@ function getRequestBody(indexName) { summary: { type: 'string' }, utm: { type: 'nested', properties: { - code: { type: 'string' } + code: { type: 'string' }, } }, TBD_usageDescription: { type: 'string' }, TBD_features: { type: 'nested', From 7a78c2f2606dc511f515aaf7550e1ca7a2a307a3 Mon Sep 17 00:00:00 2001 From: gondzo Date: Mon, 15 Jan 2018 17:40:27 +0100 Subject: [PATCH 10/27] remove ref code search --- src/routes/projects/list.js | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/routes/projects/list.js b/src/routes/projects/list.js index 16e8c98a..f1cd2c9a 100755 --- a/src/routes/projects/list.js +++ b/src/routes/projects/list.js @@ -118,24 +118,7 @@ const parseElasticSearchCriteria = (criteria, fields, order) => { query_string: { query: `*${keyword}*`, analyze_wildcard: true, - fields: ['name^3', 'description', 'type', 'details.utm.code'], // boost name field - }, - }, - { - nested: { - path: 'details', - query: { - nested: { - path: 'details.utm', - query: { - query_string: { - query: `*${keyword}*`, - analyze_wildcard: true, - fields: ['details.utm.code'], - }, - }, - }, - }, + fields: ['name^3', 'description', 'type'], // boost name field }, }, { From ac3166d8a465ba645f02439fa9d1f3f9668b4563 Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Wed, 17 Jan 2018 11:21:17 +0530 Subject: [PATCH 11/27] =?UTF-8?q?Github=20issue#1604,=20Allow=20managers/a?= =?UTF-8?q?dmins=20to=20assign=20Manager=20role=20to=20people=20added=20to?= =?UTF-8?q?=20a=20project=20=E2=80=94=20Added=20back=20end=20support=20req?= =?UTF-8?q?uired.=20It=20prevents=20any=20non=20privileged/eligible=20user?= =?UTF-8?q?=20to=20be=20added=20as=20Manager.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants.js | 1 + src/routes/projectMembers/create.js | 16 +- src/routes/projectMembers/create.spec.js | 230 ++++++++++++++++++++++- src/util.js | 31 +++ 4 files changed, 275 insertions(+), 3 deletions(-) diff --git a/src/constants.js b/src/constants.js index e29d3fcb..50f11de6 100644 --- a/src/constants.js +++ b/src/constants.js @@ -35,6 +35,7 @@ export const USER_ROLE = { export const ADMIN_ROLES = [USER_ROLE.CONNECT_ADMIN, USER_ROLE.TOPCODER_ADMIN]; +export const MANAGER_ROLES = [...ADMIN_ROLES, USER_ROLE.MANAGER]; export const EVENT = { ROUTING_KEY: { diff --git a/src/routes/projectMembers/create.js b/src/routes/projectMembers/create.js index 64f2dfe7..2556af63 100644 --- a/src/routes/projectMembers/create.js +++ b/src/routes/projectMembers/create.js @@ -6,7 +6,7 @@ import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; -import { PROJECT_MEMBER_ROLE, EVENT } from '../../constants'; +import { PROJECT_MEMBER_ROLE, MANAGER_ROLES, EVENT } from '../../constants'; /** * API to add a project member. @@ -53,10 +53,21 @@ module.exports = [ if (_.isUndefined(member.isPrimary)) { member.isPrimary = _.isUndefined(_.find(members, m => m.isPrimary && m.role === member.role)); } + let promise = Promise.resolve(); + if (member.role === PROJECT_MEMBER_ROLE.MANAGER) { + promise = util.getUser(member.userId); + } req.log.debug('creating member', member); let newMember = null; // register member - return models.ProjectMember.create(member) + return promise.then((memberUser) => { + if (member.role === PROJECT_MEMBER_ROLE.MANAGER + && (!memberUser || !memberUser.roles || !util.hasIntersection(MANAGER_ROLES, memberUser.roles))) { + const err = new Error('This user can\'t be added as a Manager to the project'); + err.status = 400; + return next(err); + } + return models.ProjectMember.create(member) .then((_newMember) => { newMember = _newMember.get({ plain: true }); // publish event @@ -72,5 +83,6 @@ module.exports = [ req.log.error('Unable to register ', err); next(err); }); + }); }, ]; diff --git a/src/routes/projectMembers/create.spec.js b/src/routes/projectMembers/create.spec.js index 0c714775..337978f5 100644 --- a/src/routes/projectMembers/create.spec.js +++ b/src/routes/projectMembers/create.spec.js @@ -8,13 +8,14 @@ import models from '../../models'; import util from '../../util'; import server from '../../app'; import testUtil from '../../tests/util'; +import { USER_ROLE } from '../../constants'; const should = chai.should(); describe('Project Members create', () => { let project1; let project2; - before((done) => { + beforeEach((done) => { testUtil.clearDb() .then(() => { models.Project.create({ @@ -246,5 +247,232 @@ describe('Project Members create', () => { } }); }); + + it('should return 400 for trying to add customers as manager', (done) => { + const mockHttpClient = _.merge(testUtil.mockHttpClient, { + get: () => Promise.resolve({ + status: 200, + data: { + id: 'requesterId', + version: 'v3', + result: { + success: true, + status: 200, + content: { + roles: ['Topcoder User'], + }, + }, + }, + }), + }); + sandbox.stub(util, 'getHttpClient', () => mockHttpClient); + request(server) + .post(`/v4/projects/${project1.id}/members/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send({ + param: { + userId: 3, + role: 'manager', + }, + }) + .expect('Content-Type', /json/) + .expect(400) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + const errorMessage = _.get(resJson, 'message', ''); + sinon.assert.match(errorMessage, /.*can't be added as a Manager/); + done(); + } + }); + }); + + it('should return 400 for trying to add copilot as manager', (done) => { + const mockHttpClient = _.merge(testUtil.mockHttpClient, { + get: () => Promise.resolve({ + status: 200, + data: { + id: 'requesterId', + version: 'v3', + result: { + success: true, + status: 200, + content: { + roles: [USER_ROLE.COPILOT], + }, + }, + }, + }), + }); + sandbox.stub(util, 'getHttpClient', () => mockHttpClient); + request(server) + .post(`/v4/projects/${project1.id}/members/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send({ + param: { + userId: 3, + role: 'manager', + }, + }) + .expect('Content-Type', /json/) + .expect(400) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + done(); + } + }); + }); + + it('should return 201 and register Connect Manager as manager', (done) => { + const mockHttpClient = _.merge(testUtil.mockHttpClient, { + get: () => Promise.resolve({ + status: 200, + data: { + id: 'requesterId', + version: 'v3', + result: { + success: true, + status: 200, + content: { + roles: [USER_ROLE.MANAGER], + }, + }, + }, + }), + }); + sandbox.stub(util, 'getHttpClient', () => mockHttpClient); + request(server) + .post(`/v4/projects/${project1.id}/members/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send({ + param: { + userId: 3, + role: 'manager', + }, + }) + .expect('Content-Type', /json/) + .expect(201) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + resJson.role.should.equal('manager'); + resJson.isPrimary.should.be.truthy; + resJson.projectId.should.equal(project1.id); + resJson.userId.should.equal(3); + server.services.pubsub.publish.calledWith('project.member.added').should.be.true; + done(); + } + }); + }); + + it('should return 201 and register Connect Admin as manager', (done) => { + const mockHttpClient = _.merge(testUtil.mockHttpClient, { + get: () => Promise.resolve({ + status: 200, + data: { + id: 'requesterId', + version: 'v3', + result: { + success: true, + status: 200, + content: { + roles: [USER_ROLE.CONNECT_ADMIN], + }, + }, + }, + }), + }); + sandbox.stub(util, 'getHttpClient', () => mockHttpClient); + request(server) + .post(`/v4/projects/${project1.id}/members/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send({ + param: { + userId: 3, + role: 'manager', + }, + }) + .expect('Content-Type', /json/) + .expect(201) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + resJson.role.should.equal('manager'); + resJson.isPrimary.should.be.truthy; + resJson.projectId.should.equal(project1.id); + resJson.userId.should.equal(3); + server.services.pubsub.publish.calledWith('project.member.added').should.be.true; + done(); + } + }); + }); + + it('should return 201 and register Topcoder Admin as manager', (done) => { + const mockHttpClient = _.merge(testUtil.mockHttpClient, { + get: () => Promise.resolve({ + status: 200, + data: { + id: 'requesterId', + version: 'v3', + result: { + success: true, + status: 200, + content: { + roles: [USER_ROLE.TOPCODER_ADMIN], + }, + }, + }, + }), + }); + sandbox.stub(util, 'getHttpClient', () => mockHttpClient); + request(server) + .post(`/v4/projects/${project1.id}/members/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send({ + param: { + userId: 3, + role: 'manager', + }, + }) + .expect('Content-Type', /json/) + .expect(201) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + resJson.role.should.equal('manager'); + resJson.isPrimary.should.be.truthy; + resJson.projectId.should.equal(project1.id); + resJson.userId.should.equal(3); + server.services.pubsub.publish.calledWith('project.member.added').should.be.true; + done(); + } + }); + }); }); }); diff --git a/src/util.js b/src/util.js index 27df14ea..ae2a01be 100644 --- a/src/util.js +++ b/src/util.js @@ -85,6 +85,16 @@ _.assignIn(util, { authRoles = authRoles.map(s => s.toLowerCase()); return _.intersection(authRoles, roles.map(r => r.toLowerCase())).length > 0; }, + /** + * Helper funtion to find intersection (case insensitive) between two arrays + * @param {Array} array1 first array of strings + * @param {Array} array2 second array of strings + * @return {boolean} true/false + */ + hasIntersection: (array1, array2) => { + const lowercased = array1.map(s => s.toLowerCase()); + return _.intersection(lowercased, array2.map(r => r.toLowerCase())).length > 0; + }, /** * Helper funtion to verify if user has admin roles * @param {object} req Request object that should contain authUser @@ -307,6 +317,27 @@ _.assignIn(util, { return Promise.reject(err); } }), + + /** + * Retrieve member details from userIds + */ + getUser: Promise.coroutine(function* (userId, logger, requestId) { // eslint-disable-line func-names + try { + const token = yield this.getSystemUserToken(logger); + const httpClient = this.getHttpClient({ id: requestId, log: logger }); + return httpClient.get(`${config.identityServiceEndpoint}/users/${userId}`, { + params: { + fields: 'id,roles', + }, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + }).then(res => _.get(res, 'data.result.content', null)); + } catch (err) { + return Promise.reject(err); + } + }), }); export default util; From dcaee3f2d294b067921a1305abd48b2baf9f0b9d Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Wed, 17 Jan 2018 11:21:57 +0530 Subject: [PATCH 12/27] Temp deployment of feature branch to dev --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index f5993dff..028867ab 100644 --- a/circle.yml +++ b/circle.yml @@ -24,7 +24,7 @@ dependencies: deployment: development: - branch: dev + branch: [dev, 'feature/allow_managers_to_be_added'] commands: - ./ebs_deploy.sh tc-project-service DEV $CIRCLE_BUILD_NUM From eef87a1f0af6ef421555e7d1fc36ac77bf6acd02 Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Wed, 17 Jan 2018 12:31:31 +0530 Subject: [PATCH 13/27] =?UTF-8?q?Github=20issue#1604,=20Allow=20managers/a?= =?UTF-8?q?dmins=20to=20assign=20Manager=20role=20to=20people=20added=20to?= =?UTF-8?q?=20a=20project=20=E2=80=94=20Used=20roles=20endpoint=20to=20ide?= =?UTF-8?q?ntity=20roles=20of=20the=20given=20user=20because=20we=20don?= =?UTF-8?q?=E2=80=99t=20return=20the=20roles=20of=20a=20user=20with=20/use?= =?UTF-8?q?rs/userId=20endpoint=20even=20for=20the=20admins?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/projectMembers/create.js | 6 ++--- src/routes/projectMembers/create.spec.js | 30 ++++++++++++------------ src/util.js | 8 +++---- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/routes/projectMembers/create.js b/src/routes/projectMembers/create.js index 2556af63..2db03037 100644 --- a/src/routes/projectMembers/create.js +++ b/src/routes/projectMembers/create.js @@ -55,14 +55,14 @@ module.exports = [ } let promise = Promise.resolve(); if (member.role === PROJECT_MEMBER_ROLE.MANAGER) { - promise = util.getUser(member.userId); + promise = util.getUserRoles(member.userId); } req.log.debug('creating member', member); let newMember = null; // register member - return promise.then((memberUser) => { + return promise.then((memberRoles) => { if (member.role === PROJECT_MEMBER_ROLE.MANAGER - && (!memberUser || !memberUser.roles || !util.hasIntersection(MANAGER_ROLES, memberUser.roles))) { + && (!memberRoles || !util.hasIntersection(MANAGER_ROLES, memberRoles))) { const err = new Error('This user can\'t be added as a Manager to the project'); err.status = 400; return next(err); diff --git a/src/routes/projectMembers/create.spec.js b/src/routes/projectMembers/create.spec.js index 337978f5..4d0c9f2f 100644 --- a/src/routes/projectMembers/create.spec.js +++ b/src/routes/projectMembers/create.spec.js @@ -258,9 +258,9 @@ describe('Project Members create', () => { result: { success: true, status: 200, - content: { - roles: ['Topcoder User'], - }, + content: [{ + roleName: 'Topcoder User', + }], }, }, }), @@ -302,9 +302,9 @@ describe('Project Members create', () => { result: { success: true, status: 200, - content: { - roles: [USER_ROLE.COPILOT], - }, + content: [{ + roleName: USER_ROLE.COPILOT, + }], }, }, }), @@ -344,9 +344,9 @@ describe('Project Members create', () => { result: { success: true, status: 200, - content: { - roles: [USER_ROLE.MANAGER], - }, + content: [{ + roleName: USER_ROLE.MANAGER, + }], }, }, }), @@ -391,9 +391,9 @@ describe('Project Members create', () => { result: { success: true, status: 200, - content: { - roles: [USER_ROLE.CONNECT_ADMIN], - }, + content: [{ + roleName: USER_ROLE.CONNECT_ADMIN, + }], }, }, }), @@ -438,9 +438,9 @@ describe('Project Members create', () => { result: { success: true, status: 200, - content: { - roles: [USER_ROLE.TOPCODER_ADMIN], - }, + content: [{ + roleName: USER_ROLE.TOPCODER_ADMIN, + }], }, }, }), diff --git a/src/util.js b/src/util.js index ae2a01be..cb7af1f3 100644 --- a/src/util.js +++ b/src/util.js @@ -321,19 +321,19 @@ _.assignIn(util, { /** * Retrieve member details from userIds */ - getUser: Promise.coroutine(function* (userId, logger, requestId) { // eslint-disable-line func-names + getUserRoles: Promise.coroutine(function* (userId, logger, requestId) { // eslint-disable-line func-names try { const token = yield this.getSystemUserToken(logger); const httpClient = this.getHttpClient({ id: requestId, log: logger }); - return httpClient.get(`${config.identityServiceEndpoint}/users/${userId}`, { + return httpClient.get(`${config.identityServiceEndpoint}/roles`, { params: { - fields: 'id,roles', + filter: `subjectID=${userId}`, }, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, - }).then(res => _.get(res, 'data.result.content', null)); + }).then(res => _.get(res, 'data.result.content', []).map(r => r.roleName)); } catch (err) { return Promise.reject(err); } From 90feb2fe94cb2f833ebc6e7da7381fca34fa9186 Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Wed, 17 Jan 2018 14:53:01 +0530 Subject: [PATCH 14/27] debugging --- src/routes/projectMembers/create.js | 5 +++++ src/util.js | 1 + 2 files changed, 6 insertions(+) diff --git a/src/routes/projectMembers/create.js b/src/routes/projectMembers/create.js index 2db03037..98e10a8c 100644 --- a/src/routes/projectMembers/create.js +++ b/src/routes/projectMembers/create.js @@ -61,14 +61,19 @@ module.exports = [ let newMember = null; // register member return promise.then((memberRoles) => { + console.log('memberRoles', memberRoles); + console.log('member.role', member.role); if (member.role === PROJECT_MEMBER_ROLE.MANAGER && (!memberRoles || !util.hasIntersection(MANAGER_ROLES, memberRoles))) { + console.log('Should throw error'); const err = new Error('This user can\'t be added as a Manager to the project'); err.status = 400; return next(err); } + console.log('All good'); return models.ProjectMember.create(member) .then((_newMember) => { + console.log(_newMember, '_newMember'); newMember = _newMember.get({ plain: true }); // publish event req.app.services.pubsub.publish( diff --git a/src/util.js b/src/util.js index cb7af1f3..0adbcf72 100644 --- a/src/util.js +++ b/src/util.js @@ -324,6 +324,7 @@ _.assignIn(util, { getUserRoles: Promise.coroutine(function* (userId, logger, requestId) { // eslint-disable-line func-names try { const token = yield this.getSystemUserToken(logger); + console.log("token", token); const httpClient = this.getHttpClient({ id: requestId, log: logger }); return httpClient.get(`${config.identityServiceEndpoint}/roles`, { params: { From 6bc605c3ac8f957c299e9d2c7ece3fff25c7fedc Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Wed, 17 Jan 2018 14:56:39 +0530 Subject: [PATCH 15/27] debugging --- src/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.js b/src/util.js index 0adbcf72..2a6bd8f3 100644 --- a/src/util.js +++ b/src/util.js @@ -324,7 +324,7 @@ _.assignIn(util, { getUserRoles: Promise.coroutine(function* (userId, logger, requestId) { // eslint-disable-line func-names try { const token = yield this.getSystemUserToken(logger); - console.log("token", token); + console.log('token', token); const httpClient = this.getHttpClient({ id: requestId, log: logger }); return httpClient.get(`${config.identityServiceEndpoint}/roles`, { params: { From 5a8f1b9c8e9c493504dbfc6f3bcb9840a3b994fe Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Wed, 17 Jan 2018 15:20:58 +0530 Subject: [PATCH 16/27] more debugging --- src/routes/projectMembers/create.js | 1 + src/util.js | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/routes/projectMembers/create.js b/src/routes/projectMembers/create.js index 98e10a8c..d0a433a3 100644 --- a/src/routes/projectMembers/create.js +++ b/src/routes/projectMembers/create.js @@ -56,6 +56,7 @@ module.exports = [ let promise = Promise.resolve(); if (member.role === PROJECT_MEMBER_ROLE.MANAGER) { promise = util.getUserRoles(member.userId); + console.log('after getting promise for getUserRoles'); } req.log.debug('creating member', member); let newMember = null; diff --git a/src/util.js b/src/util.js index 2a6bd8f3..199e031f 100644 --- a/src/util.js +++ b/src/util.js @@ -326,6 +326,7 @@ _.assignIn(util, { const token = yield this.getSystemUserToken(logger); console.log('token', token); const httpClient = this.getHttpClient({ id: requestId, log: logger }); + console.log(`${config.identityServiceEndpoint}/roles`); return httpClient.get(`${config.identityServiceEndpoint}/roles`, { params: { filter: `subjectID=${userId}`, @@ -334,7 +335,8 @@ _.assignIn(util, { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, - }).then(res => _.get(res, 'data.result.content', []).map(r => r.roleName)); + }).then(res => _.get(res, 'data.result.content', []).map(r => r.roleName)) + .catch( (e) => return Promise.reject(e)); } catch (err) { return Promise.reject(err); } From 648e94a6e246740b3bb472ac22f5410df3c69c7d Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Wed, 17 Jan 2018 15:41:51 +0530 Subject: [PATCH 17/27] added request id --- src/routes/projectMembers/create.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/projectMembers/create.js b/src/routes/projectMembers/create.js index d0a433a3..f93b0547 100644 --- a/src/routes/projectMembers/create.js +++ b/src/routes/projectMembers/create.js @@ -55,7 +55,7 @@ module.exports = [ } let promise = Promise.resolve(); if (member.role === PROJECT_MEMBER_ROLE.MANAGER) { - promise = util.getUserRoles(member.userId); + promise = util.getUserRoles(member.userId, req.log, req.id); console.log('after getting promise for getUserRoles'); } req.log.debug('creating member', member); From 3e24201408df1f9789ba94c42fc2ed96e4f0abc1 Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Wed, 17 Jan 2018 15:46:34 +0530 Subject: [PATCH 18/27] Fixed lint error --- src/util.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/util.js b/src/util.js index 199e031f..6ee5264e 100644 --- a/src/util.js +++ b/src/util.js @@ -335,8 +335,7 @@ _.assignIn(util, { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, - }).then(res => _.get(res, 'data.result.content', []).map(r => r.roleName)) - .catch( (e) => return Promise.reject(e)); + }).then(res => _.get(res, 'data.result.content', []).map(r => r.roleName)); } catch (err) { return Promise.reject(err); } From 15d292a5ec020c91dd6b1b684a854213b12056c6 Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Wed, 17 Jan 2018 15:58:59 +0530 Subject: [PATCH 19/27] Fixing URL creation for identity api endpoint --- src/util.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util.js b/src/util.js index 6ee5264e..97b44fb0 100644 --- a/src/util.js +++ b/src/util.js @@ -326,8 +326,8 @@ _.assignIn(util, { const token = yield this.getSystemUserToken(logger); console.log('token', token); const httpClient = this.getHttpClient({ id: requestId, log: logger }); - console.log(`${config.identityServiceEndpoint}/roles`); - return httpClient.get(`${config.identityServiceEndpoint}/roles`, { + console.log(`${config.identityServiceEndpoint}roles`); + return httpClient.get(`${config.identityServiceEndpoint}roles`, { params: { filter: `subjectID=${userId}`, }, From f5545ab7ce52cb3594c98d84f60b4fe3884848ef Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Wed, 17 Jan 2018 16:23:05 +0530 Subject: [PATCH 20/27] removed debug statements --- src/routes/projectMembers/create.js | 6 ------ src/util.js | 2 -- 2 files changed, 8 deletions(-) diff --git a/src/routes/projectMembers/create.js b/src/routes/projectMembers/create.js index f93b0547..ac809e0a 100644 --- a/src/routes/projectMembers/create.js +++ b/src/routes/projectMembers/create.js @@ -56,25 +56,19 @@ module.exports = [ let promise = Promise.resolve(); if (member.role === PROJECT_MEMBER_ROLE.MANAGER) { promise = util.getUserRoles(member.userId, req.log, req.id); - console.log('after getting promise for getUserRoles'); } req.log.debug('creating member', member); let newMember = null; // register member return promise.then((memberRoles) => { - console.log('memberRoles', memberRoles); - console.log('member.role', member.role); if (member.role === PROJECT_MEMBER_ROLE.MANAGER && (!memberRoles || !util.hasIntersection(MANAGER_ROLES, memberRoles))) { - console.log('Should throw error'); const err = new Error('This user can\'t be added as a Manager to the project'); err.status = 400; return next(err); } - console.log('All good'); return models.ProjectMember.create(member) .then((_newMember) => { - console.log(_newMember, '_newMember'); newMember = _newMember.get({ plain: true }); // publish event req.app.services.pubsub.publish( diff --git a/src/util.js b/src/util.js index 97b44fb0..6371f726 100644 --- a/src/util.js +++ b/src/util.js @@ -324,9 +324,7 @@ _.assignIn(util, { getUserRoles: Promise.coroutine(function* (userId, logger, requestId) { // eslint-disable-line func-names try { const token = yield this.getSystemUserToken(logger); - console.log('token', token); const httpClient = this.getHttpClient({ id: requestId, log: logger }); - console.log(`${config.identityServiceEndpoint}roles`); return httpClient.get(`${config.identityServiceEndpoint}roles`, { params: { filter: `subjectID=${userId}`, From f1d2f434ec57143bd8d1d07b9ecba7de72010670 Mon Sep 17 00:00:00 2001 From: gondzo Date: Thu, 18 Jan 2018 15:07:01 +0100 Subject: [PATCH 21/27] add utm code search query --- src/routes/projects/list.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/routes/projects/list.js b/src/routes/projects/list.js index f1cd2c9a..b2bfe6eb 100755 --- a/src/routes/projects/list.js +++ b/src/routes/projects/list.js @@ -120,6 +120,23 @@ const parseElasticSearchCriteria = (criteria, fields, order) => { analyze_wildcard: true, fields: ['name^3', 'description', 'type'], // boost name field }, + }, + { + nested: { + path: 'details', + query: { + nested: { + path: 'details.utm', + query: { + query_string: { + query: `*${keyword}*`, + analyze_wildcard: true, + fields: ['details.utm.code'], + }, + }, + }, + }, + }, }, { nested: { From b12e94000ae617ef898a9c7980ab3f338c41b6b2 Mon Sep 17 00:00:00 2001 From: Samir Gondzetovic Date: Thu, 18 Jan 2018 14:17:10 +0000 Subject: [PATCH 22/27] lin fix --- src/routes/projects/list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/projects/list.js b/src/routes/projects/list.js index b2bfe6eb..e3292fde 100755 --- a/src/routes/projects/list.js +++ b/src/routes/projects/list.js @@ -121,7 +121,7 @@ const parseElasticSearchCriteria = (criteria, fields, order) => { fields: ['name^3', 'description', 'type'], // boost name field }, }, - { + { nested: { path: 'details', query: { From cc8967457a9e0fb942dc7010a9af7c72e19dbd93 Mon Sep 17 00:00:00 2001 From: Samir Gondzetovic Date: Sat, 20 Jan 2018 00:27:40 +0000 Subject: [PATCH 23/27] update ES index format --- migrations/elasticsearch_sync.js | 347 +++++++++++++++++++++++-------- 1 file changed, 258 insertions(+), 89 deletions(-) diff --git a/migrations/elasticsearch_sync.js b/migrations/elasticsearch_sync.js index 14c283b7..b55f4e5d 100644 --- a/migrations/elasticsearch_sync.js +++ b/migrations/elasticsearch_sync.js @@ -33,97 +33,266 @@ function getRequestBody(indexName) { const projectMapping = { _all: { enabled: false }, properties: { - id: { type: 'long' }, - directProjectId: { type: 'long' }, - billingAccountId: { type: 'long' }, - name: { type: 'string' }, - description: { type: 'string' }, - external: { type: 'object', - properties: { - id: { type: 'string', index: 'not_analyzed' }, - type: { type: 'string', index: 'not_analyzed' }, - data: { type: 'string' }, - } }, - bookmarks: { type: 'nested', - properties: { - title: { type: 'string' }, - address: { type: 'string' }, - } }, - utm: { type: 'object', - properties: { - campaign: { type: 'string' }, - medium: { type: 'string' }, - source: { type: 'string' }, - } }, - estimatedPrice: { type: 'double' }, - actualPrice: { type: 'double' }, - terms: { type: 'integer' }, - type: { type: 'string', index: 'not_analyzed' }, - status: { type: 'string', index: 'not_analyzed' }, - details: { type: 'nested', - properties: { - summary: { type: 'string' }, - utm: { type: 'nested', - properties: { - code: { type: 'string' }, - } }, - TBD_usageDescription: { type: 'string' }, - TBD_features: { type: 'nested', - properties: { - id: { type: 'integer' }, - title: { type: 'string' }, - description: { type: 'string' }, - isCustom: { type: 'boolean' }, - } }, - } }, - challengeEligibility: { type: 'nested', - properties: { - role: { type: 'string', index: 'not_analyzed' }, - users: { type: 'long' }, - groups: { type: 'long' }, - } }, - cancelReason: { type: 'string' }, - createdAt: { type: 'date' }, - updatedAt: { type: 'date' }, - createdBy: { type: 'integer' }, - updatedBy: { type: 'integer' }, - // project members nested data type - members: { - type: 'nested', - properties: { - id: { type: 'long' }, - userId: { type: 'long' }, - projectId: { type: 'long' }, - role: { type: 'string', index: 'not_analyzed' }, - firstName: { type: 'string' }, - lastName: { type: 'string' }, - email: { type: 'string', index: 'not_analyzed' }, - handle: { type: 'string', index: 'not_analyzed' }, - isPrimary: { type: 'boolean' }, - createdAt: { type: 'date' }, - updatedAt: { type: 'date' }, - createdBy: { type: 'integer' }, - updatedBy: { type: 'integer' }, - }, + "actualPrice": { + "type": "double" }, - // project attachments nested data type - attachments: { - type: 'nested', - properties: { - id: { type: 'long' }, - title: { type: 'string' }, - size: { type: 'double' }, - category: { type: 'string', index: 'not_analyzed' }, - description: { type: 'string' }, - filePath: { type: 'string' }, - projectId: { type: 'long' }, - contentType: { type: 'string', index: 'not_analyzed' }, - createdAt: { type: 'date' }, - updatedAt: { type: 'date' }, - createdBy: { type: 'integer' }, - updatedBy: { type: 'integer' }, - }, + "attachments": { + "type": "nested", + "properties": { + "category": { + "type": "string", + "index": "not_analyzed" + }, + "contentType": { + "type": "string", + "index": "not_analyzed" + }, + "createdAt": { + "type": "date", + "format": "strict_date_optional_time||epoch_millis" + }, + "createdBy": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "filePath": { + "type": "string" + }, + "id": { + "type": "long" + }, + "projectId": { + "type": "long" + }, + "size": { + "type": "double" + }, + "title": { + "type": "string" + }, + "updatedAt": { + "type": "date", + "format": "strict_date_optional_time||epoch_millis" + }, + "updatedBy": { + "type": "integer" + } + } + }, + "billingAccountId": { + "type": "long" + }, + "bookmarks": { + "type": "nested", + "properties": { + "address": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "cancelReason": { + "type": "string" + }, + "challengeEligibility": { + "type": "nested", + "properties": { + "groups": { + "type": "long" + }, + "role": { + "type": "string", + "index": "not_analyzed" + }, + "users": { + "type": "long" + } + } + }, + "createdAt": { + "type": "date", + "format": "strict_date_optional_time||epoch_millis" + }, + "createdBy": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "details": { + "type": "nested", + "properties": { + "TBD_features": { + "type": "nested", + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isCustom": { + "type": "boolean" + }, + "title": { + "type": "string" + } + } + }, + "TBD_usageDescription": { + "type": "string" + }, + "appDefinition": { + "properties": { + "goal": { + "properties": { + "value": { + "type": "string" + } + } + }, + "primaryTarget": { + "type": "string" + }, + "users": { + "properties": { + "value": { + "type": "string" + } + } + } + } + }, + "hideDiscussions": { + "type": "boolean" + }, + "products": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "utm": { + "type": "nested", + "properties": { + "code": { + "type": "string" + } + } + } + } + }, + "directProjectId": { + "type": "long" + }, + "estimatedPrice": { + "type": "double" + }, + "external": { + "properties": { + "data": { + "type": "string" + }, + "id": { + "type": "string", + "index": "not_analyzed" + }, + "type": { + "type": "string", + "index": "not_analyzed" + } + } + }, + "id": { + "type": "long" + }, + "members": { + "type": "nested", + "properties": { + "createdAt": { + "type": "date", + "format": "strict_date_optional_time||epoch_millis" + }, + "createdBy": { + "type": "integer" + }, + "email": { + "type": "string", + "index": "not_analyzed" + }, + "firstName": { + "type": "string" + }, + "handle": { + "type": "string", + "index": "not_analyzed" + }, + "id": { + "type": "long" + }, + "isPrimary": { + "type": "boolean" + }, + "lastName": { + "type": "string" + }, + "projectId": { + "type": "long" + }, + "role": { + "type": "string", + "index": "not_analyzed" + }, + "updatedAt": { + "type": "date", + "format": "strict_date_optional_time||epoch_millis" + }, + "updatedBy": { + "type": "integer" + }, + "userId": { + "type": "long" + } + } + }, + "name": { + "type": "string" + }, + "status": { + "type": "string", + "index": "not_analyzed" + }, + "terms": { + "type": "integer" + }, + "type": { + "type": "string", + "index": "not_analyzed" + }, + "updatedAt": { + "type": "date", + "format": "strict_date_optional_time||epoch_millis" + }, + "updatedBy": { + "type": "integer" }, + "utm": { + "properties": { + "campaign": { + "type": "string" + }, + "medium": { + "type": "string" + }, + "source": { + "type": "string" + } + } + } }, }; switch (indexName) { From abc8017665e5ad1f9650c2957f2ae4e68071e0c0 Mon Sep 17 00:00:00 2001 From: Samir Gondzetovic Date: Sat, 20 Jan 2018 00:32:30 +0000 Subject: [PATCH 24/27] lint fix --- migrations/elasticsearch_sync.js | 422 +++++++++++++++---------------- 1 file changed, 211 insertions(+), 211 deletions(-) diff --git a/migrations/elasticsearch_sync.js b/migrations/elasticsearch_sync.js index b55f4e5d..1c9e5713 100644 --- a/migrations/elasticsearch_sync.js +++ b/migrations/elasticsearch_sync.js @@ -33,266 +33,266 @@ function getRequestBody(indexName) { const projectMapping = { _all: { enabled: false }, properties: { - "actualPrice": { - "type": "double" + actualPrice: { + type: 'double', }, - "attachments": { - "type": "nested", - "properties": { - "category": { - "type": "string", - "index": "not_analyzed" + attachments: { + type: 'nested', + properties: { + category: { + type: 'string', + index: 'not_analyzed', }, - "contentType": { - "type": "string", - "index": "not_analyzed" + contentType: { + type: 'string', + index: 'not_analyzed', }, - "createdAt": { - "type": "date", - "format": "strict_date_optional_time||epoch_millis" + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', }, - "createdBy": { - "type": "integer" + createdBy: { + type: 'integer', }, - "description": { - "type": "string" + description: { + type: 'string', }, - "filePath": { - "type": "string" + filePath: { + type: 'string', }, - "id": { - "type": "long" + id: { + type: 'long', }, - "projectId": { - "type": "long" + projectId: { + type: 'long', }, - "size": { - "type": "double" + size: { + type: 'double', }, - "title": { - "type": "string" + title: { + type: 'string', }, - "updatedAt": { - "type": "date", - "format": "strict_date_optional_time||epoch_millis" + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', }, - "updatedBy": { - "type": "integer" - } - } + updatedBy: { + type: 'integer', + }, + }, }, - "billingAccountId": { - "type": "long" + billingAccountId: { + type: 'long', }, - "bookmarks": { - "type": "nested", - "properties": { - "address": { - "type": "string" - }, - "title": { - "type": "string" - } - } + bookmarks: { + type: 'nested', + properties: { + address: { + type: 'string', + }, + title: { + type: 'string', + }, + }, }, - "cancelReason": { - "type": "string" + cancelReason: { + type: 'string', }, - "challengeEligibility": { - "type": "nested", - "properties": { - "groups": { - "type": "long" - }, - "role": { - "type": "string", - "index": "not_analyzed" - }, - "users": { - "type": "long" - } - } + challengeEligibility: { + type: 'nested', + properties: { + groups: { + type: 'long', + }, + role: { + type: 'string', + index: 'not_analyzed', + }, + users: { + type: 'long', + }, + }, }, - "createdAt": { - "type": "date", - "format": "strict_date_optional_time||epoch_millis" + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', }, - "createdBy": { - "type": "integer" + createdBy: { + type: 'integer', }, - "description": { - "type": "string" + description: { + type: 'string', }, - "details": { - "type": "nested", - "properties": { - "TBD_features": { - "type": "nested", - "properties": { - "description": { - "type": "string" + details: { + type: 'nested', + properties: { + TBD_features: { + type: 'nested', + properties: { + description: { + type: 'string', + }, + id: { + type: 'integer', }, - "id": { - "type": "integer" + isCustom: { + type: 'boolean', }, - "isCustom": { - "type": "boolean" + title: { + type: 'string', }, - "title": { - "type": "string" - } - } - }, - "TBD_usageDescription": { - "type": "string" - }, - "appDefinition": { - "properties": { - "goal": { - "properties": { - "value": { - "type": "string" - } - } + }, + }, + TBD_usageDescription: { + type: 'string', + }, + appDefinition: { + properties: { + goal: { + properties: { + value: { + type: 'string', + }, + }, }, - "primaryTarget": { - "type": "string" + primaryTarget: { + type: 'string', }, - "users": { - "properties": { - "value": { - "type": "string" - } - } - } - } - }, - "hideDiscussions": { - "type": "boolean" - }, - "products": { - "type": "string" - }, - "summary": { - "type": "string" - }, - "utm": { - "type": "nested", - "properties": { - "code": { - "type": "string" - } - } - } - } + users: { + properties: { + value: { + type: 'string', + }, + }, + }, + }, + }, + hideDiscussions: { + type: 'boolean', + }, + products: { + type: 'string', + }, + summary: { + type: 'string', + }, + utm: { + type: 'nested', + properties: { + code: { + type: 'string', + }, + }, + }, + }, }, - "directProjectId": { - "type": "long" + directProjectId: { + type: 'long', }, - "estimatedPrice": { - "type": "double" + estimatedPrice: { + type: 'double', }, - "external": { - "properties": { - "data": { - "type": "string" - }, - "id": { - "type": "string", - "index": "not_analyzed" - }, - "type": { - "type": "string", - "index": "not_analyzed" - } - } + external: { + properties: { + data: { + type: 'string', + }, + id: { + type: 'string', + index: 'not_analyzed', + }, + type: { + type: 'string', + index: 'not_analyzed', + }, + }, }, - "id": { - "type": "long" + id: { + type: 'long', }, - "members": { - "type": "nested", - "properties": { - "createdAt": { - "type": "date", - "format": "strict_date_optional_time||epoch_millis" + members: { + type: 'nested', + properties: { + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', }, - "createdBy": { - "type": "integer" + createdBy: { + type: 'integer', }, - "email": { - "type": "string", - "index": "not_analyzed" + email: { + type: 'string', + index: 'not_analyzed', }, - "firstName": { - "type": "string" + firstName: { + type: 'string', }, - "handle": { - "type": "string", - "index": "not_analyzed" + handle: { + type: 'string', + index: 'not_analyzed', }, - "id": { - "type": "long" + id: { + type: 'long', }, - "isPrimary": { - "type": "boolean" + isPrimary: { + type: 'boolean', }, - "lastName": { - "type": "string" + lastName: { + type: 'string', }, - "projectId": { - "type": "long" + projectId: { + type: 'long', }, - "role": { - "type": "string", - "index": "not_analyzed" + role: { + type: 'string', + index: 'not_analyzed', }, - "updatedAt": { - "type": "date", - "format": "strict_date_optional_time||epoch_millis" + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', }, - "updatedBy": { - "type": "integer" + updatedBy: { + type: 'integer', }, - "userId": { - "type": "long" - } - } + userId: { + type: 'long', + }, + }, }, - "name": { - "type": "string" + name: { + type: 'string', }, - "status": { - "type": "string", - "index": "not_analyzed" + status: { + type: 'string', + index: 'not_analyzed', }, - "terms": { - "type": "integer" + terms: { + type: 'integer', }, - "type": { - "type": "string", - "index": "not_analyzed" + type: { + type: 'string', + index: 'not_analyzed', }, - "updatedAt": { - "type": "date", - "format": "strict_date_optional_time||epoch_millis" + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', }, - "updatedBy": { - "type": "integer" + updatedBy: { + type: 'integer', + }, + utm: { + properties: { + campaign: { + type: 'string', + }, + medium: { + type: 'string', + }, + source: { + type: 'string', + }, + }, }, - "utm": { - "properties": { - "campaign": { - "type": "string" - }, - "medium": { - "type": "string" - }, - "source": { - "type": "string" - } - } - } }, }; switch (indexName) { From e75f5d09d3f42dbfe9f841572fb5f4dc25dfab02 Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Thu, 25 Jan 2018 12:29:37 +0530 Subject: [PATCH 25/27] reverted temp deployment of feature branch to dev --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 028867ab..f5993dff 100644 --- a/circle.yml +++ b/circle.yml @@ -24,7 +24,7 @@ dependencies: deployment: development: - branch: [dev, 'feature/allow_managers_to_be_added'] + branch: dev commands: - ./ebs_deploy.sh tc-project-service DEV $CIRCLE_BUILD_NUM From 9bd9ca519cd61f6f571ab26ca4accb97d0e14f91 Mon Sep 17 00:00:00 2001 From: Samir Gondzetovic Date: Sun, 28 Jan 2018 12:23:51 +0000 Subject: [PATCH 26/27] add file download endpoint --- src/permissions/index.js | 1 + src/routes/attachments/download.js | 46 ++++++++++++++++++++++++++++++ src/routes/index.js | 1 + 3 files changed, 48 insertions(+) create mode 100644 src/routes/attachments/download.js diff --git a/src/permissions/index.js b/src/permissions/index.js index f8563d6a..bf19b40b 100644 --- a/src/permissions/index.js +++ b/src/permissions/index.js @@ -19,5 +19,6 @@ module.exports = () => { Authorizer.setPolicy('project.addAttachment', projectEdit); Authorizer.setPolicy('project.updateAttachment', projectEdit); Authorizer.setPolicy('project.removeAttachment', projectEdit); + Authorizer.setPolicy('project.downloadAttachment', projectView); Authorizer.setPolicy('project.updateMember', projectEdit); }; diff --git a/src/routes/attachments/download.js b/src/routes/attachments/download.js new file mode 100644 index 00000000..f226d520 --- /dev/null +++ b/src/routes/attachments/download.js @@ -0,0 +1,46 @@ + +import _ from 'lodash'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; +import util from '../../util'; + +/** + * API to download a project attachment. + * + */ + +const permissions = tcMiddleware.permissions; + +module.exports = [ + permissions('project.downloadAttachment'), + (req, res, next) => { + const projectId = _.parseInt(req.params.projectId); + const attachmentId = _.parseInt(req.params.id); + + models.ProjectAttachment.findOne( + { + where: { + id: attachmentId, + projectId, + }, + }) + .then((attachment) => { + if (!attachment) { + const err = new Error('Record not found'); + err.status = 404; + return Promise.reject(err); + } + return util.getFileDownloadUrl(req, attachment.filePath); + }) + .then((result) => { + const url = result[1]; + res.status(200).json(util.wrapResponse(req.id, { url })); + }) + .catch((error) => { + req.log.error('Error fetching attachment', error); + const rerr = error; + rerr.status = rerr.status || 500; + next(rerr); + }); + }, +]; diff --git a/src/routes/index.js b/src/routes/index.js index 4932602c..f29c2872 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -48,6 +48,7 @@ router.route('/v4/projects/:projectId(\\d+)/members/:id(\\d+)') router.route('/v4/projects/:projectId(\\d+)/attachments') .post(require('./attachments/create')); router.route('/v4/projects/:projectId(\\d+)/attachments/:id(\\d+)') + .get(require('./attachments/download')) .patch(require('./attachments/update')) .delete(require('./attachments/delete')); From d0f3867b873c4d16cf770cf13b1e43868020187d Mon Sep 17 00:00:00 2001 From: Samir Gondzetovic Date: Wed, 31 Jan 2018 20:57:15 +0000 Subject: [PATCH 27/27] add event bus call logging --- src/events/busApi.js | 16 ++++++++-------- src/services/busApi.js | 17 ++++++++++++++--- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/events/busApi.js b/src/events/busApi.js index b6ace085..907cfe42 100644 --- a/src/events/busApi.js +++ b/src/events/busApi.js @@ -28,7 +28,7 @@ module.exports = (app, logger) => { projectId: project.id, projectName: project.name, initiatorUserId: req.authUser.userId, - }); + }, logger); }); /** @@ -43,7 +43,7 @@ module.exports = (app, logger) => { projectId: updated.id, projectName: updated.name, initiatorUserId: req.authUser.userId, - }); + }, logger); } else if ( !_.isEqual(original.details, updated.details) || !_.isEqual(original.name, updated.name) || @@ -53,14 +53,14 @@ module.exports = (app, logger) => { projectId: updated.id, projectName: updated.name, initiatorUserId: req.authUser.userId, - }); + }, logger); } else if (!_.isEqual(original.bookmarks, updated.bookmarks)) { logger.debug('project bookmarks is updated'); createEvent(BUS_API_EVENT.PROJECT_LINK_CREATED, { projectId: updated.id, projectName: updated.name, initiatorUserId: req.authUser.userId, - }); + }, logger); } }); @@ -93,7 +93,7 @@ module.exports = (app, logger) => { projectName: project.name, userId: member.userId, initiatorUserId: req.authUser.userId, - }); + }, logger); }).catch(err => null); // eslint-disable-line no-unused-vars }); @@ -121,7 +121,7 @@ module.exports = (app, logger) => { projectName: project.name, userId: member.userId, initiatorUserId: req.authUser.userId, - }); + }, logger); } }).catch(err => null); // eslint-disable-line no-unused-vars }); @@ -144,7 +144,7 @@ module.exports = (app, logger) => { projectName: project.name, userId: updated.userId, initiatorUserId: req.authUser.userId, - }); + }, logger); } }).catch(err => null); // eslint-disable-line no-unused-vars } @@ -167,7 +167,7 @@ module.exports = (app, logger) => { projectName: project.name, fileName: attachment.filePath.replace(/^.*[\\\/]/, ''), // eslint-disable-line initiatorUserId: req.authUser.userId, - }); + }, logger); }).catch(err => null); // eslint-disable-line no-unused-vars }); }; diff --git a/src/services/busApi.js b/src/services/busApi.js index 933d43c4..cff54071 100644 --- a/src/services/busApi.js +++ b/src/services/busApi.js @@ -36,16 +36,27 @@ function getClient() { * Any errors will be simply ignored * @param {String} type the event type, should be a dot separated fully qualitied name * @param {Object} message the message, should be a JSON object + * @param {Object} logger object * @return {Promise} new event promise */ -function createEvent(type, message) { +function createEvent(type, message, logger) { const body = JSON.stringify(message); + logger.debug(`Sending message: ${JSON.stringify(message)}`); return getClient().post('/eventbus/events', { type, message: body, }) - .then(resp => resp) - .catch(error => Promise.resolve()); // eslint-disable-line + .then((resp) => { + logger.debug('Sent event to bus-api'); + logger.debug(`Sent event to bus-api [data]: ${resp.data}`); + logger.debug(`Sent event to bus-api [status]: ${resp.status}`); + }) + .catch((error) => { + logger.debug('Error sending event to bus-api'); + logger.debug(`Error sending event to bus-api [message]: ${error.message}`); + logger.debug(`Error sending event to bus-api [detail]: ${error.response.data.message}`); + Promise.resolve(); // eslint-disable-line + }); }