From aa1042a28e7a17136183f9d1db42ad76b1086d26 Mon Sep 17 00:00:00 2001 From: Dushyant Bhalgami Date: Wed, 10 Jun 2020 12:40:39 +0530 Subject: [PATCH 1/4] chenges to add universal member to groups --- config/default.js | 2 + src/common/helper.js | 36 +++--- src/controllers/GroupMembershipController.js | 21 +++- src/services/GroupMembershipService.js | 116 +++++++++++++++++-- 4 files changed, 142 insertions(+), 33 deletions(-) diff --git a/config/default.js b/config/default.js index 16922f4..b6d25aa 100644 --- a/config/default.js +++ b/config/default.js @@ -37,6 +37,8 @@ module.exports = { KAFKA_GROUP_DELETE_TOPIC: process.env.KAFKA_GROUP_DELETE_TOPIC || 'groups.notification.delete', KAFKA_GROUP_MEMBER_ADD_TOPIC: process.env.KAFKA_GROUP_MEMBER_ADD_TOPIC || 'groups.notification.member.add', KAFKA_GROUP_MEMBER_DELETE_TOPIC: process.env.KAFKA_GROUP_MEMBER_DELETE_TOPIC || 'groups.notification.member.delete', + KAFKA_GROUP_UNIVERSAL_MEMBER_ADD_TOPIC: process.env.KAFKA_GROUP_UNIVERSAL_MEMBER_ADD_TOPIC || 'groups.notification.universalmember.add', + KAFKA_GROUP_UNIVERSAL_MEMBER_DELETE_TOPIC: process.env.KAFKA_GROUP_UNIVERSAL_MEMBER_DELETE_TOPIC || 'groups.notification.universalmember.delete', USER_ROLES: { Admin: 'Administrator', diff --git a/src/common/helper.js b/src/common/helper.js index 8718674..4314620 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -74,13 +74,9 @@ async function ensureExists(tx, model, id, isAdmin = false) { if (model === 'Group') { if (validate(id, 4)) { if (!isAdmin) { - res = await tx.run(`MATCH (e:${model} {id: {id}, status: '${constants.GroupStatus.Active}'}) RETURN e`, { - id - }) + res = await tx.run(`MATCH (e:${model} {id: {id}, status: '${constants.GroupStatus.Active}'}) RETURN e`, {id}) } else { - res = await tx.run(`MATCH (e:${model} {id: {id}}) RETURN e`, { - id - }) + res = await tx.run(`MATCH (e:${model} {id: {id}}) RETURN e`, {id}) } } else { if (!isAdmin) { @@ -88,9 +84,7 @@ async function ensureExists(tx, model, id, isAdmin = false) { id }) } else { - res = await tx.run(`MATCH (e:${model} {oldId: {id}}) RETURN e`, { - id - }) + res = await tx.run(`MATCH (e:${model} {oldId: {id}}) RETURN e`, {id}) } } @@ -98,12 +92,18 @@ async function ensureExists(tx, model, id, isAdmin = false) { throw new errors.NotFoundError(`Not found ${model} of id ${id}`) } } else if (model === 'User') { - res = await tx.run(`MATCH (e:${model} {id: {id}}) RETURN e`, { - id - }) + if (validate(id, 4)) { + res = await tx.run(`MATCH (e:${model} {universalUID: {id}}) RETURN e`, {id}) - if (res && res.records.length === 0) { - res = await tx.run(`CREATE (user:User {id: {id}}) RETURN user`, { id }) + if (res && res.records.length === 0) { + res = await tx.run(`CREATE (user:User {id: '00000000', universalUID: {id}}) RETURN user`, {id}) + } + } else { + res = await tx.run(`MATCH (e:${model} {id: {id}}) RETURN e`, {id}) + + if (res && res.records.length === 0) { + res = await tx.run(`CREATE (user:User {id: {id}, universalUID: '00000000'}) RETURN user`, {id}) + } } } @@ -123,7 +123,7 @@ async function ensureGroupMember(session, groupId, userId) { try { const memberCheckRes = await session.run( 'MATCH (g:Group {id: {groupId}})-[r:GroupContains {type: {membershipType}}]->(u:User {id: {userId}}) RETURN r', - { groupId, membershipType: config.MEMBERSHIP_TYPES.User, userId } + {groupId, membershipType: config.MEMBERSHIP_TYPES.User, userId} ) if (memberCheckRes.records.length === 0) { throw new errors.ForbiddenError(`User is not member of the group`) @@ -143,7 +143,7 @@ async function getChildGroups(session, groupId) { try { const res = await session.run( 'MATCH (g:Group {id: {groupId}})-[r:GroupContains]->(c:Group) RETURN c ORDER BY c.oldId', - { groupId } + {groupId} ) return _.map(res.records, (record) => record.get(0).properties) } catch (error) { @@ -161,7 +161,7 @@ async function getParentGroups(session, groupId) { try { const res = await session.run( 'MATCH (g:Group)-[r:GroupContains]->(c:Group {id: {groupId}}) RETURN g ORDER BY g.oldId', - { groupId } + {groupId} ) return _.map(res.records, (record) => record.get(0).properties) } catch (error) { @@ -176,7 +176,7 @@ async function getParentGroups(session, groupId) { * @returns {String} link for the page */ function getPageLink(req, page) { - const q = _.assignIn({}, req.query, { page }) + const q = _.assignIn({}, req.query, {page}) return `${req.protocol}://${req.get('Host')}${req.baseUrl}${req.path}?${querystring.stringify(q)}` } diff --git a/src/controllers/GroupMembershipController.js b/src/controllers/GroupMembershipController.js index 2c9629f..1a02c9b 100644 --- a/src/controllers/GroupMembershipController.js +++ b/src/controllers/GroupMembershipController.js @@ -25,12 +25,21 @@ async function getGroupMembers(req, res) { * @param res the response */ async function addGroupMember(req, res) { - const result = await service.addGroupMember( - req.authUser.isMachine ? 'M2M' : req.authUser, - req.params.groupId, - req.body - ) - res.send(result) + if(req.body.universalUID) { + const result = await service.addUniversalMember( + req.authUser.isMachine ? 'M2M' : req.authUser, + req.params.groupId, + req.body + ) + res.send(result) + } else { + const result = await service.addGroupMember( + req.authUser.isMachine ? 'M2M' : req.authUser, + req.params.groupId, + req.body + ) + res.send(result) + } } /** diff --git a/src/services/GroupMembershipService.js b/src/services/GroupMembershipService.js index 153dd09..a3cefb8 100644 --- a/src/services/GroupMembershipService.js +++ b/src/services/GroupMembershipService.js @@ -67,7 +67,7 @@ async function addGroupMember(currentUser, groupId, data) { const targetObjectType = data.membershipType === config.MEMBERSHIP_TYPES.Group ? 'Group' : 'User' const memberCheckRes = await tx.run( `MATCH (g:Group {id: {groupId}})-[r:GroupContains]->(o:${targetObjectType} {id: {memberId}}) RETURN o`, - { groupId, memberId: data.memberId } + {groupId, memberId: data.memberId} ) if (memberCheckRes.records.length > 0) { throw new errors.ConflictError('The member is already in the group') @@ -77,7 +77,7 @@ async function addGroupMember(currentUser, groupId, data) { if (data.membershipType === config.MEMBERSHIP_TYPES.Group) { const pathRes = await tx.run( 'MATCH p=shortestPath( (g1:Group {id: {fromId}})-[*]->(g2:Group {id: {toId}}) ) RETURN p', - { fromId: data.memberId, toId: groupId } + {fromId: data.memberId, toId: groupId} ) if (pathRes.records.length > 0) { throw new errors.ConflictError('There is cyclical group reference') @@ -107,9 +107,9 @@ async function addGroupMember(currentUser, groupId, data) { oldId: data.oldId, name: group.name, createdAt, - ...(currentUser === 'M2M' ? {} : { createdBy: currentUser.userId }), + ...(currentUser === 'M2M' ? {} : {createdBy: currentUser.userId}), memberId: data.memberId, - ...(data.memberOldId ? { memberOldId: data.memberOldId } : {}), + ...(data.memberOldId ? {memberOldId: data.memberOldId} : {}), membershipType: data.membershipType } @@ -174,7 +174,7 @@ async function deleteGroupMember(currentUser, groupId, memberId) { // delete membership const query = 'MATCH (g:Group {id: {groupId}})-[r:GroupContains]->(o {id: {memberId}}) DELETE r' - await tx.run(query, { groupId, memberId }) + await tx.run(query, {groupId, memberId}) if (validate(memberId, 4)) { const getMember = await helper.ensureExists(tx, 'Group', memberId) @@ -210,6 +210,103 @@ deleteGroupMember.schema = { memberId: Joi.id() } +/** + * Add universal member. + * @param {Object} currentUser the current user + * @param {String} groupId the id of group to add member + * @param {Object} data the data to add member + * @returns {Object} the added group membership + */ +async function addUniversalMember(currentUser, groupId, data) { + logger.debug(`Enter in addGroupMember - Group = ${groupId} Criteria = ${data}`) + let session = helper.createDBSession() + let tx = session.beginTransaction() + + try { + logger.debug(`Check for groupId ${groupId} exist or not`) + const group = await helper.ensureExists( + tx, + 'Group', + groupId, + currentUser !== 'M2M' && helper.hasAdminRole(currentUser) + ) + data.oldId = group.oldId + groupId = group.id + data.membershipType = config.MEMBERSHIP_TYPES.User + + if (currentUser !== 'M2M' && !helper.hasAdminRole(currentUser)) { + throw new errors.ForbiddenError('You are not allowed to perform this action!') + } + + logger.debug(`Check for memberId ${data.universalUID} exist or not`) + await helper.ensureExists(tx, 'User', data.universalUID) + + logger.debug(`check member ${data.universalUID} is part of group ${groupId}`) + const memberCheckRes = await tx.run( + `MATCH (g:Group {id: {groupId}})-[r:GroupContains]->(o:User {universalUID: {universalUID}}) RETURN o`, + {groupId, universalUID: data.universalUID} + ) + if (memberCheckRes.records.length > 0) { + throw new errors.ConflictError('The member is already in the group') + } + + // add membership + const membershipId = uuid() + const createdAt = new Date().toISOString() + const query = `MATCH (g:Group {id: {groupId}}) MATCH (o:User {universalUID: {universalUID}}) CREATE (g)-[r:GroupContains {universalUID: {universalUID}, type: {membershipType}, createdAt: {createdAt}, createdBy: {createdBy}}]->(o) RETURN r` + + const params = { + groupId, + universalUID: data.universalUID, + membershipId, + membershipType: data.membershipType, + createdAt, + createdBy: currentUser === 'M2M' ? '00000000' : currentUser.userId + } + + logger.debug(`quey for adding membership ${query} with params ${JSON.stringify(params)}`) + await tx.run(query, params) + + const result = { + id: membershipId, + groupId, + oldId: data.oldId, + name: group.name, + createdAt, + ...(currentUser === 'M2M' ? {} : {createdBy: currentUser.userId}), + memberId: '00000000', + universalUID: data.universalUID, + handle: data.handle, + ...(data.memberOldId ? {memberOldId: data.memberOldId} : {}), + membershipType: data.membershipType + } + + logger.debug(`sending message ${JSON.stringify(result)} to kafka topic ${config.KAFKA_GROUP_MEMBER_ADD_TOPIC}`) + await helper.postBusEvent(config.KAFKA_GROUP_MEMBER_ADD_TOPIC, result) + + await tx.commit() + return result + } catch (error) { + logger.error(error) + logger.debug('Transaction Rollback') + await tx.rollback() + throw error + } finally { + logger.debug('Session Close') + await session.close() + } +} + +addUniversalMember.schema = { + currentUser: Joi.any(), + groupId: Joi.id(), // defined in app-bootstrap + data: Joi.object() + .keys({ + universalUID: Joi.id(), + handle: Joi.string().required() + }) + .required() +} /** * Get group members * @param {Object} currentUser the current user @@ -235,7 +332,7 @@ async function getGroupMembers(currentUser, groupId, criteria) { } const matchClause = 'MATCH (g:Group {id: {groupId}})-[r:GroupContains]->(o)' - const params = { groupId } + const params = {groupId} // query total record count const totalRes = await session.run(`${matchClause} RETURN COUNT(o)`, params) @@ -299,7 +396,7 @@ async function getGroupMemberWithSession(session, groupId, memberId) { groupId = group.id const query = 'MATCH (g:Group {id: {groupId}})-[r:GroupContains]->(o {id: {memberId}}) RETURN r' - const membershipRes = await session.run(query, { groupId, memberId }) + const membershipRes = await session.run(query, {groupId, memberId}) if (membershipRes.records.length === 0) { throw new errors.NotFoundError('The member is not in the group') } @@ -375,9 +472,9 @@ async function getGroupMembersCount(groupId, query) { queryToExecute = 'MATCH (g:Group {id: {groupId}})-[r:GroupContains]->(o:User) RETURN COUNT(o) AS count' } - const res = await session.run(queryToExecute, { groupId }) + const res = await session.run(queryToExecute, {groupId}) - return { count: res.records[0]._fields[0].low } + return {count: res.records[0]._fields[0].low} } catch (error) { logger.error(error) throw error @@ -426,6 +523,7 @@ getMemberGroups.schema = { module.exports = { getGroupMembers, addGroupMember, + addUniversalMember, getGroupMember, deleteGroupMember, getGroupMembersCount, From bcb4bd3b29ba6f393e6535622c67b3010c805b56 Mon Sep 17 00:00:00 2001 From: Dushyant Bhalgami Date: Fri, 12 Jun 2020 23:25:15 +0530 Subject: [PATCH 2/4] chenges to add universal member to groups --- src/services/GroupMembershipService.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/services/GroupMembershipService.js b/src/services/GroupMembershipService.js index a3cefb8..52d25ed 100644 --- a/src/services/GroupMembershipService.js +++ b/src/services/GroupMembershipService.js @@ -232,7 +232,6 @@ async function addUniversalMember(currentUser, groupId, data) { ) data.oldId = group.oldId groupId = group.id - data.membershipType = config.MEMBERSHIP_TYPES.User if (currentUser !== 'M2M' && !helper.hasAdminRole(currentUser)) { throw new errors.ForbiddenError('You are not allowed to perform this action!') From 4222f61f3dc815b73dc430a37c81a76b0bd0bda0 Mon Sep 17 00:00:00 2001 From: Dushyant Bhalgami Date: Sat, 13 Jun 2020 17:30:56 +0530 Subject: [PATCH 3/4] updated to add/remove universal member to groups --- src/controllers/GroupMembershipController.js | 53 ++-- src/routes.js | 22 ++ src/services/GroupMembershipService.js | 300 +++++++++++-------- src/services/GroupService.js | 11 +- 4 files changed, 236 insertions(+), 150 deletions(-) diff --git a/src/controllers/GroupMembershipController.js b/src/controllers/GroupMembershipController.js index 1a02c9b..fa1d1bb 100644 --- a/src/controllers/GroupMembershipController.js +++ b/src/controllers/GroupMembershipController.js @@ -25,21 +25,12 @@ async function getGroupMembers(req, res) { * @param res the response */ async function addGroupMember(req, res) { - if(req.body.universalUID) { - const result = await service.addUniversalMember( - req.authUser.isMachine ? 'M2M' : req.authUser, - req.params.groupId, - req.body - ) - res.send(result) - } else { - const result = await service.addGroupMember( - req.authUser.isMachine ? 'M2M' : req.authUser, - req.params.groupId, - req.body - ) - res.send(result) - } + const result = await service.addGroupMember( + req.authUser.isMachine ? 'M2M' : req.authUser, + req.params.groupId, + req.body + ) + res.send(result) } /** @@ -65,7 +56,8 @@ async function deleteGroupMember(req, res) { const result = await service.deleteGroupMember( req.authUser.isMachine ? 'M2M' : req.authUser, req.params.groupId, - req.params.memberId + req.params.memberId ? req.params.memberId : null, + Object.keys(req.query).length !== 0 ? req.query : null ) res.send(result) } @@ -80,13 +72,36 @@ async function getGroupMembersCount(req, res) { res.send(result) } +/** + * Get list of mapping of groups and members count + * @param req the request + * @param res the response + */ +async function listGroupsMemberCount(req, res) { + const result = await service.listGroupsMemberCount(req.query) + res.send(result) +} + /** * Get group members * @param req the request * @param res the response */ async function getMemberGroups(req, res) { - const result = await service.getMemberGroups(req.authUser.isMachine ? 'M2M' : req.authUser, req.params.memberId) + const result = await service.getMemberGroups(req.authUser.isMachine ? 'M2M' : req.authUser, req.params.memberId, {}) + helper.setResHeaders(req, res, result) + res.send(result) +} + +/** + * Get group members + * @param req the request + * @param res the response + */ +async function searchMemberGroups(req, res) { + console.log('sssss') + console.log(JSON.stringify(req.query)) + const result = await service.getMemberGroups(req.authUser.isMachine ? 'M2M' : req.authUser, {}, req.query) helper.setResHeaders(req, res, result) res.send(result) } @@ -97,5 +112,7 @@ module.exports = { getGroupMember, deleteGroupMember, getGroupMembersCount, - getMemberGroups + listGroupsMemberCount, + getMemberGroups, + searchMemberGroups } diff --git a/src/routes.js b/src/routes.js index 801f956..8776931 100644 --- a/src/routes.js +++ b/src/routes.js @@ -73,6 +73,13 @@ module.exports = { auth: 'jwt', access: [constants.UserRoles.Admin, constants.UserRoles.User], scopes: ['write:groups', 'all:groups'] + }, + delete: { + controller: 'GroupMembershipController', + method: 'deleteGroupMember', + auth: 'jwt', + access: [constants.UserRoles.Admin], + scopes: ['write:groups', 'all:groups'] } }, '/groups/:groupId/members/:memberId': { @@ -97,6 +104,12 @@ module.exports = { method: 'getGroupMembersCount' } }, + '/groupsMemberCount': { + get: { + controller: 'GroupMembershipController', + method: 'listGroupsMemberCount' + } + }, '/groups/memberGroups/:memberId': { get: { controller: 'GroupMembershipController', @@ -106,6 +119,15 @@ module.exports = { scopes: ['read:groups'] } }, + '/groups/memberGroups/': { + get: { + controller: 'GroupMembershipController', + method: 'searchMemberGroups', + auth: 'jwt', + access: [constants.UserRoles.Admin, constants.UserRoles.User], + scopes: ['read:groups'] + } + }, '/health': { get: { controller: 'HealthController', diff --git a/src/services/GroupMembershipService.js b/src/services/GroupMembershipService.js index 52d25ed..58e0544 100644 --- a/src/services/GroupMembershipService.js +++ b/src/services/GroupMembershipService.js @@ -10,6 +10,7 @@ const logger = require('../common/logger') const errors = require('../common/errors') const validate = require('uuid-validate') const constants = require('../../app-constants') +const {ConsoleTransportOptions} = require('winston/lib/winston/transports') /** * Add group member. @@ -34,24 +35,26 @@ async function addGroupMember(currentUser, groupId, data) { data.oldId = group.oldId groupId = group.id + const memberId = data.memberId ? data.memberId : data.universalUID + if ( currentUser !== 'M2M' && !helper.hasAdminRole(currentUser) && !( group.selfRegister && data.membershipType === config.MEMBERSHIP_TYPES.User && - Number(currentUser.userId) === Number(data.memberId) + Number(currentUser.userId) === Number(memberId) ) ) { throw new errors.ForbiddenError('You are not allowed to perform this action!') } if (data.membershipType === config.MEMBERSHIP_TYPES.Group) { - if (data.memberId === groupId) { + if (memberId === groupId) { throw new errors.BadRequestError('A group can not add to itself.') } - logger.debug(`Check for groupId ${data.memberId} exist or not`) - const childGroup = await helper.ensureExists(tx, 'Group', data.memberId) + logger.debug(`Check for groupId ${memberId} exist or not`) + const childGroup = await helper.ensureExists(tx, 'Group', memberId) data.memberOldId = childGroup.oldId // if parent group is private, the sub group must be private too @@ -59,16 +62,26 @@ async function addGroupMember(currentUser, groupId, data) { throw new errors.ConflictError('Parent group is private, the child group must be private too.') } } else { - logger.debug(`Check for memberId ${data.memberId} exist or not`) - await helper.ensureExists(tx, 'User', data.memberId) + logger.debug(`Check for memberId ${memberId} exist or not`) + await helper.ensureExists(tx, 'User', memberId) } - logger.debug(`check member ${data.memberId} is part of group ${groupId}`) + logger.debug(`check member ${memberId} is part of group ${groupId}`) const targetObjectType = data.membershipType === config.MEMBERSHIP_TYPES.Group ? 'Group' : 'User' - const memberCheckRes = await tx.run( - `MATCH (g:Group {id: {groupId}})-[r:GroupContains]->(o:${targetObjectType} {id: {memberId}}) RETURN o`, - {groupId, memberId: data.memberId} - ) + + let memberCheckRes + if (data.universalUID) { + memberCheckRes = await tx.run( + `MATCH (g:Group {id: {groupId}})-[r:GroupContains]->(o:${targetObjectType} {universalUID: {memberId}}) RETURN o`, + {groupId, memberId} + ) + } else { + memberCheckRes = await tx.run( + `MATCH (g:Group {id: {groupId}})-[r:GroupContains]->(o:${targetObjectType} {id: {memberId}}) RETURN o`, + {groupId, memberId: data.memberId} + ) + } + if (memberCheckRes.records.length > 0) { throw new errors.ConflictError('The member is already in the group') } @@ -87,11 +100,17 @@ async function addGroupMember(currentUser, groupId, data) { // add membership const membershipId = uuid() const createdAt = new Date().toISOString() - const query = `MATCH (g:Group {id: {groupId}}) MATCH (o:${targetObjectType} {id: {memberId}}) CREATE (g)-[r:GroupContains {id: {membershipId}, type: {membershipType}, createdAt: {createdAt}, createdBy: {createdBy}}]->(o) RETURN r` + + let query + if (validate(memberId, 4)) { + query = `MATCH (g:Group {id: {groupId}}) MATCH (o:User {universalUID: {memberId}}) CREATE (g)-[r:GroupContains {id: {membershipId}, type: {membershipType}, createdAt: {createdAt}, createdBy: {createdBy}}]->(o) RETURN r` + } else { + query = `MATCH (g:Group {id: {groupId}}) MATCH (o:${targetObjectType} {id: {memberId}}) CREATE (g)-[r:GroupContains {id: {membershipId}, type: {membershipType}, createdAt: {createdAt}, createdBy: {createdBy}}]->(o) RETURN r` + } const params = { groupId, - memberId: data.memberId, + memberId, membershipId, membershipType: data.membershipType, createdAt, @@ -108,7 +127,8 @@ async function addGroupMember(currentUser, groupId, data) { name: group.name, createdAt, ...(currentUser === 'M2M' ? {} : {createdBy: currentUser.userId}), - memberId: data.memberId, + ...(data.memberId ? {memberId: data.memberId} : {}), + ...(data.universalUID ? {universalUID: data.universalUID} : {}), ...(data.memberOldId ? {memberOldId: data.memberOldId} : {}), membershipType: data.membershipType } @@ -129,16 +149,28 @@ async function addGroupMember(currentUser, groupId, data) { } } -addGroupMember.schema = { - currentUser: Joi.any(), - groupId: Joi.id(), // defined in app-bootstrap - data: Joi.object() - .keys({ - memberId: Joi.id(), - membershipType: Joi.string().valid(_.values(config.MEMBERSHIP_TYPES)).required() - }) - .required() -} +addGroupMember.schema = Joi.alternatives().try( + Joi.object().keys({ + currentUser: Joi.any(), + groupId: Joi.id(), // defined in app-bootstrap + data: Joi.object() + .keys({ + memberId: Joi.id(), + membershipType: Joi.string().valid(_.values(config.MEMBERSHIP_TYPES)).required() + }) + .required() + }), + Joi.object().keys({ + currentUser: Joi.any(), + groupId: Joi.id(), // defined in app-bootstrap + data: Joi.object() + .keys({ + universalUID: Joi.id(), + membershipType: Joi.string().valid(_.values(config.MEMBERSHIP_TYPES)).required() + }) + .required() + }) +) /** * Delete group member. @@ -147,7 +179,7 @@ addGroupMember.schema = { * @param {String} memberId the member id * @returns {Object} the deleted group membership */ -async function deleteGroupMember(currentUser, groupId, memberId) { +async function deleteGroupMember(currentUser, groupId, memberId, query) { logger.debug(`Enter in deleteGroupMember - Group = ${groupId} memberId = ${memberId}`) let session = helper.createDBSession() let tx = session.beginTransaction() @@ -163,6 +195,7 @@ async function deleteGroupMember(currentUser, groupId, memberId) { groupId = group.id const oldId = group.oldId const name = group.name + const universalUID = query ? query.universalUID : null if ( currentUser !== 'M2M' && @@ -173,8 +206,19 @@ async function deleteGroupMember(currentUser, groupId, memberId) { } // delete membership - const query = 'MATCH (g:Group {id: {groupId}})-[r:GroupContains]->(o {id: {memberId}}) DELETE r' - await tx.run(query, {groupId, memberId}) + if (universalUID) { + const query = 'MATCH (g:Group {id: {groupId}})-[r:GroupContains]->(o {universalUID: {universalUID}}) DELETE r' + await tx.run(query, {groupId, universalUID}) + + const matchClause = 'MATCH (u:User {universalUID: {universalUID}})' + const params = {universalUID} + + const res = await tx.run(`${matchClause} RETURN u.id as memberId`, params) + memberId = _.head(_.head(res.records)._fields) + } else { + const query = 'MATCH (g:Group {id: {groupId}})-[r:GroupContains]->(o {id: {memberId}}) DELETE r' + await tx.run(query, {groupId, memberId}) + } if (validate(memberId, 4)) { const getMember = await helper.ensureExists(tx, 'Group', memberId) @@ -188,8 +232,8 @@ async function deleteGroupMember(currentUser, groupId, memberId) { memberId } - logger.debug(`sending message ${JSON.stringify(result)} to kafka topic ${config.KAFKA_GROUP_MEMBER_DELETE_TOPIC}`) - await helper.postBusEvent(config.KAFKA_GROUP_MEMBER_DELETE_TOPIC, result) + // logger.debug(`sending message ${JSON.stringify(result)} to kafka topic ${config.KAFKA_GROUP_MEMBER_DELETE_TOPIC}`) + // await helper.postBusEvent(config.KAFKA_GROUP_MEMBER_DELETE_TOPIC, result) await tx.commit() return result @@ -207,105 +251,10 @@ async function deleteGroupMember(currentUser, groupId, memberId) { deleteGroupMember.schema = { currentUser: Joi.any(), groupId: Joi.id(), // defined in app-bootstrap - memberId: Joi.id() + memberId: Joi.optionalId().allow('', null), + query: Joi.object().allow('', null) } -/** - * Add universal member. - * @param {Object} currentUser the current user - * @param {String} groupId the id of group to add member - * @param {Object} data the data to add member - * @returns {Object} the added group membership - */ -async function addUniversalMember(currentUser, groupId, data) { - logger.debug(`Enter in addGroupMember - Group = ${groupId} Criteria = ${data}`) - let session = helper.createDBSession() - let tx = session.beginTransaction() - - try { - logger.debug(`Check for groupId ${groupId} exist or not`) - const group = await helper.ensureExists( - tx, - 'Group', - groupId, - currentUser !== 'M2M' && helper.hasAdminRole(currentUser) - ) - data.oldId = group.oldId - groupId = group.id - - if (currentUser !== 'M2M' && !helper.hasAdminRole(currentUser)) { - throw new errors.ForbiddenError('You are not allowed to perform this action!') - } - - logger.debug(`Check for memberId ${data.universalUID} exist or not`) - await helper.ensureExists(tx, 'User', data.universalUID) - - logger.debug(`check member ${data.universalUID} is part of group ${groupId}`) - const memberCheckRes = await tx.run( - `MATCH (g:Group {id: {groupId}})-[r:GroupContains]->(o:User {universalUID: {universalUID}}) RETURN o`, - {groupId, universalUID: data.universalUID} - ) - if (memberCheckRes.records.length > 0) { - throw new errors.ConflictError('The member is already in the group') - } - - // add membership - const membershipId = uuid() - const createdAt = new Date().toISOString() - const query = `MATCH (g:Group {id: {groupId}}) MATCH (o:User {universalUID: {universalUID}}) CREATE (g)-[r:GroupContains {universalUID: {universalUID}, type: {membershipType}, createdAt: {createdAt}, createdBy: {createdBy}}]->(o) RETURN r` - - const params = { - groupId, - universalUID: data.universalUID, - membershipId, - membershipType: data.membershipType, - createdAt, - createdBy: currentUser === 'M2M' ? '00000000' : currentUser.userId - } - - logger.debug(`quey for adding membership ${query} with params ${JSON.stringify(params)}`) - await tx.run(query, params) - - const result = { - id: membershipId, - groupId, - oldId: data.oldId, - name: group.name, - createdAt, - ...(currentUser === 'M2M' ? {} : {createdBy: currentUser.userId}), - memberId: '00000000', - universalUID: data.universalUID, - handle: data.handle, - ...(data.memberOldId ? {memberOldId: data.memberOldId} : {}), - membershipType: data.membershipType - } - - logger.debug(`sending message ${JSON.stringify(result)} to kafka topic ${config.KAFKA_GROUP_MEMBER_ADD_TOPIC}`) - await helper.postBusEvent(config.KAFKA_GROUP_MEMBER_ADD_TOPIC, result) - - await tx.commit() - return result - } catch (error) { - logger.error(error) - logger.debug('Transaction Rollback') - await tx.rollback() - throw error - } finally { - logger.debug('Session Close') - await session.close() - } -} - -addUniversalMember.schema = { - currentUser: Joi.any(), - groupId: Joi.id(), // defined in app-bootstrap - data: Joi.object() - .keys({ - universalUID: Joi.id(), - handle: Joi.string().required() - }) - .required() -} /** * Get group members * @param {Object} currentUser the current user @@ -354,6 +303,7 @@ async function getGroupMembers(currentUser, groupId, criteria) { createdAt: r.createdAt, createdBy: r.createdBy, memberId: o.id, + universalUID: o.universalUID, membershipType: r.type } }) @@ -466,7 +416,7 @@ async function getGroupMembersCount(groupId, query) { let queryToExecute = '' if (query.includeSubGroups) { - queryToExecute = 'MATCH (g:Group {id: {groupId}})-[r:GroupContains*1..10]->(o:User) RETURN COUNT(o) AS count' + queryToExecute = 'MATCH (g:Group {id: {groupId}})-[r:GroupContains*1..]->(o:User) RETURN COUNT(o) AS count' } else { queryToExecute = 'MATCH (g:Group {id: {groupId}})-[r:GroupContains]->(o:User) RETURN COUNT(o) AS count' } @@ -484,17 +434,107 @@ async function getGroupMembersCount(groupId, query) { } getGroupMembersCount.schema = { - groupId: Joi.id(), // defined in app-bootstrap + groupId: Joi.optionalId(), // defined in app-bootstrap query: Joi.object().keys({ includeSubGroups: Joi.boolean().default(false) }) } +/** + * Get list of groups for specified user and member count of those groups. Optionally may include sub groups. + * @param {Object} query the query parameters + * @returns {Object} list of groupId and memberCount mapping + */ +async function listGroupsMemberCount(query) { + const session = helper.createDBSession() + try { + let queryToExecute = '' + + if (query.includeSubGroups) { + queryToExecute = + 'MATCH (g:Group)-[r:GroupContains*1..]->(o:User) WHERE exists(g.oldId) AND g.status = {status} RETURN g.oldId, g.id, COUNT(o) AS count order by g.oldId' + } else { + queryToExecute = + 'MATCH (g:Group)-[r:GroupContains]->(o:User) WHERE exists(g.oldId) AND g.status = {status} RETURN g.oldId, g.id, COUNT(o) AS count order by g.oldId' + } + let res = await session.run(queryToExecute, {status: constants.GroupStatus.Active}) + + const groupsMemberCount = [] + res.records.forEach(function (record) { + let groupMemberCount = { + id: record._fields[1], + oldId: record._fields[0], + count: record._fields[2].low + } + groupsMemberCount.push(groupMemberCount) + }) + + if (query.includeSubGroups) { + if (query.universalUID) { + queryToExecute = + 'MATCH (g:Group)-[r:GroupContains*1..]->(o:User {universalUID: {universalUID}}) WHERE exists(g.oldId) AND g.status = {status} RETURN DISTINCT g.oldId order by g.oldId' + res = await session.run(queryToExecute, { + universalUID: query.universalUID, + status: constants.GroupStatus.Active + }) + } + + if (query.organizationId) { + queryToExecute = + 'MATCH (g:Group)-[r:GroupContains*1..]->(o:User) WHERE g.organizationId = {organizationId} AND exists(g.oldId) AND g.status = {status} RETURN DISTINCT g.oldId order by g.oldId' + res = await session.run(queryToExecute, { + organizationId: query.organizationId, + status: constants.GroupStatus.Active + }) + } + } else { + if (query.universalUID) { + queryToExecute = + 'MATCH (g:Group)-[r:GroupContains]->(o:User {universalUID: {universalUID}}) WHERE exists(g.oldId) AND g.status = {status} RETURN DISTINCT g.oldId order by g.oldId' + res = await session.run(queryToExecute, { + universalUID: query.universalUID, + status: constants.GroupStatus.Active + }) + } + + if (query.organizationId) { + queryToExecute = + 'MATCH (g:Group)-[r:GroupContains]->(o:User) WHERE g.organizationId = {organizationId} AND exists(g.oldId) AND g.status = {status} RETURN DISTINCT g.oldId order by g.oldId' + res = await session.run(queryToExecute, { + organizationId: query.organizationId, + status: constants.GroupStatus.Active + }) + } + } + + const groupList = _.flatten(_.map(res.records, '_fields')) + const finalRes = _.filter(groupsMemberCount, function (n) { + return _.includes(groupList, n.oldId) + }) + + return finalRes + } catch (error) { + logger.error(error) + throw error + } finally { + logger.debug('Session Close') + await session.close() + } +} + +listGroupsMemberCount.schema = { + query: Joi.object().keys({ + includeSubGroups: Joi.boolean().default(false), + universalUID: Joi.optionalId(), + organizationId: Joi.optionalId() + }) +} + /** * Get member groups * @param {Object} currentUser the current user * @param {Object} memberId - * @param {Object} criteria the search criteria + * @param {Object} query the search criteria * @returns {Object} the search result */ async function getMemberGroups(currentUser, memberId) { @@ -522,10 +562,10 @@ getMemberGroups.schema = { module.exports = { getGroupMembers, addGroupMember, - addUniversalMember, getGroupMember, deleteGroupMember, getGroupMembersCount, + listGroupsMemberCount, getMemberGroups } diff --git a/src/services/GroupService.js b/src/services/GroupService.js index 27aab31..5e376df 100644 --- a/src/services/GroupService.js +++ b/src/services/GroupService.js @@ -19,10 +19,10 @@ const constants = require('../../app-constants') async function searchGroups(criteria, isAdmin = false) { logger.debug(`Search Group - Criteria - ${JSON.stringify(criteria)}`) - if (criteria.memberId && !criteria.membershipType) { + if ((criteria.memberId || criteria.universalUID) && !criteria.membershipType) { throw new errors.BadRequestError('The membershipType parameter should be provided if memberId is provided.') } - if (!criteria.memberId && criteria.membershipType) { + if (!(criteria.memberId || criteria.universalUID) && criteria.membershipType) { throw new errors.BadRequestError('The memberId parameter should be provided if membershipType is provided.') } @@ -35,6 +35,12 @@ async function searchGroups(criteria, isAdmin = false) { matchClause = `MATCH (g:Group)` } + if (criteria.universalUID) { + matchClause = `MATCH (g:Group)-[r:GroupContains {type: "${criteria.membershipType}"}]->(o {universalUID: "${criteria.universalUID}"})` + } else { + matchClause = `MATCH (g:Group)` + } + let whereClause = '' if (criteria.oldId) { whereClause = ` WHERE g.oldId = "${criteria.oldId}"` @@ -149,6 +155,7 @@ searchGroups.schema = { isAdmin: Joi.boolean(), criteria: Joi.object().keys({ memberId: Joi.optionalId(), // defined in app-bootstrap + universalUID: Joi.optionalId(), membershipType: Joi.string().valid(_.values(config.MEMBERSHIP_TYPES)), name: Joi.string(), page: Joi.page(), From 19e6e5b7929ccb69336d6dc80e2cbd6214abe813 Mon Sep 17 00:00:00 2001 From: Dushyant Bhalgami Date: Tue, 16 Jun 2020 11:16:34 +0530 Subject: [PATCH 4/4] added access control expose headers --- src/common/helper.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/common/helper.js b/src/common/helper.js index 4314620..fc21db1 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -188,6 +188,10 @@ function getPageLink(req, page) { */ function setResHeaders(req, res, result) { const totalPages = Math.ceil(result.total / result.perPage) + if (result.page > 1) { + res.set('X-Prev-Page', result.page - 1) + } + if (result.page < totalPages) { res.set('X-Next-Page', result.page + 1) } @@ -206,6 +210,14 @@ function setResHeaders(req, res, result) { } res.set('Link', link) } + + // Allow browsers access pagination data in headers + let accessControlExposeHeaders = res.get('Access-Control-Expose-Headers') || '' + accessControlExposeHeaders += accessControlExposeHeaders ? ', ' : '' + // append new values, to not override values set by someone else + accessControlExposeHeaders += 'X-Page, X-Per-Page, X-Total, X-Total-Pages, X-Prev-Page, X-Next-Page' + + res.set('Access-Control-Expose-Headers', accessControlExposeHeaders) } /**