Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions migrations/elasticsearch_sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,39 @@ function getRequestBody(indexName) {
},
},
},
invites: {
type: 'nested',
properties: {
createdAt: {
type: 'date',
format: 'strict_date_optional_time||epoch_millis',
},
createdBy: {
type: 'integer',
},
email: {
type: 'string',
index: 'not_analyzed',
},
id: {
type: 'long',
},
role: {
type: 'string',
index: 'not_analyzed',
},
updatedAt: {
type: 'date',
format: 'strict_date_optional_time||epoch_millis',
},
updatedBy: {
type: 'integer',
},
userId: {
type: 'long',
},
},
},
name: {
type: 'string',
},
Expand Down
39 changes: 29 additions & 10 deletions src/models/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ module.exports = function defineProject(sequelize, DataTypes) {
if (parameters.filters.id.$in.length === 0) {
parameters.filters.id.$in.push(-1);
}
query += `AND id IN (${parameters.filters.id.$in}) `;
query += `AND projects.id IN (${parameters.filters.id.$in}) `;
} else if (_.isString(parameters.filters.id) || _.isNumber(parameters.filters.id)) {
query += `AND id = ${parameters.filters.id} `;
}
Expand All @@ -107,32 +107,51 @@ module.exports = function defineProject(sequelize, DataTypes) {
const statusFilter = parameters.filters.status;
if (_.isObject(statusFilter)) {
const statuses = statusFilter.$in.join("','");
query += `AND status IN ('${statuses}') `;
query += `AND projects.status IN ('${statuses}') `;
} else if (_.isString(statusFilter)) {
query += `AND status ='${statusFilter}'`;
query += `AND projects.status ='${statusFilter}'`;
}
}
if (_.has(parameters.filters, 'type')) {
query += `AND type = '${parameters.filters.type}' `;
query += `AND projects.type = '${parameters.filters.type}' `;
}
if (_.has(parameters.filters, 'keyword')) {
query += `AND "projectFullText" ~ lower('${parameters.filters.keyword}')`;
query += `AND projects."projectFullText" ~ lower('${parameters.filters.keyword}')`;
}

const attributesStr = `"${parameters.attributes.join('","')}"`;
let innerQuery = '';
if (_.has(parameters.filters, 'userId') || _.has(parameters.filters, 'email')) {
query += ` AND (members."userId" = ${parameters.filters.userId}
OR invites."userId" = ${parameters.filters.userId}
OR invites."email" = '${parameters.filters.email}') GROUP BY projects.id`;

innerQuery = `LEFT OUTER JOIN project_members AS members ON projects.id = members."projectId"
LEFT OUTER JOIN project_member_invites AS invites ON projects.id = invites."projectId"`;
}

let attributesStr = _.map(parameters.attributes, attr => `projects."${attr}"`);
attributesStr = `${attributesStr.join(',')}`;
const orderStr = `"${parameters.order[0][0]}" ${parameters.order[0][1]}`;

// select count of projects
return sequelize.query(`SELECT COUNT(1) FROM projects WHERE ${query}`,
return sequelize.query(`SELECT COUNT(1) FROM projects AS projects
${innerQuery}
WHERE ${query}`,
{ type: sequelize.QueryTypes.SELECT,
logging: (str) => { log.debug(str); },
raw: true,
})
.then((fcount) => {
const count = fcount[0].count;
let count = fcount.length;
if (fcount.length === 1) {
count = fcount[0].count;
}

// select project attributes
return sequelize.query(`SELECT ${attributesStr} FROM projects WHERE ${query} ORDER BY ` +
` ${orderStr} LIMIT ${parameters.limit} OFFSET ${parameters.offset}`,
return sequelize.query(`SELECT ${attributesStr} FROM projects AS projects
${innerQuery}
WHERE ${query} ORDER BY ` +
` projects.${orderStr} LIMIT ${parameters.limit} OFFSET ${parameters.offset}`,
{ type: sequelize.QueryTypes.SELECT,
logging: (str) => { log.debug(str); },
raw: true,
Expand Down
33 changes: 33 additions & 0 deletions src/routes/admin/project-create-index.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,39 @@ function getRequestBody(indexName, docType) {
},
},
},
invites: {
type: 'nested',
properties: {
createdAt: {
type: 'date',
format: 'strict_date_optional_time||epoch_millis',
},
createdBy: {
type: 'integer',
},
email: {
type: 'string',
index: 'not_analyzed',
},
id: {
type: 'long',
},
role: {
type: 'string',
index: 'not_analyzed',
},
updatedAt: {
type: 'date',
format: 'strict_date_optional_time||epoch_millis',
},
updatedBy: {
type: 'integer',
},
userId: {
type: 'long',
},
},
},
name: {
type: 'string',
},
Expand Down
30 changes: 5 additions & 25 deletions src/routes/projects/list-db.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,30 +133,10 @@ module.exports = [
}

// regular users can only see projects they are members of (or invited, handled bellow)
const getProjectIds = models.ProjectMember.getProjectIdsForUser(req.authUser.userId);

return getProjectIds
.then((accessibleProjectIds) => {
let allowedProjectIds = accessibleProjectIds;
// get projects with pending invite for current user
const invites = models.ProjectMemberInvite.getProjectInvitesForUser(
req.authUser.email,
req.authUser.userId);
if (invites) {
allowedProjectIds = _.union(allowedProjectIds, invites);
}
// filter based on accessible
if (_.get(criteria.filters, 'id', null)) {
criteria.filters.id.$in = _.intersection(
allowedProjectIds,
criteria.filters.id.$in,
);
} else {
criteria.filters.id = { $in: allowedProjectIds };
}
return retrieveProjects(req, criteria, sort, req.query.fields);
})
.then(result => res.json(util.wrapResponse(req.id, result.rows, result.count)))
.catch(err => next(err));
criteria.filters.userId = req.authUser.userId;
criteria.filters.email = req.authUser.email;
return retrieveProjects(req, criteria, sort, req.query.fields)
.then(result => res.json(util.wrapResponse(req.id, result.rows, result.count)))
.catch(err => next(err));
},
];
106 changes: 77 additions & 29 deletions src/routes/projects/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,56 @@ const buildEsFullTextQuery = (keyword, matchType, singleFieldName) => {
};
};

/**
* Build ES query search request body based on userId and email
*
* @param {String} userId the user id
* @param {String} email the email
* @return {Array} query
*/
const buildEsShouldQuery = (userId, email) => {
const should = [];
if (userId) {
should.push({
nested: {
path: 'members',
query: {
query_string: {
query: userId,
fields: ['members.userId'],
},
},
},
});
should.push({
nested: {
path: 'invites',
query: {
query_string: {
query: userId,
fields: ['invites.userId'],
},
},
},
});
}

if (email) {
should.push({
nested: {
path: 'invites',
query: {
query_string: {
query: email,
fields: ['invites.email'],
},
},
},
});
}
return should;
};

/**
* Build ES query search request body based on value, keyword, matchType and fieldName
*
Expand Down Expand Up @@ -234,6 +284,7 @@ const parseElasticSearchCriteria = (criteria, fields, order) => {
// prepare the elasticsearch filter criteria
const boolQuery = [];
let mustQuery = [];
let shouldQuery = [];
let fullTextQuery;
if (_.has(criteria, 'filters.id.$in')) {
boolQuery.push({
Expand Down Expand Up @@ -269,6 +320,10 @@ const parseElasticSearchCriteria = (criteria, fields, order) => {
['members.firstName', 'members.lastName']));
}

if (_.has(criteria, 'filters.userId') || _.has(criteria, 'filters.email')) {
shouldQuery = buildEsShouldQuery(criteria.filters.userId, criteria.filters.email);
}

if (_.has(criteria, 'filters.status.$in')) {
// status is an array
boolQuery.push({
Expand Down Expand Up @@ -348,14 +403,29 @@ const parseElasticSearchCriteria = (criteria, fields, order) => {
must: mustQuery,
});
}

if (shouldQuery.length > 0) {
const newBody = { query: { bool: { must: [] } } };
newBody.query.bool.must.push({
bool: {
should: shouldQuery,
},
});
if (mustQuery.length > 0 || boolQuery.length > 0) {
newBody.query.bool.must.push(body.query);
}

body.query = newBody.query;
}

if (fullTextQuery) {
body.query = _.merge(body.query, fullTextQuery);
if (body.query.bool) {
body.query.bool.minimum_should_match = 1;
}
}

if (fullTextQuery || boolQuery.length > 0 || mustQuery.length > 0) {
if (fullTextQuery || boolQuery.length > 0 || mustQuery.length > 0 || shouldQuery.length > 0) {
searchCriteria.body = body;
}
return searchCriteria;
Expand Down Expand Up @@ -427,7 +497,6 @@ module.exports = [
offset: req.query.offset || 0,
};
req.log.info(criteria);

if (!memberOnly
&& (util.hasAdminRole(req)
|| util.hasRoles(req, MANAGER_ROLES))) {
Expand All @@ -437,32 +506,11 @@ module.exports = [
.catch(err => next(err));
}

// regular users can only see projects they are members of (or invited, handled bellow)
const getProjectIds = models.ProjectMember.getProjectIdsForUser(req.authUser.userId);

return getProjectIds
.then((accessibleProjectIds) => {
const allowedProjectIds = accessibleProjectIds;
// get projects with pending invite for current user
const invites = models.ProjectMemberInvite.getProjectInvitesForUser(
req.authUser.email,
req.authUser.userId);

return invites.then((ids => _.union(allowedProjectIds, ids)));
})
.then((allowedProjectIds) => {
// filter based on accessible
if (_.get(criteria.filters, 'id', null)) {
criteria.filters.id.$in = _.intersection(
allowedProjectIds,
criteria.filters.id.$in,
);
} else {
criteria.filters.id = { $in: allowedProjectIds };
}
return retrieveProjects(req, criteria, sort, req.query.fields);
})
.then(result => res.json(util.wrapResponse(req.id, result.rows, result.count)))
.catch(err => next(err));
// regular users can only see projects they are members of (or invited, handled below)
criteria.filters.email = req.authUser.email;
criteria.filters.userId = req.authUser.userId;
return retrieveProjects(req, criteria, sort, req.query.fields)
.then(result => res.json(util.wrapResponse(req.id, result.rows, result.count)))
.catch(err => next(err));
},
];