Skip to content

Commit ea4d430

Browse files
authored
Merge pull request #259 from topcoder-platform/dev
Supporting release for Connect 2.4.10
2 parents 88fc172 + 085930b commit ea4d430

File tree

21 files changed

+823
-132
lines changed

21 files changed

+823
-132
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Microservice to manage CRUD operations for all things Projects.
55
### Note : Steps mentioned below are best to our capability as guide for local deployment, however, we expect from contributor, being a developer, to resolve run-time issues (e.g. OS and node version issues etc), if any.
66

77
### Local Development
8+
89
* We use docker-compose for running dependencies locally. Instructions for Docker compose setup - https://docs.docker.com/compose/install/
910
* Nodejs 8.9.4 - consider using [nvm](https://github.com/creationix/nvm) or equivalent to manage your node version
1011
* Install [libpg](https://www.npmjs.com/package/pg-native)

postman.json

Lines changed: 132 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"info": {
3-
"_postman_id": "97085cd7-b298-4f1c-9629-24af14ff5f13",
3+
"_postman_id": "4fc2b7cf-404a-4fd7-b6d2-4828a3994859",
44
"name": "tc-project-service",
55
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
66
},
@@ -1041,6 +1041,31 @@
10411041
},
10421042
"response": []
10431043
},
1044+
{
1045+
"name": "Invite with userIds and emails - both success and failed",
1046+
"request": {
1047+
"url": "{{api-url}}/v4/projects/1/members/invite",
1048+
"method": "POST",
1049+
"header": [
1050+
{
1051+
"key": "Authorization",
1052+
"value": "Bearer {{jwt-token-manager-40051334}}",
1053+
"description": ""
1054+
},
1055+
{
1056+
"key": "Content-Type",
1057+
"value": "application/json",
1058+
"description": ""
1059+
}
1060+
],
1061+
"body": {
1062+
"mode": "raw",
1063+
"raw": "{\n\t\"param\": {\n\t\t\"userIds\": [40051331, 40051334],\n\t\t\"emails\": [\"divyalife526@gmail.com\"],\n\t\t\"role\": \"manager\"\n\t}\n}"
1064+
},
1065+
"description": ""
1066+
},
1067+
"response": []
1068+
},
10441069
{
10451070
"name": "Update invite status with userId",
10461071
"request": {
@@ -1452,7 +1477,109 @@
14521477
"response": []
14531478
},
14541479
{
1455-
"name": "List projects with filters applied",
1480+
"name": "List projects with filters - type (exact)",
1481+
"request": {
1482+
"method": "GET",
1483+
"header": [
1484+
{
1485+
"key": "Authorization",
1486+
"value": "Bearer {{jwt-token}}"
1487+
}
1488+
],
1489+
"body": {
1490+
"mode": "raw",
1491+
"raw": ""
1492+
},
1493+
"url": {
1494+
"raw": "{{api-url}}/v4/projects?filter=type%3Dapp",
1495+
"host": [
1496+
"{{api-url}}"
1497+
],
1498+
"path": [
1499+
"v4",
1500+
"projects"
1501+
],
1502+
"query": [
1503+
{
1504+
"key": "filter",
1505+
"value": "type%3Dapp"
1506+
}
1507+
]
1508+
},
1509+
"description": "List all the project with filters applied. The filter string should be url encoded. Default limit and offset is applicable"
1510+
},
1511+
"response": []
1512+
},
1513+
{
1514+
"name": "List projects with filters - id (exact)",
1515+
"request": {
1516+
"method": "GET",
1517+
"header": [
1518+
{
1519+
"key": "Authorization",
1520+
"value": "Bearer {{jwt-token}}"
1521+
}
1522+
],
1523+
"body": {
1524+
"mode": "raw",
1525+
"raw": ""
1526+
},
1527+
"url": {
1528+
"raw": "{{api-url}}/v4/projects?filter=id%3D1",
1529+
"host": [
1530+
"{{api-url}}"
1531+
],
1532+
"path": [
1533+
"v4",
1534+
"projects"
1535+
],
1536+
"query": [
1537+
{
1538+
"key": "filter",
1539+
"value": "id%3D1"
1540+
}
1541+
]
1542+
},
1543+
"description": "List all the project with filters applied. The filter string should be url encoded. Default limit and offset is applicable"
1544+
},
1545+
"response": []
1546+
},
1547+
{
1548+
"name": "List projects with filters - name, code, customer, manager",
1549+
"request": {
1550+
"method": "GET",
1551+
"header": [
1552+
{
1553+
"key": "Authorization",
1554+
"value": "Bearer {{jwt-token}}"
1555+
}
1556+
],
1557+
"body": {
1558+
"mode": "raw",
1559+
"raw": ""
1560+
},
1561+
"url": {
1562+
"raw": "{{api-url}}/v4/projects?filter=id%3D1*%26name%3Dtes*%26code=test*%26customer%3DDiya*%26manager=first*",
1563+
"host": [
1564+
"{{api-url}}"
1565+
],
1566+
"path": [
1567+
"v4",
1568+
"projects"
1569+
],
1570+
"query": [
1571+
{
1572+
"key": "filter",
1573+
"value": "id%3D1*%26name%3Dtes*%26code=test*%26customer%3DDiya*%26manager=first*"
1574+
}
1575+
]
1576+
},
1577+
"description": "List all the project with filters applied. The filter string should be url encoded. Default limit and offset is applicable"
1578+
},
1579+
"response": []
1580+
},
1581+
{
1582+
"name": "List projects with filters - code",
14561583
"request": {
14571584
"method": "GET",
14581585
"header": [
@@ -1466,7 +1593,7 @@
14661593
"raw": ""
14671594
},
14681595
"url": {
1469-
"raw": "{{api-url}}/v4/projects?filter=type%3Dgeneric",
1596+
"raw": "{{api-url}}/v4/projects?filter=code%3Dtest*",
14701597
"host": [
14711598
"{{api-url}}"
14721599
],
@@ -1477,7 +1604,7 @@
14771604
"query": [
14781605
{
14791606
"key": "filter",
1480-
"value": "type%3Dgeneric"
1607+
"value": "code%3Dtest*"
14811608
}
14821609
]
14831610
},
@@ -5395,4 +5522,4 @@
53955522
]
53965523
}
53975524
]
5398-
}
5525+
}

src/constants.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,28 @@ export const PROJECT_MEMBER_ROLE = {
1818
OBSERVER: 'observer',
1919
CUSTOMER: 'customer',
2020
COPILOT: 'copilot',
21+
ACCOUNT_MANAGER: 'account_manager',
2122
};
2223

2324
export const PROJECT_MEMBER_MANAGER_ROLES = [PROJECT_MEMBER_ROLE.MANAGER, PROJECT_MEMBER_ROLE.OBSERVER];
2425

2526
export const USER_ROLE = {
2627
TOPCODER_ADMIN: 'administrator',
2728
MANAGER: 'Connect Manager',
29+
TOPCODER_ACCOUNT_MANAGER: 'Connect Account Manager',
2830
COPILOT: 'Connect Copilot',
2931
CONNECT_ADMIN: 'Connect Admin',
32+
COPILOT_MANAGER: 'Connect Copilot Manager',
3033
};
3134

3235
export const ADMIN_ROLES = [USER_ROLE.CONNECT_ADMIN, USER_ROLE.TOPCODER_ADMIN];
3336

34-
export const MANAGER_ROLES = [...ADMIN_ROLES, USER_ROLE.MANAGER];
37+
export const MANAGER_ROLES = [
38+
...ADMIN_ROLES,
39+
USER_ROLE.MANAGER,
40+
USER_ROLE.TOPCODER_ACCOUNT_MANAGER,
41+
USER_ROLE.COPILOT_MANAGER,
42+
];
3543

3644
export const EVENT = {
3745
ROUTING_KEY: {
@@ -130,7 +138,10 @@ export const BUS_API_EVENT = {
130138

131139
// Project Member Invites
132140
PROJECT_MEMBER_INVITE_CREATED: 'notifications.connect.project.member.invite.created',
141+
PROJECT_MEMBER_INVITE_REQUESTED: 'notifications.connect.project.member.invite.requested',
133142
PROJECT_MEMBER_INVITE_UPDATED: 'notifications.connect.project.member.invite.updated',
143+
PROJECT_MEMBER_INVITE_APPROVED: 'notifications.connect.project.member.invite.approved',
144+
PROJECT_MEMBER_INVITE_REJECTED: 'notifications.connect.project.member.invite.rejected',
134145
PROJECT_MEMBER_EMAIL_INVITE_CREATED: 'connect.action.email.project.member.invite.created',
135146
};
136147

@@ -156,5 +167,8 @@ export const INVITE_STATUS = {
156167
PENDING: 'pending',
157168
ACCEPTED: 'accepted',
158169
REFUSED: 'refused',
170+
REQUESTED: 'requested',
171+
REQUEST_REJECTED: 'request_rejected',
172+
REQUEST_APPROVED: 'request_approved',
159173
CANCELED: 'canceled',
160174
};

src/events/busApi.js

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import _ from 'lodash';
22
import config from 'config';
3-
import { EVENT, BUS_API_EVENT, PROJECT_STATUS, PROJECT_PHASE_STATUS, PROJECT_MEMBER_ROLE, MILESTONE_STATUS }
3+
import { EVENT, BUS_API_EVENT, PROJECT_STATUS, PROJECT_PHASE_STATUS, PROJECT_MEMBER_ROLE, MILESTONE_STATUS,
4+
INVITE_STATUS }
45
from '../constants';
56
import { createEvent } from '../services/busApi';
67
import models from '../models';
@@ -696,30 +697,66 @@ module.exports = (app, logger) => {
696697
}
697698
});
698699

699-
app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, ({ req, userId, email }) => {
700+
app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, ({ req, userId, email, status, role }) => {
700701
logger.debug('receive PROJECT_MEMBER_INVITE_CREATED event');
701702
const projectId = _.parseInt(req.params.projectId);
702703

703-
// send event to bus api
704-
createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, {
705-
projectId,
706-
userId,
707-
email,
708-
initiatorUserId: req.authUser.userId,
709-
}, logger);
704+
if (status === INVITE_STATUS.REQUESTED) {
705+
createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_REQUESTED, {
706+
projectId,
707+
userId,
708+
email,
709+
role,
710+
initiatorUserId: req.authUser.userId,
711+
}, logger);
712+
} else {
713+
// send event to bus api
714+
createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, {
715+
projectId,
716+
userId,
717+
email,
718+
role,
719+
initiatorUserId: req.authUser.userId,
720+
}, logger);
721+
}
710722
});
711723

712-
app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED, ({ req, userId, email, status }) => {
724+
app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED, ({ req, userId, email, status, role, createdBy }) => {
713725
logger.debug('receive PROJECT_MEMBER_INVITE_UPDATED event');
714726
const projectId = _.parseInt(req.params.projectId);
715727

716-
// send event to bus api
717-
createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, {
718-
projectId,
719-
userId,
720-
email,
721-
status,
722-
initiatorUserId: req.authUser.userId,
723-
}, logger);
728+
if (status === INVITE_STATUS.REQUEST_APPROVED) {
729+
// send event to bus api
730+
createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_APPROVED, {
731+
projectId,
732+
userId,
733+
originator: createdBy,
734+
email,
735+
role,
736+
status,
737+
initiatorUserId: req.authUser.userId,
738+
}, logger);
739+
} else if (status === INVITE_STATUS.REQUEST_REJECTED) {
740+
// send event to bus api
741+
createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_REJECTED, {
742+
projectId,
743+
userId,
744+
originator: createdBy,
745+
email,
746+
role,
747+
status,
748+
initiatorUserId: req.authUser.userId,
749+
}, logger);
750+
} else {
751+
// send event to bus api
752+
createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, {
753+
projectId,
754+
userId,
755+
email,
756+
role,
757+
status,
758+
initiatorUserId: req.authUser.userId,
759+
}, logger);
760+
}
724761
});
725762
};

src/models/projectMemberInvite.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,15 @@ module.exports = function defineProjectMemberInvite(sequelize, DataTypes) {
5555
raw: true,
5656
});
5757
},
58+
getPendingAndReguestedInvitesForProject(projectId) {
59+
return this.findAll({
60+
where: {
61+
projectId,
62+
status: { $in: [INVITE_STATUS.PENDING, INVITE_STATUS.REQUESTED] },
63+
},
64+
raw: true,
65+
});
66+
},
5867
getPendingInviteByEmailOrUserId(projectId, email, userId) {
5968
const where = { projectId, status: INVITE_STATUS.PENDING };
6069

@@ -69,6 +78,16 @@ module.exports = function defineProjectMemberInvite(sequelize, DataTypes) {
6978
where,
7079
});
7180
},
81+
getRequestedInvite(projectId, userId) {
82+
const where = { projectId, status: INVITE_STATUS.REQUESTED };
83+
84+
if (userId) {
85+
_.assign(where, { userId });
86+
}
87+
return this.findOne({
88+
where,
89+
});
90+
},
7291
getProjectInvitesForUser(email, userId) {
7392
const where = { status: INVITE_STATUS.PENDING };
7493

src/permissions/project.edit.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import _ from 'lodash';
44
import util from '../util';
55
import models from '../models';
6-
import { USER_ROLE } from '../constants';
6+
import { MANAGER_ROLES } from '../constants';
77

88
/**
99
* Super admin, Topcoder Managers are allowed to edit any project
@@ -20,7 +20,7 @@ module.exports = freq => new Promise((resolve, reject) => {
2020
req.context.currentProjectMembers = members;
2121
// check if auth user has acecss to this project
2222
const hasAccess = util.hasAdminRole(req)
23-
|| util.hasRole(req, USER_ROLE.MANAGER)
23+
|| util.hasRoles(req, MANAGER_ROLES)
2424
|| !_.isUndefined(_.find(members, m => m.userId === req.authUser.userId));
2525

2626
if (!hasAccess) {

src/permissions/project.view.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import _ from 'lodash';
33
import util from '../util';
44
import models from '../models';
5-
import { USER_ROLE, PROJECT_STATUS, PROJECT_MEMBER_ROLE } from '../constants';
5+
import { USER_ROLE, PROJECT_STATUS, PROJECT_MEMBER_ROLE, MANAGER_ROLES } from '../constants';
66

77
/**
88
* Super admin, Topcoder Managers are allowed to view any projects
@@ -21,7 +21,7 @@ module.exports = freq => new Promise((resolve, reject) => {
2121
req.context.currentProjectMembers = members;
2222
// check if auth user has acecss to this project
2323
const hasAccess = util.hasAdminRole(req)
24-
|| util.hasRole(req, USER_ROLE.MANAGER)
24+
|| util.hasRoles(req, MANAGER_ROLES)
2525
|| !_.isUndefined(_.find(members, m => m.userId === currentUserId));
2626

2727
// if user is co-pilot and the project doesn't have any copilots then

0 commit comments

Comments
 (0)