From a6587ccc0259dfb2f2d1af06404dc35a099faafc Mon Sep 17 00:00:00 2001 From: imcaizheng Date: Thu, 25 Feb 2021 13:00:07 +0800 Subject: [PATCH 1/6] Add new endpoint `GET /taas-teams/me` --- ...coder-bookings-api.postman_collection.json | 30 ++++++++++ docs/swagger.yaml | 58 ++++++++++++++++++- src/common/helper.js | 37 ++++++------ src/controllers/TeamController.js | 12 +++- src/routes/TeamRoutes.js | 8 +++ src/services/TeamService.js | 21 ++++++- 6 files changed, 146 insertions(+), 20 deletions(-) diff --git a/docs/Topcoder-bookings-api.postman_collection.json b/docs/Topcoder-bookings-api.postman_collection.json index 6daa5e1c..a8975b43 100644 --- a/docs/Topcoder-bookings-api.postman_collection.json +++ b/docs/Topcoder-bookings-api.postman_collection.json @@ -4656,6 +4656,36 @@ } }, "response": [] + }, + { + "name": "GET /taas-teams/me", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token_member}}" + } + ], + "url": { + "raw": "{{URL}}/taas-teams/me?enrich=true", + "host": [ + "{{URL}}" + ], + "path": [ + "taas-teams", + "me" + ], + "query": [ + { + "key": "enrich", + "value": "true" + } + ] + } + }, + "response": [] } ] }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index ad0a3e19..131f6cf4 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1881,6 +1881,59 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + + /taas-teams/me: + get: + tags: + - Teams + description: | + Return details about the current user. + security: + - bearerAuth: [] + parameters: + - in: query + name: enrich + required: false + schema: + type: boolean + description: whether to return enriched data or not. + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/UbahnUser' + '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: Forbidden + 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' /health: get: tags: @@ -2697,10 +2750,13 @@ components: example: 'xxx@xxx.com' ProjectMember: type: object - example: {"id": 14329, "userId": 40159097, "role": "customer", "createdAt": "2021-02-24T12:34:45.074Z", "updatedAt": "2021-02-24T12:34:45.075Z", "createdBy": -101, "updatedBy": -101, "handle": "tester1234", "photoURL": null, "workingHourStart": "9:00", "workingHourEnd": "17:00", "timeZone": "Asia/Kolkata", "email": "sathya.jayabal@gmail.com"} + example: {"id": 14329, "userId": 40159097, "role": "customer", "createdAt": "2021-02-24T12:34:45.074Z", "updatedAt": "2021-02-24T12:34:45.075Z", "createdBy": -101, "updatedBy": -101, "handle": "tester1234", "photoURL": null, "workingHourStart": "9:00", "workingHourEnd": "17:00", "timeZone": "Asia/Kolkata", "email": "xxx@xxx.com"} ProjectMemberInvite: type: object example: {"createdAt": "2021-02-24T11:02:12.673Z", "deletedAt": null, "role": "customer", "updatedBy": -101, "createdBy": -101, "id": 3686, "projectId": 16705, "userId": 23008602, "email": null, "deletedBy": null, "updatedAt": "2021-02-24T11:02:12.674Z", "status": "pending"} + UbahnUser: + type: object + example: {"lastName": "DeLaurentis", "updatedBy": "tcAdmin", "achievements": [{"certifierId": "certifierId", "updatedBy": "tcAdmin", "createdBy": "tc-user", "certifiedDate": "2020-05-04T07:36:28.036Z", "created": "2020-05-13T08:44:27.244Z", "name": "Topcoder", "id": "a49e1013-fd42-4c08-bc12-492510cadb96", "achievementsProviderId": "ce05133f-129e-484d-9ef9-72bf51ff81f9", "uri": "http://www.google.com/xx", "updated": "2021-01-05T10:58:32.429Z", "userId": "0bcb0d86-09bb-410a-b2b1-fba90d1a7699", "achievementprovider": {"updatedBy": "tcAdmin", "createdBy": "tc-user", "created": "2020-05-13T08:42:41.877Z", "name": "achievementsProviders_02", "id": "ce05133f-129e-484d-9ef9-72bf51ff81f9", "updated": "2021-01-05T10:58:32.341Z"}}], "created": "2020-05-05T10:18:03.882Z", "handle": "lazybaer", "skills": [{"certifierId": null, "skillId": "d67f35c3-fa42-4866-a0f9-0a4b84fcf4a9", "updatedBy": "tcAdmin", "createdBy": "lazybaer", "certifiedDate": null, "created": "2020-10-23T16:22:11.208Z", "skill": {"updatedBy": "tcAdmin", "skillprovider": {"updatedBy": "tcAdmin", "createdBy": "TonyJ", "created": "2020-08-31T12:30:00.543Z", "name": "Wipro Digital", "id": "26fb37b1-5f9f-4727-baa9-f3c87de84ab1", "updated": "2021-01-05T10:58:32.836Z"}, "createdBy": "0", "created": "2020-09-01T21:59:21.554Z", "skillProviderId": "26fb37b1-5f9f-4727-baa9-f3c87de84ab1", "name": "GitHub", "externalId": null, "id": "d67f35c3-fa42-4866-a0f9-0a4b84fcf4a9", "uri": null, "updated": "2021-01-05T10:58:33.332Z"}, "metricValue": null, "id": "8a84c1b4-1884-4a3c-90b2-eb86bf469bb6", "updated": "2021-01-05T10:58:34.080Z", "userId": "0bcb0d86-09bb-410a-b2b1-fba90d1a7699"}], "firstName": "Christopher", "externalProfiles": [{"organizationId": "0d2320f9-be61-4ba4-973e-edc3bb682a69", "updatedBy": "tcAdmin", "createdBy": "TonyJ", "isInactive": false, "created": "2020-08-31T12:30:38.495Z", "organization": {"updatedBy": "tcAdmin", "createdBy": "TonyJ", "created": "2020-08-31T12:29:58.081Z", "name": "Wipro Digital", "skillProviders": [{"organizationId": "0d2320f9-be61-4ba4-973e-edc3bb682a69", "updatedBy": "tcAdmin", "createdBy": "TonyJ", "created": "2020-08-31T12:30:08.410Z", "skillProviderId": "26fb37b1-5f9f-4727-baa9-f3c87de84ab1", "id": "5b26cdd3-fe68-4b30-85c5-ceaf280bd688", "updated": "2021-01-05T10:58:32.919Z"}], "id": "0d2320f9-be61-4ba4-973e-edc3bb682a69", "updated": "2021-01-05T10:58:32.261Z"}, "externalId": "8547899", "id": "870af97b-8c3b-4659-92d6-cac126bbe9de", "uri": null, "updated": "2021-01-05T10:58:32.724Z", "userId": "0bcb0d86-09bb-410a-b2b1-fba90d1a7699"}], "createdBy": "tc-Copilot", "attributes": [{"attributeId": "d709276a-80c3-491c-9b29-a4f065b2a56f", "updatedBy": "tcAdmin", "createdBy": "tc-Admin", "created": "2020-05-13T08:19:13.709Z", "id": "21de9324-900d-41ea-b127-f297dfb9a873", "attribute": {"updatedBy": "tcAdmin", "attributegroup": {"organizationId": "36ed815b-3da1-49f1-a043-aaed0a4e81ad", "updatedBy": "tc-Admin", "createdBy": "tc-Admin", "created": "2020-05-13T07:15:01.215Z", "name": "group 03", "id": "84634bbd-8191-40cf-a03e-9962d7e39fda", "updated": "2020-05-13T07:16:20.636Z"}, "createdBy": "tc-Admin", "created": "2020-05-13T07:32:03.128Z", "name": "Billing Account", "id": "d709276a-80c3-491c-9b29-a4f065b2a56f", "attributeGroupId": "84634bbd-8191-40cf-a03e-9962d7e39fda", "updated": "2021-01-05T10:58:32.604Z"}, "value": "74314457", "updated": "2021-01-05T10:58:33.739Z", "userId": "0bcb0d86-09bb-410a-b2b1-fba90d1a7699"}], "id": "0bcb0d86-09bb-410a-b2b1-fba90d1a7699", "updated": "2021-01-05T10:58:32.113Z"} Error: required: - message diff --git a/src/common/helper.js b/src/common/helper.js index 13917568..0197a68b 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -595,17 +595,18 @@ function encodeQueryString (queryObj, nesting = '') { } /** - * Function to get user ids - * @param {Integer} userId user id from jwt token - * @returns {String} user id. + * Function to list users by external id. + * @param {Integer} externalId the legacy user id + * @param {Boolean} enrich whether to return enriched data or not + * @returns {Array} the users found */ -async function getUserIds (userId) { +async function listUsersByExternalId (externalId, enrich = false) { const token = await getM2MUbahnToken() const q = { - enrich: true, + enrich, externalProfile: { organizationId: config.ORG_ID, - externalId: userId + externalId } } const url = `${config.TC_API}/users?${encodeQueryString(q)}` @@ -614,21 +615,22 @@ async function getUserIds (userId) { .set('Authorization', `Bearer ${token}`) .set('Content-Type', 'application/json') .set('Accept', 'application/json') - localLogger.debug({ context: 'getUserIds', message: `response body: ${JSON.stringify(res.body)}` }) + localLogger.debug({ context: 'listUserByExternalId', message: `response body: ${JSON.stringify(res.body)}` }) return res.body } /** - * Function to get user id - * @param {Integer} userId user id from jwt token - * @returns {String} user id. + * Function to get user by external id. + * @param {Integer} externalId the legacy user id + * @param {Boolean} enrich whether to return enriched data or not + * @returns {Object} the user */ -async function getUserId (userId) { - const ids = await getUserIds(userId) - if (_.isEmpty(ids)) { - throw new errors.NotFoundError(`userId: ${userId} "user" not found`) +async function getUserByExternalId (externalId, enrich) { + const users = await listUsersByExternalId(externalId, enrich) + if (_.isEmpty(users)) { + throw new errors.NotFoundError(`externalId: ${externalId} "user" not found`) } - return ids[0].id + return users[0] } /** @@ -880,7 +882,7 @@ async function getSkillById (skillId) { } /** - * Encapsulate the getUserId function. + * Encapsulate the getUserByExternalId function. * Make sure a user exists in ubahn(/v5/users) and return the id of the user. * * In the case the user does not exist in /v5/users but can be found in /v3/users @@ -891,7 +893,7 @@ async function getSkillById (skillId) { */ async function ensureUbahnUserId (currentUser) { try { - return await getUserId(currentUser.userId) + return (await getUserByExternalId(currentUser.userId, true)).id } catch (err) { if (!(err instanceof errors.NotFoundError)) { throw err @@ -1135,6 +1137,7 @@ module.exports = { } return ensureUbahnUserId({ userId }) }, + getUserByExternalId, getM2MToken, getM2MUbahnToken, postEvent, diff --git a/src/controllers/TeamController.js b/src/controllers/TeamController.js index 6cf1a6b4..138f99f3 100644 --- a/src/controllers/TeamController.js +++ b/src/controllers/TeamController.js @@ -83,6 +83,15 @@ async function deleteMember (req, res) { res.status(HttpStatus.NO_CONTENT).end() } +/** + * Return details about the current user. + * @param req the request + * @param res the response + */ +async function getMe (req, res) { + res.send(await service.getMe(req.authUser, req.query)) +} + module.exports = { searchTeams, getTeam, @@ -91,5 +100,6 @@ module.exports = { addMembers, searchMembers, searchInvites, - deleteMember + deleteMember, + getMe } diff --git a/src/routes/TeamRoutes.js b/src/routes/TeamRoutes.js index 3df14b0c..f5d062c6 100644 --- a/src/routes/TeamRoutes.js +++ b/src/routes/TeamRoutes.js @@ -28,6 +28,14 @@ module.exports = { scopes: [constants.Scopes.READ_TAAS_TEAM] } }, + '/taas-teams/me': { + get: { + controller: 'TeamController', + method: 'getMe', + auth: 'jwt', + scopes: [constants.Scopes.READ_TAAS_TEAM] + } + }, '/taas-teams/:id': { get: { controller: 'TeamController', diff --git a/src/services/TeamService.js b/src/services/TeamService.js index 792eca7f..4aa1a86e 100644 --- a/src/services/TeamService.js +++ b/src/services/TeamService.js @@ -513,6 +513,24 @@ deleteMember.schema = Joi.object().keys({ projectMemberId: Joi.number().integer().required() }).required() +/** + * Return details about the current user. + * + * @param {Object} currentUser the user who perform this operation. + * @params {Object} criteria the search criteria + * @returns {Object} the user data for current user + */ +async function getMe (currentUser, criteria) { + return helper.getUserByExternalId(currentUser.userId, criteria.enrich) +} + +getMe.schema = Joi.object().keys({ + currentUser: Joi.object().required(), + criteria: Joi.object().keys({ + enrich: Joi.boolean() + }).required() +}).required() + module.exports = { searchTeams, getTeam, @@ -521,5 +539,6 @@ module.exports = { addMembers, searchMembers, searchInvites, - deleteMember + deleteMember, + getMe } From 53c453b326a3f7b1b2dc752e63405d20f5fc5479 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Fri, 26 Feb 2021 13:37:30 +0200 Subject: [PATCH 2/6] feat: config for "extension-request" email --- config/default.js | 5 + config/email_template.config.js | 20 +++ ...coder-bookings-api.postman_collection.json | 132 +++++++++++------- 3 files changed, 110 insertions(+), 47 deletions(-) diff --git a/config/default.js b/config/default.js index 7831923c..e475a677 100644 --- a/config/default.js +++ b/config/default.js @@ -116,8 +116,13 @@ module.exports = { // the emails address for receiving the issue report // REPORT_ISSUE_EMAILS may contain comma-separated list of email which is converted to array REPORT_ISSUE_EMAILS: (process.env.REPORT_ISSUE_EMAILS || '').split(','), + // the emails address for receiving the issue report + // REPORT_ISSUE_EMAILS may contain comma-separated list of email which is converted to array + REQUEST_EXTENSION_EMAILS: (process.env.REQUEST_EXTENSION_EMAILS || '').split(','), // SendGrid email template ID for reporting issue REPORT_ISSUE_SENDGRID_TEMPLATE_ID: process.env.REPORT_ISSUE_SENDGRID_TEMPLATE_ID, + // SendGrid email template ID for requesting extension + REQUEST_EXTENSION_SENDGRID_TEMPLATE_ID: process.env.REQUEST_EXTENSION_SENDGRID_TEMPLATE_ID, // the URL where TaaS App is hosted TAAS_APP_URL: process.env.TAAS_APP_URL || 'https://platform.topcoder-dev.com/taas/myteams' } diff --git a/config/email_template.config.js b/config/email_template.config.js index ea4375f7..bc2d803a 100644 --- a/config/email_template.config.js +++ b/config/email_template.config.js @@ -22,6 +22,7 @@ module.exports = { recipients: config.REPORT_ISSUE_EMAILS, sendgridTemplateId: config.REPORT_ISSUE_SENDGRID_TEMPLATE_ID }, + /* Report issue for a particular member * * - userHandle: the user handle. Example: "bili_2021" @@ -39,5 +40,24 @@ module.exports = { '{{reportText}}', recipients: config.REPORT_ISSUE_EMAILS, sendgridTemplateId: config.REPORT_ISSUE_SENDGRID_TEMPLATE_ID + }, + + /* Request extension for a particular member + * + * - userHandle: the user handle. Example: "bili_2021" + * - projectId: the project ID. Example: 123412 + * - projectName: the project name. Example: "TaaS API Misc Updates" + * - text: comment for the request. Example: "I would like to keep working with this member for 2 months..." + */ + 'extension-request': { + subject: 'Extension Requested for member {{userHandle}} on TaaS Team {{projectName}} ({{projectId}}).', + body: 'User Handle: {{userHandle}}' + '\n' + + 'Project Name: {{projectName}}' + '\n' + + 'Project ID: {{projectId}}' + '\n' + + `Project URL: ${config.TAAS_APP_URL}/{{projectId}}` + '\n' + + '\n' + + '{{text}}', + recipients: config.REPORT_ISSUE_EMAILS, + sendgridTemplateId: config.REQUEST_EXTENSION_SENDGRID_TEMPLATE_ID } } diff --git a/docs/Topcoder-bookings-api.postman_collection.json b/docs/Topcoder-bookings-api.postman_collection.json index 6daa5e1c..ec79947b 100644 --- a/docs/Topcoder-bookings-api.postman_collection.json +++ b/docs/Topcoder-bookings-api.postman_collection.json @@ -4407,7 +4407,7 @@ }, "response": [] }, - { + { "name": "POST /taas-teams/email - team-issue-report", "request": { "method": "POST", @@ -4483,51 +4483,89 @@ }, "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": [] - }, + { + "name": "POST /taas-teams/email - extension-request", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token_member}}" + }, + { + "key": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"template\": \"extension-request\",\n \"data\": {\n \"projectName\": \"TaaS Project Name\",\n \"projectId\": 12345,\n \"userHandle\": \"pshah_manager\",\n \"reportText\": \"I would like to keep working with this member for 2 months...\"\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/taas-teams/email", + "host": [ + "{{URL}}" + ], + "path": [ + "taas-teams", + "email" + ] + } + }, + "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": [] + }, { "name": "GET /taas-teams/:id/members", "request": { @@ -9772,4 +9810,4 @@ ] } ] -} +} \ No newline at end of file From 868fbaf8e6dacc521b5889a9ea72af3657b95883 Mon Sep 17 00:00:00 2001 From: imcaizheng Date: Sat, 27 Feb 2021 15:13:40 +0800 Subject: [PATCH 3/6] Add new field `isApplicationPageActive` to the Job model --- docs/swagger.yaml | 22 +++++++++++++++++++ ...ob-add-is-application-page-active-field.js | 17 ++++++++++++++ src/common/helper.js | 1 + src/models/Job.js | 6 +++++ src/services/JobService.js | 20 ++++++++++++++--- 5 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 migrations/2021-02-27-job-add-is-application-page-active-field.js diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 16f11034..5b8f7d2b 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -23,6 +23,11 @@ paths: Create job. **Authorization** All topcoder members are allowed + + Permission rules on field `isApplicationPageActive`: + - M2M user is allowed to set the value of the field + - Other users are not allowed to set the value of the field + security: - bearerAuth: [] requestBody: @@ -352,6 +357,10 @@ paths: Update the job. **Authorization** Every topcoder member can update the job he/she created. bookingmanager and connectmember can update all jobs. + + Permission rules on field `isApplicationPageActive`: + - M2M user is allowed to update the value of the field + - Other users are not allowed to update the value of the field security: - bearerAuth: [] parameters: @@ -413,6 +422,10 @@ paths: Update job. **Authorization** Topcoder token with patch job scope is allowed + + Permission rules on field `isApplicationPageActive`: + - M2M user is allowed to update the value of the field + - Other users are not allowed to update the value of the field security: - bearerAuth: [] parameters: @@ -1980,6 +1993,9 @@ components: description: "The job candidates." items: $ref: '#/components/schemas/JobCandidate' + isApplicationPageActive: + type: boolean + default: false createdAt: type: string format: date-time @@ -2057,6 +2073,9 @@ components: type: string format: uuid description: "The skill id." + isApplicationPageActive: + type: boolean + default: false JobCandidate: required: - id @@ -2186,6 +2205,9 @@ components: type: string format: uuid description: "The skill id." + isApplicationPageActive: + type: boolean + default: false ResourceBooking: required: - id diff --git a/migrations/2021-02-27-job-add-is-application-page-active-field.js b/migrations/2021-02-27-job-add-is-application-page-active-field.js new file mode 100644 index 00000000..d75c5e9c --- /dev/null +++ b/migrations/2021-02-27-job-add-is-application-page-active-field.js @@ -0,0 +1,17 @@ +/* + * Add isApplicationPageActive field to the Job model. + */ + +module.exports = { + up: queryInterface => { + return Promise.all([ + queryInterface.sequelize.query('ALTER TABLE bookings.jobs ADD is_application_page_active BOOLEAN NOT NULL DEFAULT false'), + queryInterface.sequelize.query('UPDATE bookings.jobs SET is_application_page_active=false WHERE is_application_page_active is NULL'), + ]) + }, + down: queryInterface => { + return Promise.all([ + queryInterface.sequelize.query('ALTER TABLE bookings.jobs DROP is_application_page_active') + ]) + } +} diff --git a/src/common/helper.js b/src/common/helper.js index cb018ac6..50adc94b 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -70,6 +70,7 @@ esIndexPropertyMapping[config.get('esConfig.ES_INDEX_JOB')] = { workload: { type: 'keyword' }, skills: { type: 'keyword' }, status: { type: 'keyword' }, + isApplicationPageActive: { type: 'boolean' }, createdAt: { type: 'date' }, createdBy: { type: 'keyword' }, updatedAt: { type: 'date' }, diff --git a/src/models/Job.js b/src/models/Job.js index c124caec..49d34ff7 100644 --- a/src/models/Job.js +++ b/src/models/Job.js @@ -98,6 +98,12 @@ module.exports = (sequelize) => { type: Sequelize.STRING(255), allowNull: false }, + isApplicationPageActive: { + field: 'is_application_page_active', + type: Sequelize.BOOLEAN, + defaultValue: false, + allowNull: false + }, createdBy: { field: 'created_by', type: Sequelize.UUID, diff --git a/src/services/JobService.js b/src/services/JobService.js index bea40b45..7d855bd0 100644 --- a/src/services/JobService.js +++ b/src/services/JobService.js @@ -148,6 +148,11 @@ async function createJob (currentUser, job) { await helper.checkIsMemberOfProject(currentUser.userId, job.projectId) } + // the "isApplicationPageActive" field can be set/updated only by M2M user + if (!_.isUndefined(job.isApplicationPageActive) && !currentUser.isMachine) { + throw new errors.ForbiddenError('You are not allowed to set/update the value of field "isApplicationPageActive".') + } + await _validateSkills(job.skills) job.id = uuid() job.createdBy = await helper.getUserId(currentUser.userId) @@ -171,7 +176,8 @@ createJob.schema = Joi.object().keys({ resourceType: Joi.stringAllowEmpty().allow(null), rateType: Joi.rateType().allow(null), workload: Joi.workload().allow(null), - skills: Joi.array().items(Joi.string().uuid()).required() + skills: Joi.array().items(Joi.string().uuid()).required(), + isApplicationPageActive: Joi.boolean() }).required() }).required() @@ -188,6 +194,12 @@ async function updateJob (currentUser, id, data) { } let job = await Job.findById(id) const oldValue = job.toJSON() + + // the "isApplicationPageActive" field can be set/updated only by M2M user + if (!_.isUndefined(data.isApplicationPageActive) && !currentUser.isMachine) { + throw new errors.ForbiddenError('You are not allowed to set/update the value of field "isApplicationPageActive".') + } + const ubahnUserId = await helper.getUserId(currentUser.userId) if (!currentUser.hasManagePermission && !currentUser.isMachine) { // Check whether user can update the job. @@ -232,7 +244,8 @@ partiallyUpdateJob.schema = Joi.object().keys({ resourceType: Joi.stringAllowEmpty().allow(null), rateType: Joi.rateType().allow(null), workload: Joi.workload().allow(null), - skills: Joi.array().items(Joi.string().uuid()) + skills: Joi.array().items(Joi.string().uuid()), + isApplicationPageActive: Joi.boolean() }).required() }).required() @@ -262,7 +275,8 @@ fullyUpdateJob.schema = Joi.object().keys({ rateType: Joi.rateType().allow(null).default(null), workload: Joi.workload().allow(null).default(null), skills: Joi.array().items(Joi.string().uuid()).required(), - status: Joi.jobStatus().default('sourcing') + status: Joi.jobStatus().default('sourcing'), + isApplicationPageActive: Joi.boolean() }).required() }).required() From 033f203131d4330e15dfee19e2453ff9c098e104 Mon Sep 17 00:00:00 2001 From: maxceem Date: Wed, 3 Mar 2021 13:23:45 +0200 Subject: [PATCH 4/6] Revert "Add new endpoint `GET /taas-teams/me`" --- ...coder-bookings-api.postman_collection.json | 30 ---------- docs/swagger.yaml | 58 +------------------ src/common/helper.js | 37 ++++++------ src/controllers/TeamController.js | 12 +--- src/routes/TeamRoutes.js | 8 --- src/services/TeamService.js | 21 +------ 6 files changed, 20 insertions(+), 146 deletions(-) diff --git a/docs/Topcoder-bookings-api.postman_collection.json b/docs/Topcoder-bookings-api.postman_collection.json index 8505ca37..ec79947b 100644 --- a/docs/Topcoder-bookings-api.postman_collection.json +++ b/docs/Topcoder-bookings-api.postman_collection.json @@ -4694,36 +4694,6 @@ } }, "response": [] - }, - { - "name": "GET /taas-teams/me", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_member}}" - } - ], - "url": { - "raw": "{{URL}}/taas-teams/me?enrich=true", - "host": [ - "{{URL}}" - ], - "path": [ - "taas-teams", - "me" - ], - "query": [ - { - "key": "enrich", - "value": "true" - } - ] - } - }, - "response": [] } ] }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 96bd4b5b..5b8f7d2b 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1894,59 +1894,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - - /taas-teams/me: - get: - tags: - - Teams - description: | - Return details about the current user. - security: - - bearerAuth: [] - parameters: - - in: query - name: enrich - required: false - schema: - type: boolean - description: whether to return enriched data or not. - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/UbahnUser' - '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: Forbidden - 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' /health: get: tags: @@ -2772,13 +2719,10 @@ components: example: 'xxx@xxx.com' ProjectMember: type: object - example: {"id": 14329, "userId": 40159097, "role": "customer", "createdAt": "2021-02-24T12:34:45.074Z", "updatedAt": "2021-02-24T12:34:45.075Z", "createdBy": -101, "updatedBy": -101, "handle": "tester1234", "photoURL": null, "workingHourStart": "9:00", "workingHourEnd": "17:00", "timeZone": "Asia/Kolkata", "email": "xxx@xxx.com"} + example: {"id": 14329, "userId": 40159097, "role": "customer", "createdAt": "2021-02-24T12:34:45.074Z", "updatedAt": "2021-02-24T12:34:45.075Z", "createdBy": -101, "updatedBy": -101, "handle": "tester1234", "photoURL": null, "workingHourStart": "9:00", "workingHourEnd": "17:00", "timeZone": "Asia/Kolkata", "email": "sathya.jayabal@gmail.com"} ProjectMemberInvite: type: object example: {"createdAt": "2021-02-24T11:02:12.673Z", "deletedAt": null, "role": "customer", "updatedBy": -101, "createdBy": -101, "id": 3686, "projectId": 16705, "userId": 23008602, "email": null, "deletedBy": null, "updatedAt": "2021-02-24T11:02:12.674Z", "status": "pending"} - UbahnUser: - type: object - example: {"lastName": "DeLaurentis", "updatedBy": "tcAdmin", "achievements": [{"certifierId": "certifierId", "updatedBy": "tcAdmin", "createdBy": "tc-user", "certifiedDate": "2020-05-04T07:36:28.036Z", "created": "2020-05-13T08:44:27.244Z", "name": "Topcoder", "id": "a49e1013-fd42-4c08-bc12-492510cadb96", "achievementsProviderId": "ce05133f-129e-484d-9ef9-72bf51ff81f9", "uri": "http://www.google.com/xx", "updated": "2021-01-05T10:58:32.429Z", "userId": "0bcb0d86-09bb-410a-b2b1-fba90d1a7699", "achievementprovider": {"updatedBy": "tcAdmin", "createdBy": "tc-user", "created": "2020-05-13T08:42:41.877Z", "name": "achievementsProviders_02", "id": "ce05133f-129e-484d-9ef9-72bf51ff81f9", "updated": "2021-01-05T10:58:32.341Z"}}], "created": "2020-05-05T10:18:03.882Z", "handle": "lazybaer", "skills": [{"certifierId": null, "skillId": "d67f35c3-fa42-4866-a0f9-0a4b84fcf4a9", "updatedBy": "tcAdmin", "createdBy": "lazybaer", "certifiedDate": null, "created": "2020-10-23T16:22:11.208Z", "skill": {"updatedBy": "tcAdmin", "skillprovider": {"updatedBy": "tcAdmin", "createdBy": "TonyJ", "created": "2020-08-31T12:30:00.543Z", "name": "Wipro Digital", "id": "26fb37b1-5f9f-4727-baa9-f3c87de84ab1", "updated": "2021-01-05T10:58:32.836Z"}, "createdBy": "0", "created": "2020-09-01T21:59:21.554Z", "skillProviderId": "26fb37b1-5f9f-4727-baa9-f3c87de84ab1", "name": "GitHub", "externalId": null, "id": "d67f35c3-fa42-4866-a0f9-0a4b84fcf4a9", "uri": null, "updated": "2021-01-05T10:58:33.332Z"}, "metricValue": null, "id": "8a84c1b4-1884-4a3c-90b2-eb86bf469bb6", "updated": "2021-01-05T10:58:34.080Z", "userId": "0bcb0d86-09bb-410a-b2b1-fba90d1a7699"}], "firstName": "Christopher", "externalProfiles": [{"organizationId": "0d2320f9-be61-4ba4-973e-edc3bb682a69", "updatedBy": "tcAdmin", "createdBy": "TonyJ", "isInactive": false, "created": "2020-08-31T12:30:38.495Z", "organization": {"updatedBy": "tcAdmin", "createdBy": "TonyJ", "created": "2020-08-31T12:29:58.081Z", "name": "Wipro Digital", "skillProviders": [{"organizationId": "0d2320f9-be61-4ba4-973e-edc3bb682a69", "updatedBy": "tcAdmin", "createdBy": "TonyJ", "created": "2020-08-31T12:30:08.410Z", "skillProviderId": "26fb37b1-5f9f-4727-baa9-f3c87de84ab1", "id": "5b26cdd3-fe68-4b30-85c5-ceaf280bd688", "updated": "2021-01-05T10:58:32.919Z"}], "id": "0d2320f9-be61-4ba4-973e-edc3bb682a69", "updated": "2021-01-05T10:58:32.261Z"}, "externalId": "8547899", "id": "870af97b-8c3b-4659-92d6-cac126bbe9de", "uri": null, "updated": "2021-01-05T10:58:32.724Z", "userId": "0bcb0d86-09bb-410a-b2b1-fba90d1a7699"}], "createdBy": "tc-Copilot", "attributes": [{"attributeId": "d709276a-80c3-491c-9b29-a4f065b2a56f", "updatedBy": "tcAdmin", "createdBy": "tc-Admin", "created": "2020-05-13T08:19:13.709Z", "id": "21de9324-900d-41ea-b127-f297dfb9a873", "attribute": {"updatedBy": "tcAdmin", "attributegroup": {"organizationId": "36ed815b-3da1-49f1-a043-aaed0a4e81ad", "updatedBy": "tc-Admin", "createdBy": "tc-Admin", "created": "2020-05-13T07:15:01.215Z", "name": "group 03", "id": "84634bbd-8191-40cf-a03e-9962d7e39fda", "updated": "2020-05-13T07:16:20.636Z"}, "createdBy": "tc-Admin", "created": "2020-05-13T07:32:03.128Z", "name": "Billing Account", "id": "d709276a-80c3-491c-9b29-a4f065b2a56f", "attributeGroupId": "84634bbd-8191-40cf-a03e-9962d7e39fda", "updated": "2021-01-05T10:58:32.604Z"}, "value": "74314457", "updated": "2021-01-05T10:58:33.739Z", "userId": "0bcb0d86-09bb-410a-b2b1-fba90d1a7699"}], "id": "0bcb0d86-09bb-410a-b2b1-fba90d1a7699", "updated": "2021-01-05T10:58:32.113Z"} Error: required: - message diff --git a/src/common/helper.js b/src/common/helper.js index 519ddb02..50adc94b 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -598,18 +598,17 @@ function encodeQueryString (queryObj, nesting = '') { } /** - * Function to list users by external id. - * @param {Integer} externalId the legacy user id - * @param {Boolean} enrich whether to return enriched data or not - * @returns {Array} the users found + * Function to get user ids + * @param {Integer} userId user id from jwt token + * @returns {String} user id. */ -async function listUsersByExternalId (externalId, enrich = false) { +async function getUserIds (userId) { const token = await getM2MUbahnToken() const q = { - enrich, + enrich: true, externalProfile: { organizationId: config.ORG_ID, - externalId + externalId: userId } } const url = `${config.TC_API}/users?${encodeQueryString(q)}` @@ -618,22 +617,21 @@ async function listUsersByExternalId (externalId, enrich = false) { .set('Authorization', `Bearer ${token}`) .set('Content-Type', 'application/json') .set('Accept', 'application/json') - localLogger.debug({ context: 'listUserByExternalId', message: `response body: ${JSON.stringify(res.body)}` }) + localLogger.debug({ context: 'getUserIds', message: `response body: ${JSON.stringify(res.body)}` }) return res.body } /** - * Function to get user by external id. - * @param {Integer} externalId the legacy user id - * @param {Boolean} enrich whether to return enriched data or not - * @returns {Object} the user + * Function to get user id + * @param {Integer} userId user id from jwt token + * @returns {String} user id. */ -async function getUserByExternalId (externalId, enrich) { - const users = await listUsersByExternalId(externalId, enrich) - if (_.isEmpty(users)) { - throw new errors.NotFoundError(`externalId: ${externalId} "user" not found`) +async function getUserId (userId) { + const ids = await getUserIds(userId) + if (_.isEmpty(ids)) { + throw new errors.NotFoundError(`userId: ${userId} "user" not found`) } - return users[0] + return ids[0].id } /** @@ -885,7 +883,7 @@ async function getSkillById (skillId) { } /** - * Encapsulate the getUserByExternalId function. + * Encapsulate the getUserId function. * Make sure a user exists in ubahn(/v5/users) and return the id of the user. * * In the case the user does not exist in /v5/users but can be found in /v3/users @@ -896,7 +894,7 @@ async function getSkillById (skillId) { */ async function ensureUbahnUserId (currentUser) { try { - return (await getUserByExternalId(currentUser.userId, true)).id + return await getUserId(currentUser.userId) } catch (err) { if (!(err instanceof errors.NotFoundError)) { throw err @@ -1140,7 +1138,6 @@ module.exports = { } return ensureUbahnUserId({ userId }) }, - getUserByExternalId, getM2MToken, getM2MUbahnToken, postEvent, diff --git a/src/controllers/TeamController.js b/src/controllers/TeamController.js index 138f99f3..6cf1a6b4 100644 --- a/src/controllers/TeamController.js +++ b/src/controllers/TeamController.js @@ -83,15 +83,6 @@ async function deleteMember (req, res) { res.status(HttpStatus.NO_CONTENT).end() } -/** - * Return details about the current user. - * @param req the request - * @param res the response - */ -async function getMe (req, res) { - res.send(await service.getMe(req.authUser, req.query)) -} - module.exports = { searchTeams, getTeam, @@ -100,6 +91,5 @@ module.exports = { addMembers, searchMembers, searchInvites, - deleteMember, - getMe + deleteMember } diff --git a/src/routes/TeamRoutes.js b/src/routes/TeamRoutes.js index f5d062c6..3df14b0c 100644 --- a/src/routes/TeamRoutes.js +++ b/src/routes/TeamRoutes.js @@ -28,14 +28,6 @@ module.exports = { scopes: [constants.Scopes.READ_TAAS_TEAM] } }, - '/taas-teams/me': { - get: { - controller: 'TeamController', - method: 'getMe', - auth: 'jwt', - scopes: [constants.Scopes.READ_TAAS_TEAM] - } - }, '/taas-teams/:id': { get: { controller: 'TeamController', diff --git a/src/services/TeamService.js b/src/services/TeamService.js index cf320ea0..640791a2 100644 --- a/src/services/TeamService.js +++ b/src/services/TeamService.js @@ -513,24 +513,6 @@ deleteMember.schema = Joi.object().keys({ projectMemberId: Joi.number().integer().required() }).required() -/** - * Return details about the current user. - * - * @param {Object} currentUser the user who perform this operation. - * @params {Object} criteria the search criteria - * @returns {Object} the user data for current user - */ -async function getMe (currentUser, criteria) { - return helper.getUserByExternalId(currentUser.userId, criteria.enrich) -} - -getMe.schema = Joi.object().keys({ - currentUser: Joi.object().required(), - criteria: Joi.object().keys({ - enrich: Joi.boolean() - }).required() -}).required() - module.exports = { searchTeams, getTeam, @@ -539,6 +521,5 @@ module.exports = { addMembers, searchMembers, searchInvites, - deleteMember, - getMe + deleteMember } From 302cfea83399b22e71e89c6224f50c5e5d9f376d Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Wed, 3 Mar 2021 13:29:50 +0200 Subject: [PATCH 5/6] chore: typo in docker service name --- README.md | 20 ++++++++++---------- local/docker-compose.yml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index fecd2bd9..dc66b0dd 100644 --- a/README.md +++ b/README.md @@ -61,21 +61,21 @@ - first it would be waiting for `kafka-client` to create all the required topics and exit, you would see: ``` - tc-taas-es-procesor | Waiting for kafka-client to exit.... + tc-taas-es-processor | Waiting for kafka-client to exit.... ``` - after that, `taas-es-processor` would be started itself. Make sure it successfully connected to Kafka, you should see 9 lines with text `Subscribed to taas.`: ``` - tc-taas-es-procesor | 2021-01-22T14:27:48.971Z DEBUG no-kafka-client Subscribed to taas.jobcandidate.create:0 offset 0 leader kafka:9093 - tc-taas-es-procesor | 2021-01-22T14:27:48.972Z DEBUG no-kafka-client Subscribed to taas.job.create:0 offset 0 leader kafka:9093 - tc-taas-es-procesor | 2021-01-22T14:27:48.972Z DEBUG no-kafka-client Subscribed to taas.resourcebooking.delete:0 offset 0 leader kafka:9093 - tc-taas-es-procesor | 2021-01-22T14:27:48.973Z DEBUG no-kafka-client Subscribed to taas.jobcandidate.delete:0 offset 0 leader kafka:9093 - tc-taas-es-procesor | 2021-01-22T14:27:48.974Z DEBUG no-kafka-client Subscribed to taas.jobcandidate.update:0 offset 0 leader kafka:9093 - tc-taas-es-procesor | 2021-01-22T14:27:48.975Z DEBUG no-kafka-client Subscribed to taas.resourcebooking.create:0 offset 0 leader kafka:9093 - tc-taas-es-procesor | 2021-01-22T14:27:48.976Z DEBUG no-kafka-client Subscribed to taas.job.delete:0 offset 0 leader kafka:9093 - tc-taas-es-procesor | 2021-01-22T14:27:48.977Z DEBUG no-kafka-client Subscribed to taas.job.update:0 offset 0 leader kafka:9093 - tc-taas-es-procesor | 2021-01-22T14:27:48.978Z DEBUG no-kafka-client Subscribed to taas.resourcebooking.update:0 offset 0 leader kafka:9093 + tc-taas-es-processor | 2021-01-22T14:27:48.971Z DEBUG no-kafka-client Subscribed to taas.jobcandidate.create:0 offset 0 leader kafka:9093 + tc-taas-es-processor | 2021-01-22T14:27:48.972Z DEBUG no-kafka-client Subscribed to taas.job.create:0 offset 0 leader kafka:9093 + tc-taas-es-processor | 2021-01-22T14:27:48.972Z DEBUG no-kafka-client Subscribed to taas.resourcebooking.delete:0 offset 0 leader kafka:9093 + tc-taas-es-processor | 2021-01-22T14:27:48.973Z DEBUG no-kafka-client Subscribed to taas.jobcandidate.delete:0 offset 0 leader kafka:9093 + tc-taas-es-processor | 2021-01-22T14:27:48.974Z DEBUG no-kafka-client Subscribed to taas.jobcandidate.update:0 offset 0 leader kafka:9093 + tc-taas-es-processor | 2021-01-22T14:27:48.975Z DEBUG no-kafka-client Subscribed to taas.resourcebooking.create:0 offset 0 leader kafka:9093 + tc-taas-es-processor | 2021-01-22T14:27:48.976Z DEBUG no-kafka-client Subscribed to taas.job.delete:0 offset 0 leader kafka:9093 + tc-taas-es-processor | 2021-01-22T14:27:48.977Z DEBUG no-kafka-client Subscribed to taas.job.update:0 offset 0 leader kafka:9093 + tc-taas-es-processor | 2021-01-22T14:27:48.978Z DEBUG no-kafka-client Subscribed to taas.resourcebooking.update:0 offset 0 leader kafka:9093 ``` diff --git a/local/docker-compose.yml b/local/docker-compose.yml index 7c7104b8..3894d100 100644 --- a/local/docker-compose.yml +++ b/local/docker-compose.yml @@ -45,7 +45,7 @@ services: - 9200:9200 taas-es-processor: - container_name: tc-taas-es-procesor + container_name: tc-taas-es-processor build: context: ./generic-tc-service args: From 151c02633a73e568c3529d7e49a1e4e6ac8b9def Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Wed, 3 Mar 2021 13:30:02 +0200 Subject: [PATCH 6/6] docs: added comment --- .../2021-02-27-job-add-is-application-page-active-field.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/migrations/2021-02-27-job-add-is-application-page-active-field.js b/migrations/2021-02-27-job-add-is-application-page-active-field.js index d75c5e9c..84e548a4 100644 --- a/migrations/2021-02-27-job-add-is-application-page-active-field.js +++ b/migrations/2021-02-27-job-add-is-application-page-active-field.js @@ -6,6 +6,8 @@ module.exports = { up: queryInterface => { return Promise.all([ queryInterface.sequelize.query('ALTER TABLE bookings.jobs ADD is_application_page_active BOOLEAN NOT NULL DEFAULT false'), + // this command looks like does nothing, because we already set default value to `null` and this column cannot be `NULL` + // but we keep it as it was tested this way, and it looks harmful queryInterface.sequelize.query('UPDATE bookings.jobs SET is_application_page_active=false WHERE is_application_page_active is NULL'), ]) },