Skip to content

add new endpoint POST /taas-teams/:id/members #150

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 2 commits into from
Feb 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 2 additions & 2 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ app.use((err, req, res, next) => {
}

if (err.response) {
// extract error message from V3 API
errorResponse.message = _.get(err, 'response.body.result.content')
// extract error message from V3/V5 API
errorResponse.message = _.get(err, 'response.body.result.content') || _.get(err, 'response.body.message')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a slight improvement here by the way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, this change looks good

}

if (_.isUndefined(errorResponse.message)) {
Expand Down
4 changes: 4 additions & 0 deletions config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ module.exports = {
TOPCODER_SKILL_PROVIDER_ID: process.env.TOPCODER_SKILL_PROVIDER_ID || '9cc0795a-6e12-4c84-9744-15858dba1861',

TOPCODER_USERS_API: process.env.TOPCODER_USERS_API || 'https://api.topcoder-dev.com/v3/users',
// the api to find topcoder members
TOPCODER_MEMBERS_API: process.env.TOPCODER_MEMBERS_API || 'https://api.topcoder-dev.com/v3/members',
// rate limit of requests to user api
MAX_PARALLEL_REQUEST_TOPCODER_USERS_API: process.env.MAX_PARALLEL_REQUEST_TOPCODER_USERS_API || 100,

// PostgreSQL database url.
DATABASE_URL: process.env.DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/postgres',
Expand Down
47 changes: 46 additions & 1 deletion docs/Topcoder-bookings-api.postman_collection.json
Original file line number Diff line number Diff line change
Expand Up @@ -4482,7 +4482,52 @@
}
},
"response": []
}
},
{
"name": "POST /taas-teams/:id/members",
"request": {
"method": "POST",
"header": [
{
"key": "Authorization",
"type": "text",
"value": "Bearer {{token_administrator}}"
},
{
"key": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"handles\": [\n \"tester1234\",\n \"non-existing\"\n ],\n \"emails\": [\n \"non-existing@domain.com\",\n \"email@domain.com\"\n ]\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{URL}}/taas-teams/:id/members",
"host": [
"{{URL}}"
],
"path": [
"taas-teams",
":id",
"members"
],
"variable": [
{
"key": "id",
"value": "16705"
}
]
}
},
"response": []
}
]
},
{
Expand Down
106 changes: 106 additions & 0 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1523,6 +1523,63 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
/taas-teams/{id}/members:
post:
tags:
- Teams
description: |
Add members to a team by handle or email.
security:
- bearerAuth: []
parameters:
- in: path
name: id
required: true
schema:
type: integer
description: The team/project id.
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AddMembersRequestBody'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/AddMembersResponseBody'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Not authorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: Not Found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/taas-teams/skills:
get:
tags:
Expand Down Expand Up @@ -2419,6 +2476,55 @@ components:
type: object
example: {"projectName": "TaaS Project Name", "projectId": 12345, "reportText": "I have issue with ..."}
description: "Arbitrary data to feed the specified template"
AddMembersRequestBody:
properties:
handles:
type: array
description: "The handles."
items:
type: string
description: "the handle of a member"
example: topcoder321
emails:
type: array
description: "The emails."
items:
type: string
description: "the email of a member"
example: 'xxx@xxx.com'
AddMembersResponseBody:
properties:
success:
type: array
description: "The handles."
items:
type: object
example: {"createdAt": "2021-02-18T19:58:50.610Z", "createdBy": -101, "email": "email@domain.com", "handle": "Scud", "id": 14155, "photoURL": "https://topcoder-dev-media.s3.amazonaws.com/member/profile/Scud-1450982908556.png", "role": "customer", "timeZone": null, "updatedAt": "2021-02-18T19:58:50.611Z", "updatedBy": -101, "userId": 1800091, "workingHourEnd": null, "workingHourStart": null}
failed:
type: array
description: "The emails."
items:
oneOf:
- type: object
properties:
error:
type: string
description: the error message
example: "User doesn't exist"
handle:
type: string
description: "the handle of a member"
example: topcoder321
- type: object
properties:
error:
type: string
description: the error message
example: "User is already added"
email:
type: string
description: "the email of a member"
example: 'xxx@xxx.com'
Error:
required:
- message
Expand Down
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@elastic/elasticsearch": "^7.9.1",
"@topcoder-platform/topcoder-bus-api-wrapper": "github:topcoder-platform/tc-bus-api-wrapper",
"aws-sdk": "^2.787.0",
"bottleneck": "^2.19.5",
"config": "^3.3.2",
"cors": "^2.8.5",
"date-fns": "^2.16.1",
Expand Down
89 changes: 88 additions & 1 deletion src/common/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
const fs = require('fs')
const querystring = require('querystring')
const Confirm = require('prompt-confirm')
const Bottleneck = require('bottleneck')
const AWS = require('aws-sdk')
const config = require('config')
const HttpStatus = require('http-status-codes')
Expand Down Expand Up @@ -968,6 +969,89 @@ async function checkIsMemberOfProject (userId, projectId) {
}
}

/**
* Find topcoder members by handles.
*
* @param {Array} handles the array of handles
* @returns {Array} the member details
*/
async function getMemberDetailsByHandles (handles) {
if (!handles.length) {
return []
}
const token = await getM2MToken()
const res = await request
.get(`${config.TOPCODER_MEMBERS_API}/_search`)
.query({
query: _.map(handles, handle => `handleLower:${handle.toLowerCase()}`).join(' OR '),
fields: 'userId,handle,firstName,lastName,email'
})
.set('Authorization', `Bearer ${token}`)
.set('Accept', 'application/json')
localLogger.debug({ context: 'getMemberDetailsByHandles', message: `response body: ${JSON.stringify(res.body)}` })
return _.get(res.body, 'result.content')
}

/**
* Find topcoder members by email.
*
* @param {String} token the auth token
* @param {String} email the email
* @returns {Array} the member details
*/
async function _getMemberDetailsByEmail (token, email) {
const res = await request
.get(config.TOPCODER_USERS_API)
.query({
filter: `email=${email}`,
fields: 'handle,id,email'
})
.set('Authorization', `Bearer ${token}`)
.set('Accept', 'application/json')
localLogger.debug({ context: '_getMemberDetailsByEmail', message: `response body: ${JSON.stringify(res.body)}` })
return _.get(res.body, 'result.content')
}

/**
* Find topcoder members by emails.
* Maximum concurrent requests is limited by MAX_PARALLEL_REQUEST_TOPCODER_USERS_API.
*
* @param {Array} emails the array of emails
* @returns {Array} the member details
*/
async function getMemberDetailsByEmails (emails) {
const token = await getM2MToken()
const limiter = new Bottleneck({ maxConcurrent: config.MAX_PARALLEL_REQUEST_TOPCODER_USERS_API })
const membersArray = await Promise.all(emails.map(email => limiter.schedule(() => _getMemberDetailsByEmail(token, email)
.catch(() => {
localLogger.error({ context: 'getMemberDetailsByEmails', message: `email: ${email} user not found` })
return []
})
)))
return _.flatten(membersArray)
}

/**
* Add a member to a project.
*
* @param {Number} projectId project id
* @param {Object} data the userId and the role of the member
* @param {Object} criteria the filtering criteria
* @returns {Object} the member created
*/
async function createProjectMember (projectId, data, criteria) {
const m2mToken = await getM2MToken()
const { body: member } = await request
.post(`${config.TC_API}/projects/${projectId}/members`)
.set('Authorization', `Bearer ${m2mToken}`)
.set('Content-Type', 'application/json')
.set('Accept', 'application/json')
.query(criteria)
.send(data)
localLogger.debug({ context: 'createProjectMember', message: `response body: ${JSON.stringify(member)}` })
return member
}

module.exports = {
getParamFromCliArgs,
promptUser,
Expand Down Expand Up @@ -1002,5 +1086,8 @@ module.exports = {
ensureJobById,
ensureUserById,
getAuditM2Muser,
checkIsMemberOfProject
checkIsMemberOfProject,
getMemberDetailsByHandles,
getMemberDetailsByEmails,
createProjectMember
}
12 changes: 11 additions & 1 deletion src/controllers/TeamController.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,19 @@ async function sendEmail (req, res) {
res.status(HttpStatus.NO_CONTENT).end()
}

/**
* Add members to a team.
* @param req the request
* @param res the response
*/
async function addMembers (req, res) {
res.send(await service.addMembers(req.authUser, req.params.id, req.body))
}

module.exports = {
searchTeams,
getTeam,
getTeamJob,
sendEmail
sendEmail,
addMembers
}
8 changes: 8 additions & 0 deletions src/routes/TeamRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,13 @@ module.exports = {
auth: 'jwt',
scopes: [constants.Scopes.READ_TAAS_TEAM]
}
},
'/taas-teams/:id/members': {
post: {
controller: 'TeamController',
method: 'addMembers',
auth: 'jwt',
scopes: [constants.Scopes.READ_TAAS_TEAM]
}
}
}
Loading