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..3c6d9bfa --- /dev/null +++ b/src/routes/copilotOpportunity/delete.js @@ -0,0 +1,81 @@ +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', opportunityId); + 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.CopilotApplication.findAll({ + where: { + opportunityId: opportunity.id, + }, + transaction, + }); + + const promises = []; + applications.forEach((application) => { + promises.push(application.update({ + status: COPILOT_APPLICATION_STATUS.CANCELED, + }, { + transaction, + })); + }); + + await Promise.all(promises); + + await copilotRequest.update({ + status: COPILOT_REQUEST_STATUS.CANCELED, + }, { + transaction, + }); + + await 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'));