diff --git a/.circleci/config.yml b/.circleci/config.yml index 9089815..a2d6118 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -72,6 +72,7 @@ workflows: only: - develop - plat-1226-bulk-apis + - refactor-neo4j-448 - "build-prod": context : org-global filters: diff --git a/config/default.js b/config/default.js index 6dfb3a6..564b6d8 100644 --- a/config/default.js +++ b/config/default.js @@ -7,6 +7,7 @@ module.exports = { PORT: process.env.PORT || 3000, // it is configured to be empty at present, but may add prefix like '/api/v5' API_PREFIX: process.env.API_PREFIX || '', + GRAPH_DB_DATABASE: process.env.GRAPH_DB_DATABASE || undefined, GRAPH_DB_URI: process.env.GRAPH_DB_URI || process.env.GRAPHENEDB_BOLT_URL || 'bolt://localhost:7687', GRAPH_DB_USER: process.env.GRAPH_DB_USER || process.env.GRAPHENEDB_BOLT_USER || 'neo4j', GRAPH_DB_PASSWORD: process.env.GRAPH_DB_PASSWORD || process.env.GRAPHENEDB_BOLT_PASSWORD || '123456', diff --git a/src/common/helper.js b/src/common/helper.js index e14934d..de85875 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -17,18 +17,14 @@ const constants = require('../../app-constants') // Bus API Client let busApiClient -const driver = neo4j.driver(config.GRAPH_DB_URI, neo4j.auth.basic(config.GRAPH_DB_USER, config.GRAPH_DB_PASSWORD), { - maxConnectionLifetime: 3 * 60 * 60 * 1000, - maxConnectionPoolSize: 75, - connectionAcquisitionTimeout: 240000 -}) +const driver = neo4j.driver(config.GRAPH_DB_URI, neo4j.auth.basic(config.GRAPH_DB_USER, config.GRAPH_DB_PASSWORD)) /** * Wrap async function to standard express function * @param {Function} fn the async function * @returns {Function} the wrapped function */ -function wrapExpress (fn) { +function wrapExpress(fn) { return function (req, res, next) { fn(req, res, next).catch(next) } @@ -39,7 +35,7 @@ function wrapExpress (fn) { * @param obj the object (controller exports) * @returns {Object|Array} the wrapped object */ -function autoWrapExpress (obj) { +function autoWrapExpress(obj) { if (_.isArray(obj)) { return obj.map(autoWrapExpress) } @@ -59,14 +55,16 @@ function autoWrapExpress (obj) { * Create DB session. * @returns {Object} new db session */ -function createDBSession () { - return driver.session() +function createDBSession() { + return driver.session({ + ...{ database: config.GRAPH_DB_DATABASE } + }) } /** * Close driver connection once the app exit */ -async function closeDB () { +async function closeDB() { await driver.close() } @@ -77,22 +75,23 @@ async function closeDB () { * @param {String} id the entity id * @returns {Object} the found entity */ -async function ensureExists (tx, model, id, isAdmin = false) { +async function ensureExists(tx, model, id, isAdmin = false) { let res + 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) { - res = await tx.run(`MATCH (e:${model} {oldId: {id}, status: '${constants.GroupStatus.Active}'}) RETURN e`, { + res = await tx.run(`MATCH (e:${model} {oldId: $id, status: '${constants.GroupStatus.Active}'}) RETURN e`, { 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 }) } } @@ -101,16 +100,16 @@ async function ensureExists (tx, model, id, isAdmin = false) { } } else if (model === 'User') { if (validate(id, 4)) { - res = await tx.run(`MATCH (e:${model} {universalUID: {id}}) RETURN e`, { id }) + 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: \'00000000\', universalUID: {id}}) RETURN user', { id }) + 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 }) + 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 }) + res = await tx.run('CREATE (user:User {id: $id, universalUID: \'00000000\'}) RETURN user', { id }) } } } @@ -124,9 +123,9 @@ async function ensureExists (tx, model, id, isAdmin = false) { * @param {String} groupId the group id * @param {String} userId the user id */ -async function ensureGroupMember (session, groupId, userId) { +async function ensureGroupMember(session, groupId, userId) { const memberCheckRes = await session.run( - 'MATCH (g:Group {id: {groupId}})-[r:GroupContains {type: {membershipType}}]->(u:User {id: {userId}}) RETURN r', + 'MATCH (g:Group {id: $groupId})-[r:GroupContains {type: $membershipType}]->(u:User {id: $userId}) RETURN r', { groupId, membershipType: config.MEMBERSHIP_TYPES.User, userId } ) if (memberCheckRes.records.length === 0) { @@ -142,9 +141,9 @@ async function ensureGroupMember (session, groupId, userId) { * @param {Array} roles an array of group roles * @returns {Boolean} true if user has one of the group roles */ -async function hasGroupRole (session, groupId, userId, roles) { +async function hasGroupRole(session, groupId, userId, roles) { const memberCheckRes = await session.run( - 'MATCH (g:Group {id: {groupId}})-[r:GroupContains {type: {membershipType}}]->(u:User {id: {userId}}) RETURN r', + 'MATCH (g:Group {id: $groupId})-[r:GroupContains {type: $membershipType}]->(u:User {id: $userId}) RETURN r', { groupId, membershipType: config.MEMBERSHIP_TYPES.User, userId } ) if (memberCheckRes.records.length === 0) { @@ -160,10 +159,9 @@ async function hasGroupRole (session, groupId, userId, roles) { * @param {String} groupId the group id * @returns {Array} the child groups */ -async function getChildGroups (session, groupId) { +async function getChildGroups(session, groupId) { const res = await session.run( - 'MATCH (g:Group {id: {groupId}})-[r:GroupContains]->(c:Group) RETURN c ORDER BY c.oldId', - { groupId } + 'MATCH (g:Group {id: $groupId})-[r:GroupContains]->(c:Group) RETURN c ORDER BY c.oldId', { groupId } ) return _.map(res.records, (record) => record.get(0).properties) } @@ -174,10 +172,9 @@ async function getChildGroups (session, groupId) { * @param {String} groupId the group id * @returns {Array} the parent groups */ -async function getParentGroups (session, groupId) { +async function getParentGroups(session, groupId) { const res = await session.run( - 'MATCH (g:Group)-[r:GroupContains]->(c:Group {id: {groupId}}) RETURN g ORDER BY g.oldId', - { groupId } + 'MATCH (g:Group)-[r:GroupContains]->(c:Group {id: $groupId}) RETURN g ORDER BY g.oldId', { groupId } ) return _.map(res.records, (record) => record.get(0).properties) } @@ -188,7 +185,7 @@ async function getParentGroups (session, groupId) { * @param {Number} page the page number * @returns {String} link for the page */ -function getPageLink (req, page) { +function getPageLink(req, page) { const q = _.assignIn({}, req.query, { page }) return `${req.protocol}://${req.get('Host')}${req.baseUrl}${req.path}?${querystring.stringify(q)}` } @@ -199,7 +196,7 @@ function getPageLink (req, page) { * @param {Object} res the HTTP response * @param {Object} result the operation result */ -function setResHeaders (req, res, result) { +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) @@ -239,7 +236,7 @@ function setResHeaders (req, res, result) { * @param {Array} source the array in which to search for the term * @param {Array | String} term the term to search */ -function checkIfExists (source, term) { +function checkIfExists(source, term) { let terms if (!_.isArray(source)) { @@ -269,7 +266,7 @@ function checkIfExists (source, term) { * Check if the user has admin role * @param {Object} authUser the user */ -function hasAdminRole (authUser) { +function hasAdminRole(authUser) { for (let i = 0; i < authUser.roles.length; i++) { if (authUser.roles[i].toLowerCase() === config.USER_ROLES.Admin.toLowerCase()) { return true @@ -282,7 +279,7 @@ function hasAdminRole (authUser) { * Get Bus API Client * @return {Object} Bus API Client Instance */ -function getBusApiClient () { +function getBusApiClient() { // if there is no bus API client instance, then create a new instance if (!busApiClient) { busApiClient = busApi( @@ -307,7 +304,7 @@ function getBusApiClient () { * @param {String} topic the event topic * @param {Object} payload the event payload */ -async function postBusEvent (topic, payload) { +async function postBusEvent(topic, payload) { const client = getBusApiClient() await client.postEvent({ topic, @@ -318,9 +315,9 @@ async function postBusEvent (topic, payload) { }) } -async function createGroup (tx, data, currentUser) { +async function createGroup(tx, data, currentUser) { // check whether group name is already used - const nameCheckRes = await tx.run('MATCH (g:Group {name: {name}}) RETURN g LIMIT 1', { + const nameCheckRes = await tx.run('MATCH (g:Group {name: $name}) RETURN g LIMIT 1', { name: data.name }) if (nameCheckRes.records.length > 0) { @@ -339,14 +336,14 @@ async function createGroup (tx, data, currentUser) { groupData.organizationId = groupData.organizationId ? groupData.organizationId : '' const createRes = await tx.run( - 'CREATE (group:Group {id: {id}, name: {name}, description: {description}, privateGroup: {privateGroup}, selfRegister: {selfRegister}, createdAt: {createdAt}, createdBy: {createdBy}, domain: {domain}, ssoId: {ssoId}, organizationId: {organizationId}, status: {status}}) RETURN group', + 'CREATE (group:Group {id: $id, name: $name, description: $description, privateGroup: $privateGroup, selfRegister: $selfRegister, createdAt: $createdAt, createdBy: $createdBy, domain: $domain, ssoId: $ssoId, organizationId: $organizationId, status: $status}) RETURN group', groupData ) return createRes.records[0]._fields[0].properties } -async function deleteGroup (tx, group) { +async function deleteGroup(tx, group) { const groupsToDelete = [group] let index = 0 while (index < groupsToDelete.length) { @@ -373,12 +370,12 @@ async function deleteGroup (tx, group) { for (let i = 0; i < groupsToDelete.length; i += 1) { const id = groupsToDelete[i].id // delete group's relationships - await tx.run('MATCH (g:Group {id: {groupId}})-[r]-() DELETE r', { + await tx.run('MATCH (g:Group {id: $groupId})-[r]-() DELETE r', { groupId: id }) // delete group - await tx.run('MATCH (g:Group {id: {groupId}}) DELETE g', { + await tx.run('MATCH (g:Group {id: $groupId}) DELETE g', { groupId: id }) } diff --git a/src/controllers/GroupController.js b/src/controllers/GroupController.js index 7aa8a4c..5279bac 100644 --- a/src/controllers/GroupController.js +++ b/src/controllers/GroupController.js @@ -43,6 +43,16 @@ async function updateGroup (req, res) { res.send(result) } +/** + * Patch group + * @param req the request + * @param res the response + */ + async function patchGroup (req, res) { + const result = await service.patchGroup(req.authUser.isMachine ? 'M2M' : req.authUser, req.params.groupId, req.body) + res.send(result) +} + /** * Get group * @param req the request @@ -85,6 +95,7 @@ module.exports = { searchGroups, createGroup, updateGroup, + patchGroup, getGroup, deleteGroup, getGroupByOldId diff --git a/src/routes.js b/src/routes.js index 504fca8..c969e33 100644 --- a/src/routes.js +++ b/src/routes.js @@ -42,6 +42,13 @@ module.exports = { access: [constants.UserRoles.Admin], scopes: ['write:groups', 'all:groups'] }, + patch: { + controller: 'GroupController', + method: 'patchGroup', + auth: 'jwt', + access: [constants.UserRoles.Admin], + scopes: ['write:groups', 'all:groups'] + }, delete: { controller: 'GroupController', method: 'deleteGroup', diff --git a/src/services/GroupMembershipService.js b/src/services/GroupMembershipService.js index bd99297..b27b008 100644 --- a/src/services/GroupMembershipService.js +++ b/src/services/GroupMembershipService.js @@ -19,7 +19,7 @@ const constants = require('../../app-constants') * @returns {Object} the added group membership */ async function addGroupMember (currentUser, groupId, data) { - logger.debug(`Enter in addGroupMember - Group = ${groupId} Criteria = ${data}`) + logger.debug(`START: addGroupMember - Group = ${groupId} Criteria = ${data}`) const session = helper.createDBSession() const tx = session.beginTransaction() const isAdmin = currentUser === 'M2M' || helper.hasAdminRole(currentUser) @@ -72,12 +72,12 @@ async function addGroupMember (currentUser, groupId, data) { let memberCheckRes if (data.universalUID) { memberCheckRes = await tx.run( - `MATCH (g:Group {id: {groupId}})-[r:GroupContains]->(o:${targetObjectType} {universalUID: {memberId}}) RETURN o`, + `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`, + `MATCH (g:Group {id: $groupId})-[r:GroupContains]->(o:${targetObjectType} {id: $memberId}) RETURN o`, { groupId, memberId: data.memberId } ) } @@ -89,7 +89,7 @@ async function addGroupMember (currentUser, groupId, data) { // check cyclical reference 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', + 'MATCH p=shortestPath( (g1:Group {id: $fromId})-[*]->(g2:Group {id: $toId}) ) RETURN p', { fromId: data.memberId, toId: groupId } ) if (pathRes.records.length > 0) { @@ -103,9 +103,9 @@ async function addGroupMember (currentUser, groupId, data) { let query if (validate(memberId, 4) && data.universalUID) { - 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' + 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` + 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 = { @@ -180,7 +180,7 @@ addGroupMember.schema = Joi.alternatives().try( * @returns {Object} the added group membership */ async function addGroupMemberBulk (currentUser, groupId, data) { - logger.debug(`Enter in addGroupMemberBulk - Group = ${groupId} Data = ${JSON.stringify(data)}`) + logger.debug(`START: addGroupMemberBulk - Group = ${groupId} Data = ${JSON.stringify(data)}`) const membersAddRes = await Promise.allSettled(data.members.map(member => addGroupMember(currentUser, groupId, member))) @@ -227,10 +227,11 @@ addGroupMemberBulk.schema = Joi.object().keys({ * @param {Object} currentUser the current user * @param {String} groupId the group id * @param {String} memberId the member id + * @param {Object} criteria the search criteria * @returns {Object} the deleted group membership */ -async function deleteGroupMember (currentUser, groupId, memberId, query) { - logger.debug(`Enter in deleteGroupMember - Group = ${groupId} memberId = ${memberId}`) +async function deleteGroupMember (currentUser, groupId, memberId, criteria) { + logger.debug(`START: deleteGroupMember - Group = ${groupId} Member = ${memberId} Criteria = ${JSON.stringify(criteria)}`) const session = helper.createDBSession() const tx = session.beginTransaction() const isAdmin = currentUser === 'M2M' || helper.hasAdminRole(currentUser) @@ -246,7 +247,7 @@ async function deleteGroupMember (currentUser, groupId, memberId, query) { groupId = group.id const oldId = group.oldId const name = group.name - const universalUID = query ? query.universalUID : null + const universalUID = criteria ? criteria.universalUID : null if ( !isAdmin && @@ -258,16 +259,16 @@ async function deleteGroupMember (currentUser, groupId, memberId, query) { // delete membership if (universalUID) { - const query = 'MATCH (g:Group {id: {groupId}})-[r:GroupContains]->(o {universalUID: {universalUID}}) DELETE r' + 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 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' + const query = 'MATCH (g:Group {id: $groupId})-[r:GroupContains]->(o {id: $memberId}) DELETE r' await tx.run(query, { groupId, memberId }) } @@ -304,7 +305,7 @@ deleteGroupMember.schema = { currentUser: Joi.any(), groupId: Joi.id(), // defined in app-bootstrap memberId: Joi.optionalId().allow('', null), - query: Joi.object().allow('', null) + criteria: Joi.object().allow('', null) } /** @@ -315,7 +316,7 @@ deleteGroupMember.schema = { * @returns {Object} the deleted group membership */ async function deleteGroupMemberBulk (currentUser, groupId, data) { - logger.debug(`Enter in deleteGroupMemberBulk - Group = ${groupId} Data = ${JSON.stringify(data)}`) + logger.debug(`START: deleteGroupMemberBulk - Group = ${groupId} Data = ${JSON.stringify(data)}`) const membersAddRes = await Promise.allSettled(data.members.map(member => deleteGroupMember(currentUser, groupId, member))) @@ -359,6 +360,7 @@ deleteGroupMemberBulk.schema = Joi.object().keys({ * @returns {Object} the search result */ async function getGroupMembers (currentUser, groupId, criteria) { + logger.debug(`START: getGroupMembers - Group = ${groupId} Criteria = ${JSON.stringify(criteria)}`) const session = helper.createDBSession() const isAdmin = currentUser === 'M2M' || helper.hasAdminRole(currentUser) @@ -376,7 +378,7 @@ async function getGroupMembers (currentUser, groupId, criteria) { await helper.ensureGroupMember(session, groupId, currentUser.userId) } - const matchClause = 'MATCH (g:Group {id: {groupId}})-[r:GroupContains]->(o)' + const matchClause = 'MATCH (g:Group {id: $groupId})-[r:GroupContains]->(o)' const params = { groupId } // query total record count @@ -437,11 +439,12 @@ getGroupMembers.schema = { * @returns {Object} the group membership */ async function getGroupMemberWithSession (session, groupId, memberId) { + logger.debug(`START: getGroupMemberWithSession - Group = ${groupId} Member = ${memberId}`) try { const group = await helper.ensureExists(session, 'Group', groupId) groupId = group.id - const query = 'MATCH (g:Group {id: {groupId}})-[r:GroupContains]->(o {id: {memberId}}) RETURN r' + const query = 'MATCH (g:Group {id: $groupId})-[r:GroupContains]->(o {id: $memberId}) RETURN r' const membershipRes = await session.run(query, { groupId, memberId }) if (membershipRes.records.length === 0) { throw new errors.NotFoundError('The member is not in the group') @@ -470,6 +473,7 @@ async function getGroupMemberWithSession (session, groupId, memberId) { * @returns {Object} the group membership */ async function getGroupMember (currentUser, groupId, memberId) { + logger.debug(`START: getGroupMember - Group = ${groupId} Member = ${memberId}`) const isAdmin = currentUser === 'M2M' || helper.hasAdminRole(currentUser) const session = helper.createDBSession() try { @@ -503,20 +507,21 @@ getGroupMember.schema = { /** * Get distinct user members count of given group. Optionally may include sub groups. * @param {String} groupId the group id - * @param {Object} query the query parameters + * @param {Object} criteria the query parameters * @returns {Object} the group members count data */ -async function getGroupMembersCount (groupId, query) { +async function getGroupMembersCount (groupId, criteria) { + logger.debug(`START: getGroupMembersCount - Group = ${groupId} Criteria = ${JSON.stringify(criteria)}`) const session = helper.createDBSession() try { const group = await helper.ensureExists(session, 'Group', groupId) groupId = group.id let queryToExecute = '' - if (query.includeSubGroups) { - queryToExecute = 'MATCH (g:Group {id: {groupId}})-[r:GroupContains*1..]->(o:User) RETURN COUNT(o) AS count' + if (criteria.includeSubGroups) { + 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' + queryToExecute = 'MATCH (g:Group {id: $groupId})-[r:GroupContains]->(o:User) RETURN COUNT(o) AS count' } const res = await session.run(queryToExecute, { groupId }) @@ -533,27 +538,28 @@ async function getGroupMembersCount (groupId, query) { getGroupMembersCount.schema = { groupId: Joi.optionalId(), // defined in app-bootstrap - query: Joi.object().keys({ + criteria: 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 + * @param {Object} criteria the query parameters * @returns {Object} list of groupId and memberCount mapping */ -async function listGroupsMemberCount (query) { +async function listGroupsMemberCount (criteria) { + logger.debug(`START: listGroupsMemberCount - Criteria = ${criteria}`) const session = helper.createDBSession() try { let queryToExecute = '' - if (query.includeSubGroups) { + if (criteria.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' + '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' + '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 }) @@ -567,39 +573,39 @@ async function listGroupsMemberCount (query) { groupsMemberCount.push(groupMemberCount) }) - if (query.includeSubGroups) { - if (query.universalUID) { + if (criteria.includeSubGroups) { + if (criteria.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' + '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, + universalUID: criteria.universalUID, status: constants.GroupStatus.Active }) } - if (query.organizationId) { + if (criteria.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' + '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, + organizationId: criteria.organizationId, status: constants.GroupStatus.Active }) } } else { - if (query.universalUID) { + if (criteria.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' + '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, + universalUID: criteria.universalUID, status: constants.GroupStatus.Active }) } - if (query.organizationId) { + if (criteria.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, + organizationId: criteria.organizationId, status: constants.GroupStatus.Active }) } @@ -621,7 +627,7 @@ async function listGroupsMemberCount (query) { } listGroupsMemberCount.schema = { - query: Joi.object().keys({ + criteria: Joi.object().keys({ includeSubGroups: Joi.boolean().default(false), universalUID: Joi.optionalId(), organizationId: Joi.optionalId() @@ -631,13 +637,14 @@ listGroupsMemberCount.schema = { /** * Get member groups * @param {Object} memberId - * @param {Object} query the search criteria + * @param {Object} criteria the search criteria * @returns {Object} the search result */ -async function getMemberGroups (memberId, query) { +async function getMemberGroups (memberId, criteria) { + logger.debug(`START: getMemberGroups - Member = ${memberId}`) const session = helper.createDBSession() try { - const returnUuid = query.uuid + const returnUuid = criteria.uuid const res = await session.run( `MATCH (g:Group)-[r:GroupContains*1..]->(o {id: "${memberId}"}) WHERE exists(g.oldId) AND g.status = '${constants.GroupStatus.Active}' RETURN ${returnUuid ? 'g.id order by g.id' : 'g.oldId order by g.oldId'}` ) @@ -654,12 +661,13 @@ async function getMemberGroups (memberId, query) { getMemberGroups.schema = { memberId: Joi.id(), - query: Joi.object().keys({ + criteria: Joi.object().keys({ uuid: Joi.boolean().default(false) }) } async function groupValidityCheck (memberId, groupId) { + logger.debug(`START: groupValidityCheck - Group = ${groupId} Member = ${memberId}`) const session = helper.createDBSession() try { const res = await session.run( diff --git a/src/services/GroupRoleService.js b/src/services/GroupRoleService.js index cc34715..77447c2 100644 --- a/src/services/GroupRoleService.js +++ b/src/services/GroupRoleService.js @@ -16,7 +16,7 @@ const constants = require('../../app-constants') * @returns {Object} an object contains an array of objects, where the object has two properties: the groupId and the role */ async function getGroupRole (userId, criteria) { - logger.debug(`Get Group Role - UserId - ${userId} , Criteria - ${JSON.stringify(criteria)}`) + logger.debug(`START: getGroupRole - UserId - ${userId} , Criteria - ${JSON.stringify(criteria)}`) const session = helper.createDBSession() try { const matchClause = `MATCH (g:Group)-[r:GroupContains {type: "${config.MEMBERSHIP_TYPES.User}"}]->(o {id: "${userId}"}) UNWIND r.roles as role` @@ -57,7 +57,7 @@ getGroupRole.schema = { * @returns {Object} the added role */ async function addGroupRole (currentUser, userId, groupId, role) { - logger.debug(`Add Group Role - user - ${userId} , group - ${groupId}, role - ${role}`) + logger.debug(`START: addGroupRole - user - ${userId} , group - ${groupId}, role - ${role}`) const session = helper.createDBSession() const tx = session.beginTransaction() const isAdmin = currentUser === 'M2M' || helper.hasAdminRole(currentUser) @@ -85,7 +85,7 @@ async function addGroupRole (currentUser, userId, groupId, role) { logger.debug(`Membership ${JSON.stringify(membership)} to add role ${role}`) - await tx.run('MATCH (:Group)-[r:GroupContains {type: {type}, id: {id}}]-> () SET r.roles={roles} RETURN r', { type: config.MEMBERSHIP_TYPES.User, id: membership.id, roles }) + await tx.run('MATCH (:Group)-[r:GroupContains {type: $type, id: $id}]-> () SET r.roles=$roles RETURN r', { type: config.MEMBERSHIP_TYPES.User, id: membership.id, roles }) await helper.postBusEvent(config.KAFKA_GROUP_MEMBER_ROLE_ADD_TOPIC, { id: membership.id, userId, groupId, role }) await tx.commit() @@ -121,6 +121,7 @@ addGroupRole.schema = { * @returns {Object} the deleted group role */ async function deleteGroupRole (userId, groupId, role, isAdmin) { + logger.debug(`START: deleteGroupRole - user - ${userId} , group - ${groupId}, role - ${role}`) const session = helper.createDBSession() const tx = session.beginTransaction() try { @@ -143,7 +144,7 @@ async function deleteGroupRole (userId, groupId, role, isAdmin) { logger.debug(`Membership ${JSON.stringify(membership)} to delete role ${role}`) - await tx.run('MATCH (:Group)-[r:GroupContains {type: {type}, id: {id}}]-> () SET r.roles={roles} RETURN r', { type: config.MEMBERSHIP_TYPES.User, id: membership.id, roles }) + await tx.run('MATCH (:Group)-[r:GroupContains {type: $type, id: $id}]-> () SET r.roles=$roles RETURN r', { type: config.MEMBERSHIP_TYPES.User, id: membership.id, roles }) await helper.postBusEvent(config.KAFKA_GROUP_MEMBER_ROLE_DELETE_TOPIC, { id: membership.id, userId, groupId, role }) await tx.commit() diff --git a/src/services/GroupService.js b/src/services/GroupService.js index 230e433..2a2c4f0 100644 --- a/src/services/GroupService.js +++ b/src/services/GroupService.js @@ -15,101 +15,78 @@ const constants = require('../../app-constants') * @param {Boolean} isAdmin flag indicating whether the current user is an admin or not * @returns {Object} the search result */ -async function searchGroups (criteria, isAdmin) { - logger.debug(`Search Group - Criteria - ${JSON.stringify(criteria)}`) +async function searchGroups(criteria, isAdmin) { + logger.debug(`START: searchGroups - Criteria - ${JSON.stringify(criteria)}`) - if ((criteria.memberId || criteria.universalUID) && !criteria.membershipType) { + if (criteria.memberId && !criteria.membershipType) { throw new errors.BadRequestError('The membershipType parameter should be provided if memberId is provided.') } - if (!(criteria.memberId || criteria.universalUID) && criteria.membershipType) { - throw new errors.BadRequestError('The memberId or universalUID parameter should be provided if membershipType is provided.') + if (!criteria.memberId && criteria.membershipType) { + throw new errors.BadRequestError('The memberId parameter should be provided if membershipType is provided.') } const session = helper.createDBSession() + try { let matchClause if (criteria.memberId) { matchClause = `MATCH (g:Group)-[r:GroupContains {type: "${criteria.membershipType}"}]->(o {id: "${criteria.memberId}"})` - } else if (criteria.universalUID) { - matchClause = `MATCH (g:Group)-[r:GroupContains {type: "${criteria.membershipType}"}]->(o {universalUID: "${criteria.universalUID}"})` } else { matchClause = 'MATCH (g:Group)' } - let whereClause = '' + const filterCriterias = [] + + filterCriterias.push('exists(g.oldId)') + if (criteria.oldId) { - whereClause = ` WHERE g.oldId = "${criteria.oldId}"` + filterCriterias.push(`g.oldId = '${criteria.oldId}'`) } if (criteria.name) { - if (whereClause === '') { - whereClause = ` WHERE LOWER(g.name) CONTAINS "${criteria.name.toLowerCase()}"` - } else { - whereClause = whereClause.concat(` AND LOWER(g.name) CONTAINS "${criteria.name.toLowerCase()}"`) - } + filterCriterias.push(`toLower(g.name) CONTAINS '${criteria.name.toLowerCase()}'`) } if (criteria.ssoId) { - if (whereClause === '') { - whereClause = ` WHERE LOWER(g.ssoId) = "${criteria.ssoId.toLowerCase()}"` - } else { - whereClause = whereClause.concat(` AND LOWER(g.ssoId) = "${criteria.ssoId.toLowerCase()}"`) - } + filterCriterias.push(`toLower(g.ssoId) = '${criteria.ssoId.toLowerCase()}'`) } if (criteria.organizationId) { - if (whereClause === '') { - whereClause = ` WHERE LOWER(g.organizationId) = "${criteria.organizationId.toLowerCase()}"` - } else { - whereClause = whereClause.concat(` AND LOWER(g.organizationId) = "${criteria.organizationId.toLowerCase()}"`) - } + filterCriterias.push(`toLower(g.organizationId) = '${criteria.organizationId.toLowerCase()}'`) } if (criteria.selfRegister !== undefined) { - if (whereClause === '') { - whereClause = ` WHERE g.selfRegister = ${criteria.selfRegister}` - } else { - whereClause = whereClause.concat(` AND g.selfRegister = ${criteria.selfRegister}`) - } + filterCriterias.push(`g.selfRegister = ${criteria.selfRegister}`) } if (criteria.privateGroup !== undefined) { - if (whereClause === '') { - whereClause = ` WHERE g.privateGroup = ${criteria.privateGroup}` - } else { - whereClause = whereClause.concat(` AND g.privateGroup = ${criteria.privateGroup}`) - } - } - - if (whereClause === '') { - whereClause = ' WHERE exists(g.oldId)' - } else { - whereClause = whereClause.concat(' AND exists(g.oldId)') + filterCriterias.push(`g.privateGroup = ${criteria.privateGroup}`) } if (!isAdmin) { - if (whereClause === '') { - whereClause = ` WHERE g.status = '${constants.GroupStatus.Active}'` - } else { - whereClause = whereClause.concat(` AND g.status = '${constants.GroupStatus.Active}'`) - } + filterCriterias.push(`g.status = '${constants.GroupStatus.Active}'`) } + const whereClause = filterCriterias.join(' AND ') + // query total record count - const totalRes = await session.run(`${matchClause}${whereClause} RETURN COUNT(g)`) + const totalRes = await session.run(`${matchClause} WHERE ${whereClause} RETURN COUNT(g)`) const total = totalRes.records[0].get(0).low || 0 - console.log(`${matchClause}${whereClause} RETURN g ORDER BY g.oldId SKIP ${(criteria.page - 1) * criteria.perPage} - LIMIT ${criteria.perPage}`) + console.log( + `${matchClause} WHERE ${whereClause} RETURN g ORDER BY g.oldId SKIP ${ + (criteria.page - 1) * criteria.perPage + } LIMIT ${criteria.perPage}` + ) // query page of records let result = [] if (criteria.page <= Math.ceil(total / criteria.perPage)) { const pageRes = await session.run( - `${matchClause}${whereClause} RETURN g ORDER BY g.oldId SKIP ${(criteria.page - 1) * criteria.perPage} LIMIT ${ - criteria.perPage - }` + `${matchClause} WHERE ${whereClause} RETURN g ORDER BY g.oldId SKIP ${ + (criteria.page - 1) * criteria.perPage + } LIMIT ${criteria.perPage}` ) result = _.map(pageRes.records, (record) => record.get(0).properties) @@ -179,15 +156,14 @@ searchGroups.schema = { * @param {Object} data the data to create group * @returns {Object} the created group */ -async function createGroup (currentUser, data) { +async function createGroup(currentUser, data) { + logger.debug(`START: createGroup - data - ${JSON.stringify(data)}`) const session = helper.createDBSession() const tx = session.beginTransaction() try { - logger.debug(`Create Group - user - ${currentUser} , data - ${JSON.stringify(data)}`) - const group = await helper.createGroup(tx, data, currentUser) - logger.debug(`Group = ${JSON.stringify(group)}`) + logger.debug(`Group Created = ${JSON.stringify(group)}`) // post bus event await helper.postBusEvent(config.KAFKA_GROUP_CREATE_TOPIC, group) @@ -229,11 +205,11 @@ createGroup.schema = { * @param {Object} data the data to update group * @returns {Object} the updated group */ -async function updateGroup (currentUser, groupId, data) { +async function updateGroup(currentUser, groupId, data) { + logger.debug(`START: updateGroup - data - ${JSON.stringify(data)}`) const session = helper.createDBSession() const tx = session.beginTransaction() try { - logger.debug(`Update Group - user - ${currentUser} , data - ${JSON.stringify(data)}`) const group = await helper.ensureExists( tx, 'Group', @@ -253,19 +229,19 @@ async function updateGroup (currentUser, groupId, data) { let updateRes if (groupData.status) { updateRes = await tx.run( - 'MATCH (g:Group {id: {id}}) SET g.name={name}, g.description={description}, g.privateGroup={privateGroup}, g.selfRegister={selfRegister}, g.updatedAt={updatedAt}, g.updatedBy={updatedBy}, g.domain={domain}, g.ssoId={ssoId}, g.organizationId={organizationId}, g.oldId={oldId}, g.status={status} RETURN g', + 'MATCH (g:Group {id: $id}) SET g.name=$name, g.description=$description, g.privateGroup=$privateGroup, g.selfRegister=$selfRegister, g.updatedAt=$updatedAt, g.updatedBy=$updatedBy, g.domain=$domain, g.ssoId=$ssoId, g.organizationId=$organizationId, g.oldId=$oldId, g.status=$status RETURN g', groupData ) } else { updateRes = await tx.run( - 'MATCH (g:Group {id: {id}}) SET g.name={name}, g.description={description}, g.privateGroup={privateGroup}, g.selfRegister={selfRegister}, g.updatedAt={updatedAt}, g.updatedBy={updatedBy}, g.domain={domain}, g.ssoId={ssoId}, g.organizationId={organizationId}, g.oldId={oldId} RETURN g', + 'MATCH (g:Group {id: $id}) SET g.name=$name, g.description=$description, g.privateGroup=$privateGroup, g.selfRegister=$selfRegister, g.updatedAt=$updatedAt, g.updatedBy=$updatedBy, g.domain=$domain, g.ssoId=$ssoId, g.organizationId=$organizationId, g.oldId=$oldId RETURN g', groupData ) } const updatedGroup = updateRes.records[0].get(0).properties updatedGroup.oldName = group.name - logger.debug(`Group = ${JSON.stringify(updatedGroup)}`) + logger.debug(`Group Updated = ${JSON.stringify(updatedGroup)}`) await helper.postBusEvent(config.KAFKA_GROUP_UPDATE_TOPIC, updatedGroup) await tx.commit() @@ -290,6 +266,48 @@ updateGroup.schema = { }) } +/** + * Patch group + * @param {Object} currentUser the current user + * @param {String} groupId the id of group to update + * @param {Object} data the data to update group + * @returns {Object} the updated group + */ +async function patchGroup(currentUser, groupId, data) { + logger.debug(`START: patchGroup ${groupId} - data - ${JSON.stringify(data)}`) + const session = helper.createDBSession() + const tx = session.beginTransaction() + try { + await helper.ensureExists(tx, 'Group', groupId, currentUser === 'M2M' || helper.hasAdminRole(currentUser)) + + data.id = groupId + + const patchRes = await tx.run('MATCH (g:Group {id: $id}) SET g.oldId=$oldId RETURN g', data) + const patchedGroup = patchRes.records[0].get(0).properties + + logger.debug(`Group Updated = ${JSON.stringify(patchedGroup)}`) + + await tx.commit() + return patchedGroup + } catch (error) { + logger.error(error) + logger.debug('Transaction Rollback') + await tx.rollback() + throw error + } finally { + logger.debug('Session Close') + await session.close() + } +} + +patchGroup.schema = { + currentUser: Joi.any(), + groupId: Joi.string(), // defined in app-bootstrap + data: Joi.object().keys({ + oldId: Joi.string() + }) +} + /** * Get group. * @param {Object} currentUser the current user @@ -297,13 +315,9 @@ updateGroup.schema = { * @param {Object} criteria the query criteria * @returns {Object} the group */ -async function getGroup (currentUser, groupId, criteria) { +async function getGroup(currentUser, groupId, criteria) { + logger.debug(`START: getGroup - groupId - ${groupId} , criteria - ${JSON.stringify(criteria)}`) const isAdmin = currentUser === 'M2M' || helper.hasAdminRole(currentUser) - logger.debug( - `Get Group - admin - ${isAdmin} - user - ${currentUser} , groupId - ${groupId} , criteria - ${JSON.stringify( - criteria - )}` - ) if (criteria.includeSubGroups && criteria.includeParentGroup) { throw new errors.BadRequestError('includeSubGroups and includeParentGroup can not be both true') @@ -436,11 +450,11 @@ getGroup.schema = { * @param {Boolean} isAdmin flag indicating whether the current user is an admin or not * @returns {Object} the deleted group */ -async function deleteGroup (groupId, isAdmin) { +async function deleteGroup(groupId, isAdmin) { + logger.debug(`START: deleteGroup - ${groupId}`) const session = helper.createDBSession() const tx = session.beginTransaction() try { - logger.debug(`Delete Group - ${groupId}`) const group = await helper.ensureExists(tx, 'Group', groupId, isAdmin) const groupsToDelete = await helper.deleteGroup(tx, group) @@ -470,6 +484,7 @@ module.exports = { searchGroups, createGroup, updateGroup, + patchGroup, getGroup, deleteGroup } diff --git a/src/services/SubGroupService.js b/src/services/SubGroupService.js index 12cc552..4639dd0 100644 --- a/src/services/SubGroupService.js +++ b/src/services/SubGroupService.js @@ -17,7 +17,7 @@ const constants = require('../../app-constants') * @returns {Object} the created group */ async function createSubGroup (currentUser, groupId, data) { - logger.debug(`Create Sub Group - user - ${currentUser} , groupId - ${groupId} , data - ${JSON.stringify(data)}`) + logger.debug(`START: createSubGroup - user - ${currentUser} , groupId - ${groupId} , data - ${JSON.stringify(data)}`) const session = helper.createDBSession() const tx = session.beginTransaction() @@ -43,7 +43,7 @@ async function createSubGroup (currentUser, groupId, data) { const membershipId = uuid() - await tx.run('MATCH (g:Group {id: {groupId}}) MATCH (o:Group {id: {subGroupId}}) CREATE (g)-[r:GroupContains {id: {membershipId}, type: {membershipType}, createdAt: {createdAt}, createdBy: {createdBy}}]->(o) RETURN r', + await tx.run('MATCH (g:Group {id: $groupId}) MATCH (o:Group {id: $subGroupId}) CREATE (g)-[r:GroupContains {id: $membershipId, type: $membershipType, createdAt: $createdAt, createdBy: $createdBy}]->(o) RETURN r', { groupId, subGroupId: subGroup.id, membershipId, membershipType: config.MEMBERSHIP_TYPES.Group, createdAt: new Date().toISOString(), createdBy: currentUser === 'M2M' ? '00000000' : currentUser.userId }) const result = { @@ -94,7 +94,7 @@ createSubGroup.schema = { * @returns {Object} the deleted group */ async function deleteSubGroup (currentUser, groupId, subGroupId) { - logger.debug(`Delete Sub Group - ${groupId}, Sub Group - ${subGroupId}`) + logger.debug(`START: deleteSubGroup Group - ${groupId}, Sub Group - ${subGroupId}`) const session = helper.createDBSession() const tx = session.beginTransaction() const isAdmin = currentUser === 'M2M' || helper.hasAdminRole(currentUser) @@ -115,7 +115,7 @@ async function deleteSubGroup (currentUser, groupId, subGroupId) { } // delete relationship - await tx.run('MATCH (g:Group {id: {groupId}})-[r:GroupContains]->(o {id: {subGroupId}}) DELETE r', { groupId, subGroupId }) + await tx.run('MATCH (g:Group {id: $groupId})-[r:GroupContains]->(o {id: $subGroupId}) DELETE r', { groupId, subGroupId }) // delete sub group const groupsToDelete = await helper.deleteGroup(tx, subGroup)