From 4d5a0b269272ade414973e398b29cb9065eb0712 Mon Sep 17 00:00:00 2001 From: imcaizheng Date: Tue, 22 Dec 2020 16:21:21 +0800 Subject: [PATCH 1/6] implement local event handling --- app-routes.js | 3 +- app.js | 2 + src/common/eventDispatcher.js | 33 ++++++ src/common/helper.js | 14 ++- src/eventHandlers/JobEventHandler.js | 83 +++++++++++++ .../ResourceBookingEventHandler.js | 111 ++++++++++++++++++ src/eventHandlers/index.js | 52 ++++++++ src/services/ResourceBookingService.js | 33 +----- 8 files changed, 299 insertions(+), 32 deletions(-) create mode 100644 src/common/eventDispatcher.js create mode 100644 src/eventHandlers/JobEventHandler.js create mode 100644 src/eventHandlers/ResourceBookingEventHandler.js create mode 100644 src/eventHandlers/index.js diff --git a/app-routes.js b/app-routes.js index 023dda0a..8711071e 100644 --- a/app-routes.js +++ b/app-routes.js @@ -43,8 +43,7 @@ module.exports = (app) => { if (!req.authUser.scopes || !helper.checkIfExists(def.scopes, req.authUser.scopes)) { next(new errors.ForbiddenError('You are not allowed to perform this action!')) } else { - req.authUser.userId = config.m2m.M2M_AUDIT_USER_ID - req.authUser.handle = config.m2m.M2M_AUDIT_HANDLE + req.authUser = helper.authUserAsM2M() next() } } else { diff --git a/app.js b/app.js index f60a0565..56521d9a 100644 --- a/app.js +++ b/app.js @@ -10,6 +10,7 @@ const cors = require('cors') const HttpStatus = require('http-status-codes') const interceptor = require('express-interceptor') const logger = require('./src/common/logger') +const eventHandlers = require('./src/eventHandlers') // setup express app const app = express() @@ -91,6 +92,7 @@ app.use((err, req, res, next) => { const server = app.listen(app.get('port'), () => { logger.info({ component: 'app', message: `Express server listening on port ${app.get('port')}` }) + eventHandlers.init() }) if (process.env.NODE_ENV === 'test') { diff --git a/src/common/eventDispatcher.js b/src/common/eventDispatcher.js new file mode 100644 index 00000000..495b8788 --- /dev/null +++ b/src/common/eventDispatcher.js @@ -0,0 +1,33 @@ +/* + * Implement an event dispatcher that handles events synchronously. + */ + +const handlers = [] + +/** + * Handle event. + * + * @param {String} topic the topic name + * @param {Object} payload the message payload + * @returns {undefined} + */ +async function handleEvent (topic, payload) { + for (const handler of handlers) { + await handler.handleEvent(topic, payload) + } +} + +/** + * Register to the dispatcher. + * + * @param {Object} handler the handler containing the `handleEvent` function + * @returns {undefined} + */ +function register (handler) { + handlers.push(handler) +} + +module.exports = { + handleEvent, + register +} diff --git a/src/common/helper.js b/src/common/helper.js index 715d9981..9a098153 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -12,6 +12,7 @@ const elasticsearch = require('@elastic/elasticsearch') const errors = require('../common/errors') const logger = require('./logger') const models = require('../models') +const eventDispatcher = require('./eventDispatcher') const busApi = require('@topcoder-platform/topcoder-bus-api-wrapper') const localLogger = { @@ -313,6 +314,7 @@ async function postEvent (topic, payload) { payload } await client.postEvent(message) + await eventDispatcher.handleEvent(topic, payload) } /** @@ -589,6 +591,15 @@ async function ensureUserById (userId) { } } +/** + * Generate M2M auth user. + * + * @returns {Object} the M2M auth user + */ +function authUserAsM2M () { + return { isMachine: true, userId: config.m2m.M2M_AUDIT_USER_ID, handle: config.m2m.M2M_AUDIT_HANDLE } +} + module.exports = { checkIfExists, autoWrapExpress, @@ -615,5 +626,6 @@ module.exports = { getSkillById, getUserSkill, ensureJobById, - ensureUserById + ensureUserById, + authUserAsM2M } diff --git a/src/eventHandlers/JobEventHandler.js b/src/eventHandlers/JobEventHandler.js new file mode 100644 index 00000000..7e190e5e --- /dev/null +++ b/src/eventHandlers/JobEventHandler.js @@ -0,0 +1,83 @@ +/* + * Handle events for Job. + */ + +const { Op } = require('sequelize') +const models = require('../models') +const logger = require('../common/logger') +const helper = require('../common/helper') +const JobCandidateService = require('../services/JobCandidateService') +const ResourceBookingService = require('../services/ResourceBookingService') + +/** + * Cancel all related resource bookings and reject all related candidates when a job is cancelled. + * + * @param {Object} payload the event payload + * @returns {undefined} + */ +async function cancelJob (payload) { + if (payload.status !== 'cancelled') { + logger.info({ + component: 'JobEventHandler', + context: 'cancelJob', + message: `not interested job - status: ${payload.status}` + }) + return + } + const candidates = await models.JobCandidate.findAll({ + where: { + jobId: payload.id, + status: { + [Op.not]: 'rejected' + }, + deletedAt: null + } + }) + const resourceBookings = await models.ResourceBooking.findAll({ + where: { + projectId: payload.projectId, + status: { + [Op.not]: 'cancelled' + }, + deletedAt: null + } + }) + await Promise.all([ + ...candidates.map(candidate => JobCandidateService.partiallyUpdateJobCandidate( + helper.authUserAsM2M(), + candidate.id, + { status: 'rejected' } + ).then(result => { + logger.info({ + component: 'JobEventHandler', + context: 'cancelJob', + message: `id: ${result.id} candidate got rejected.` + }) + })), + ...resourceBookings.map(resource => ResourceBookingService.partiallyUpdateResourceBooking( + helper.authUserAsM2M(), + resource.id, + { status: 'cancelled' } + ).then(result => { + logger.info({ + component: 'JobEventHandler', + context: 'cancelJob', + message: `id: ${result.id} resource booking got cancelled.` + }) + })) + ]) +} + +/** + * Process job update event. + * + * @param {Object} payload the event payload + * @returns {undefined} + */ +async function processUpdate (payload) { + await cancelJob(payload) +} + +module.exports = { + processUpdate +} diff --git a/src/eventHandlers/ResourceBookingEventHandler.js b/src/eventHandlers/ResourceBookingEventHandler.js new file mode 100644 index 00000000..23f105f1 --- /dev/null +++ b/src/eventHandlers/ResourceBookingEventHandler.js @@ -0,0 +1,111 @@ +/* + * Handle events for ResourceBooking. + */ + +const { Op } = require('sequelize') +const models = require('../models') +const logger = require('../common/logger') +const helper = require('../common/helper') +const JobService = require('../services/JobService') +const JobCandidateService = require('../services/JobCandidateService') + +/** + * When ResourceBooking's status is changed to `assigned` + * the corresponding JobCandidate record (with the same userId and jobId) + * should be updated with status `selected` + * + * @param {Object} payload the event payload + * @returns {undefined} + */ +async function selectJobCandidate (payload) { + if (payload.status !== 'assigned') { + logger.info({ + component: 'ResourceBookingEventHandler', + context: 'selectJobCandidate', + message: `not interested resource booking - status: ${payload.status}` + }) + return + } + const candidates = await models.JobCandidate.findAll({ + where: { + jobId: payload.jobId, + userId: payload.userId, + status: { + [Op.not]: 'selected' + }, + deletedAt: null + } + }) + await Promise.all(candidates.map(candidate => JobCandidateService.partiallyUpdateJobCandidate( + helper.authUserAsM2M(), + candidate.id, + { status: 'selected' } + ).then(result => { + logger.info({ + component: 'ResourceBookingEventHandler', + context: 'selectJobCandidate', + message: `id: ${result.id} candidate got selected.` + }) + }))) +} + +/** + * Update the status of the Job to assigned when it positions requirement is fullfilled. + * + * @param {Object} payload the event payload + * @returns {undefined} + */ +async function assignJob (payload) { + if (payload.status !== 'assigned') { + logger.info({ + component: 'ResourceBookingEventHandler', + context: 'assignJob', + message: `not interested resource booking - status: ${payload.status}` + }) + return + } + const job = await models.Job.findOne({ + where: { + projectId: payload.projectId, + deletedAt: null + } + }) + if (job.status === 'assigned') { + logger.info({ + component: 'ResourceBookingEventHandler', + context: 'assignJob', + message: `job with projectId ${payload.projectId} is already assigned` + }) + return + } + const resourceBookings = await models.ResourceBooking.findAll({ + where: { + status: 'assigned', + deletedAt: null + } + }) + logger.debug({ + component: 'ResourceBookingEventHandler', + context: 'assignJob', + message: `the number of assigned resource bookings is ${resourceBookings.length} - the numPositions of the job is ${job.numPositions}` + }) + if (job.numPositions === resourceBookings.length) { + await JobService.partiallyUpdateJob(helper.authUserAsM2M(), job.id, { status: 'assigned' }) + logger.info({ component: 'ResourceBookingEventHandler', context: 'assignJob', message: `job with projectId ${payload.projectId} is assigned` }) + } +} + +/** + * Process resource booking update event. + * + * @param {Object} payload the event payload + * @returns {undefined} + */ +async function processUpdate (payload) { + await selectJobCandidate(payload) + await assignJob(payload) +} + +module.exports = { + processUpdate +} diff --git a/src/eventHandlers/index.js b/src/eventHandlers/index.js new file mode 100644 index 00000000..e9835d03 --- /dev/null +++ b/src/eventHandlers/index.js @@ -0,0 +1,52 @@ +/* + * The entry of event handlers. + */ + +const config = require('config') +const eventDispatcher = require('../common/eventDispatcher') +const JobEventHandler = require('./JobEventHandler') +const ResourceBookingEventHandler = require('./ResourceBookingEventHandler') +const logger = require('../common/logger') + +const TopicOperationMapping = { + [config.TAAS_JOB_UPDATE_TOPIC]: JobEventHandler.processUpdate, + [config.TAAS_RESOURCE_BOOKING_UPDATE_TOPIC]: ResourceBookingEventHandler.processUpdate +} + +/** + * Handle event. + * + * @param {String} topic the topic name + * @param {Object} payload the message payload + * @returns {undefined} + */ +async function handleEvent (topic, payload) { + if (!TopicOperationMapping[topic]) { + logger.info({ component: 'eventHanders', context: 'handleEvent', message: `not interested event - topic: ${topic}` }) + return + } + logger.debug({ component: 'eventHanders', context: 'handleEvent', message: `handling event - topic: ${topic} - payload: ${JSON.stringify(payload)}` }) + try { + await TopicOperationMapping[topic](payload) + } catch (err) { + logger.error({ component: 'eventHanders', context: 'handleEvent', message: 'failed to handle event' }) + // throw error so that it can be handled by the app + throw err + } + logger.info({ component: 'eventHanders', context: 'handleEvent', message: 'event successfully handled' }) +} + +/** + * Attach the handlers to the event dispatcher. + * + * @returns {undefined} + */ +function init () { + eventDispatcher.register({ + handleEvent + }) +} + +module.exports = { + init +} diff --git a/src/services/ResourceBookingService.js b/src/services/ResourceBookingService.js index 6cc739aa..accf7bd2 100644 --- a/src/services/ResourceBookingService.js +++ b/src/services/ResourceBookingService.js @@ -11,7 +11,6 @@ const helper = require('../common/helper') const logger = require('../common/logger') const errors = require('../common/errors') const models = require('../models') -const JobCandidateService = require('./JobCandidateService') const ResourceBooking = models.ResourceBooking const esClient = helper.getESClient() @@ -118,6 +117,7 @@ createResourceBooking.schema = Joi.object().keys({ async function updateResourceBooking (currentUser, id, data) { const resourceBooking = await ResourceBooking.findById(id) const isDiffStatus = resourceBooking.status !== data.status + if (!currentUser.isBookingManager && !currentUser.isMachine) { const connect = await helper.isConnectMember(resourceBooking.dataValues.projectId, currentUser.jwtToken) if (!connect) { @@ -127,34 +127,9 @@ async function updateResourceBooking (currentUser, id, data) { data.updatedAt = new Date() data.updatedBy = await helper.getUserId(currentUser.userId) - const updatedResourceBooking = await resourceBooking.update(data) - await helper.postEvent(config.TAAS_RESOURCE_BOOKING_UPDATE_TOPIC, { id, ...data }) - // When we are updating the status of ResourceBooking to `assigned` - // the corresponding JobCandidate record (with the same userId and jobId) - // should be updated with the status `selected` - if (isDiffStatus && data.status === 'assigned') { - const candidates = await models.JobCandidate.findAll({ - where: { - jobId: updatedResourceBooking.jobId, - userId: updatedResourceBooking.userId, - status: { - [Op.not]: 'selected' - }, - deletedAt: null - } - }) - await Promise.all(candidates.map(candidate => JobCandidateService.partiallyUpdateJobCandidate( - currentUser, - candidate.id, - { status: 'selected' } - ).then(result => { - logger.debug({ - component: 'ResourceBookingService', - context: 'updatedResourceBooking', - message: `id: ${result.id} candidate got selected.` - }) - }))) - } + await resourceBooking.update(data) + const eventPayload = { id, ...(isDiffStatus ? data : _.omit(data, ['status'])) } // send data with status only if status is changed + await helper.postEvent(config.TAAS_RESOURCE_BOOKING_UPDATE_TOPIC, eventPayload) const result = helper.clearObject(_.assign(resourceBooking.dataValues, data)) return result } From d96200fcb6b6af4bd2c2cc00acf9f6e88ab97d39 Mon Sep 17 00:00:00 2001 From: imcaizheng Date: Wed, 23 Dec 2020 06:58:05 +0800 Subject: [PATCH 2/6] `cancelled` jobcandidate instead of `reject` --- src/eventHandlers/JobEventHandler.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/eventHandlers/JobEventHandler.js b/src/eventHandlers/JobEventHandler.js index 7e190e5e..d8125038 100644 --- a/src/eventHandlers/JobEventHandler.js +++ b/src/eventHandlers/JobEventHandler.js @@ -10,7 +10,7 @@ const JobCandidateService = require('../services/JobCandidateService') const ResourceBookingService = require('../services/ResourceBookingService') /** - * Cancel all related resource bookings and reject all related candidates when a job is cancelled. + * Cancel all related resource bookings and all related candidates when a job is cancelled. * * @param {Object} payload the event payload * @returns {undefined} @@ -28,7 +28,7 @@ async function cancelJob (payload) { where: { jobId: payload.id, status: { - [Op.not]: 'rejected' + [Op.not]: 'cancelled' }, deletedAt: null } @@ -46,12 +46,12 @@ async function cancelJob (payload) { ...candidates.map(candidate => JobCandidateService.partiallyUpdateJobCandidate( helper.authUserAsM2M(), candidate.id, - { status: 'rejected' } + { status: 'cancelled' } ).then(result => { logger.info({ component: 'JobEventHandler', context: 'cancelJob', - message: `id: ${result.id} candidate got rejected.` + message: `id: ${result.id} candidate got cancelled.` }) })), ...resourceBookings.map(resource => ResourceBookingService.partiallyUpdateResourceBooking( From 98105a2b6246c264276a64c9e8924ef5ac8af9e2 Mon Sep 17 00:00:00 2001 From: imcaizheng Date: Wed, 23 Dec 2020 07:03:02 +0800 Subject: [PATCH 3/6] update swagger - add `cancelled` status value to JobCandidate --- docs/swagger.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index e5d52f0a..ea2b5b88 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -575,8 +575,8 @@ paths: required: false schema: type: string - enum: ['open', 'selected', 'shortlist', 'rejected'] - description: The user id. + enum: ['open', 'selected', 'shortlist', 'rejected', 'cancelled'] + description: The job candidate status. responses: '200': description: OK @@ -1686,8 +1686,8 @@ components: description: "The user id." status: type: string - enum: ['open', 'selected', 'shortlist', 'rejected'] - description: "The job status." + enum: ['open', 'selected', 'shortlist', 'rejected', 'cancelled'] + description: "The job candidate status." createdAt: type: string format: date-time @@ -1722,7 +1722,7 @@ components: properties: status: type: string - enum: ['open', 'selected', 'shortlist', 'rejected'] + enum: ['open', 'selected', 'shortlist', 'rejected', 'cancelled'] JobPatchRequestBody: properties: status: @@ -2130,8 +2130,8 @@ components: description: 'The link for the resume that can be downloaded' status: type: string - enum: ['open', 'selected', 'shortlist', 'rejected'] - description: "The job status." + enum: ['open', 'selected', 'shortlist', 'rejected', 'cancelled'] + description: "The job candidate status." skills: type: array items: From f7f1808ebd840107814f48e659d88444139248cf Mon Sep 17 00:00:00 2001 From: imcaizheng Date: Sat, 26 Dec 2020 16:04:05 +0800 Subject: [PATCH 4/6] pull data from db instead of directly extract data from event payload --- src/bootstrap.js | 2 +- src/eventHandlers/JobEventHandler.js | 7 ++- .../ResourceBookingEventHandler.js | 58 +++++++++---------- 3 files changed, 32 insertions(+), 35 deletions(-) diff --git a/src/bootstrap.js b/src/bootstrap.js index a35212e0..c051b045 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -8,7 +8,7 @@ Joi.perPage = () => Joi.number().integer().min(1).default(20) Joi.rateType = () => Joi.string().valid('hourly', 'daily', 'weekly', 'monthly') Joi.jobStatus = () => Joi.string().valid('sourcing', 'in-review', 'assigned', 'closed', 'cancelled') Joi.workload = () => Joi.string().valid('full-time', 'fractional') -Joi.jobCandidateStatus = () => Joi.string().valid('open', 'selected', 'shortlist', 'rejected') +Joi.jobCandidateStatus = () => Joi.string().valid('open', 'selected', 'shortlist', 'rejected', 'cancelled') function buildServices (dir) { const files = fs.readdirSync(dir) diff --git a/src/eventHandlers/JobEventHandler.js b/src/eventHandlers/JobEventHandler.js index d8125038..249f8dd2 100644 --- a/src/eventHandlers/JobEventHandler.js +++ b/src/eventHandlers/JobEventHandler.js @@ -24,9 +24,12 @@ async function cancelJob (payload) { }) return } + // pull data from db instead of directly extract data from the payload + // since the payload may not contain all fields when it is from partically update operation. + const job = await models.Job.findById(payload.id) const candidates = await models.JobCandidate.findAll({ where: { - jobId: payload.id, + jobId: job.id, status: { [Op.not]: 'cancelled' }, @@ -35,7 +38,7 @@ async function cancelJob (payload) { }) const resourceBookings = await models.ResourceBooking.findAll({ where: { - projectId: payload.projectId, + projectId: job.projectId, status: { [Op.not]: 'cancelled' }, diff --git a/src/eventHandlers/ResourceBookingEventHandler.js b/src/eventHandlers/ResourceBookingEventHandler.js index 23f105f1..6d5e47a5 100644 --- a/src/eventHandlers/ResourceBookingEventHandler.js +++ b/src/eventHandlers/ResourceBookingEventHandler.js @@ -14,22 +14,15 @@ const JobCandidateService = require('../services/JobCandidateService') * the corresponding JobCandidate record (with the same userId and jobId) * should be updated with status `selected` * - * @param {Object} payload the event payload + * @param {String} jobId the job id + * @param {String} userId the user id * @returns {undefined} */ -async function selectJobCandidate (payload) { - if (payload.status !== 'assigned') { - logger.info({ - component: 'ResourceBookingEventHandler', - context: 'selectJobCandidate', - message: `not interested resource booking - status: ${payload.status}` - }) - return - } +async function selectJobCandidate (jobId, userId) { const candidates = await models.JobCandidate.findAll({ where: { - jobId: payload.jobId, - userId: payload.userId, + jobId, + userId, status: { [Op.not]: 'selected' }, @@ -52,29 +45,15 @@ async function selectJobCandidate (payload) { /** * Update the status of the Job to assigned when it positions requirement is fullfilled. * - * @param {Object} payload the event payload + * @param {Object} job the job data * @returns {undefined} */ -async function assignJob (payload) { - if (payload.status !== 'assigned') { - logger.info({ - component: 'ResourceBookingEventHandler', - context: 'assignJob', - message: `not interested resource booking - status: ${payload.status}` - }) - return - } - const job = await models.Job.findOne({ - where: { - projectId: payload.projectId, - deletedAt: null - } - }) +async function assignJob (job) { if (job.status === 'assigned') { logger.info({ component: 'ResourceBookingEventHandler', context: 'assignJob', - message: `job with projectId ${payload.projectId} is already assigned` + message: `job with projectId ${job.projectId} is already assigned` }) return } @@ -91,7 +70,7 @@ async function assignJob (payload) { }) if (job.numPositions === resourceBookings.length) { await JobService.partiallyUpdateJob(helper.authUserAsM2M(), job.id, { status: 'assigned' }) - logger.info({ component: 'ResourceBookingEventHandler', context: 'assignJob', message: `job with projectId ${payload.projectId} is assigned` }) + logger.info({ component: 'ResourceBookingEventHandler', context: 'assignJob', message: `job with projectId ${job.projectId} is assigned` }) } } @@ -102,8 +81,23 @@ async function assignJob (payload) { * @returns {undefined} */ async function processUpdate (payload) { - await selectJobCandidate(payload) - await assignJob(payload) + if (payload.status !== 'assigned') { + logger.info({ + component: 'ResourceBookingEventHandler', + context: 'selectJobCandidate', + message: `not interested resource booking - status: ${payload.status}` + }) + return + } + const resourceBooking = await models.ResourceBooking.findById(payload.id) + const job = await models.Job.findOne({ + where: { + projectId: resourceBooking.projectId, + deletedAt: null + } + }) + await selectJobCandidate(job.id, resourceBooking.userId) + await assignJob(job) } module.exports = { From 40b1f93b79f60b65fd8d092879ab1056b9270588 Mon Sep 17 00:00:00 2001 From: imcaizheng Date: Sat, 26 Dec 2020 16:27:37 +0800 Subject: [PATCH 5/6] revert the change made to app-routes.js --- app-routes.js | 3 ++- src/common/helper.js | 4 ++-- src/eventHandlers/JobEventHandler.js | 4 ++-- src/eventHandlers/ResourceBookingEventHandler.js | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app-routes.js b/app-routes.js index 8711071e..023dda0a 100644 --- a/app-routes.js +++ b/app-routes.js @@ -43,7 +43,8 @@ module.exports = (app) => { if (!req.authUser.scopes || !helper.checkIfExists(def.scopes, req.authUser.scopes)) { next(new errors.ForbiddenError('You are not allowed to perform this action!')) } else { - req.authUser = helper.authUserAsM2M() + req.authUser.userId = config.m2m.M2M_AUDIT_USER_ID + req.authUser.handle = config.m2m.M2M_AUDIT_HANDLE next() } } else { diff --git a/src/common/helper.js b/src/common/helper.js index 9a098153..1eb6776b 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -596,7 +596,7 @@ async function ensureUserById (userId) { * * @returns {Object} the M2M auth user */ -function authUserAsM2M () { +function getAuditM2Muser () { return { isMachine: true, userId: config.m2m.M2M_AUDIT_USER_ID, handle: config.m2m.M2M_AUDIT_HANDLE } } @@ -627,5 +627,5 @@ module.exports = { getUserSkill, ensureJobById, ensureUserById, - authUserAsM2M + getAuditM2Muser } diff --git a/src/eventHandlers/JobEventHandler.js b/src/eventHandlers/JobEventHandler.js index 249f8dd2..715f58a5 100644 --- a/src/eventHandlers/JobEventHandler.js +++ b/src/eventHandlers/JobEventHandler.js @@ -47,7 +47,7 @@ async function cancelJob (payload) { }) await Promise.all([ ...candidates.map(candidate => JobCandidateService.partiallyUpdateJobCandidate( - helper.authUserAsM2M(), + helper.getAuditM2Muser(), candidate.id, { status: 'cancelled' } ).then(result => { @@ -58,7 +58,7 @@ async function cancelJob (payload) { }) })), ...resourceBookings.map(resource => ResourceBookingService.partiallyUpdateResourceBooking( - helper.authUserAsM2M(), + helper.getAuditM2Muser(), resource.id, { status: 'cancelled' } ).then(result => { diff --git a/src/eventHandlers/ResourceBookingEventHandler.js b/src/eventHandlers/ResourceBookingEventHandler.js index 6d5e47a5..12d96573 100644 --- a/src/eventHandlers/ResourceBookingEventHandler.js +++ b/src/eventHandlers/ResourceBookingEventHandler.js @@ -30,7 +30,7 @@ async function selectJobCandidate (jobId, userId) { } }) await Promise.all(candidates.map(candidate => JobCandidateService.partiallyUpdateJobCandidate( - helper.authUserAsM2M(), + helper.getAuditM2Muser(), candidate.id, { status: 'selected' } ).then(result => { @@ -69,7 +69,7 @@ async function assignJob (job) { message: `the number of assigned resource bookings is ${resourceBookings.length} - the numPositions of the job is ${job.numPositions}` }) if (job.numPositions === resourceBookings.length) { - await JobService.partiallyUpdateJob(helper.authUserAsM2M(), job.id, { status: 'assigned' }) + await JobService.partiallyUpdateJob(helper.getAuditM2Muser(), job.id, { status: 'assigned' }) logger.info({ component: 'ResourceBookingEventHandler', context: 'assignJob', message: `job with projectId ${job.projectId} is assigned` }) } } From 4adca39ddffa0c5b3e1035cfcb76d4db4a77719f Mon Sep 17 00:00:00 2001 From: imcaizheng Date: Sat, 26 Dec 2020 18:54:09 +0800 Subject: [PATCH 6/6] Revert "Merge branch 'dev' into implement-event-handlers" This reverts commit fdf0571f5f55d0628e33fc8d07c9deac022442a9, reversing changes made to 40b1f93b79f60b65fd8d092879ab1056b9270588. --- README.md | 1 - app-constants.js | 10 +- app-routes.js | 8 +- config/default.js | 1 - ...coder-bookings-api.postman_collection.json | 2614 ----------------- docs/swagger.yaml | 126 - ...topcoder-bookings.postman_environment.json | 17 +- src/common/helper.js | 62 +- src/controllers/JobCandidateController.js | 4 +- src/controllers/JobController.js | 4 +- src/controllers/ResourceBookingController.js | 2 +- src/controllers/SkillController.js | 20 - src/models/JobCandidate.js | 9 + src/routes/TeamRoutes.js | 8 - src/services/JobCandidateService.js | 66 +- src/services/JobService.js | 75 +- src/services/ResourceBookingService.js | 65 +- src/services/SkillService.js | 26 - src/services/TeamService.js | 23 +- 19 files changed, 90 insertions(+), 3051 deletions(-) delete mode 100644 src/controllers/SkillController.js delete mode 100644 src/services/SkillService.js diff --git a/README.md b/README.md index 14625a85..f94f0802 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,6 @@ The following parameters can be set in config files or in env variables: - `PROJECT_API_URL`: the project service url - `TC_API`: the Topcoder v5 url - `ORG_ID`: the organization id -- `TOPCODER_SKILL_PROVIDER_ID`: the referenced skill provider id - `esConfig.HOST`: the elasticsearch host - `esConfig.ES_INDEX_JOB`: the job index diff --git a/app-constants.js b/app-constants.js index e9747f85..bf1a43b7 100644 --- a/app-constants.js +++ b/app-constants.js @@ -3,16 +3,9 @@ */ const UserRoles = { - BookingManager: 'bookingmanager', - Administrator: 'administrator', - ConnectManager: 'Connect Manager' + BookingManager: 'bookingmanager' } -const FullManagePermissionRoles = [ - UserRoles.BookingManager, - UserRoles.Administrator -] - const Scopes = { // job READ_JOB: 'read:taas-jobs', @@ -38,6 +31,5 @@ const Scopes = { module.exports = { UserRoles, - FullManagePermissionRoles, Scopes } diff --git a/app-routes.js b/app-routes.js index d3987ba2..023dda0a 100644 --- a/app-routes.js +++ b/app-routes.js @@ -49,12 +49,8 @@ module.exports = (app) => { } } else { req.authUser.jwtToken = req.headers.authorization - // check if user has full manage permission - if (_.intersection(req.authUser.roles, constants.FullManagePermissionRoles).length) { - req.authUser.hasManagePermission = true - } - if (_.includes(req.authUser.roles, constants.UserRoles.ConnectManager)) { - req.authUser.isConnectManager = true + if (_.includes(req.authUser.roles, constants.UserRoles.BookingManager)) { + req.authUser.isBookingManager = true } next() } diff --git a/config/default.js b/config/default.js index 4aafc86e..cccdf99c 100644 --- a/config/default.js +++ b/config/default.js @@ -21,7 +21,6 @@ module.exports = { TC_API: process.env.TC_API || 'https://api.topcoder-dev.com/v5', ORG_ID: process.env.ORG_ID || '36ed815b-3da1-49f1-a043-aaed0a4e81ad', - 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', diff --git a/docs/Topcoder-bookings-api.postman_collection.json b/docs/Topcoder-bookings-api.postman_collection.json index bf61a0b2..67804f49 100644 --- a/docs/Topcoder-bookings-api.postman_collection.json +++ b/docs/Topcoder-bookings-api.postman_collection.json @@ -4366,45 +4366,6 @@ } }, "response": [] - }, - { - "name": "GET /taas-teams/skills", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{token_member}}", - "type": "text" - } - ], - "url": { - "raw": "{{URL}}/taas-teams/skills?perPage=10&page=1&orderBy=name", - "host": [ - "{{URL}}" - ], - "path": [ - "taas-teams", - "skills" - ], - "query": [ - { - "key": "perPage", - "value": "10" - }, - { - "key": "page", - "value": "1" - }, - { - "key": "orderBy", - "value": "name", - "description": "possible values are defined by /v5/skills" - } - ] - } - }, - "response": [] } ], "protocolProfileBehavior": {} @@ -7122,2581 +7083,6 @@ } ], "protocolProfileBehavior": {} - }, - { - "name": "Test Permission Rules", - "item": [ - { - "name": "Request with Administrator Role", - "item": [ - { - "name": "Jobs", - "item": [ - { - "name": "✔ create job with administrator", - "event": [ - { - "listen": "test", - "script": { - "id": "30445900-0393-477d-900a-c3b5f43006ed", - "exec": [ - "var data = JSON.parse(responseBody);\r", - "postman.setEnvironmentVariable(\"job_id_created_by_administrator\",data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{token_administrator}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"projectId\": {{projectId}},\r\n \"externalId\": \"1212\",\r\n \"description\": \"Dummy Description\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"numPositions\": 13,\r\n \"resourceType\": \"Dummy Resource Type\",\r\n \"rateType\": \"hourly\",\r\n \"workload\": \"full-time\",\r\n \"skills\": [\r\n \"23e00d92-207a-4b5b-b3c9-4c5662644941\",\r\n \"7d076384-ccf6-4e43-a45d-1b24b1e624aa\",\r\n \"cbac57a3-7180-4316-8769-73af64893158\",\r\n \"a2b4bc11-c641-4a19-9eb7-33980378f82e\"\r\n ]\r\n}\r\n", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobs", - "host": [ - "{{URL}}" - ], - "path": [ - "jobs" - ] - } - }, - "response": [] - }, - { - "name": "✔ get job with administrator", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_administrator}}" - } - ], - "url": { - "raw": "{{URL}}/jobs/{{job_id_created_by_administrator}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobs", - "{{job_id_created_by_administrator}}" - ] - } - }, - "response": [] - }, - { - "name": "✔ search jobs with administrator", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_administrator}}" - } - ], - "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": "endDate", - "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 - } - ] - } - }, - "response": [] - }, - { - "name": "✔ put job with administrator", - "request": { - "method": "PUT", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_administrator}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"projectId\": {{projectId}},\r\n \"externalId\": \"1212\",\r\n \"description\": \"Dummy Description\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"numPositions\": 13,\r\n \"resourceType\": \"Dummy Resource Type\",\r\n \"rateType\": \"hourly\",\r\n \"workload\": \"fractional\",\r\n \"skills\": [\r\n \"cbac57a3-7180-4316-8769-73af64893158\",\r\n \"a2b4bc11-c641-4a19-9eb7-33980378f82e\"\r\n ],\r\n \"status\": \"sourcing\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobs/{{job_id_created_by_administrator}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobs", - "{{job_id_created_by_administrator}}" - ] - } - }, - "response": [] - }, - { - "name": "✔ patch job with administrator", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_administrator}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"description\": \"Dummy Description\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"numPositions\": 13,\r\n \"resourceType\": \"Dummy Resource Type\",\r\n \"rateType\": \"hourly\",\r\n \"workload\": \"fractional\",\r\n \"skills\": [\r\n \"cbac57a3-7180-4316-8769-73af64893158\"\r\n ],\r\n \"status\": \"sourcing\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobs/{{job_id_created_by_administrator}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobs", - "{{job_id_created_by_administrator}}" - ] - } - }, - "response": [] - }, - { - "name": "✔ delete job with administrator", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_administrator}}" - } - ], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobs/{{job_id_created_by_administrator}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobs", - "{{job_id_created_by_administrator}}" - ] - } - }, - "response": [] - } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true - }, - { - "name": "Job Candidates", - "item": [ - { - "name": "✔ create job candidate with administrator", - "event": [ - { - "listen": "test", - "script": { - "id": "802acbec-5c9d-4fa5-925f-76ebd2cc3476", - "exec": [ - "var data = JSON.parse(responseBody);\r", - "postman.setEnvironmentVariable(\"job_candidate_id_created_by_administrator\",data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_administrator}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{job_id_created_by_administrator}}\",\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobCandidates", - "host": [ - "{{URL}}" - ], - "path": [ - "jobCandidates" - ] - } - }, - "response": [] - }, - { - "name": "✔ get job candidate with administrator", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_administrator}}" - } - ], - "url": { - "raw": "{{URL}}/jobCandidates/{{job_candidate_id_created_by_administrator}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobCandidates", - "{{job_candidate_id_created_by_administrator}}" - ] - } - }, - "response": [] - }, - { - "name": "✔ search job candidates with administrator", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_administrator}}" - } - ], - "url": { - "raw": "{{URL}}/jobCandidates", - "host": [ - "{{URL}}" - ], - "path": [ - "jobCandidates" - ], - "query": [ - { - "key": "page", - "value": "1", - "disabled": true - }, - { - "key": "perPage", - "value": "1", - "disabled": true - }, - { - "key": "sortBy", - "value": "id", - "disabled": true - }, - { - "key": "sortOrder", - "value": "asc", - "disabled": true - }, - { - "key": "jobId", - "value": "46225f4c-c2a3-4603-a141-0277e96fabfa", - "disabled": true - }, - { - "key": "userId", - "value": "a55fe1bc-1754-45fa-9adc-cf3d6d7c377a", - "disabled": true - }, - { - "key": "status", - "value": "shortlist", - "disabled": true - } - ] - } - }, - "response": [] - }, - { - "name": "✔ put job candidate with administrator", - "request": { - "method": "PUT", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_administrator}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{job_id_created_by_administrator}}\",\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"status\": \"selected\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobCandidates/{{job_candidate_id_created_by_administrator}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobCandidates", - "{{job_candidate_id_created_by_administrator}}" - ] - } - }, - "response": [] - }, - { - "name": "✔ patch job candidate with administrator", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_administrator}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"status\": \"shortlist\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobCandidates/{{job_candidate_id_created_by_administrator}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobCandidates", - "{{job_candidate_id_created_by_administrator}}" - ] - } - }, - "response": [] - }, - { - "name": "✔ delete job candidate with administrator", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_administrator}}" - } - ], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobCandidates/{{job_candidate_id_created_by_administrator}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobCandidates", - "{{job_candidate_id_created_by_administrator}}" - ] - } - }, - "response": [] - } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true - }, - { - "name": "Resource Bookings", - "item": [ - { - "name": "✔ create resource booking with administrator", - "event": [ - { - "listen": "test", - "script": { - "id": "75f5abd2-9751-4d24-9252-e0cd70f6157b", - "exec": [ - "var data = JSON.parse(responseBody);\r", - "postman.setEnvironmentVariable(\"resource_bookings_id_created_by_administrator\",data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_administrator}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"projectId\": {{projectId}},\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"jobId\": \"{{job_id_created_by_administrator}}\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/resourceBookings", - "host": [ - "{{URL}}" - ], - "path": [ - "resourceBookings" - ] - } - }, - "response": [] - }, - { - "name": "✔ get resource booking with administrator", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_administrator}}" - } - ], - "url": { - "raw": "{{URL}}/resourceBookings/{{resource_bookings_id_created_by_administrator}}", - "host": [ - "{{URL}}" - ], - "path": [ - "resourceBookings", - "{{resource_bookings_id_created_by_administrator}}" - ] - } - }, - "response": [] - }, - { - "name": "✔ search resource bookings with administrator", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_administrator}}" - } - ], - "url": { - "raw": "{{URL}}/resourceBookings", - "host": [ - "{{URL}}" - ], - "path": [ - "resourceBookings" - ], - "query": [ - { - "key": "page", - "value": "1", - "disabled": true - }, - { - "key": "perPage", - "value": "5", - "disabled": true - }, - { - "key": "sortBy", - "value": "id", - "disabled": true - }, - { - "key": "sortOrder", - "value": "desc", - "disabled": true - }, - { - "key": "startDate", - "value": "2020-09-27T04:17:23.131Z", - "disabled": true - }, - { - "key": "endDate", - "value": "2020-09-27T04:17:23.131Z", - "disabled": true - }, - { - "key": "rateType", - "value": "hourly", - "disabled": true - }, - { - "key": "status", - "value": "sourcing", - "disabled": true - }, - { - "key": "projectIds", - "value": "111, 16705", - "disabled": true - } - ] - } - }, - "response": [] - }, - { - "name": "✔ put resource booking with administrator", - "request": { - "method": "PUT", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_administrator}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"projectId\": {{projectId}},\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"jobId\": \"{{job_id_created_by_administrator}}\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"assigned\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/resourceBookings/{{resource_bookings_id_created_by_administrator}}", - "host": [ - "{{URL}}" - ], - "path": [ - "resourceBookings", - "{{resource_bookings_id_created_by_administrator}}" - ] - } - }, - "response": [] - }, - { - "name": "✔ patch resource booking with administrator", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_administrator}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"status\": \"assigned\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/resourceBookings/{{resource_bookings_id_created_by_administrator}}", - "host": [ - "{{URL}}" - ], - "path": [ - "resourceBookings", - "{{resource_bookings_id_created_by_administrator}}" - ] - } - }, - "response": [] - }, - { - "name": "✔ delete resource booking with administrator", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_administrator}}" - } - ], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/resourceBookings/{{resource_bookings_id_created_by_administrator}}", - "host": [ - "{{URL}}" - ], - "path": [ - "resourceBookings", - "{{resource_bookings_id_created_by_administrator}}" - ] - } - }, - "response": [] - } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true - } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true - }, - { - "name": "Request with Topcoder User Role", - "item": [ - { - "name": "Before Test", - "item": [ - { - "name": "[STUB] refresh the jwt token for user tester1234", - "request": { - "method": "LOCK", - "header": [], - "url": { - "raw": "" - } - }, - "response": [] - } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true - }, - { - "name": "Jobs", - "item": [ - { - "name": "✔ create job with member", - "event": [ - { - "listen": "test", - "script": { - "id": "368f7f55-b18f-4f62-ad6c-a162fdbed90d", - "exec": [ - "var data = JSON.parse(responseBody);\r", - "postman.setEnvironmentVariable(\"job_id_created_by_member\",data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{token_member_tester1234}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"projectId\": {{project_id_16718}},\r\n \"externalId\": \"1212\",\r\n \"description\": \"Dummy Description\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"numPositions\": 13,\r\n \"resourceType\": \"Dummy Resource Type\",\r\n \"rateType\": \"hourly\",\r\n \"workload\": \"full-time\",\r\n \"skills\": [\r\n \"23e00d92-207a-4b5b-b3c9-4c5662644941\",\r\n \"7d076384-ccf6-4e43-a45d-1b24b1e624aa\",\r\n \"cbac57a3-7180-4316-8769-73af64893158\",\r\n \"a2b4bc11-c641-4a19-9eb7-33980378f82e\"\r\n ]\r\n}\r\n", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobs", - "host": [ - "{{URL}}" - ], - "path": [ - "jobs" - ] - } - }, - "response": [] - }, - { - "name": "✔ get job with member", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_member_tester1234}}" - } - ], - "url": { - "raw": "{{URL}}/jobs/{{job_id_created_by_member}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobs", - "{{job_id_created_by_member}}" - ] - } - }, - "response": [] - }, - { - "name": "✔ search jobs with member filtering by \"projectId\"", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_member_tester1234}}" - } - ], - "url": { - "raw": "{{URL}}/jobs?projectId={{project_id_16718}}", - "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": "{{project_id_16718}}" - }, - { - "key": "externalId", - "value": "1212", - "disabled": true - }, - { - "key": "description", - "value": "Dummy", - "disabled": true - }, - { - "key": "startDate", - "value": "2020-09-27T04:17:23.131Z", - "disabled": true - }, - { - "key": "endDate", - "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 - } - ] - } - }, - "response": [] - }, - { - "name": "✔ put job with member", - "request": { - "method": "PUT", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_member_tester1234}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"projectId\": {{project_id_16718}},\r\n \"externalId\": \"1212\",\r\n \"description\": \"Dummy Description\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"numPositions\": 13,\r\n \"resourceType\": \"Dummy Resource Type\",\r\n \"rateType\": \"hourly\",\r\n \"workload\": \"fractional\",\r\n \"skills\": [\r\n \"cbac57a3-7180-4316-8769-73af64893158\",\r\n \"a2b4bc11-c641-4a19-9eb7-33980378f82e\"\r\n ],\r\n \"status\": \"sourcing\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobs/{{job_id_created_by_member}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobs", - "{{job_id_created_by_member}}" - ] - } - }, - "response": [] - }, - { - "name": "✔ patch job with member", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_member_tester1234}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"description\": \"Dummy Description\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"numPositions\": 13,\r\n \"resourceType\": \"Dummy Resource Type\",\r\n \"rateType\": \"hourly\",\r\n \"workload\": \"fractional\",\r\n \"skills\": [\r\n \"cbac57a3-7180-4316-8769-73af64893158\"\r\n ],\r\n \"status\": \"sourcing\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobs/{{job_id_created_by_member}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobs", - "{{job_id_created_by_member}}" - ] - } - }, - "response": [] - }, - { - "name": "✘ delete job with member", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_member_tester1234}}" - } - ], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobs/{{job_id_created_by_member}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobs", - "{{job_id_created_by_member}}" - ] - } - }, - "response": [] - } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true - }, - { - "name": "Job Candidates", - "item": [ - { - "name": "Before Test", - "item": [ - { - "name": "create job candidate", - "event": [ - { - "listen": "test", - "script": { - "id": "6f84340d-c8dd-49f9-bee9-660b3e555296", - "exec": [ - "var data = JSON.parse(responseBody);\r", - "postman.setEnvironmentVariable(\"job_candidate_id_created_for_member\",data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_administrator}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{job_id_created_by_member}}\",\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobCandidates", - "host": [ - "{{URL}}" - ], - "path": [ - "jobCandidates" - ] - } - }, - "response": [] - } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true - }, - { - "name": "✘ create job candidate with member", - "event": [ - { - "listen": "test", - "script": { - "id": "f25317af-4933-4c93-b02b-cae7feddac50", - "exec": [ - "var data = JSON.parse(responseBody);\r", - "postman.setEnvironmentVariable(\"job_candidate_id_created_for_member\",data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_member_tester1234}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{job_id_created_by_member}}\",\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobCandidates", - "host": [ - "{{URL}}" - ], - "path": [ - "jobCandidates" - ] - } - }, - "response": [] - }, - { - "name": "✔ get job candidate with member", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_member_tester1234}}" - } - ], - "url": { - "raw": "{{URL}}/jobCandidates/{{job_candidate_id_created_for_member}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobCandidates", - "{{job_candidate_id_created_for_member}}" - ] - } - }, - "response": [] - }, - { - "name": "✔ search job candidates with member filtering by \"jobId\"", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_member_tester1234}}" - } - ], - "url": { - "raw": "{{URL}}/jobCandidates?jobId={{job_id_created_by_member}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobCandidates" - ], - "query": [ - { - "key": "page", - "value": "1", - "disabled": true - }, - { - "key": "perPage", - "value": "1", - "disabled": true - }, - { - "key": "sortBy", - "value": "id", - "disabled": true - }, - { - "key": "sortOrder", - "value": "asc", - "disabled": true - }, - { - "key": "jobId", - "value": "{{job_id_created_by_member}}" - }, - { - "key": "userId", - "value": "fe38eed1-af73-41fd-85a2-ac4da1ff09a3", - "disabled": true - }, - { - "key": "status", - "value": "shortlist", - "disabled": true - } - ] - } - }, - "response": [] - }, - { - "name": "✔ put job candidate with member", - "request": { - "method": "PUT", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_member_tester1234}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{job_id_created_by_member}}\",\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"status\": \"selected\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobCandidates/{{job_candidate_id_created_for_member}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobCandidates", - "{{job_candidate_id_created_for_member}}" - ] - } - }, - "response": [] - }, - { - "name": "✔ patch job candidate with member", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_member_tester1234}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"status\": \"shortlist\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobCandidates/{{job_candidate_id_created_for_member}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobCandidates", - "{{job_candidate_id_created_for_member}}" - ] - } - }, - "response": [] - }, - { - "name": "✘ delete job candidate with member", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_member_tester1234}}" - } - ], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobCandidates/{{job_candidate_id_created_for_member}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobCandidates", - "{{job_candidate_id_created_for_member}}" - ] - } - }, - "response": [] - } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true - }, - { - "name": "Resource Bookings", - "item": [ - { - "name": "Before Test", - "item": [ - { - "name": "create resource booking", - "event": [ - { - "listen": "test", - "script": { - "id": "dbf53485-021e-49d5-b877-4b8d970a0331", - "exec": [ - "var data = JSON.parse(responseBody);\r", - "postman.setEnvironmentVariable(\"resource_booking_id_created_for_member\",data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_administrator}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"projectId\": {{project_id_16718}},\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"jobId\": \"{{job_id_created_by_member}}\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/resourceBookings", - "host": [ - "{{URL}}" - ], - "path": [ - "resourceBookings" - ] - } - }, - "response": [] - } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true - }, - { - "name": "✘ create resource booking with member", - "event": [ - { - "listen": "test", - "script": { - "id": "9754578e-91dd-437d-b5a2-cdb5668e14e4", - "exec": [ - "var data = JSON.parse(responseBody);\r", - "postman.setEnvironmentVariable(\"resource_booking_id_created_for_member\",data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_member_tester1234}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"projectId\": {{project_id_16718}},\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"jobId\": \"{{job_id_created_by_member}}\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/resourceBookings", - "host": [ - "{{URL}}" - ], - "path": [ - "resourceBookings" - ] - } - }, - "response": [] - }, - { - "name": "✔ get resource booking with member", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_member_tester1234}}" - } - ], - "url": { - "raw": "{{URL}}/resourceBookings/{{resource_booking_id_created_for_member}}", - "host": [ - "{{URL}}" - ], - "path": [ - "resourceBookings", - "{{resource_booking_id_created_for_member}}" - ] - } - }, - "response": [] - }, - { - "name": "✔ search resource bookings with member filtering by \"projectId\"", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_member_tester1234}}" - } - ], - "url": { - "raw": "{{URL}}/resourceBookings?projectId={{project_id_16718}}", - "host": [ - "{{URL}}" - ], - "path": [ - "resourceBookings" - ], - "query": [ - { - "key": "page", - "value": "1", - "disabled": true - }, - { - "key": "perPage", - "value": "5", - "disabled": true - }, - { - "key": "sortBy", - "value": "id", - "disabled": true - }, - { - "key": "sortOrder", - "value": "desc", - "disabled": true - }, - { - "key": "startDate", - "value": "2020-09-27T04:17:23.131Z", - "disabled": true - }, - { - "key": "endDate", - "value": "2020-09-27T04:17:23.131Z", - "disabled": true - }, - { - "key": "rateType", - "value": "hourly", - "disabled": true - }, - { - "key": "status", - "value": "sourcing", - "disabled": true - }, - { - "key": "projectIds", - "value": "111, 16705", - "disabled": true - }, - { - "key": "projectId", - "value": "{{project_id_16718}}" - } - ] - } - }, - "response": [] - }, - { - "name": "✘ put resource booking with member", - "request": { - "method": "PUT", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_member_tester1234}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"projectId\": {{project_id_16718}},\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"jobId\": \"{{job_id_created_by_member}}\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"assigned\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/resourceBookings/{{resource_booking_id_created_for_member}}", - "host": [ - "{{URL}}" - ], - "path": [ - "resourceBookings", - "{{resource_booking_id_created_for_member}}" - ] - } - }, - "response": [] - }, - { - "name": "✘ patch resource booking with member", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_member_tester1234}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"status\": \"assigned\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/resourceBookings/{{resource_booking_id_created_for_member}}", - "host": [ - "{{URL}}" - ], - "path": [ - "resourceBookings", - "{{resource_booking_id_created_for_member}}" - ] - } - }, - "response": [] - }, - { - "name": "✘ delete resource booking with member", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_member_tester1234}}" - } - ], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/resourceBookings/{{resource_booking_id_created_for_member}}", - "host": [ - "{{URL}}" - ], - "path": [ - "resourceBookings", - "{{resource_booking_id_created_for_member}}" - ] - } - }, - "response": [] - } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true - } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true - }, - { - "name": "Request with Connect Manager Role", - "item": [ - { - "name": "Before Test", - "item": [ - { - "name": "[STUB] refresh the jwt token for connect manager", - "request": { - "method": "LOCK", - "header": [], - "url": { - "raw": "" - } - }, - "response": [] - } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true - }, - { - "name": "Jobs", - "item": [ - { - "name": "Before Test", - "item": [ - { - "name": "create job", - "event": [ - { - "listen": "test", - "script": { - "id": "faaf5dc1-9869-4615-992c-3cace41f65e8", - "exec": [ - "var data = JSON.parse(responseBody);\r", - "postman.setEnvironmentVariable(\"job_id_created_for_connect_manager\",data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{token_administrator}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"projectId\": {{project_id_16718}},\r\n \"externalId\": \"1212\",\r\n \"description\": \"Dummy Description\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"numPositions\": 13,\r\n \"resourceType\": \"Dummy Resource Type\",\r\n \"rateType\": \"hourly\",\r\n \"workload\": \"full-time\",\r\n \"skills\": [\r\n \"23e00d92-207a-4b5b-b3c9-4c5662644941\",\r\n \"7d076384-ccf6-4e43-a45d-1b24b1e624aa\",\r\n \"cbac57a3-7180-4316-8769-73af64893158\",\r\n \"a2b4bc11-c641-4a19-9eb7-33980378f82e\"\r\n ]\r\n}\r\n", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobs", - "host": [ - "{{URL}}" - ], - "path": [ - "jobs" - ] - } - }, - "response": [] - } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true - }, - { - "name": "✘ create job with connect manager", - "event": [ - { - "listen": "test", - "script": { - "id": "ab2fa9b2-71fc-4cda-b72d-60cf2d99525d", - "exec": [ - "var data = JSON.parse(responseBody);\r", - "postman.setEnvironmentVariable(\"job_id_created_for_connect_manager\",data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{token_connectUser}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"projectId\": {{project_id_16718}},\r\n \"externalId\": \"1212\",\r\n \"description\": \"Dummy Description\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"numPositions\": 13,\r\n \"resourceType\": \"Dummy Resource Type\",\r\n \"rateType\": \"hourly\",\r\n \"workload\": \"full-time\",\r\n \"skills\": [\r\n \"23e00d92-207a-4b5b-b3c9-4c5662644941\",\r\n \"7d076384-ccf6-4e43-a45d-1b24b1e624aa\",\r\n \"cbac57a3-7180-4316-8769-73af64893158\",\r\n \"a2b4bc11-c641-4a19-9eb7-33980378f82e\"\r\n ]\r\n}\r\n", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobs", - "host": [ - "{{URL}}" - ], - "path": [ - "jobs" - ] - } - }, - "response": [] - }, - { - "name": "✔ get job with connect manager", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_connectUser}}" - } - ], - "url": { - "raw": "{{URL}}/jobs/{{job_id_created_for_connect_manager}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobs", - "{{job_id_created_for_connect_manager}}" - ] - } - }, - "response": [] - }, - { - "name": "✔ search jobs with connect manager", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_connectUser}}" - } - ], - "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": "endDate", - "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 - } - ] - } - }, - "response": [] - }, - { - "name": "✘ put job with connect manager", - "request": { - "method": "PUT", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_connectUser}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"projectId\": {{project_id_16718}},\r\n \"externalId\": \"1212\",\r\n \"description\": \"Dummy Description\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"numPositions\": 13,\r\n \"resourceType\": \"Dummy Resource Type\",\r\n \"rateType\": \"hourly\",\r\n \"workload\": \"fractional\",\r\n \"skills\": [\r\n \"cbac57a3-7180-4316-8769-73af64893158\",\r\n \"a2b4bc11-c641-4a19-9eb7-33980378f82e\"\r\n ],\r\n \"status\": \"sourcing\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobs/{{job_id_created_for_connect_manager}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobs", - "{{job_id_created_for_connect_manager}}" - ] - } - }, - "response": [] - }, - { - "name": "✘ patch job with connect manager", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_connectUser}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"description\": \"Dummy Description\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"numPositions\": 13,\r\n \"resourceType\": \"Dummy Resource Type\",\r\n \"rateType\": \"hourly\",\r\n \"workload\": \"fractional\",\r\n \"skills\": [\r\n \"cbac57a3-7180-4316-8769-73af64893158\"\r\n ],\r\n \"status\": \"sourcing\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobs/{{job_id_created_for_connect_manager}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobs", - "{{job_id_created_for_connect_manager}}" - ] - } - }, - "response": [] - }, - { - "name": "✘ delete job with connect manager", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_connectUser}}" - } - ], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobs/{{job_id_created_for_connect_manager}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobs", - "{{job_id_created_for_connect_manager}}" - ] - } - }, - "response": [] - } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true - }, - { - "name": "Job Candidates", - "item": [ - { - "name": "Before Test", - "item": [ - { - "name": "create job candidate", - "event": [ - { - "listen": "test", - "script": { - "id": "5ce6dd7a-39aa-4910-aba1-37f02559d293", - "exec": [ - "var data = JSON.parse(responseBody);\r", - "postman.setEnvironmentVariable(\"job_candidate_id_created_for_connect_manager\",data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_administrator}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{job_id_created_by_member}}\",\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobCandidates", - "host": [ - "{{URL}}" - ], - "path": [ - "jobCandidates" - ] - } - }, - "response": [] - } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true - }, - { - "name": "✘ create job candidate with connect manager", - "event": [ - { - "listen": "test", - "script": { - "id": "74e63fe5-8d71-4791-a722-5d7347e28f83", - "exec": [ - "var data = JSON.parse(responseBody);\r", - "postman.setEnvironmentVariable(\"job_candidate_id_created_for_connect_manager\",data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_connectUser}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{job_id_created_for_connect_manager}}\",\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobCandidates", - "host": [ - "{{URL}}" - ], - "path": [ - "jobCandidates" - ] - } - }, - "response": [] - }, - { - "name": "✔ get job candidate with connect manager", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_connectUser}}" - } - ], - "url": { - "raw": "{{URL}}/jobCandidates/{{job_candidate_id_created_for_connect_manager}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobCandidates", - "{{job_candidate_id_created_for_connect_manager}}" - ] - } - }, - "response": [] - }, - { - "name": "✔ search job candidates with connect manager", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_connectUser}}" - } - ], - "url": { - "raw": "{{URL}}/jobCandidates", - "host": [ - "{{URL}}" - ], - "path": [ - "jobCandidates" - ], - "query": [ - { - "key": "page", - "value": "1", - "disabled": true - }, - { - "key": "perPage", - "value": "1", - "disabled": true - }, - { - "key": "sortBy", - "value": "id", - "disabled": true - }, - { - "key": "sortOrder", - "value": "asc", - "disabled": true - }, - { - "key": "jobId", - "value": "46225f4c-c2a3-4603-a141-0277e96fabfa", - "disabled": true - }, - { - "key": "userId", - "value": "fe38eed1-af73-41fd-85a2-ac4da1ff09a3", - "disabled": true - }, - { - "key": "status", - "value": "shortlist", - "disabled": true - } - ] - } - }, - "response": [] - }, - { - "name": "✘ put job candidate with connect manager", - "request": { - "method": "PUT", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_connectUser}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{job_id_created_for_connect_manager}}\",\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"status\": \"selected\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobCandidates/{{job_candidate_id_created_for_connect_manager}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobCandidates", - "{{job_candidate_id_created_for_connect_manager}}" - ] - } - }, - "response": [] - }, - { - "name": "✘ patch job candidate with connect manager", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_connectUser}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"status\": \"shortlist\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobCandidates/{{job_candidate_id_created_for_connect_manager}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobCandidates", - "{{job_candidate_id_created_for_connect_manager}}" - ] - } - }, - "response": [] - }, - { - "name": "✘ delete job candidate with connect manager", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_connectUser}}" - } - ], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobCandidates/{{job_candidate_id_created_for_connect_manager}}", - "host": [ - "{{URL}}" - ], - "path": [ - "jobCandidates", - "{{job_candidate_id_created_for_connect_manager}}" - ] - } - }, - "response": [] - } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true - }, - { - "name": "Resource Bookings", - "item": [ - { - "name": "Before Test", - "item": [ - { - "name": "create resource booking", - "event": [ - { - "listen": "test", - "script": { - "id": "513617f1-b4ba-4041-9aaf-fd99f883939b", - "exec": [ - "var data = JSON.parse(responseBody);\r", - "postman.setEnvironmentVariable(\"resource_booking_id_created_for_connect_manager\",data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_administrator}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"projectId\": {{project_id_16718}},\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"jobId\": \"{{job_id_created_by_member}}\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/resourceBookings", - "host": [ - "{{URL}}" - ], - "path": [ - "resourceBookings" - ] - } - }, - "response": [] - } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true - }, - { - "name": "✘ create resource booking with connect manager", - "event": [ - { - "listen": "test", - "script": { - "id": "be4bde84-1e50-4bb4-a99c-4d03ce055023", - "exec": [ - "var data = JSON.parse(responseBody);\r", - "postman.setEnvironmentVariable(\"resource_booking_id_created_for_connect_manager\",data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_connectUser}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"projectId\": {{project_id_16718}},\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"jobId\": \"{{job_id_created_for_connect_manager}}\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/resourceBookings", - "host": [ - "{{URL}}" - ], - "path": [ - "resourceBookings" - ] - } - }, - "response": [] - }, - { - "name": "✔ get resource booking with connect manager", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_connectUser}}" - } - ], - "url": { - "raw": "{{URL}}/resourceBookings/{{resource_booking_id_created_for_connect_manager}}", - "host": [ - "{{URL}}" - ], - "path": [ - "resourceBookings", - "{{resource_booking_id_created_for_connect_manager}}" - ] - } - }, - "response": [] - }, - { - "name": "✔ search resource bookings with connect manager", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_connectUser}}" - } - ], - "url": { - "raw": "{{URL}}/resourceBookings", - "host": [ - "{{URL}}" - ], - "path": [ - "resourceBookings" - ], - "query": [ - { - "key": "page", - "value": "1", - "disabled": true - }, - { - "key": "perPage", - "value": "5", - "disabled": true - }, - { - "key": "sortBy", - "value": "id", - "disabled": true - }, - { - "key": "sortOrder", - "value": "desc", - "disabled": true - }, - { - "key": "startDate", - "value": "2020-09-27T04:17:23.131Z", - "disabled": true - }, - { - "key": "endDate", - "value": "2020-09-27T04:17:23.131Z", - "disabled": true - }, - { - "key": "rateType", - "value": "hourly", - "disabled": true - }, - { - "key": "status", - "value": "sourcing", - "disabled": true - }, - { - "key": "projectIds", - "value": "111, 16705", - "disabled": true - } - ] - } - }, - "response": [] - }, - { - "name": "✘ put resource booking with connect manager", - "request": { - "method": "PUT", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_connectUser}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"projectId\": {{project_id_16718}},\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"jobId\": \"{{job_id_created_for_connect_manager}}\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"assigned\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/resourceBookings/{{resource_booking_id_created_for_connect_manager}}", - "host": [ - "{{URL}}" - ], - "path": [ - "resourceBookings", - "{{resource_booking_id_created_for_connect_manager}}" - ] - } - }, - "response": [] - }, - { - "name": "✘ patch resource booking with connect manager", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_connectUser}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"status\": \"assigned\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/resourceBookings/{{resource_booking_id_created_for_connect_manager}}", - "host": [ - "{{URL}}" - ], - "path": [ - "resourceBookings", - "{{resource_booking_id_created_for_connect_manager}}" - ] - } - }, - "response": [] - }, - { - "name": "✘ delete resource booking with connect manager", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token_connectUser}}" - } - ], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/resourceBookings/{{resource_booking_id_created_for_connect_manager}}", - "host": [ - "{{URL}}" - ], - "path": [ - "resourceBookings", - "{{resource_booking_id_created_for_connect_manager}}" - ] - } - }, - "response": [] - } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true - } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true - } - ], - "protocolProfileBehavior": {} } ], "protocolProfileBehavior": {} diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 3c709069..ea2b5b88 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1491,96 +1491,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - /taas-teams/skills: - get: - tags: - - Teams - description: | - Serves as a proxy endpoint for /v5/skills, allowing to be accessed by any topcoder user. - parameters: - - in: query - name: page - required: false - schema: - type: integer - default: 1 - description: The page number. - - in: query - name: perPage - required: false - schema: - type: integer - default: 20 - description: The number of items to list per page. - - name: orderBy - in: query - schema: - type: string - description: "Specify by which field to sort by. Sorts in ascending order only" - security: - - bearerAuth: [] - responses: - '200': - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/UbahnSkill' - headers: - X-Next-Page: - schema: - type: integer - description: The index of the next page - X-Page: - schema: - type: integer - description: The index of the current page (starting at 1) - X-Per-Page: - schema: - type: integer - description: The number of items to list per page - X-Prev-Page: - schema: - type: integer - description: The index of the previous page - X-Total: - schema: - type: integer - description: The total number of items - X-Total-Pages: - schema: - type: integer - description: The total number of pages - Link: - schema: - type: string - description: Pagination link header. - '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' - '500': - description: Internal Server Error - content: - application/json: - schema: - $ref: '#/components/schemas/Error' /health: get: tags: @@ -2146,42 +2056,6 @@ components: type: string example: 'React' description: The skill name. - UbahnSkill: - type: object - properties: - id: - type: "string" - format: "UUID" - description: "The skill id" - skillProviderId: - type: "string" - format: "UUID" - description: "The referenced skill provider id" - name: - type: "string" - description: "The name of the skill" - externalId: - type: "string" - description: "The external id for the skill" - uri: - type: "string" - description: "The uri for the skill" - created: - type: "string" - format: "date-time" - description: "When the entity was created." - updated: - type: "string" - format: "date-time" - description: "When the entity was updated." - createdBy: - type: "string" - format: "UUID" - description: "Creator of the entity." - updatedBy: - type: "string" - format: "UUID" - description: "User that last updated the entity." JobForTeam: properties: id: diff --git a/docs/topcoder-bookings.postman_environment.json b/docs/topcoder-bookings.postman_environment.json index 17491678..a97d2383 100644 --- a/docs/topcoder-bookings.postman_environment.json +++ b/docs/topcoder-bookings.postman_environment.json @@ -22,11 +22,6 @@ "value": "", "enabled": true }, - { - "key": "token_administrator", - "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjIxNDc0ODM2NDgsInVzZXJJZCI6IjQwMTUyODU2IiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.PKv0QrMCPf0-ZPjv4PGWT7eXne54m7i9YX9eq-fceMU", - "enabled": true - }, { "key": "token_bookingManager", "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik5VSkZORGd4UlRVME5EWTBOVVkzTlRkR05qTXlRamxETmpOQk5UYzVRVUV3UlRFeU56TTJRUSJ9.eyJodHRwczovL3RvcGNvZGVyLWRldi5jb20vcm9sZXMiOlsiVG9wY29kZXIgVXNlciIsImNvcGlsb3QiLCJDb25uZWN0IE1hbmFnZXIiLCJib29raW5nbWFuYWdlciIsInUtYmFobiJdLCJodHRwczovL3RvcGNvZGVyLWRldi5jb20vdXNlcklkIjoiNDAxNTI4NTYiLCJodHRwczovL3RvcGNvZGVyLWRldi5jb20vaGFuZGxlIjoicHNoYWhfbWFuYWdlciIsImh0dHBzOi8vdG9wY29kZXItZGV2LmNvbS91c2VyX2lkIjoiYXV0aDB8NDAxNTI4NTYiLCJodHRwczovL3RvcGNvZGVyLWRldi5jb20vdGNzc28iOiI0MDE1Mjg1Nnw4MTM0ZjQ4ZWJlMTFhODQ4YTM3NTllNWVmOWU5MmYyMTQ2OTJlMjExMzA0MGM4MmI1ZDhmNTgxYzZkZmNjYzg4IiwiaHR0cHM6Ly90b3Bjb2Rlci1kZXYuY29tL2FjdGl2ZSI6dHJ1ZSwibmlja25hbWUiOiJwc2hhaF9tYW5hZ2VyIiwibmFtZSI6InZpa2FzLmFnYXJ3YWwrcHNoYWhfbWFuYWdlckB0b3Bjb2Rlci5jb20iLCJwaWN0dXJlIjoiaHR0cHM6Ly9zLmdyYXZhdGFyLmNvbS9hdmF0YXIvOTJhZmIyZjBlZDUyZmRmYWUxZjM3MTAyMWFlNjUwMTM_cz00ODAmcj1wZyZkPWh0dHBzJTNBJTJGJTJGY2RuLmF1dGgwLmNvbSUyRmF2YXRhcnMlMkZ2aS5wbmciLCJ1cGRhdGVkX2F0IjoiMjAyMC0xMC0yNFQwODoyODoyNC4xODRaIiwiZW1haWwiOiJ2aWthcy5hZ2Fyd2FsK3BzaGFoX21hbmFnZXJAdG9wY29kZXIuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlzcyI6Imh0dHBzOi8vYXV0aC50b3Bjb2Rlci1kZXYuY29tLyIsInN1YiI6ImF1dGgwfDQwMTUyODU2IiwiYXVkIjoiQlhXWFVXbmlsVlVQZE4wMXQyU2UyOVR3MlpZTkdadkgiLCJpYXQiOjE2MDM1NDMzMzgsImV4cCI6MzMxNjA0NTI3MzgsIm5vbmNlIjoiUjFBMmN6WXVWVFptYmpaSFJHOTJWbDlEU1VKNlVsbHZRWGMzUkhoNVMzWldkV1pEY0ROWE1FWjFYdz09In0.2gPsqZTgS1rtiNa1USm3KPA6Xsv3TcHxuDFofgIbeOM", @@ -42,11 +37,6 @@ "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik5VSkZORGd4UlRVME5EWTBOVVkzTlRkR05qTXlRamxETmpOQk5UYzVRVUV3UlRFeU56TTJRUSJ9.eyJodHRwczovL3RvcGNvZGVyLWRldi5jb20vcm9sZXMiOlsiVG9wY29kZXIgVXNlciJdLCJodHRwczovL3RvcGNvZGVyLWRldi5jb20vdXNlcklkIjoiODU0Nzg5OSIsImh0dHBzOi8vdG9wY29kZXItZGV2LmNvbS9oYW5kbGUiOiJwc2hhaF9tYW5hZ2VyIiwiaHR0cHM6Ly90b3Bjb2Rlci1kZXYuY29tL3VzZXJfaWQiOiJhdXRoMHw0MDE1Mjg1NiIsImh0dHBzOi8vdG9wY29kZXItZGV2LmNvbS90Y3NzbyI6IjQwMTUyODU2fDgxMzRmNDhlYmUxMWE4NDhhMzc1OWU1ZWY5ZTkyZjIxNDY5MmUyMTEzMDQwYzgyYjVkOGY1ODFjNmRmY2NjODgiLCJodHRwczovL3RvcGNvZGVyLWRldi5jb20vYWN0aXZlIjp0cnVlLCJuaWNrbmFtZSI6InBzaGFoX21hbmFnZXIiLCJuYW1lIjoidmlrYXMuYWdhcndhbCtwc2hhaF9tYW5hZ2VyQHRvcGNvZGVyLmNvbSIsInBpY3R1cmUiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci85MmFmYjJmMGVkNTJmZGZhZTFmMzcxMDIxYWU2NTAxMz9zPTQ4MCZyPXBnJmQ9aHR0cHMlM0ElMkYlMkZjZG4uYXV0aDAuY29tJTJGYXZhdGFycyUyRnZpLnBuZyIsInVwZGF0ZWRfYXQiOiIyMDIwLTEwLTI0VDA4OjI4OjI0LjE4NFoiLCJlbWFpbCI6InZpa2FzLmFnYXJ3YWwrcHNoYWhfbWFuYWdlckB0b3Bjb2Rlci5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaXNzIjoiaHR0cHM6Ly9hdXRoLnRvcGNvZGVyLWRldi5jb20vIiwic3ViIjoiYXV0aDB8NDAxNTI4NTYiLCJhdWQiOiJCWFdYVVduaWxWVVBkTjAxdDJTZTI5VHcyWllOR1p2SCIsImlhdCI6MTYwMzU0MzMzOCwiZXhwIjozMzE2MDQ1MjczOCwibm9uY2UiOiJSMUEyY3pZdVZUWm1ialpIUkc5MlZsOURTVUo2VWxsdlFYYzNSSGg1UzNaV2RXWkRjRE5YTUVaMVh3PT0ifQ.HbAisH30DLcbFNQeIifSzk1yhDmlGHNpPi9LSZbAowo", "enabled": true }, - { - "key": "token_member_tester1234", - "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwczovL3RvcGNvZGVyLWRldi5jb20vcm9sZXMiOlsiVG9wY29kZXIgVXNlciJdLCJodHRwczovL3RvcGNvZGVyLWRldi5jb20vdXNlcklkIjoiNDAxNTkwOTciLCJodHRwczovL3RvcGNvZGVyLWRldi5jb20vaGFuZGxlIjoidGVzdGVyMTIzNCIsImh0dHBzOi8vdG9wY29kZXItZGV2LmNvbS91c2VyX2lkIjoiYXV0aDB8NDAxNTkwOTciLCJodHRwczovL3RvcGNvZGVyLWRldi5jb20vdGNzc28iOiI0MDE1OTA5N3xhYjZhMTlkYThhZTdjMDY0MjVhMzNmYTMzMTkwNWI2MjIyZTY3ZDQ2MWY5MzEzZjNlZTNjODI2NWZkOWMxNWYiLCJodHRwczovL3RvcGNvZGVyLWRldi5jb20vYWN0aXZlIjp0cnVlLCJuaWNrbmFtZSI6InRlc3RlcjEyMzQiLCJuYW1lIjoic2F0aHlhLmpheWFiYWxAZ21haWwuY29tIiwicGljdHVyZSI6Imh0dHBzOi8vcy5ncmF2YXRhci5jb20vYXZhdGFyLzg1NDNjMGI0OTAyODZmMjViOTgyZTc0ZDlkZjM3MzU4P3M9NDgwJnI9cGcmZD1odHRwcyUzQSUyRiUyRmNkbi5hdXRoMC5jb20lMkZhdmF0YXJzJTJGc2EucG5nIiwidXBkYXRlZF9hdCI6IjIwMjAtMTItMjFUMTE6MDQ6MTkuODg2WiIsImVtYWlsIjoic2F0aHlhLmpheWFiYWxAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlzcyI6Imh0dHBzOi8vYXV0aC50b3Bjb2Rlci1kZXYuY29tLyIsInN1YiI6ImF1dGgwfDQwMTU5MDk3IiwiYXVkIjoiQlhXWFVXbmlsVlVQZE4wMXQyU2UyOVR3MlpZTkdadkgiLCJpYXQiOjE2MDg1NDg2NjEsImV4cCI6MjE0NzQ4MzY0OCwibm9uY2UiOiJTVlowTVhwTlRqTkVUSFJQTkZNeU9HdG9SazFLUlhNMlVsTm1iM0ptVTBkRmJrSk9lVlp2TlM1MGVBPT0ifQ.l_SqklfrEJBBdxEw1R0mszRhY8jZMAUOvUh_mpyihkM", - "enabled": true - }, { "key": "token_userId_not_exist", "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik5VSkZORGd4UlRVME5EWTBOVVkzTlRkR05qTXlRamxETmpOQk5UYzVRVUV3UlRFeU56TTJRUSJ9.eyJodHRwczovL3RvcGNvZGVyLWRldi5jb20vcm9sZXMiOlsiVG9wY29kZXIgVXNlciIsImNvcGlsb3QiLCJDb25uZWN0IE1hbmFnZXIiLCJib29raW5nbWFuYWdlciIsInUtYmFobiJdLCJodHRwczovL3RvcGNvZGVyLWRldi5jb20vdXNlcklkIjoibm90X2V4aXN0IiwiaHR0cHM6Ly90b3Bjb2Rlci1kZXYuY29tL2hhbmRsZSI6InBzaGFoX21hbmFnZXIiLCJodHRwczovL3RvcGNvZGVyLWRldi5jb20vdXNlcl9pZCI6ImF1dGgwfDQwMTUyODU2IiwiaHR0cHM6Ly90b3Bjb2Rlci1kZXYuY29tL3Rjc3NvIjoiNDAxNTI4NTZ8ODEzNGY0OGViZTExYTg0OGEzNzU5ZTVlZjllOTJmMjE0NjkyZTIxMTMwNDBjODJiNWQ4ZjU4MWM2ZGZjY2M4OCIsImh0dHBzOi8vdG9wY29kZXItZGV2LmNvbS9hY3RpdmUiOnRydWUsIm5pY2tuYW1lIjoicHNoYWhfbWFuYWdlciIsIm5hbWUiOiJ2aWthcy5hZ2Fyd2FsK3BzaGFoX21hbmFnZXJAdG9wY29kZXIuY29tIiwicGljdHVyZSI6Imh0dHBzOi8vcy5ncmF2YXRhci5jb20vYXZhdGFyLzkyYWZiMmYwZWQ1MmZkZmFlMWYzNzEwMjFhZTY1MDEzP3M9NDgwJnI9cGcmZD1odHRwcyUzQSUyRiUyRmNkbi5hdXRoMC5jb20lMkZhdmF0YXJzJTJGdmkucG5nIiwidXBkYXRlZF9hdCI6IjIwMjAtMTAtMjRUMDg6Mjg6MjQuMTg0WiIsImVtYWlsIjoidmlrYXMuYWdhcndhbCtwc2hhaF9tYW5hZ2VyQHRvcGNvZGVyLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJpc3MiOiJodHRwczovL2F1dGgudG9wY29kZXItZGV2LmNvbS8iLCJzdWIiOiJhdXRoMHw0MDE1Mjg1NiIsImF1ZCI6IkJYV1hVV25pbFZVUGROMDF0MlNlMjlUdzJaWU5HWnZIIiwiaWF0IjoxNjAzNTQzMzM4LCJleHAiOjMzMTYwNDUyNzM4LCJub25jZSI6IlIxQTJjell1VlRabWJqWkhSRzkyVmw5RFNVSjZVbGx2UVhjM1JIaDVTM1pXZFdaRGNETlhNRVoxWHc9PSJ9.411P_6dTLAJ8TuwdUeyo9Pggzmmtjv37trsvK7ydWns", @@ -57,11 +47,6 @@ "value": "111", "enabled": true }, - { - "key": "project_id_16718", - "value": "16718", - "enabled": true - }, { "key": "jobIdCreatedByMember", "value": "", @@ -166,4 +151,4 @@ "_postman_variable_scope": "environment", "_postman_exported_at": "2020-12-04T12:09:30.809Z", "_postman_exported_using": "Postman/7.29.0" -} +} \ No newline at end of file diff --git a/src/common/helper.js b/src/common/helper.js index 7b06d140..1eb6776b 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -176,6 +176,26 @@ function clearObject (obj) { } } +/** + * Check whether connect member or not + * @param {Number} projectId the project id + * @param {String} jwtToken the jwt token + * @param {Boolean} + */ +async function isConnectMember (projectId, jwtToken) { + const url = `${config.PROJECT_API_URL}/v5/projects/${projectId}` + try { + await request + .get(url) + .set('Authorization', jwtToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json') + } catch (err) { + return false + } + return true +} + /** * Get ES Client * @return {Object} Elastic Host Client Instance @@ -318,7 +338,7 @@ function isDocumentMissingException (err) { */ async function getProjects (currentUser, criteria = {}) { let token - if (currentUser.hasManagePermission || currentUser.isMachine) { + if (currentUser.isBookingManager || currentUser.isMachine) { const m2mToken = await getM2Mtoken() token = `Bearer ${m2mToken}` } else { @@ -449,7 +469,7 @@ async function getMembers (handles) { */ async function getProjectById (currentUser, id) { let token - if (currentUser.hasManagePermission || currentUser.isMachine) { + if (currentUser.isBookingManager || currentUser.isMachine) { const m2mToken = await getM2Mtoken() token = `Bearer ${m2mToken}` } else { @@ -466,7 +486,7 @@ async function getProjectById (currentUser, id) { return _.pick(res.body, ['id', 'name']) } catch (err) { if (err.status === HttpStatus.FORBIDDEN) { - throw new errors.ForbiddenError(`You are not allowed to access the project with id ${id}`) + throw new errors.UnauthorizedError(`You are not allowed to access the project with id ${id}`) } if (err.status === HttpStatus.NOT_FOUND) { throw new errors.NotFoundError(`id: ${id} project not found`) @@ -475,40 +495,6 @@ async function getProjectById (currentUser, id) { } } -/** - * Function to search skills from v5/skills - * - only returns skills from Topcoder Skills Provider defined by `TOPCODER_SKILL_PROVIDER_ID` - * - * @param {Object} criteria the search criteria - * @returns the request result - */ -async function getTopcoderSkills (criteria) { - const token = await getM2Mtoken() - try { - const res = await request - .get(`${config.TC_API}/skills`) - .query({ - skillProviderId: config.TOPCODER_SKILL_PROVIDER_ID, - ...criteria - }) - .set('Authorization', `Bearer ${token}`) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - localLogger.debug({ context: 'getTopcoderSkills', message: `response body: ${JSON.stringify(res.body)}` }) - return { - total: Number(_.get(res.headers, 'x-total')), - page: Number(_.get(res.headers, 'x-page')), - perPage: Number(_.get(res.headers, 'x-per-page')), - result: res.body - } - } catch (err) { - if (err.status === HttpStatus.BAD_REQUEST) { - throw new errors.BadRequestError(err.response.body.message) - } - throw err - } -} - /** * Function to get skill by id * @param {String} skillId the skill Id @@ -619,6 +605,7 @@ module.exports = { autoWrapExpress, setResHeaders, clearObject, + isConnectMember, getESClient, getUserId: async (userId) => { // check m2m user id @@ -636,7 +623,6 @@ module.exports = { getUserById, getMembers, getProjectById, - getTopcoderSkills, getSkillById, getUserSkill, ensureJobById, diff --git a/src/controllers/JobCandidateController.js b/src/controllers/JobCandidateController.js index 4f81c7ec..1b1cf971 100644 --- a/src/controllers/JobCandidateController.js +++ b/src/controllers/JobCandidateController.js @@ -11,7 +11,7 @@ const helper = require('../common/helper') * @param res the response */ async function getJobCandidate (req, res) { - res.send(await service.getJobCandidate(req.authUser, req.params.id, req.query.fromDb)) + res.send(await service.getJobCandidate(req.params.id, req.query.fromDb)) } /** @@ -57,7 +57,7 @@ async function deleteJobCandidate (req, res) { * @param res the response */ async function searchJobCandidates (req, res) { - const result = await service.searchJobCandidates(req.authUser, req.query) + const result = await service.searchJobCandidates(req.query) helper.setResHeaders(req, res, result) res.send(result.result) } diff --git a/src/controllers/JobController.js b/src/controllers/JobController.js index 14f5cfc5..f15f0a66 100644 --- a/src/controllers/JobController.js +++ b/src/controllers/JobController.js @@ -11,7 +11,7 @@ const helper = require('../common/helper') * @param res the response */ async function getJob (req, res) { - res.send(await service.getJob(req.authUser, req.params.id, req.query.fromDb)) + res.send(await service.getJob(req.params.id, req.query.fromDb)) } /** @@ -57,7 +57,7 @@ async function deleteJob (req, res) { * @param res the response */ async function searchJobs (req, res) { - const result = await service.searchJobs(req.authUser, req.query) + const result = await service.searchJobs(req.query) helper.setResHeaders(req, res, result) res.send(result.result) } diff --git a/src/controllers/ResourceBookingController.js b/src/controllers/ResourceBookingController.js index 098fd8e5..d222adc5 100644 --- a/src/controllers/ResourceBookingController.js +++ b/src/controllers/ResourceBookingController.js @@ -57,7 +57,7 @@ async function deleteResourceBooking (req, res) { * @param res the response */ async function searchResourceBookings (req, res) { - const result = await service.searchResourceBookings(req.authUser, req.query) + const result = await service.searchResourceBookings(req.query) helper.setResHeaders(req, res, result) res.send(result.result) } diff --git a/src/controllers/SkillController.js b/src/controllers/SkillController.js deleted file mode 100644 index 0dd13ea7..00000000 --- a/src/controllers/SkillController.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Controller for skills endpoints - */ -const service = require('../services/SkillService') -const helper = require('../common/helper') - -/** - * Search skills - * @param req the request - * @param res the response - */ -async function searchSkills (req, res) { - const result = await service.searchSkills(req.query) - helper.setResHeaders(req, res, result) - res.send(result.result) -} - -module.exports = { - searchSkills -} diff --git a/src/models/JobCandidate.js b/src/models/JobCandidate.js index 285d6f36..e6761eaa 100644 --- a/src/models/JobCandidate.js +++ b/src/models/JobCandidate.js @@ -33,6 +33,15 @@ module.exports = (sequelize) => { } return jobCandidate } + + static async getProjectId (jobId) { + try { + const job = await JobCandidate._models.Job.findById(jobId) + return job.dataValues.projectId + } catch (error) { + return null + } + } } JobCandidate.init( { diff --git a/src/routes/TeamRoutes.js b/src/routes/TeamRoutes.js index 7cfff792..43a9a261 100644 --- a/src/routes/TeamRoutes.js +++ b/src/routes/TeamRoutes.js @@ -12,14 +12,6 @@ module.exports = { scopes: [constants.Scopes.READ_TAAS_TEAM] } }, - '/taas-teams/skills': { - get: { - controller: 'SkillController', - method: 'searchSkills', - auth: 'jwt', - scopes: [constants.Scopes.READ_TAAS_TEAM] - } - }, '/taas-teams/:id': { get: { controller: 'TeamController', diff --git a/src/services/JobCandidateService.js b/src/services/JobCandidateService.js index 106ac1c6..d48e409f 100644 --- a/src/services/JobCandidateService.js +++ b/src/services/JobCandidateService.js @@ -5,72 +5,44 @@ const _ = require('lodash') const Joi = require('joi') const config = require('config') -const HttpStatus = require('http-status-codes') const { Op } = require('sequelize') const { v4: uuid } = require('uuid') const helper = require('../common/helper') const logger = require('../common/logger') const errors = require('../common/errors') const models = require('../models') -const JobService = require('./JobService') const JobCandidate = models.JobCandidate const esClient = helper.getESClient() -/** - * Check whether user can access associated job of a candidate. - * - * @param {Object} currentUser the user who perform this operation. - * @param {String} jobId the job id - * @returns {undefined} - */ -async function _checkUserAccessAssociatedJob (currentUser, jobId) { - if (!currentUser.hasManagePermission && !currentUser.isMachine && !currentUser.isConnectManager) { - await JobService.getJob(currentUser, jobId) - } -} - /** * Get jobCandidate by id - * @param {Object} currentUser the user who perform this operation. * @param {String} id the jobCandidate id * @param {Boolean} fromDb flag if query db for data or not * @returns {Object} the jobCandidate */ -async function getJobCandidate (currentUser, id, fromDb = false) { +async function getJobCandidate (id, fromDb = false) { if (!fromDb) { try { const jobCandidate = await esClient.get({ index: config.esConfig.ES_INDEX_JOB_CANDIDATE, id }) - - // check whether user can access the job associated with the jobCandidate - await _checkUserAccessAssociatedJob(currentUser, jobCandidate.body._source.jobId) - const jobCandidateRecord = { id: jobCandidate.body._id, ...jobCandidate.body._source } return jobCandidateRecord } catch (err) { if (helper.isDocumentMissingException(err)) { throw new errors.NotFoundError(`id: ${id} "JobCandidate" not found`) } - if (err.httpStatus === HttpStatus.FORBIDDEN) { - throw err - } logger.logFullError(err, { component: 'JobCandidateService', context: 'getJobCandidate' }) } } logger.info({ component: 'JobCandidateService', context: 'getJobCandidate', message: 'try to query db for data' }) const jobCandidate = await JobCandidate.findById(id) - - // check whether user can access the job associated with the jobCandidate - await _checkUserAccessAssociatedJob(currentUser, jobCandidate.jobId) - return helper.clearObject(jobCandidate.dataValues) } getJobCandidate.schema = Joi.object().keys({ - currentUser: Joi.object().required(), id: Joi.string().guid().required(), fromDb: Joi.boolean() }).required() @@ -82,10 +54,6 @@ getJobCandidate.schema = Joi.object().keys({ * @returns {Object} the created jobCandidate */ async function createJobCandidate (currentUser, jobCandidate) { - if (!currentUser.hasManagePermission && !currentUser.isMachine) { - throw new errors.ForbiddenError('You are not allowed to perform this action!') - } - await helper.ensureJobById(jobCandidate.jobId) // ensure job exists await helper.ensureUserById(jobCandidate.userId) // ensure user exists @@ -116,17 +84,14 @@ createJobCandidate.schema = Joi.object().keys({ */ async function updateJobCandidate (currentUser, id, data) { const jobCandidate = await JobCandidate.findById(id) - + const projectId = await JobCandidate.getProjectId(jobCandidate.dataValues.jobId) const userId = await helper.getUserId(currentUser.userId) - if (!currentUser.hasManagePermission && !currentUser.isMachine) { - if (currentUser.isConnectManager) { - throw new errors.ForbiddenError('You are not allowed to perform this action!') - } - // check whether user can access the job associated with the jobCandidate - await JobService.getJob(currentUser, jobCandidate.dataValues.jobId) - // check whether user are allowed to update the candidate - if (jobCandidate.dataValues.userId !== userId) { - throw new errors.ForbiddenError('You are not allowed to perform this action!') + if (projectId && !currentUser.isBookingManager && !currentUser.isMachine) { + const connect = await helper.isConnectMember(projectId, currentUser.jwtToken) + if (!connect) { + if (jobCandidate.dataValues.userId !== userId) { + throw new errors.ForbiddenError('You are not allowed to perform this action!') + } } } data.updatedAt = new Date() @@ -186,7 +151,7 @@ fullyUpdateJobCandidate.schema = Joi.object().keys({ * @params {String} id the jobCandidate id */ async function deleteJobCandidate (currentUser, id) { - if (!currentUser.hasManagePermission && !currentUser.isMachine) { + if (!currentUser.isBookingManager && !currentUser.isMachine) { throw new errors.ForbiddenError('You are not allowed to perform this action!') } @@ -202,20 +167,10 @@ deleteJobCandidate.schema = Joi.object().keys({ /** * List resourceBookings - * @param {Object} currentUser the user who perform this operation. * @params {Object} criteria the search criteria * @returns {Object} the search result, contain total/page/perPage and result array */ -async function searchJobCandidates (currentUser, criteria) { - if (!currentUser.hasManagePermission && !currentUser.isMachine && !currentUser.isConnectManager) { - // regular user can only search with filtering by "jobId" - if (!criteria.jobId) { - throw new errors.ForbiddenError('Not allowed without filtering by "jobId"') - } - // check whether user can access the job associated with the jobCandidate - await JobService.getJob(currentUser, criteria.jobId) - } - +async function searchJobCandidates (criteria) { const page = criteria.page > 0 ? criteria.page : 1 const perPage = criteria.perPage > 0 ? criteria.perPage : 20 if (!criteria.sortBy) { @@ -293,7 +248,6 @@ async function searchJobCandidates (currentUser, criteria) { } searchJobCandidates.schema = Joi.object().keys({ - currentUser: Joi.object().required(), criteria: Joi.object().keys({ page: Joi.number().integer(), perPage: Joi.number().integer(), diff --git a/src/services/JobService.js b/src/services/JobService.js index f4aa8d26..90c78bf2 100644 --- a/src/services/JobService.js +++ b/src/services/JobService.js @@ -74,37 +74,19 @@ async function _validateSkills (skills) { } } -/** - * Check whether user can access associated project of a job. - * - * @param {Object} currentUser the user who perform this operation. - * @param {String} projectId the project id - * @returns {undefined} - */ -async function _checkUserAccessAssociatedProject (currentUser, projectId) { - if (!currentUser.hasManagePermission && !currentUser.isMachine && !currentUser.isConnectManager) { - await helper.getProjectById(currentUser, projectId) - } -} - /** * Get job by id - * @param {Object} currentUser the user who perform this operation. * @param {String} id the job id * @param {Boolean} fromDb flag if query db for data or not * @returns {Object} the job */ -async function getJob (currentUser, id, fromDb = false) { +async function getJob (id, fromDb = false) { if (!fromDb) { try { const job = await esClient.get({ index: config.esConfig.ES_INDEX_JOB, id }) - - // check whether user can access the project associated with the job - await _checkUserAccessAssociatedProject(currentUser, job.body._source.projectId) - const jobId = job.body._id const jobRecord = { id: jobId, ...job.body._source } const candidates = await _getJobCandidates(jobId) @@ -116,24 +98,16 @@ async function getJob (currentUser, id, fromDb = false) { if (helper.isDocumentMissingException(err)) { throw new errors.NotFoundError(`id: ${id} "Job" not found`) } - if (err.httpStatus === HttpStatus.FORBIDDEN) { - throw err - } logger.logFullError(err, { component: 'JobService', context: 'getJob' }) } } logger.info({ component: 'JobService', context: 'getJob', message: 'try to query db for data' }) const job = await Job.findById(id, true) - - // check whether user can access the project associated with the job - await _checkUserAccessAssociatedProject(currentUser, job.projectId) - job.dataValues.candidates = _.map(job.dataValues.candidates, (c) => helper.clearObject(c.dataValues)) return helper.clearObject(job.dataValues) } getJob.schema = Joi.object().keys({ - currentUser: Joi.object().required(), id: Joi.string().guid().required(), fromDb: Joi.boolean() }).required() @@ -145,14 +119,6 @@ getJob.schema = Joi.object().keys({ * @returns {Object} the created job */ async function createJob (currentUser, job) { - // check if user can access the project - if (!currentUser.hasManagePermission && !currentUser.isMachine) { - if (currentUser.isConnectManager) { - throw new errors.ForbiddenError('You are not allowed to perform this action!') - } - await helper.getProjectById(currentUser, job.projectId) - } - await _validateSkills(job.skills) job.id = uuid() job.createdAt = new Date() @@ -193,15 +159,12 @@ async function updateJob (currentUser, id, data) { } let job = await Job.findById(id) const ubhanUserId = await helper.getUserId(currentUser.userId) - if (!currentUser.hasManagePermission && !currentUser.isMachine) { - if (currentUser.isConnectManager) { - throw new errors.ForbiddenError('You are not allowed to perform this action!') - } - // Check whether user can update the job. - // Note that there is no need to check if user is member of the project associated with the job here - // because user who created the job must be the member of the project associated with the job - if (ubhanUserId !== job.createdBy) { - throw new errors.ForbiddenError('You are not allowed to perform this action!') + if (!currentUser.isBookingManager && !currentUser.isMachine) { + const connect = await helper.isConnectMember(job.dataValues.projectId, currentUser.jwtToken) + if (!connect) { + if (ubhanUserId !== job.createdBy) { + throw new errors.ForbiddenError('You are not allowed to perform this action!') + } } } @@ -272,16 +235,19 @@ fullyUpdateJob.schema = Joi.object().keys({ }).required() /** - * Delete job by id. + * Delete job by id. Normal user can only delete the job he/she created. * @params {Object} currentUser the user who perform this operation * @params {String} id the job id */ async function deleteJob (currentUser, id) { - if (!currentUser.hasManagePermission && !currentUser.isMachine) { - throw new errors.ForbiddenError('You are not allowed to perform this action!') + const job = await Job.findById(id) + if (!currentUser.isBookingManager && !currentUser.isMachine) { + const ubhanUserId = await helper.getUserId(currentUser.userId) + if (ubhanUserId !== job.createdBy) { + throw new errors.ForbiddenError('You are not allowed to perform this action!') + } } - const job = await Job.findById(id) await job.update({ deletedAt: new Date() }) await helper.postEvent(config.TAAS_JOB_DELETE_TOPIC, { id }) } @@ -293,21 +259,11 @@ deleteJob.schema = Joi.object().keys({ /** * List jobs - * @param {Object} currentUser the user who perform this operation. * @params {Object} criteria the search criteria * @params {Object} options the extra options to control the function * @returns {Object} the search result, contain total/page/perPage and result array */ -async function searchJobs (currentUser, criteria, options = { returnAll: false }) { - if (!currentUser.hasManagePermission && !currentUser.isMachine && !currentUser.isConnectManager) { - // regular user can only search with filtering by "projectId" - if (!criteria.projectId) { - throw new errors.ForbiddenError('Not allowed without filtering by "projectId"') - } - // check if user can access the project - await helper.getProjectById(currentUser, criteria.projectId) - } - +async function searchJobs (criteria, options = { returnAll: false }) { const page = criteria.page > 0 ? criteria.page : 1 let perPage if (options.returnAll) { @@ -468,7 +424,6 @@ async function searchJobs (currentUser, criteria, options = { returnAll: false } } searchJobs.schema = Joi.object().keys({ - currentUser: Joi.object().required(), criteria: Joi.object().keys({ page: Joi.number().integer(), perPage: Joi.number().integer(), diff --git a/src/services/ResourceBookingService.js b/src/services/ResourceBookingService.js index 21b9fa9e..accf7bd2 100644 --- a/src/services/ResourceBookingService.js +++ b/src/services/ResourceBookingService.js @@ -5,7 +5,6 @@ const _ = require('lodash') const Joi = require('joi') const config = require('config') -const HttpStatus = require('http-status-codes') const { Op } = require('sequelize') const { v4: uuid } = require('uuid') const helper = require('../common/helper') @@ -23,22 +22,12 @@ const esClient = helper.getESClient() * @returns {Object} the resourceBooking */ async function _getResourceBookingFilteringFields (currentUser, resourceBooking) { - if (currentUser.hasManagePermission || currentUser.isMachine) { + if (currentUser.isBookingManager || currentUser.isMachine) { return helper.clearObject(resourceBooking) - } - return _.omit(helper.clearObject(resourceBooking), 'memberRate') -} - -/** - * Check whether user can access associated project of a job. - * - * @param {Object} currentUser the user who perform this operation. - * @param {String} projectId the project id - * @returns {undefined} - */ -async function _checkUserAccessAssociatedProject (currentUser, projectId) { - if (!currentUser.hasManagePermission && !currentUser.isMachine && !currentUser.isConnectManager) { - await helper.getProjectById(currentUser, projectId) + } else if (await helper.isConnectMember(resourceBooking.projectId, currentUser.jwtToken)) { + return _.omit(helper.clearObject(resourceBooking), 'memberRate') + } else { + return _.omit(helper.clearObject(resourceBooking), 'customerRate') } } @@ -56,28 +45,17 @@ async function getResourceBooking (currentUser, id, fromDb = false) { index: config.esConfig.ES_INDEX_RESOURCE_BOOKING, id }) - - // check if user can access the project associated with the resourceBooking - await _checkUserAccessAssociatedProject(currentUser, resourceBooking.body._source.projectId) - const resourceBookingRecord = { id: resourceBooking.body._id, ...resourceBooking.body._source } return _getResourceBookingFilteringFields(currentUser, resourceBookingRecord) } catch (err) { if (helper.isDocumentMissingException(err)) { throw new errors.NotFoundError(`id: ${id} "ResourceBooking" not found`) } - if (err.httpStatus === HttpStatus.FORBIDDEN) { - throw err - } logger.logFullError(err, { component: 'ResourceBookingService', context: 'getResourceBooking' }) } } logger.info({ component: 'ResourceBookingService', context: 'getResourceBooking', message: 'try to query db for data' }) const resourceBooking = await ResourceBooking.findById(id) - - // check if user can access the project associated with the resourceBooking - await _checkUserAccessAssociatedProject(currentUser, resourceBooking.projectId) - return _getResourceBookingFilteringFields(currentUser, resourceBooking.dataValues) } @@ -94,16 +72,17 @@ getResourceBooking.schema = Joi.object().keys({ * @returns {Object} the created resourceBooking */ async function createResourceBooking (currentUser, resourceBooking) { - // check permission - if (!currentUser.hasManagePermission && !currentUser.isMachine) { - throw new errors.ForbiddenError('You are not allowed to perform this action!') - } - if (resourceBooking.jobId) { await helper.ensureJobById(resourceBooking.jobId) // ensure job exists } await helper.ensureUserById(resourceBooking.userId) // ensure user exists + if (!currentUser.isBookingManager && !currentUser.isMachine) { + const connect = await helper.isConnectMember(resourceBooking.projectId, currentUser.jwtToken) + if (!connect) { + throw new errors.ForbiddenError('You are not allowed to perform this action!') + } + } resourceBooking.id = uuid() resourceBooking.createdAt = new Date() resourceBooking.createdBy = await helper.getUserId(currentUser.userId) @@ -136,11 +115,6 @@ createResourceBooking.schema = Joi.object().keys({ * @returns {Object} the updated resourceBooking */ async function updateResourceBooking (currentUser, id, data) { - // check permission - if (!currentUser.hasManagePermission && !currentUser.isMachine) { - throw new errors.ForbiddenError('You are not allowed to perform this action!') - } - const resourceBooking = await ResourceBooking.findById(id) const isDiffStatus = resourceBooking.status !== data.status @@ -150,7 +124,6 @@ async function updateResourceBooking (currentUser, id, data) { throw new errors.ForbiddenError('You are not allowed to perform this action!') } } - data.updatedAt = new Date() data.updatedBy = await helper.getUserId(currentUser.userId) @@ -222,8 +195,7 @@ fullyUpdateResourceBooking.schema = Joi.object().keys({ * @params {String} id the resourceBooking id */ async function deleteResourceBooking (currentUser, id) { - // check permission - if (!currentUser.hasManagePermission && !currentUser.isMachine) { + if (!currentUser.isBookingManager && !currentUser.isMachine) { throw new errors.ForbiddenError('You are not allowed to perform this action!') } @@ -239,21 +211,11 @@ deleteResourceBooking.schema = Joi.object().keys({ /** * List resourceBookings - * @param {Object} currentUser the user who perform this operation. * @params {Object} criteria the search criteria * @params {Object} options the extra options to control the function * @returns {Object} the search result, contain total/page/perPage and result array */ -async function searchResourceBookings (currentUser, criteria, options = { returnAll: false }) { - if (!currentUser.hasManagePermission && !currentUser.isMachine && !currentUser.isConnectManager) { - // regular user can only search with filtering by "projectId" - if (!criteria.projectId) { - throw new errors.ForbiddenError('Not allowed without filtering by "projectId"') - } - // check if user can access the project - await helper.getProjectById(currentUser, criteria.projectId) - } - +async function searchResourceBookings (criteria, options = { returnAll: false }) { // `criteria`.projectIds` could be array of ids, or comma separated string of ids // in case it's comma separated string of ids we have to convert it to an array of ids if ((typeof criteria.projectIds) === 'string') { @@ -364,7 +326,6 @@ async function searchResourceBookings (currentUser, criteria, options = { return } searchResourceBookings.schema = Joi.object().keys({ - currentUser: Joi.object().required(), criteria: Joi.object().keys({ page: Joi.number().integer(), perPage: Joi.number().integer(), diff --git a/src/services/SkillService.js b/src/services/SkillService.js deleted file mode 100644 index b3d33025..00000000 --- a/src/services/SkillService.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * This service provides operations of Skill. - */ -const Joi = require('joi') -const helper = require('../common/helper') - -/** - * Search skills - * @param {Object} criteria the search criteria - * @returns {Object} the search result, contain total/page/perPage and result array - */ -async function searchSkills (criteria) { - return helper.getTopcoderSkills(criteria) -} - -searchSkills.schema = Joi.object().keys({ - criteria: Joi.object().keys({ - page: Joi.page(), - perPage: Joi.perPage(), - orderBy: Joi.string() - }).required() -}).required() - -module.exports = { - searchSkills -} diff --git a/src/services/TeamService.js b/src/services/TeamService.js index b4b329bf..2e69dec4 100644 --- a/src/services/TeamService.js +++ b/src/services/TeamService.js @@ -13,24 +13,22 @@ const ResourceBookingService = require('./ResourceBookingService') /** * Function to get assigned resource bookings with specific projectIds - * @param {Object} currentUser the user who perform this operation. * @param {Array} projectIds project ids * @returns the request result */ -async function _getAssignedResourceBookingsByProjectIds (currentUser, projectIds) { +async function _getAssignedResourceBookingsByProjectIds (projectIds) { const criteria = { status: 'assigned', projectIds } - const { result } = await ResourceBookingService.searchResourceBookings(currentUser, criteria, { returnAll: true }) + const { result } = await ResourceBookingService.searchResourceBookings(criteria, { returnAll: true }) return result } /** * Function to get jobs by projectIds - * @param {Object} currentUser the user who perform this operation. * @param {Array} projectIds project ids * @returns the request result */ -async function _getJobsByProjectIds (currentUser, projectIds) { - const { result } = await JobService.searchJobs(currentUser, { projectIds }, { returnAll: true }) +async function _getJobsByProjectIds (projectIds) { + const { result } = await JobService.searchJobs({ projectIds }, { returnAll: true }) return result } @@ -56,7 +54,7 @@ async function searchTeams (currentUser, criteria) { total, page, perPage, - result: await getTeamDetail(currentUser, projects) + result: await getTeamDetail(projects) } } @@ -77,17 +75,16 @@ searchTeams.schema = Joi.object().keys({ /** * Get team details - * @param {Object} currentUser the user who perform this operation * @param {Object} projects the projects * @param {Object} isSearch the flag whether for search function * @returns {Object} the search result */ -async function getTeamDetail (currentUser, projects, isSearch = true) { +async function getTeamDetail (projects, isSearch = true) { const projectIds = _.map(projects, 'id') // Get all assigned resourceBookings filtered by projectIds - const resourceBookings = await _getAssignedResourceBookingsByProjectIds(currentUser, projectIds) + const resourceBookings = await _getAssignedResourceBookingsByProjectIds(projectIds) // Get all jobs filtered by projectIds - const jobs = await _getJobsByProjectIds(currentUser, projectIds) + const jobs = await _getJobsByProjectIds(projectIds) // Get first week day and last week day const curr = new Date() @@ -200,7 +197,7 @@ async function getTeamDetail (currentUser, projects, isSearch = true) { */ async function getTeam (currentUser, id) { const project = await helper.getProjectById(currentUser, id) - const result = await getTeamDetail(currentUser, [project], false) + const result = await getTeamDetail([project], false) const teamDetail = result[0] // add job skills for result @@ -249,7 +246,7 @@ getTeam.schema = Joi.object().keys({ */ async function getTeamJob (currentUser, id, jobId) { const project = await helper.getProjectById(currentUser, id) - const jobs = await _getJobsByProjectIds(currentUser, [project.id]) + const jobs = await _getJobsByProjectIds([project.id]) const job = _.find(jobs, { id: jobId }) if (!job) {