From 8d2840c6702c6015fc4235e729db41d818f09250 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 19 Jun 2025 19:33:58 +0530 Subject: [PATCH 1/6] feat: cancel copilot opportunity --- .circleci/config.yml | 2 +- src/permissions/constants.js | 13 +++++ src/routes/copilotOpportunity/delete.js | 76 +++++++++++++++++++++++++ src/routes/copilotOpportunity/get.js | 1 - src/routes/index.js | 4 ++ 5 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 src/routes/copilotOpportunity/delete.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 8c07e37e..ff93f090 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup', 'pm-1273'] + only: ['develop', 'migration-setup', 'pm-1378'] - deployProd: context : org-global filters: diff --git a/src/permissions/constants.js b/src/permissions/constants.js index c32e16d4..7f55c362 100644 --- a/src/permissions/constants.js +++ b/src/permissions/constants.js @@ -288,6 +288,19 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export ], scopes: SCOPES_PROJECTS_WRITE, }, + + CANCEL_COPILOT_OPPORTUNITY: { + meta: { + title: 'Cancel copilot opportunity', + group: 'Cancel copilot opportunity', + description: 'Who can cancel copilot opportunity.', + }, + topcoderRoles: [ + USER_ROLE.PROJECT_MANAGER, + USER_ROLE.TOPCODER_ADMIN, + ], + scopes: SCOPES_PROJECTS_WRITE, + }, LIST_COPILOT_OPPORTUNITY: { meta: { diff --git a/src/routes/copilotOpportunity/delete.js b/src/routes/copilotOpportunity/delete.js new file mode 100644 index 00000000..bb096406 --- /dev/null +++ b/src/routes/copilotOpportunity/delete.js @@ -0,0 +1,76 @@ +import _ from 'lodash'; + +import models from '../../models'; +import util from '../../util'; +import { COPILOT_APPLICATION_STATUS, COPILOT_OPPORTUNITY_STATUS, COPILOT_REQUEST_STATUS } from '../../constants'; +import { PERMISSION } from '../../permissions/constants'; + +module.exports = [ + (req, res, next) => { + if (!util.hasPermissionByReq(PERMISSION.CANCEL_COPILOT_OPPORTUNITY, req)) { + const err = new Error('Unable to cancel copilot opportunity'); + _.assign(err, { + details: JSON.stringify({ message: 'You do not have permission to cancel copilot opportunity' }), + status: 403, + }); + return Promise.reject(err); + } + // default values + const opportunityId = _.parseInt(req.params.id); + + return models.sequelize.transaction(async (transaction) => { + req.log.debug('Canceling Copilot opportunity transaction', data); + const opportunity = await models.CopilotOpportunity.findOne({ + where: { id: opportunityId }, + transaction, + }); + + if (!opportunity) { + const err = new Error(`No opportunity available for id ${opportunityId}`); + err.status = 404; + throw err; + } + + const copilotRequest = await models.CopilotRequest.findOne({ + where: { + id: opportunity.copilotRequestId, + }, + transaction, + }); + + const applications = await models.CopilotApplications.findAll({ + where: { + opportunityId: opportunity.id, + }, + transaction, + }); + + applications.update({ + status: COPILOT_APPLICATION_STATUS.CANCELED, + }, { + transaction, + }); + + copilotRequest.update({ + status: COPILOT_REQUEST_STATUS.CANCELED, + }, { + transaction, + }); + + opportunity.update({ + status: COPILOT_OPPORTUNITY_STATUS.CANCELED, + }, { + transaction, + }); + + res.status(200).send({ id: opportunity.id }); + }) + + .catch((err) => { + if (err.message) { + _.assign(err, { details: err.message }); + } + util.handleError('Error canceling copilot opportunity', err, req, next); + }); + }, +]; diff --git a/src/routes/copilotOpportunity/get.js b/src/routes/copilotOpportunity/get.js index 2fd1856c..9202a845 100644 --- a/src/routes/copilotOpportunity/get.js +++ b/src/routes/copilotOpportunity/get.js @@ -32,7 +32,6 @@ module.exports = [ }) .then((copilotOpportunity) => { const plainOpportunity = copilotOpportunity.get({ plain: true }); - req.log.info("authUser", req.authUser); const memberIds = plainOpportunity.project.members && plainOpportunity.project.members.map((member) => member.userId); let canApplyAsCopilot = false; if (req.authUser) { diff --git a/src/routes/index.js b/src/routes/index.js index ab3b6308..fd069fcd 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -419,6 +419,10 @@ router.route('/v5/projects/copilots/opportunity/:id(\\d+)/applications') router.route('/v5/projects/copilots/opportunity/:id(\\d+)/assign') .post(require('./copilotOpportunity/assign')); +// Cancel Copilot opportunity +router.route('/v5/projects/copilots/opportunity/:id(\\d+)/cancel') +.delete(require('./copilotOpportunity/delete')); + // Project Estimation Items router.route('/v5/projects/:projectId(\\d+)/estimations/:estimationId(\\d+)/items') .get(require('./projectEstimationItems/list')); From 70422b7cd89807701ae2f22050e0aa8099e89361 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 19 Jun 2025 21:06:33 +0530 Subject: [PATCH 2/6] fix: undefined --- src/routes/copilotOpportunity/delete.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunity/delete.js b/src/routes/copilotOpportunity/delete.js index bb096406..09c38ded 100644 --- a/src/routes/copilotOpportunity/delete.js +++ b/src/routes/copilotOpportunity/delete.js @@ -19,7 +19,7 @@ module.exports = [ const opportunityId = _.parseInt(req.params.id); return models.sequelize.transaction(async (transaction) => { - req.log.debug('Canceling Copilot opportunity transaction', data); + req.log.debug('Canceling Copilot opportunity transaction', opportunityId); const opportunity = await models.CopilotOpportunity.findOne({ where: { id: opportunityId }, transaction, From c3390fc33a9ee9d1b5f45ba8ac5798995dce47c3 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 19 Jun 2025 21:48:03 +0530 Subject: [PATCH 3/6] fix: lint --- src/routes/copilotOpportunity/delete.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunity/delete.js b/src/routes/copilotOpportunity/delete.js index 09c38ded..2c8ecd80 100644 --- a/src/routes/copilotOpportunity/delete.js +++ b/src/routes/copilotOpportunity/delete.js @@ -38,7 +38,7 @@ module.exports = [ transaction, }); - const applications = await models.CopilotApplications.findAll({ + const applications = await models.CopilotApplication.findAll({ where: { opportunityId: opportunity.id, }, From 66a6ffa7a761fefca38424182ca5bcfd77dc280e Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 19 Jun 2025 23:20:22 +0530 Subject: [PATCH 4/6] fix: cancel endpoint --- src/routes/copilotOpportunity/delete.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/routes/copilotOpportunity/delete.js b/src/routes/copilotOpportunity/delete.js index 2c8ecd80..b3d5cf0d 100644 --- a/src/routes/copilotOpportunity/delete.js +++ b/src/routes/copilotOpportunity/delete.js @@ -45,19 +45,21 @@ module.exports = [ transaction, }); - applications.update({ - status: COPILOT_APPLICATION_STATUS.CANCELED, - }, { - transaction, + applications.forEach(async (application) => { + await application.update({ + status: COPILOT_APPLICATION_STATUS.CANCELED, + }, { + transaction, + }); }); - copilotRequest.update({ + await copilotRequest.update({ status: COPILOT_REQUEST_STATUS.CANCELED, }, { transaction, }); - opportunity.update({ + await opportunity.update({ status: COPILOT_OPPORTUNITY_STATUS.CANCELED, }, { transaction, From a09a791a3a9ebb9618b46c6c227adae5c8905a4c Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 19 Jun 2025 23:31:21 +0530 Subject: [PATCH 5/6] fix: cancel endpoint --- src/routes/copilotOpportunity/delete.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/routes/copilotOpportunity/delete.js b/src/routes/copilotOpportunity/delete.js index b3d5cf0d..e22cff92 100644 --- a/src/routes/copilotOpportunity/delete.js +++ b/src/routes/copilotOpportunity/delete.js @@ -45,14 +45,17 @@ module.exports = [ transaction, }); + const promises = []; applications.forEach(async (application) => { - await application.update({ + promises.push(application.update({ status: COPILOT_APPLICATION_STATUS.CANCELED, }, { transaction, - }); + })); }); + await Promise.all(promises); + await copilotRequest.update({ status: COPILOT_REQUEST_STATUS.CANCELED, }, { From 852c89acf1ff69ae71e0f2fcd21534defd5b2268 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 19 Jun 2025 23:31:38 +0530 Subject: [PATCH 6/6] fix: cancel endpoint --- src/routes/copilotOpportunity/delete.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunity/delete.js b/src/routes/copilotOpportunity/delete.js index e22cff92..3c6d9bfa 100644 --- a/src/routes/copilotOpportunity/delete.js +++ b/src/routes/copilotOpportunity/delete.js @@ -46,7 +46,7 @@ module.exports = [ }); const promises = []; - applications.forEach(async (application) => { + applications.forEach((application) => { promises.push(application.update({ status: COPILOT_APPLICATION_STATUS.CANCELED, }, {