diff --git a/migrations/elasticsearch_sync.js b/migrations/elasticsearch_sync.js index 6974f3d7..1c9e5713 100644 --- a/migrations/elasticsearch_sync.js +++ b/migrations/elasticsearch_sync.js @@ -33,91 +33,264 @@ 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', + actualPrice: { + type: 'double', + }, + attachments: { + type: 'nested', properties: { - id: { type: 'string', index: 'not_analyzed' }, - type: { type: 'string', index: 'not_analyzed' }, - data: { type: 'string' }, - } }, - bookmarks: { type: 'nested', + 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: { - title: { type: 'string' }, - address: { type: 'string' }, - } }, - utm: { type: 'object', + address: { + type: 'string', + }, + title: { + type: 'string', + }, + }, + }, + cancelReason: { + type: 'string', + }, + challengeEligibility: { + type: 'nested', 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', + 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: { - summary: { type: 'string' }, - TBD_usageDescription: { type: 'string' }, - TBD_features: { type: 'nested', + TBD_features: { + type: 'nested', properties: { - id: { type: 'integer' }, - title: { type: 'string' }, - description: { type: 'string' }, - isCustom: { type: 'boolean' }, - } }, - } }, - challengeEligibility: { type: 'nested', + 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: { - 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 + data: { + type: 'string', + }, + id: { + type: 'string', + index: 'not_analyzed', + }, + type: { + type: 'string', + index: 'not_analyzed', + }, + }, + }, + id: { + type: 'long', + }, 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' }, + 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', + }, }, }, - // project attachments nested data type - attachments: { - type: 'nested', + 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: { - 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' }, + campaign: { + type: 'string', + }, + medium: { + type: 'string', + }, + source: { + type: 'string', + }, }, }, }, diff --git a/src/constants.js b/src/constants.js index 3e5a14c6..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: { @@ -71,3 +72,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\.\/\?\%\#\&\=]*)?$/, // eslint-disable-line +}; diff --git a/src/events/busApi.js b/src/events/busApi.js index 79230b2b..907cfe42 100644 --- a/src/events/busApi.js +++ b/src/events/busApi.js @@ -27,8 +27,8 @@ module.exports = (app, logger) => { createEvent(BUS_API_EVENT.PROJECT_CREATED, { projectId: project.id, projectName: project.name, - userId: req.authUser.userId, - }); + initiatorUserId: req.authUser.userId, + }, logger); }); /** @@ -42,8 +42,8 @@ module.exports = (app, logger) => { createEvent(mapEventTypes[updated.status], { projectId: updated.id, projectName: updated.name, - userId: req.authUser.userId, - }); + initiatorUserId: req.authUser.userId, + }, logger); } else if ( !_.isEqual(original.details, updated.details) || !_.isEqual(original.name, updated.name) || @@ -52,15 +52,15 @@ 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, + }, 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, - userId: req.authUser.userId, - }); + initiatorUserId: req.authUser.userId, + }, logger); } }); @@ -92,7 +92,8 @@ module.exports = (app, logger) => { projectId, projectName: project.name, userId: member.userId, - }); + initiatorUserId: req.authUser.userId, + }, logger); }).catch(err => null); // eslint-disable-line no-unused-vars }); @@ -119,7 +120,8 @@ module.exports = (app, logger) => { projectId, projectName: project.name, userId: member.userId, - }); + initiatorUserId: req.authUser.userId, + }, logger); } }).catch(err => null); // eslint-disable-line no-unused-vars }); @@ -141,7 +143,8 @@ module.exports = (app, logger) => { projectId, projectName: project.name, userId: updated.userId, - }); + initiatorUserId: req.authUser.userId, + }, logger); } }).catch(err => null); // eslint-disable-line no-unused-vars } @@ -163,8 +166,8 @@ module.exports = (app, logger) => { projectId, projectName: project.name, fileName: attachment.filePath.replace(/^.*[\\\/]/, ''), // eslint-disable-line - userId: req.authUser.userId, - }); + initiatorUserId: req.authUser.userId, + }, logger); }).catch(err => null); // eslint-disable-line no-unused-vars }); }; 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')); diff --git a/src/routes/projectMembers/create.js b/src/routes/projectMembers/create.js index 64f2dfe7..ac809e0a 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.getUserRoles(member.userId, req.log, req.id); + } req.log.debug('creating member', member); let newMember = null; // register member - return models.ProjectMember.create(member) + return promise.then((memberRoles) => { + if (member.role === PROJECT_MEMBER_ROLE.MANAGER + && (!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); + } + 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..4d0c9f2f 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: [{ + roleName: '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: [{ + roleName: 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: [{ + roleName: 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: [{ + roleName: 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: [{ + roleName: 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/routes/projects/create.js b/src/routes/projects/create.js index 9e84bf2c..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(), + address: Joi.string().regex(REGEX.URL), })).optional().allow(null), estimatedPrice: Joi.number().precision(2).positive().optional() .allow(null), diff --git a/src/routes/projects/create.spec.js b/src/routes/projects/create.spec.js index c41fa612..9e9cb0dc 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', }], }, }; @@ -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(); } diff --git a/src/routes/projects/list.js b/src/routes/projects/list.js index f1cd2c9a..e3292fde 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'], // 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', diff --git a/src/routes/projects/update.js b/src/routes/projects/update.js index 985c7c82..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(), + address: Joi.string().regex(REGEX.URL), })).optional().allow(null), type: Joi.any().valid(_.values(PROJECT_TYPE)), details: Joi.any(), 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 + }); } diff --git a/src/util.js b/src/util.js index 27df14ea..6371f726 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 + */ + 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}roles`, { + params: { + filter: `subjectID=${userId}`, + }, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + }).then(res => _.get(res, 'data.result.content', []).map(r => r.roleName)); + } catch (err) { + return Promise.reject(err); + } + }), }); export default util;