Skip to content

Supporting release for Connect 2.4.10 #259

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Mar 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7ac6135
trigger redeployment
maxceem Feb 13, 2019
1a679eb
account manager changes
gondzo Feb 19, 2019
85ab047
winning submission for challenge 30083089 - Topcoder Connect - Filter…
maxceem Feb 19, 2019
281af4a
remove filtering by id substring (only exact match support for now)
maxceem Feb 19, 2019
80ab3f2
Merge pull request #256 from topcoder-platform/feature/new-project-fi…
RishiRajSahu Feb 20, 2019
db19f45
update permissions for admin
gondzo Feb 20, 2019
c8f2de9
Merge pull request #255 from topcoder-platform/feature/accountManager
RishiRajSahu Feb 20, 2019
8e2dc03
account manager topcoder role
gondzo Feb 21, 2019
6015431
update role name
gondzo Feb 21, 2019
85db099
Update list.js
gondzo Feb 21, 2019
721d68e
Update create.js
gondzo Feb 21, 2019
42a788e
Update create.js
gondzo Feb 21, 2019
b1737e5
Copilot workflow changes (#254)
gondzo Feb 21, 2019
e14627c
add role to invite created event
gondzo Feb 21, 2019
b5b574c
copilot manager updates
gondzo Feb 24, 2019
a3190cf
winning submission from challenge 30085188 - Topcoder Connect - Handl…
maxceem Mar 5, 2019
7a24574
updated so individual failed invitataions have 'userId' field instead…
maxceem Mar 5, 2019
5106789
remove unnecessary variable to avoid confusion
maxceem Mar 5, 2019
1f27184
Added more exception fields for skipping the merge to avoid absolute …
Mar 5, 2019
811a860
Merge pull request #258 from maxceem/feature/individual-invitation-er…
RishiRajSahu Mar 6, 2019
0536be1
lint fix
RishiRajSahu Mar 6, 2019
085930b
Merge branch 'master' into dev
Mar 7, 2019
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Microservice to manage CRUD operations for all things Projects.
### 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.

### Local Development

* We use docker-compose for running dependencies locally. Instructions for Docker compose setup - https://docs.docker.com/compose/install/
* Nodejs 8.9.4 - consider using [nvm](https://github.com/creationix/nvm) or equivalent to manage your node version
* Install [libpg](https://www.npmjs.com/package/pg-native)
Expand Down
137 changes: 132 additions & 5 deletions postman.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"info": {
"_postman_id": "97085cd7-b298-4f1c-9629-24af14ff5f13",
"_postman_id": "4fc2b7cf-404a-4fd7-b6d2-4828a3994859",
"name": "tc-project-service",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
Expand Down Expand Up @@ -1041,6 +1041,31 @@
},
"response": []
},
{
"name": "Invite with userIds and emails - both success and failed",
"request": {
"url": "{{api-url}}/v4/projects/1/members/invite",
"method": "POST",
"header": [
{
"key": "Authorization",
"value": "Bearer {{jwt-token-manager-40051334}}",
"description": ""
},
{
"key": "Content-Type",
"value": "application/json",
"description": ""
}
],
"body": {
"mode": "raw",
"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}"
},
"description": ""
},
"response": []
},
{
"name": "Update invite status with userId",
"request": {
Expand Down Expand Up @@ -1452,7 +1477,109 @@
"response": []
},
{
"name": "List projects with filters applied",
"name": "List projects with filters - type (exact)",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Bearer {{jwt-token}}"
}
],
"body": {
"mode": "raw",
"raw": ""
},
"url": {
"raw": "{{api-url}}/v4/projects?filter=type%3Dapp",
"host": [
"{{api-url}}"
],
"path": [
"v4",
"projects"
],
"query": [
{
"key": "filter",
"value": "type%3Dapp"
}
]
},
"description": "List all the project with filters applied. The filter string should be url encoded. Default limit and offset is applicable"
},
"response": []
},
{
"name": "List projects with filters - id (exact)",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Bearer {{jwt-token}}"
}
],
"body": {
"mode": "raw",
"raw": ""
},
"url": {
"raw": "{{api-url}}/v4/projects?filter=id%3D1",
"host": [
"{{api-url}}"
],
"path": [
"v4",
"projects"
],
"query": [
{
"key": "filter",
"value": "id%3D1"
}
]
},
"description": "List all the project with filters applied. The filter string should be url encoded. Default limit and offset is applicable"
},
"response": []
},
{
"name": "List projects with filters - name, code, customer, manager",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Bearer {{jwt-token}}"
}
],
"body": {
"mode": "raw",
"raw": ""
},
"url": {
"raw": "{{api-url}}/v4/projects?filter=id%3D1*%26name%3Dtes*%26code=test*%26customer%3DDiya*%26manager=first*",
"host": [
"{{api-url}}"
],
"path": [
"v4",
"projects"
],
"query": [
{
"key": "filter",
"value": "id%3D1*%26name%3Dtes*%26code=test*%26customer%3DDiya*%26manager=first*"
}
]
},
"description": "List all the project with filters applied. The filter string should be url encoded. Default limit and offset is applicable"
},
"response": []
},
{
"name": "List projects with filters - code",
"request": {
"method": "GET",
"header": [
Expand All @@ -1466,7 +1593,7 @@
"raw": ""
},
"url": {
"raw": "{{api-url}}/v4/projects?filter=type%3Dgeneric",
"raw": "{{api-url}}/v4/projects?filter=code%3Dtest*",
"host": [
"{{api-url}}"
],
Expand All @@ -1477,7 +1604,7 @@
"query": [
{
"key": "filter",
"value": "type%3Dgeneric"
"value": "code%3Dtest*"
}
]
},
Expand Down Expand Up @@ -5395,4 +5522,4 @@
]
}
]
}
}
16 changes: 15 additions & 1 deletion src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,28 @@ export const PROJECT_MEMBER_ROLE = {
OBSERVER: 'observer',
CUSTOMER: 'customer',
COPILOT: 'copilot',
ACCOUNT_MANAGER: 'account_manager',
};

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

export const USER_ROLE = {
TOPCODER_ADMIN: 'administrator',
MANAGER: 'Connect Manager',
TOPCODER_ACCOUNT_MANAGER: 'Connect Account Manager',
COPILOT: 'Connect Copilot',
CONNECT_ADMIN: 'Connect Admin',
COPILOT_MANAGER: 'Connect Copilot Manager',
};

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

export const MANAGER_ROLES = [...ADMIN_ROLES, USER_ROLE.MANAGER];
export const MANAGER_ROLES = [
...ADMIN_ROLES,
USER_ROLE.MANAGER,
USER_ROLE.TOPCODER_ACCOUNT_MANAGER,
USER_ROLE.COPILOT_MANAGER,
];

export const EVENT = {
ROUTING_KEY: {
Expand Down Expand Up @@ -130,7 +138,10 @@ export const BUS_API_EVENT = {

// Project Member Invites
PROJECT_MEMBER_INVITE_CREATED: 'notifications.connect.project.member.invite.created',
PROJECT_MEMBER_INVITE_REQUESTED: 'notifications.connect.project.member.invite.requested',
PROJECT_MEMBER_INVITE_UPDATED: 'notifications.connect.project.member.invite.updated',
PROJECT_MEMBER_INVITE_APPROVED: 'notifications.connect.project.member.invite.approved',
PROJECT_MEMBER_INVITE_REJECTED: 'notifications.connect.project.member.invite.rejected',
PROJECT_MEMBER_EMAIL_INVITE_CREATED: 'connect.action.email.project.member.invite.created',
};

Expand All @@ -156,5 +167,8 @@ export const INVITE_STATUS = {
PENDING: 'pending',
ACCEPTED: 'accepted',
REFUSED: 'refused',
REQUESTED: 'requested',
REQUEST_REJECTED: 'request_rejected',
REQUEST_APPROVED: 'request_approved',
CANCELED: 'canceled',
};
73 changes: 55 additions & 18 deletions src/events/busApi.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import _ from 'lodash';
import config from 'config';
import { EVENT, BUS_API_EVENT, PROJECT_STATUS, PROJECT_PHASE_STATUS, PROJECT_MEMBER_ROLE, MILESTONE_STATUS }
import { EVENT, BUS_API_EVENT, PROJECT_STATUS, PROJECT_PHASE_STATUS, PROJECT_MEMBER_ROLE, MILESTONE_STATUS,
INVITE_STATUS }
from '../constants';
import { createEvent } from '../services/busApi';
import models from '../models';
Expand Down Expand Up @@ -696,30 +697,66 @@ module.exports = (app, logger) => {
}
});

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

// send event to bus api
createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, {
projectId,
userId,
email,
initiatorUserId: req.authUser.userId,
}, logger);
if (status === INVITE_STATUS.REQUESTED) {
createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_REQUESTED, {
projectId,
userId,
email,
role,
initiatorUserId: req.authUser.userId,
}, logger);
} else {
// send event to bus api
createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, {
projectId,
userId,
email,
role,
initiatorUserId: req.authUser.userId,
}, logger);
}
});

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

// send event to bus api
createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, {
projectId,
userId,
email,
status,
initiatorUserId: req.authUser.userId,
}, logger);
if (status === INVITE_STATUS.REQUEST_APPROVED) {
// send event to bus api
createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_APPROVED, {
projectId,
userId,
originator: createdBy,
email,
role,
status,
initiatorUserId: req.authUser.userId,
}, logger);
} else if (status === INVITE_STATUS.REQUEST_REJECTED) {
// send event to bus api
createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_REJECTED, {
projectId,
userId,
originator: createdBy,
email,
role,
status,
initiatorUserId: req.authUser.userId,
}, logger);
} else {
// send event to bus api
createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, {
projectId,
userId,
email,
role,
status,
initiatorUserId: req.authUser.userId,
}, logger);
}
});
};
19 changes: 19 additions & 0 deletions src/models/projectMemberInvite.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ module.exports = function defineProjectMemberInvite(sequelize, DataTypes) {
raw: true,
});
},
getPendingAndReguestedInvitesForProject(projectId) {
return this.findAll({
where: {
projectId,
status: { $in: [INVITE_STATUS.PENDING, INVITE_STATUS.REQUESTED] },
},
raw: true,
});
},
getPendingInviteByEmailOrUserId(projectId, email, userId) {
const where = { projectId, status: INVITE_STATUS.PENDING };

Expand All @@ -69,6 +78,16 @@ module.exports = function defineProjectMemberInvite(sequelize, DataTypes) {
where,
});
},
getRequestedInvite(projectId, userId) {
const where = { projectId, status: INVITE_STATUS.REQUESTED };

if (userId) {
_.assign(where, { userId });
}
return this.findOne({
where,
});
},
getProjectInvitesForUser(email, userId) {
const where = { status: INVITE_STATUS.PENDING };

Expand Down
4 changes: 2 additions & 2 deletions src/permissions/project.edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import _ from 'lodash';
import util from '../util';
import models from '../models';
import { USER_ROLE } from '../constants';
import { MANAGER_ROLES } from '../constants';

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

if (!hasAccess) {
Expand Down
4 changes: 2 additions & 2 deletions src/permissions/project.view.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import _ from 'lodash';
import util from '../util';
import models from '../models';
import { USER_ROLE, PROJECT_STATUS, PROJECT_MEMBER_ROLE } from '../constants';
import { USER_ROLE, PROJECT_STATUS, PROJECT_MEMBER_ROLE, MANAGER_ROLES } from '../constants';

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

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