From dc1cfa43216d85b328b0697927e6087d11efa988 Mon Sep 17 00:00:00 2001 From: dengjun Date: Sat, 22 May 2021 16:16:26 +0800 Subject: [PATCH 1/7] Add JobIds to /jobs endpoint:30185451 --- ...coder-bookings-api.postman_collection.json | 109 +++++++++++++++++- docs/swagger.yaml | 65 +++++++++-- src/controllers/JobController.js | 3 + src/services/JobService.js | 23 +++- 4 files changed, 186 insertions(+), 14 deletions(-) diff --git a/docs/Topcoder-bookings-api.postman_collection.json b/docs/Topcoder-bookings-api.postman_collection.json index a6afdc2b..c6d395bb 100644 --- a/docs/Topcoder-bookings-api.postman_collection.json +++ b/docs/Topcoder-bookings-api.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "3f80d93d-6ca3-4645-970d-a9e533394e2e", + "_postman_id": "4b866040-1336-427b-9e20-56d809b87519", "name": "Topcoder-bookings-api", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -491,6 +491,113 @@ }, "response": [] }, + { + "name": "search jobs with request body", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token_bookingManager}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"jobIds\": [\"{{jobId}}\",\"{{jobIdCreatedByM2M}}\"]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/jobs", + "host": [ + "{{URL}}" + ], + "path": [ + "jobs" + ], + "query": [ + { + "key": "page", + "value": "0", + "disabled": true + }, + { + "key": "perPage", + "value": "3", + "disabled": true + }, + { + "key": "sortBy", + "value": "id", + "disabled": true + }, + { + "key": "sortOrder", + "value": "asc", + "disabled": true + }, + { + "key": "projectId", + "value": "21", + "disabled": true + }, + { + "key": "externalId", + "value": "1212", + "disabled": true + }, + { + "key": "description", + "value": "Dummy", + "disabled": true + }, + { + "key": "startDate", + "value": "2020-09-27T04:17:23.131Z", + "disabled": true + }, + { + "key": "resourceType", + "value": "Dummy Resource Type", + "disabled": true + }, + { + "key": "skill", + "value": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "disabled": true + }, + { + "key": "rateType", + "value": "hourly", + "disabled": true + }, + { + "key": "status", + "value": "sourcing", + "disabled": true + }, + { + "key": "workload", + "value": "full-time", + "disabled": true + }, + { + "key": "title", + "value": "dummy", + "disabled": true + } + ] + } + }, + "response": [] + }, { "name": "search jobs with with m2m all", "request": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 0a9fe80d..3a6ce840 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -181,6 +181,11 @@ paths: type: string enum: ["sourcing", "in-review", "assigned", "closed", "cancelled"] description: The rate type. + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/JobSearchBody" responses: "200": description: OK @@ -3317,6 +3322,15 @@ components: type: string example: "topcoder user" description: "The user who updated the job last time.(Will get the user info from the token)" + JobSearchBody: + properties: + jobIds: + type: array + items: + type: string + format: uuid + description: "The array of job ids" + JobRequestBody: required: - projectId @@ -3517,7 +3531,18 @@ components: description: "The user id." status: type: string - enum: ["open", "placed", "selected", "client rejected - screening", "client rejected - interview", "rejected - other", "cancelled", "interview", "topcoder-rejected"] + enum: + [ + "open", + "placed", + "selected", + "client rejected - screening", + "client rejected - interview", + "rejected - other", + "cancelled", + "interview", + "topcoder-rejected", + ] description: "The job candidate status." default: open externalId: @@ -3632,7 +3657,15 @@ components: description: "Interview end time." status: type: string - enum: ["Scheduling", "Scheduled", "Requested for reschedule", "Rescheduled", "Completed", "Cancelled"] + enum: + [ + "Scheduling", + "Scheduled", + "Requested for reschedule", + "Rescheduled", + "Completed", + "Cancelled", + ] description: "The interview status." rescheduleUrl: type: string @@ -3678,7 +3711,15 @@ components: format: email status: type: string - enum: ["Scheduling", "Scheduled", "Requested for reschedule", "Rescheduled", "Completed", "Cancelled"] + enum: + [ + "Scheduling", + "Scheduled", + "Requested for reschedule", + "Rescheduled", + "Completed", + "Cancelled", + ] default: "Scheduling" description: "The interview status." UpdateInterviewRequestBody: @@ -3737,7 +3778,15 @@ components: description: "Interview end time." status: type: string - enum: ["Scheduling", "Scheduled", "Requested for reschedule", "Rescheduled", "Completed", "Cancelled"] + enum: + [ + "Scheduling", + "Scheduled", + "Requested for reschedule", + "Rescheduled", + "Completed", + "Cancelled", + ] description: "The interview status." rescheduleUrl: type: string @@ -3845,7 +3894,7 @@ components: billingAccountId: type: integer example: 80000071 - description: 'the billing account id for payments' + description: "the billing account id for payments" createdAt: type: string format: date-time @@ -3913,7 +3962,7 @@ components: billingAccountId: type: integer example: 80000071 - description: 'the billing account id for payments' + description: "the billing account id for payments" ResourceBookingPatchRequestBody: properties: status: @@ -3946,7 +3995,7 @@ components: billingAccountId: type: integer example: 80000071 - description: 'the billing account id for payments' + description: "the billing account id for payments" WorkPeriod: required: - id @@ -4130,7 +4179,7 @@ components: billingAccountId: type: integer example: 80000071 - description: 'the billing account id for payments' + description: "the billing account id for payments" createdAt: type: string format: date-time diff --git a/src/controllers/JobController.js b/src/controllers/JobController.js index 14f5cfc5..b7cad958 100644 --- a/src/controllers/JobController.js +++ b/src/controllers/JobController.js @@ -57,6 +57,9 @@ async function deleteJob (req, res) { * @param res the response */ async function searchJobs (req, res) { + if (req.body && req.body.jobIds) { + req.query.jobIds = req.body.jobIds + } const result = await service.searchJobs(req.authUser, req.query) helper.setResHeaders(req, res, result) res.send(result.result) diff --git a/src/services/JobService.js b/src/services/JobService.js index 7d855bd0..61685901 100644 --- a/src/services/JobService.js +++ b/src/services/JobService.js @@ -344,7 +344,8 @@ async function searchJobs (currentUser, criteria, options = { returnAll: false } body: { query: { bool: { - must: [] + must: [], + filter: [] } }, from: (page - 1) * perPage, @@ -393,11 +394,19 @@ async function searchJobs (currentUser, criteria, options = { returnAll: false } }) // If criteria contains projectIds, filter projectId with this value if (criteria.projectIds) { - esQuery.body.query.bool.filter = [{ + esQuery.body.query.bool.filter.push({ terms: { projectId: criteria.projectIds } - }] + }) + } + // if criteria contains jobIds, filter jobIds with this value + if (criteria.jobIds && criteria.jobIds.length > 0) { + esQuery.body.query.bool.filter.push({ + terms: { + _id: criteria.jobIds + } + }) } logger.debug({ component: 'JobService', context: 'searchJobs', message: `Query: ${JSON.stringify(esQuery)}` }) @@ -422,7 +431,7 @@ async function searchJobs (currentUser, criteria, options = { returnAll: false } logger.logFullError(err, { component: 'JobService', context: 'searchJobs' }) } logger.info({ component: 'JobService', context: 'searchJobs', message: 'fallback to DB query' }) - const filter = {} + const filter = { [Op.and]: [] } _.each(_.pick(criteria, [ 'projectId', 'externalId', @@ -449,6 +458,9 @@ async function searchJobs (currentUser, criteria, options = { returnAll: false } [Op.contains]: [criteria.skills] } } + if (criteria.jobIds && criteria.jobIds.length > 0) { + filter[Op.and].push({ id: criteria.jobIds }) + } const jobs = await Job.findAll({ where: filter, offset: ((page - 1) * perPage), @@ -486,7 +498,8 @@ searchJobs.schema = Joi.object().keys({ rateType: Joi.rateType(), workload: Joi.workload(), status: Joi.jobStatus(), - projectIds: Joi.array().items(Joi.number().integer()).single() + projectIds: Joi.array().items(Joi.number().integer()).single(), + jobIds: Joi.array().items(Joi.string().uuid()) }).required(), options: Joi.object() }).required() From 8b55765ea32bf94debec6a9e27cc4e6f223058dd Mon Sep 17 00:00:00 2001 From: eisbilir Date: Sat, 22 May 2021 21:03:45 +0300 Subject: [PATCH 2/7] fix: checking if member of project --- src/services/WorkPeriodService.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/services/WorkPeriodService.js b/src/services/WorkPeriodService.js index 19346ff3..fc750bd7 100644 --- a/src/services/WorkPeriodService.js +++ b/src/services/WorkPeriodService.js @@ -177,8 +177,9 @@ async function getWorkPeriod (currentUser, id, fromDb = false) { if (!resourceBooking.body.hits.total.value) { throw new errors.NotFoundError() } - await _checkUserPermissionForGetWorkPeriod(currentUser, resourceBooking.body.hits.hits[0]._source.workPeriods.projectId) // check user permission - return _.find(resourceBooking.body.hits.hits[0]._source.workPeriods, { id }) + const workPeriod = _.find(resourceBooking.body.hits.hits[0]._source.workPeriods, { id }) + await _checkUserPermissionForGetWorkPeriod(currentUser, workPeriod.projectId) // check user permission + return workPeriod } catch (err) { if (helper.isDocumentMissingException(err)) { throw new errors.NotFoundError(`id: ${id} "WorkPeriod" not found`) From b16a6fc68707155c2c923336689ddcb7621dd96a Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Thu, 27 May 2021 12:14:58 +0300 Subject: [PATCH 3/7] chore: use v5/members instead of v3/members --- config/default.js | 2 +- src/common/helper.js | 32 +++++++++++++------------------- src/services/PaymentService.js | 2 +- 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/config/default.js b/config/default.js index 2b5ca7ba..1a3090bf 100644 --- a/config/default.js +++ b/config/default.js @@ -40,7 +40,7 @@ module.exports = { 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', + TOPCODER_MEMBERS_API: process.env.TOPCODER_MEMBERS_API || 'https://api.topcoder-dev.com/v5/members', // rate limit of requests to user api MAX_PARALLEL_REQUEST_TOPCODER_USERS_API: process.env.MAX_PARALLEL_REQUEST_TOPCODER_USERS_API || 100, diff --git a/src/common/helper.js b/src/common/helper.js index 0ce11905..e68e5c58 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -1304,13 +1304,10 @@ async function getMemberDetailsByHandles(handles) { } const token = await getM2MToken(); const res = await request - .get(`${config.TOPCODER_MEMBERS_API}/_search`) + .get(`${config.TOPCODER_MEMBERS_API}/`) .query({ - query: _.map( - handles, - (handle) => `handleLower:${handle.toLowerCase()}` - ).join(' OR '), - fields: 'userId,handle,firstName,lastName,email', + 'handlesLower[]': handles.map(handle => handle.toLowerCase()), + fields: 'userId,handle,handleLower,firstName,lastName,email', }) .set('Authorization', `Bearer ${token}`) .set('Accept', 'application/json'); @@ -1318,7 +1315,7 @@ async function getMemberDetailsByHandles(handles) { context: 'getMemberDetailsByHandles', message: `response body: ${JSON.stringify(res.body)}`, }); - return _.get(res.body, 'result.content'); + return res.body; } /** @@ -1327,17 +1324,14 @@ async function getMemberDetailsByHandles(handles) { * @param {String} handle the user handle * @returns {Object} the member details */ -async function getV3MemberDetailsByHandle(handle) { - const token = await getM2MToken(); - const res = await request - .get(`${config.TOPCODER_MEMBERS_API}/${handle}`) - .set('Authorization', `Bearer ${token}`) - .set('Accept', 'application/json'); - localLogger.debug({ - context: 'getV3MemberDetailsByHandle', - message: `response body: ${JSON.stringify(res.body)}`, - }); - return _.get(res.body, 'result.content'); +async function getMemberDetailsByHandle(handle) { + const [memberDetails] = await getMemberDetailsByHandles([handle]) + + if (!memberDetails) { + throw new errors.NotFoundError(`Member details are not found by handle "${handle}".`) + } + + return memberDetails } /** @@ -1756,7 +1750,7 @@ module.exports = { getAuditM2Muser, checkIsMemberOfProject, getMemberDetailsByHandles, - getV3MemberDetailsByHandle, + getMemberDetailsByHandle, getMemberDetailsByEmails, createProjectMember, listProjectMembers, diff --git a/src/services/PaymentService.js b/src/services/PaymentService.js index d06ad671..e61a6c34 100644 --- a/src/services/PaymentService.js +++ b/src/services/PaymentService.js @@ -153,7 +153,7 @@ async function activateChallenge (id, token) { async function closeChallenge (id, userHandle, token) { localLogger.info({ context: 'closeChallenge', message: `Closing challenge ${id}` }) try { - const { userId } = await helper.getV3MemberDetailsByHandle(userHandle) + const { userId } = await helper.getMemberDetailsByHandle(userHandle) const body = { status: constants.ChallengeStatus.COMPLETED, winners: [{ From 3ede43e70d7f0111950e3e3f5c8faf0e98f1b18b Mon Sep 17 00:00:00 2001 From: Sushil Shinde Date: Thu, 27 May 2021 15:49:58 +0530 Subject: [PATCH 4/7] fix: addded new 'offred' status --- src/bootstrap.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bootstrap.js b/src/bootstrap.js index 2999f131..8bfa6df2 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -16,7 +16,7 @@ Joi.rateType = () => Joi.string().valid('hourly', 'daily', 'weekly', 'monthly') Joi.jobStatus = () => Joi.string().valid('sourcing', 'in-review', 'assigned', 'closed', 'cancelled') Joi.resourceBookingStatus = () => Joi.string().valid('placed', 'closed', 'cancelled') Joi.workload = () => Joi.string().valid('full-time', 'fractional') -Joi.jobCandidateStatus = () => Joi.string().valid('open', 'placed', 'selected', 'client rejected - screening', 'client rejected - interview', 'rejected - other', 'cancelled', 'interview', 'topcoder-rejected', 'applied','rejected-pre-screen','skills-test','skills-test','phone-screen','job-closed') +Joi.jobCandidateStatus = () => Joi.string().valid('open', 'placed', 'selected', 'client rejected - screening', 'client rejected - interview', 'rejected - other', 'cancelled', 'interview', 'topcoder-rejected', 'applied','rejected-pre-screen','skills-test','skills-test','phone-screen','job-closed','offered') Joi.title = () => Joi.string().max(128) Joi.paymentStatus = () => Joi.string().valid('pending', 'partially-completed', 'completed', 'cancelled') Joi.xaiTemplate = () => Joi.string().valid(...allowedXAITemplate) From 93495aa0871333e8bd8ed4d698d8b634770f7e1b Mon Sep 17 00:00:00 2001 From: Sushil Shinde Date: Thu, 27 May 2021 18:36:42 +0530 Subject: [PATCH 5/7] Updated Swagger for jobCandidate statuses --- docs/swagger.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index a0b6064b..3bd72c09 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -603,6 +603,13 @@ paths: "cancelled", "interview", "topcoder-rejected", + "applied", + "rejected-pre-screen", + "skills-test", + "skills-test", + "phone-screen", + "job-closed", + "offered" ] description: The job candidate status. - in: query From 8446c87052640e2db0f7c57bf80aab131a393639 Mon Sep 17 00:00:00 2001 From: Sushil Shinde Date: Thu, 27 May 2021 19:07:47 +0530 Subject: [PATCH 6/7] Update swagger.yaml --- docs/swagger.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 3bd72c09..d209a3fb 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -606,7 +606,6 @@ paths: "applied", "rejected-pre-screen", "skills-test", - "skills-test", "phone-screen", "job-closed", "offered" From 0304e683e2df5037ec0695c9faaf4b6d7fff5aa4 Mon Sep 17 00:00:00 2001 From: Cagdas U Date: Sat, 29 May 2021 13:04:56 +0300 Subject: [PATCH 7/7] feat(job-service): accept `roles` array * Add `roles` in Job schema. * Accept `roles` in the Job related endpoints. * Fix demo-data (was blocking `npm run local:init` script). --- data/demo-data.json | 12 ++++++------ src/common/helper.js | 1 + src/models/Job.js | 3 +++ src/services/JobService.js | 17 +++++++++++++---- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/data/demo-data.json b/data/demo-data.json index e0733443..8c5e8509 100644 --- a/data/demo-data.json +++ b/data/demo-data.json @@ -184,7 +184,7 @@ "jobCandidateId": "881a19de-2b0c-4bb9-b36a-4cb5e223bdb5", "googleCalendarId": null, "customMessage": null, - "xaiTemplate": "interview-30", + "templateUrl": "interview-30", "round": 1, "startTimestamp": null, "attendeesList": null, @@ -213,7 +213,7 @@ "jobCandidateId": "827ee401-df04-42e1-abbe-7b97ce7937ff", "googleCalendarId": "dummyId", "customMessage": "This is a custom message", - "xaiTemplate": "interview-30", + "templateUrl": "interview-30", "round": 2, "startTimestamp": null, "attendeesList": null, @@ -228,7 +228,7 @@ "jobCandidateId": "827ee401-df04-42e1-abbe-7b97ce7937ff", "googleCalendarId": null, "customMessage": null, - "xaiTemplate": "interview-30", + "templateUrl": "interview-30", "round": 1, "startTimestamp": null, "attendeesList": null, @@ -257,7 +257,7 @@ "jobCandidateId": "a4ea7bcf-5b99-4381-b99c-a9bd05d83a36", "googleCalendarId": "dummyId", "customMessage": "This is a custom message", - "xaiTemplate": "interview-30", + "templateUrl": "interview-30", "round": 3, "startTimestamp": null, "attendeesList": [ @@ -275,7 +275,7 @@ "jobCandidateId": "a4ea7bcf-5b99-4381-b99c-a9bd05d83a36", "googleCalendarId": "dummyId", "customMessage": "This is a custom message", - "xaiTemplate": "interview-30", + "templateUrl": "interview-30", "round": 2, "startTimestamp": null, "attendeesList": [ @@ -293,7 +293,7 @@ "jobCandidateId": "a4ea7bcf-5b99-4381-b99c-a9bd05d83a36", "googleCalendarId": null, "customMessage": null, - "xaiTemplate": "interview-30", + "templateUrl": "interview-30", "round": 1, "startTimestamp": null, "attendeesList": null, diff --git a/src/common/helper.js b/src/common/helper.js index e68e5c58..6333457e 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -111,6 +111,7 @@ esIndexPropertyMapping[config.get('esConfig.ES_INDEX_JOB')] = { rateType: { type: 'keyword' }, workload: { type: 'keyword' }, skills: { type: 'keyword' }, + roles: { type: 'keyword' }, status: { type: 'keyword' }, isApplicationPageActive: { type: 'boolean' }, createdAt: { type: 'date' }, diff --git a/src/models/Job.js b/src/models/Job.js index 49d34ff7..62305000 100644 --- a/src/models/Job.js +++ b/src/models/Job.js @@ -94,6 +94,9 @@ module.exports = (sequelize) => { type: Sequelize.JSONB, allowNull: false }, + roles: { + type: Sequelize.ARRAY(Sequelize.UUID) + }, status: { type: Sequelize.STRING(255), allowNull: false diff --git a/src/services/JobService.js b/src/services/JobService.js index 61685901..482d0c34 100644 --- a/src/services/JobService.js +++ b/src/services/JobService.js @@ -177,6 +177,7 @@ createJob.schema = Joi.object().keys({ rateType: Joi.rateType().allow(null), workload: Joi.workload().allow(null), skills: Joi.array().items(Joi.string().uuid()).required(), + roles: Joi.array().items(Joi.string().uuid()).allow(null), isApplicationPageActive: Joi.boolean() }).required() }).required() @@ -245,6 +246,7 @@ partiallyUpdateJob.schema = Joi.object().keys({ rateType: Joi.rateType().allow(null), workload: Joi.workload().allow(null), skills: Joi.array().items(Joi.string().uuid()), + roles: Joi.array().items(Joi.string().uuid()).allow(null), isApplicationPageActive: Joi.boolean() }).required() }).required() @@ -361,6 +363,7 @@ async function searchJobs (currentUser, criteria, options = { returnAll: false } 'startDate', 'resourceType', 'skill', + 'role', 'rateType', 'workload', 'title', @@ -375,10 +378,10 @@ async function searchJobs (currentUser, criteria, options = { returnAll: false } } } } - } else if (key === 'skill') { + } else if (key === 'skill' || key === 'role') { must = { terms: { - skills: [value] + [`${key}s`]: [value] } } } else { @@ -453,9 +456,14 @@ async function searchJobs (currentUser, criteria, options = { returnAll: false } [Op.like]: `%${criteria.title}%` } } - if (criteria.skills) { + if (criteria.skill) { filter.skills = { - [Op.contains]: [criteria.skills] + [Op.contains]: [criteria.skill] + } + } + if (criteria.role) { + filter.roles = { + [Op.contains]: [criteria.role] } } if (criteria.jobIds && criteria.jobIds.length > 0) { @@ -495,6 +503,7 @@ searchJobs.schema = Joi.object().keys({ startDate: Joi.date(), resourceType: Joi.string(), skill: Joi.string().uuid(), + role: Joi.string().uuid(), rateType: Joi.rateType(), workload: Joi.workload(), status: Joi.jobStatus(),