From 79132205a6b0146430bd9ab0ce28310b1f80ca93 Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Wed, 8 Oct 2025 10:45:58 +0530 Subject: [PATCH 1/6] update logic for changed api response from identity service --- config/development.json | 2 +- src/util.js | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/config/development.json b/config/development.json index 878bc6693..5b8eb1360 100644 --- a/config/development.json +++ b/config/development.json @@ -6,7 +6,7 @@ "copilotPortalUrl": "https://copilots.topcoder-dev.com", "fileServiceEndpoint": "https://api.topcoder-dev.com/v5/files", "memberServiceEndpoint": "https://api.topcoder-dev.com/v5/members", - "identityServiceEndpoint": "https://api.topcoder-dev.com/v3/", + "identityServiceEndpoint": "https://api.topcoder-dev.com/v6/", "taasJobApiUrl": "https://api.topcoder-dev.com/v5/jobs", "sfdcBillingAccountNameField": "Billing_Account_name__c", "sfdcBillingAccountMarkupField": "Mark_Up__c", diff --git a/src/util.js b/src/util.js index 60c71d57e..98e097c7f 100644 --- a/src/util.js +++ b/src/util.js @@ -820,10 +820,10 @@ const projectServiceUtils = { const token = yield this.getM2MToken(); const httpClient = this.getHttpClient({ id: requestId, log: logger }); httpClient.defaults.timeout = 6000; - logger.debug(`${config.identityServiceEndpoint}roles/${roleId}`, "fetching role info"); + logger.debug(`${config.identityServiceEndpoint}roles/${roleId}`, 'fetching role info'); return httpClient.get(`${config.identityServiceEndpoint}roles/${roleId}`, { params: { - fields: `subjects`, + fields: 'subjects', }, headers: { 'Content-Type': 'application/json', @@ -834,7 +834,7 @@ const projectServiceUtils = { return _.get(res, 'data.result.content', []); }); } catch (err) { - logger.debug(err, "error on getting role info"); + logger.debug(err, 'error on getting role info'); return Promise.reject(err); } }), @@ -853,8 +853,9 @@ const projectServiceUtils = { Authorization: `Bearer ${token}`, }, }).then((res) => { - logger.debug(`Roles by ${roleName}: ${JSON.stringify(res.data.result.content)}`); - return _.get(res, 'data.result.content', []) + const roles = res.data; + logger.debug(`Roles by ${roleName}: ${JSON.stringify(roles)}`); + return roles .filter(item => item.roleName === roleName) .map(r => r.id); }); From 765953c625db2bb905997b4152474df639f553bf Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Wed, 8 Oct 2025 10:47:25 +0530 Subject: [PATCH 2/6] deploy fix-2321 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e2ac728e0..96e8ba80b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup', 'PM-1612', 'fix-project-exposing'] + only: ['develop', 'migration-setup', 'PM-1612', 'fix-2321'] - deployProd: context : org-global filters: From 88d3a2b900b053523b596c210acdb86b79cf8a58 Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Wed, 8 Oct 2025 11:39:41 +0530 Subject: [PATCH 3/6] test increase timeout --- src/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.js b/src/util.js index 98e097c7f..45319f4c2 100644 --- a/src/util.js +++ b/src/util.js @@ -843,7 +843,7 @@ const projectServiceUtils = { try { const token = yield this.getM2MToken(); const httpClient = this.getHttpClient({ id: requestId, log: logger }); - httpClient.defaults.timeout = 6000; + httpClient.defaults.timeout = 10000; return httpClient.get(`${config.identityServiceEndpoint}roles`, { params: { filter: `roleName=${roleName}`, From 579103f0d808f9881086cbc735962bba2a670d7f Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Thu, 9 Oct 2025 20:23:41 +0530 Subject: [PATCH 4/6] revert increase timeout --- src/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.js b/src/util.js index 45319f4c2..98e097c7f 100644 --- a/src/util.js +++ b/src/util.js @@ -843,7 +843,7 @@ const projectServiceUtils = { try { const token = yield this.getM2MToken(); const httpClient = this.getHttpClient({ id: requestId, log: logger }); - httpClient.defaults.timeout = 10000; + httpClient.defaults.timeout = 6000; return httpClient.get(`${config.identityServiceEndpoint}roles`, { params: { filter: `roleName=${roleName}`, From 0db2d55e4f1cecae4dd8c8feb2c2a189425c0191 Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Mon, 13 Oct 2025 14:40:36 +0530 Subject: [PATCH 5/6] add logging --- .../copilotRequest/approveRequest.service.js | 172 +++++++++--------- src/routes/copilotRequest/create.js | 114 ++++++------ 2 files changed, 147 insertions(+), 139 deletions(-) diff --git a/src/routes/copilotRequest/approveRequest.service.js b/src/routes/copilotRequest/approveRequest.service.js index 5ef4d2c12..6a0eb064b 100644 --- a/src/routes/copilotRequest/approveRequest.service.js +++ b/src/routes/copilotRequest/approveRequest.service.js @@ -1,10 +1,14 @@ -import _ from 'lodash'; import config from 'config'; import moment from 'moment'; import { Op } from 'sequelize'; import models from '../../models'; -import { CONNECT_NOTIFICATION_EVENT, COPILOT_OPPORTUNITY_STATUS, COPILOT_REQUEST_STATUS, TEMPLATE_IDS, USER_ROLE } from '../../constants'; +import { + CONNECT_NOTIFICATION_EVENT, + COPILOT_OPPORTUNITY_STATUS, + COPILOT_REQUEST_STATUS, + TEMPLATE_IDS, USER_ROLE, +} from '../../constants'; import util from '../../util'; import { createEvent } from '../../services/busApi'; import { getCopilotTypeLabel } from '../../utils/copilot'; @@ -17,85 +21,91 @@ const resolveTransaction = (transaction, callback) => { return models.sequelize.transaction(callback); }; -module.exports = (req, data, existingTransaction) => { +module.exports = async (req, data, existingTransaction) => { const { projectId, copilotRequestId, opportunityTitle, type, startDate } = data; - return resolveTransaction(existingTransaction, transaction => - models.Project.findOne({ - where: { id: projectId, deletedAt: { $eq: null } }, - }, { transaction }) - .then((existingProject) => { - if (!existingProject) { - const err = new Error(`active project not found for project id ${projectId}`); - err.status = 404; - throw err; - } - return models.CopilotRequest.findByPk(copilotRequestId, { transaction }) - .then((existingCopilotRequest) => { - if (!existingCopilotRequest) { - const err = new Error(`no active copilot request found for copilot request id ${copilotRequestId}`); - err.status = 404; - throw err; - } - - return existingCopilotRequest.update({ - status: COPILOT_REQUEST_STATUS.APPROVED, - }, { transaction }).then(() => models.CopilotOpportunity - .findOne({ - where: { - projectId, - type: data.type, - status: { - [Op.in]: [COPILOT_OPPORTUNITY_STATUS.ACTIVE], - } - }, - }) - .then((existingCopilotOpportunityOfSameType) => { - if (existingCopilotOpportunityOfSameType) { - const err = new Error('There\'s an active opportunity of same type already!'); - _.assign(err, { - status: 403, - }); - throw err; - } - return models.CopilotOpportunity - .create(data, { transaction }); - })) - .then(async (opportunity) => { - const roles = await util.getRolesByRoleName(USER_ROLE.TC_COPILOT, req.log, req.id); - const { subjects = [] } = await util.getRoleInfo(roles[0], req.log, req.id); - const emailEventType = CONNECT_NOTIFICATION_EVENT.EXTERNAL_ACTION_EMAIL; - const copilotPortalUrl = config.get('copilotPortalUrl'); - req.log.info("Sending emails to all copilots about new opportunity"); - - const sendNotification = (userName, recipient) => createEvent(emailEventType, { - data: { - user_name: userName, - opportunity_details_url: `${copilotPortalUrl}/opportunity/${opportunity.id}`, - work_manager_url: config.get('workManagerUrl'), - opportunity_type: getCopilotTypeLabel(type), - opportunity_title: opportunityTitle, - start_date: moment(startDate).format("DD-MM-YYYY"), - }, - sendgrid_template_id: TEMPLATE_IDS.CREATE_REQUEST, - recipients: [recipient], - version: 'v3', - }, req.log); - - subjects.forEach(subject => sendNotification(subject.handle, subject.email)); - - // send email to notify via slack - sendNotification('Copilots', config.copilotsSlackEmail); - - req.log.info("Finished sending emails to copilots"); - - return opportunity; - }) - .catch((err) => { - transaction.rollback(); - return Promise.reject(err); - }); - }); - }), - ); + return resolveTransaction(existingTransaction, async (transaction) => { + try { + req.log.debug('approveRequest: finding project', { projectId }); + + const existingProject = await models.Project.findOne({ + where: { id: projectId, deletedAt: { $eq: null } }, + transaction, + }); + + if (!existingProject) { + const err = new Error(`active project not found for project id ${projectId}`); + err.status = 404; + throw err; + } + + const copilotRequest = await models.CopilotRequest.findByPk(copilotRequestId, { transaction }); + req.log.debug('approveRequest: found copilot request', { copilotRequestId: copilotRequest.id }); + + if (!copilotRequest) { + const err = new Error(`no active copilot request found for copilot request id ${copilotRequestId}`); + err.status = 404; + throw err; + } + + await copilotRequest.update({ status: COPILOT_REQUEST_STATUS.APPROVED }, { transaction }); + req.log.debug('Copilot request status updated to APPROVED', { copilotRequestId }); + + const existingOpportunity = await models.CopilotOpportunity.findOne({ + where: { + projectId, + type: data.type, + status: { [Op.in]: [COPILOT_OPPORTUNITY_STATUS.ACTIVE] }, + }, + transaction, + }); + + if (existingOpportunity) { + const err = new Error('There\'s an active opportunity of same type already!'); + err.status = 403; + throw err; + } + + const opportunity = await models.CopilotOpportunity.create(data, { transaction }); + req.log.debug('Created new copilot opportunity', { opportunityId: opportunity.id }); + + // Send notifications + try { + const roles = await util.getRolesByRoleName(USER_ROLE.TC_COPILOT, req.log, req.id); + req.log.debug('Roles fetched', { roles }); + + const { subjects = [] } = await util.getRoleInfo(roles[0], req.log, req.id); + const emailEventType = CONNECT_NOTIFICATION_EVENT.EXTERNAL_ACTION_EMAIL; + const copilotPortalUrl = config.get('copilotPortalUrl'); + req.log.info('Sending emails to all copilots about new opportunity'); + + const sendNotification = (userName, recipient) => createEvent(emailEventType, { + data: { + user_name: userName, + opportunity_details_url: `${copilotPortalUrl}/opportunity/${opportunity.id}`, + work_manager_url: config.get('workManagerUrl'), + opportunity_type: getCopilotTypeLabel(type), + opportunity_title: opportunityTitle, + start_date: moment(startDate).format('DD-MM-YYYY'), + }, + sendgrid_template_id: TEMPLATE_IDS.CREATE_REQUEST, + recipients: [recipient], + version: 'v3', + }, req.log); + + subjects.forEach(subject => sendNotification(subject.handle, subject.email)); + + // send email to notify via slack + sendNotification('Copilots', config.copilotsSlackEmail); + req.log.info('Finished sending emails to copilots'); + } catch (emailErr) { + req.log.error('Error sending notifications', { error: emailErr }); + } + + return opportunity; + } catch (err) { + req.log.error('approveRequest failed', { error: err }); + throw err; // let outer transaction handle rollback + } + }); }; diff --git a/src/routes/copilotRequest/create.js b/src/routes/copilotRequest/create.js index 52b640ea6..e4bc586e6 100644 --- a/src/routes/copilotRequest/create.js +++ b/src/routes/copilotRequest/create.js @@ -39,7 +39,7 @@ const addCopilotRequestValidations = { module.exports = [ validate(addCopilotRequestValidations), - (req, res, next) => { + async (req, res, next) => { const data = req.body; if (!util.hasPermissionByReq(PERMISSION.MANAGE_COPILOT_REQUEST, req)) { const err = new Error('Unable to create copilot request'); @@ -58,66 +58,64 @@ module.exports = [ updatedBy: req.authUser.userId, }); - return models.sequelize.transaction((transaction) => { - req.log.debug('Create Copilot request transaction', data); - return models.Project.findOne({ - where: { id: projectId, deletedAt: { $eq: null } }, - }) - .then((existingProject) => { - if (!existingProject) { - const err = new Error(`active project not found for project id ${projectId}`); - err.status = 404; - throw err; - } - return models.CopilotRequest.findOne({ - where: { - createdBy: req.authUser.userId, - projectId, - status: { - [Op.in]: [COPILOT_REQUEST_STATUS.NEW, COPILOT_REQUEST_STATUS.APPROVED, COPILOT_REQUEST_STATUS.SEEKING], - }, - }, - }).then((copilotRequest) => { - if (copilotRequest && copilotRequest.data.projectType === data.data.projectType) { - const err = new Error('There\'s a request of same type already!'); - _.assign(err, { - status: 400, - }); - throw err; - } + try { + const copilotRequest = await models.sequelize.transaction(async (transaction) => { + req.log.debug('Starting create copilot request transaction', { data }); + + const existingProject = await models.Project.findOne({ + where: { id: projectId, deletedAt: { $eq: null } }, + transaction, + }); - return models.CopilotRequest - .create(data, { transaction }); - }).then((copilotRequest) => { - /** - * Automatically approve the copilot request. - */ - const approveData = _.assign({ - projectId, - copilotRequestId: copilotRequest.id, - createdBy: req.authUser.userId, - updatedBy: req.authUser.userId, - type: copilotRequest.data.projectType, - opportunityTitle: copilotRequest.data.opportunityTitle, - startDate: copilotRequest.data.startDate, - }); - return approveRequest(req, approveData, transaction).then(() => copilotRequest); - }).then(copilotRequest => res.status(201).json(copilotRequest)) - .catch((err) => { - try { - transaction.rollback(); - } catch (e) { - _.noop(e); - } - return Promise.reject(err); - }); + if (!existingProject) { + const err = new Error(`Active project not found for project id ${projectId}`); + err.status = 404; + throw err; + } + + const existingRequest = await models.CopilotRequest.findOne({ + where: { + createdBy: req.authUser.userId, + projectId, + status: { + [Op.in]: [ + COPILOT_REQUEST_STATUS.NEW, + COPILOT_REQUEST_STATUS.APPROVED, + COPILOT_REQUEST_STATUS.SEEKING, + ], + }, + }, + transaction, }); - }) - .catch((err) => { - if (err.message) { - _.assign(err, { details: err.message }); + + if (existingRequest && existingRequest.data.projectType === data.data.projectType) { + const err = new Error('There\'s a request of same type already!'); + err.status = 400; + throw err; } - util.handleError('Error creating copilot request', err, req, next); + + const newRequest = await models.CopilotRequest.create(data, { transaction }); + + await approveRequest(req, { + projectId, + copilotRequestId: newRequest.id, + createdBy: req.authUser.userId, + updatedBy: req.authUser.userId, + type: newRequest.data.projectType, + opportunityTitle: newRequest.data.opportunityTitle, + startDate: newRequest.data.startDate, + }, transaction); + + return newRequest; }); + + return res.status(201).json(copilotRequest); + } catch (err) { + req.log.error('Error creating copilot request', { error: err }); + if (err.message) _.assign(err, { details: err.message }); + util.handleError('Error creating copilot request', err, req, next); + return undefined; + } }, ]; + From e84d3c0701e2d70cf2032628ecb2cc8de891875a Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Mon, 13 Oct 2025 17:22:37 +0530 Subject: [PATCH 6/6] remove extra logging --- src/routes/copilotRequest/approveRequest.service.js | 8 ++------ src/routes/copilotRequest/create.js | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/routes/copilotRequest/approveRequest.service.js b/src/routes/copilotRequest/approveRequest.service.js index 6a0eb064b..ed34c9df5 100644 --- a/src/routes/copilotRequest/approveRequest.service.js +++ b/src/routes/copilotRequest/approveRequest.service.js @@ -7,7 +7,8 @@ import { CONNECT_NOTIFICATION_EVENT, COPILOT_OPPORTUNITY_STATUS, COPILOT_REQUEST_STATUS, - TEMPLATE_IDS, USER_ROLE, + TEMPLATE_IDS, + USER_ROLE, } from '../../constants'; import util from '../../util'; import { createEvent } from '../../services/busApi'; @@ -26,8 +27,6 @@ module.exports = async (req, data, existingTransaction) => { return resolveTransaction(existingTransaction, async (transaction) => { try { - req.log.debug('approveRequest: finding project', { projectId }); - const existingProject = await models.Project.findOne({ where: { id: projectId, deletedAt: { $eq: null } }, transaction, @@ -40,7 +39,6 @@ module.exports = async (req, data, existingTransaction) => { } const copilotRequest = await models.CopilotRequest.findByPk(copilotRequestId, { transaction }); - req.log.debug('approveRequest: found copilot request', { copilotRequestId: copilotRequest.id }); if (!copilotRequest) { const err = new Error(`no active copilot request found for copilot request id ${copilotRequestId}`); @@ -49,7 +47,6 @@ module.exports = async (req, data, existingTransaction) => { } await copilotRequest.update({ status: COPILOT_REQUEST_STATUS.APPROVED }, { transaction }); - req.log.debug('Copilot request status updated to APPROVED', { copilotRequestId }); const existingOpportunity = await models.CopilotOpportunity.findOne({ where: { @@ -72,7 +69,6 @@ module.exports = async (req, data, existingTransaction) => { // Send notifications try { const roles = await util.getRolesByRoleName(USER_ROLE.TC_COPILOT, req.log, req.id); - req.log.debug('Roles fetched', { roles }); const { subjects = [] } = await util.getRoleInfo(roles[0], req.log, req.id); const emailEventType = CONNECT_NOTIFICATION_EVENT.EXTERNAL_ACTION_EMAIL; diff --git a/src/routes/copilotRequest/create.js b/src/routes/copilotRequest/create.js index e4bc586e6..0fcf1aaf7 100644 --- a/src/routes/copilotRequest/create.js +++ b/src/routes/copilotRequest/create.js @@ -60,7 +60,7 @@ module.exports = [ try { const copilotRequest = await models.sequelize.transaction(async (transaction) => { - req.log.debug('Starting create copilot request transaction', { data }); + req.log.debug('Create copilot request transaction', { data }); const existingProject = await models.Project.findOne({ where: { id: projectId, deletedAt: { $eq: null } },