Skip to content

Commit 2ad7e43

Browse files
author
Parth Shah
committed
fixes #32
1 parent 222cc8b commit 2ad7e43

File tree

8 files changed

+481
-481
lines changed

8 files changed

+481
-481
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "tc-projects-service",
3-
"version": "1.3.3",
3+
"version": "1.4.0",
44
"description": "Projects microservice",
55
"main": "index.js",
66
"engines": {

src/events/projects/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const eClient = util.getElasticSearchClient();
1919
*/
2020
const projectCreatedHandler = Promise.coroutine(function* (logger, msg, channel) { // eslint-disable-line func-names
2121
const data = JSON.parse(msg.content.toString());
22-
const userIds = data.members.map(single => `userId:${single.userId}`);
22+
const userIds = data.members ? data.members.map(single => `userId:${single.userId}`) : [];
2323
try {
2424
// retrieve member details
2525
const memberDetails = yield util.getMemberDetailsByUserIds(userIds, msg.properties.correlationId, logger);
@@ -45,7 +45,7 @@ const projectCreatedHandler = Promise.coroutine(function* (logger, msg, channel)
4545
channel.ack(msg);
4646
return undefined;
4747
} catch (error) {
48-
logger.error(`Error proecessing event (projectId: ${data.id})`, error);
48+
logger.error(`Error processing event (projectId: ${data.id})`, error);
4949
channel.nack(msg, false, !msg.fields.redelivered);
5050
return undefined;
5151
}

src/routes/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ router.route('/v4/projects')
3030
.post(require('./projects/create'))
3131
.get(require('./projects/list'));
3232

33-
// temporarily adding es based list endpoint
34-
router.get('/v4/projects/es', require('./projects/list-es'));
33+
router.route('/v4/projects/db')
34+
.get(require('./projects/list-db'));
3535

3636
router.route('/v4/projects/:projectId(\\d+)')
3737
.get(require('./projects/get'))

src/routes/projects/list-db.js

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import _ from 'lodash';
2+
import Promise from 'bluebird';
3+
import models from '../../models';
4+
import { USER_ROLE } from '../../constants';
5+
import util from '../../util';
6+
7+
/**
8+
* API to handle retrieving projects
9+
*
10+
* Permissions:
11+
* Only users that have access to the project can retrieve it.
12+
*
13+
*/
14+
const PROJECT_ATTRIBUTES = _.without(_.keys(models.Project.rawAttributes),
15+
'utm',
16+
'deletedAt',
17+
);
18+
const PROJECT_MEMBER_ATTRIBUTES = _.without(
19+
_.keys(models.ProjectMember.rawAttributes),
20+
'deletedAt',
21+
);
22+
const PROJECT_ATTACHMENT_ATTRIBUTES = _.without(
23+
_.keys(models.ProjectAttachment.rawAttributes),
24+
'deletedAt',
25+
26+
);
27+
const retrieveProjects = (req, criteria, sort, ffields) => {
28+
// order by
29+
const order = sort ? [sort.split(' ')] : [['createdAt', 'asc']];
30+
let fields = ffields ? ffields.split(',') : [];
31+
// parse the fields string to determine what fields are to be returned
32+
fields = util.parseFields(fields, {
33+
projects: PROJECT_ATTRIBUTES,
34+
project_members: PROJECT_MEMBER_ATTRIBUTES,
35+
});
36+
// make sure project.id is part of fields
37+
if (_.indexOf(fields.projects, 'id') < 0) fields.projects.push('id');
38+
const retrieveAttachments = !req.query.fields || req.query.fields.indexOf('attachments') > -1;
39+
const retrieveMembers = !req.query.fields || !!fields.project_members.length;
40+
41+
return models.Project.searchText({
42+
filters: criteria.filters,
43+
order,
44+
limit: criteria.limit,
45+
offset: criteria.offset,
46+
attributes: _.get(fields, 'projects', null),
47+
}, req.log)
48+
.then(({ rows, count }) => {
49+
const projectIds = _.map(rows, 'id');
50+
const promises = [];
51+
// retrieve members
52+
if (projectIds.length && retrieveMembers) {
53+
promises.push(
54+
models.ProjectMember.findAll({
55+
attributes: _.get(fields, 'ProjectMembers'),
56+
where: { projectId: { in: projectIds } },
57+
raw: true,
58+
}),
59+
);
60+
}
61+
if (projectIds.length && retrieveAttachments) {
62+
promises.push(
63+
models.ProjectAttachment.findAll({
64+
attributes: PROJECT_ATTACHMENT_ATTRIBUTES,
65+
where: { projectId: { in: projectIds } },
66+
raw: true,
67+
}),
68+
);
69+
}
70+
// return results after promise(s) have resolved
71+
return Promise.all(promises)
72+
.then((values) => {
73+
const allMembers = retrieveMembers ? values.shift() : [];
74+
const allAttachments = retrieveAttachments ? values.shift() : [];
75+
_.forEach(rows, (fp) => {
76+
const p = fp;
77+
// if values length is 1 it could be either attachments or members
78+
if (retrieveMembers) {
79+
p.members = _.filter(allMembers, m => m.projectId === p.id);
80+
}
81+
if (retrieveAttachments) {
82+
p.attachments = _.filter(allAttachments, a => a.projectId === p.id);
83+
}
84+
});
85+
return { rows, count };
86+
});
87+
});
88+
};
89+
90+
module.exports = [
91+
/**
92+
* GET projects/
93+
* Return a list of projects that match the criteria
94+
*/
95+
(req, res, next) => {
96+
// handle filters
97+
let filters = util.parseQueryFilter(req.query.filter);
98+
let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'createdAt';
99+
if (sort && sort.indexOf(' ') === -1) {
100+
sort += ' asc';
101+
}
102+
const sortableProps = [
103+
'createdAt', 'createdAt asc', 'createdAt desc',
104+
'updatedAt', 'updatedAt asc', 'updatedAt desc',
105+
'id', 'id asc', 'id desc',
106+
'status', 'status asc', 'status desc',
107+
'name', 'name asc', 'name desc',
108+
'type', 'type asc', 'type desc',
109+
];
110+
if (!util.isValidFilter(filters, ['id', 'status', 'type', 'memberOnly', 'keyword']) ||
111+
(sort && _.indexOf(sortableProps, sort) < 0)) {
112+
return util.handleError('Invalid filters or sort', null, req, next);
113+
}
114+
// check if user only wants to retrieve projects where he/she is a member
115+
const memberOnly = _.get(filters, 'memberOnly', false);
116+
filters = _.omit(filters, 'memberOnly');
117+
118+
const criteria = {
119+
filters,
120+
limit: Math.min(req.query.limit || 20, 20),
121+
offset: req.query.offset || 0,
122+
};
123+
req.log.debug(criteria);
124+
125+
if (!memberOnly
126+
&& (util.hasRole(req, USER_ROLE.TOPCODER_ADMIN)
127+
|| util.hasRole(req, USER_ROLE.MANAGER))) {
128+
// admins & topcoder managers can see all projects
129+
return retrieveProjects(req, criteria, sort, req.query.fields)
130+
.then(result => res.json(util.wrapResponse(req.id, result.rows, result.count)))
131+
.catch(err => next(err));
132+
}
133+
// If user requested projects where he/she is a member or
134+
// if they are not a copilot then return projects that they are members in.
135+
// Copilots can view projects that they are members in or they have
136+
//
137+
const getProjectIds = !memberOnly && util.hasRole(req, USER_ROLE.COPILOT) ?
138+
models.Project.getProjectIdsForCopilot(req.authUser.userId) :
139+
models.ProjectMember.getProjectIdsForUser(req.authUser.userId);
140+
return getProjectIds
141+
.then((accessibleProjectIds) => {
142+
// filter based on accessible
143+
if (_.get(criteria.filters, 'id', null)) {
144+
criteria.filters.id.$in = _.intersection(
145+
accessibleProjectIds,
146+
criteria.filters.id.$in,
147+
);
148+
} else {
149+
criteria.filters.id = { $in: accessibleProjectIds };
150+
}
151+
return retrieveProjects(req, criteria, sort, req.query.fields);
152+
})
153+
.then(result => res.json(util.wrapResponse(req.id, result.rows, result.count)))
154+
.catch(err => next(err));
155+
},
156+
];

0 commit comments

Comments
 (0)