Skip to content

Commit 93c1ca9

Browse files
committed
feat: list copilot applications
1 parent 7dcf79a commit 93c1ca9

File tree

6 files changed

+140
-0
lines changed

6 files changed

+140
-0
lines changed

src/permissions/constants.js

+15
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,21 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
277277
scopes: SCOPES_PROJECTS_WRITE,
278278
},
279279

280+
LIST_COPILOT_OPPORTUNITY: {
281+
meta: {
282+
title: 'Apply copilot opportunity',
283+
group: 'Apply Copilot',
284+
description: 'Who can apply for copilot opportunity.',
285+
},
286+
topcoderRoles: [
287+
USER_ROLE.TOPCODER_ADMIN,
288+
],
289+
projectRoles: [
290+
USER_ROLE.PROJECT_MANAGER,
291+
],
292+
scopes: SCOPES_PROJECTS_WRITE,
293+
},
294+
280295
MANAGE_PROJECT_BILLING_ACCOUNT_ID: {
281296
meta: {
282297
title: 'Manage Project property "billingAccountId"',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
2+
import _ from 'lodash';
3+
import util from '../util';
4+
import models from '../models';
5+
6+
/**
7+
* Topcoder admin and Project managers who are part of the project can view the copilot applications in it
8+
* Also, users who had an application will have access to view it.
9+
* @param {Object} freq the express request instance
10+
* @return {Promise} Returns a promise
11+
*/
12+
module.exports = freq => new Promise((resolve, reject) => {
13+
console.log("start permission check");
14+
const opportunityId = _.parseInt(freq.params.id);
15+
const currentUserId = freq.authUser.userId;
16+
return models.CopilotOpportunity.find({
17+
where: {
18+
id: opportunityId,
19+
},
20+
})
21+
.then((opportunity) => {
22+
const req = freq;
23+
req.context = req.context || {};
24+
req.context.currentOpportunity = opportunity;
25+
const projectId = opportunity.projectId;
26+
const isProjectManager = util.hasProjectManagerRole(req);
27+
28+
console.log("got opportunity", opportunityId);
29+
return models.ProjectMember.getActiveProjectMembers(projectId)
30+
.then((members) => {
31+
32+
console.log("got active members", projectId);
33+
return models.CopilotApplications.findOne({
34+
where: {
35+
opportunityId: opportunityId,
36+
userId: currentUserId,
37+
},
38+
}).then((copilotApplication) => {
39+
const isPartOfProject = isProjectManager && members.find(member => member.userId === currentUserId);
40+
// check if auth user has acecss to this project
41+
const hasAccess = util.hasAdminRole(req) || isPartOfProject || !!copilotApplication;
42+
console.log("got assigned application", hasAccess);
43+
return Promise.resolve(hasAccess);
44+
})
45+
})
46+
})
47+
.then((hasAccess) => {
48+
if (!hasAccess) {
49+
const errorMessage = 'You do not have permissions to perform this action';
50+
// user is not an admin nor is a registered project member
51+
return reject(new Error(errorMessage));
52+
}
53+
return resolve(true);
54+
});
55+
});

src/permissions/index.js

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const copilotAndAbove = require('./copilotAndAbove');
99
const workManagementPermissions = require('./workManagementForTemplate');
1010
const projectSettingEdit = require('./projectSetting.edit');
1111
const customerPaymentConfirm = require('./customerPayment.confirm');
12+
const viewCopilotApplications = require('./copilotApplications.view');
1213

1314
const generalPermission = require('./generalPermission');
1415
const { PERMISSION } = require('./constants');
@@ -199,4 +200,7 @@ module.exports = () => {
199200
Authorizer.setPolicy('customerPayment.view', generalPermission(PERMISSION.VIEW_CUSTOMER_PAYMENT));
200201
Authorizer.setPolicy('customerPayment.edit', generalPermission(PERMISSION.UPDATE_CUSTOMER_PAYMENT));
201202
Authorizer.setPolicy('customerPayment.confirm', customerPaymentConfirm);
203+
204+
// Copilot opportunity
205+
Authorizer.setPolicy('copilotApplications.view', viewCopilotApplications);
202206
};
+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import _ from 'lodash';
2+
import { middleware as tcMiddleware } from 'tc-core-library-js';
3+
4+
import models from '../../models';
5+
import { ADMIN_ROLES } from '../../constants';
6+
import util from '../../util';
7+
8+
const permissions = tcMiddleware.permissions;
9+
10+
module.exports = [
11+
permissions('copilotApplications.view'),
12+
(req, res, next) => {
13+
14+
console.log("start list operation");
15+
const isAdmin = util.hasRoles(req, ADMIN_ROLES);
16+
17+
let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'createdAt desc';
18+
if (sort.indexOf(' ') === -1) {
19+
sort += ' asc';
20+
}
21+
const sortableProps = ['createdAt asc', 'createdAt desc'];
22+
if (_.indexOf(sortableProps, sort) < 0) {
23+
return util.handleError('Invalid sort criteria', null, req, next);
24+
}
25+
const sortParams = sort.split(' ');
26+
27+
// Admin can see all requests and the PM can only see requests created by them
28+
const whereCondition = _.assign({},
29+
isAdmin ? {} : { createdBy: userId },
30+
);
31+
32+
return models.CopilotApplication.findAll({
33+
where: whereCondition,
34+
include: [
35+
{
36+
model: models.CopilotOpportunity,
37+
as: 'copilotOpportunity',
38+
},
39+
],
40+
order: [[sortParams[0], sortParams[1]]],
41+
})
42+
.then(copilotApplications => res.json(copilotApplications))
43+
.catch((err) => {
44+
util.handleError('Error fetching copilot applications', err, req, next);
45+
});
46+
},
47+
];

src/routes/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,8 @@ router.route('/v5/projects/copilot/opportunity/:id(\\d+)')
407407
// Project copilot opportunity apply
408408
router.route('/v5/projects/copilots/opportunity/:id(\\d+)/apply')
409409
.post(require('./copilotOpportunityApply/create'));
410+
router.route('/v5/projects/copilots/opportunity/:id(\\d+)/applications')
411+
.post(require('./copilotOpportunityApply/list'));
410412

411413
// Project Estimation Items
412414
router.route('/v5/projects/:projectId(\\d+)/estimations/:estimationId(\\d+)/items')

src/util.js

+17
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,23 @@ const projectServiceUtils = {
225225
return _.intersection(roles, ADMIN_ROLES.map(r => r.toLowerCase())).length > 0;
226226
},
227227

228+
/**
229+
* Helper funtion to verify if user has project manager role
230+
* @param {object} req Request object that should contain authUser
231+
* @return {boolean} true/false
232+
*/
233+
hasProjectManagerRole: (req) => {
234+
const isMachineToken = _.get(req, 'authUser.isMachine', false);
235+
const tokenScopes = _.get(req, 'authUser.scopes', []);
236+
if (isMachineToken) {
237+
if (_.indexOf(tokenScopes, M2M_SCOPES.CONNECT_PROJECT_ADMIN) >= 0) return true;
238+
return false;
239+
}
240+
let roles = _.get(req, 'authUser.roles', []);
241+
roles = roles.map(s => s.toLowerCase());
242+
return roles.includes(USER_ROLE.PROJECT_MANAGER.toLowerCase());
243+
},
244+
228245
/**
229246
* Parses query fields and groups them per table
230247
* @param {array} queryFields list of query fields

0 commit comments

Comments
 (0)