From 61633948df63373338d7e0eb76ac6ddabe7fdf6c Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 14 Apr 2025 22:48:50 +0200 Subject: [PATCH 001/166] feat: apply for copilot opportunity --- ...0250411182312-copilot_opportunity_apply.js | 56 ++++++++++++++++++ src/models/copilotApplication.js | 30 ++++++++++ src/models/copilotOpportunity.js | 1 + src/permissions/constants.js | 12 ++++ src/routes/copilotOpportunityApply/create.js | 57 +++++++++++++++++++ src/routes/index.js | 4 ++ 6 files changed, 160 insertions(+) create mode 100644 migrations/umzug/migrations/20250411182312-copilot_opportunity_apply.js create mode 100644 src/models/copilotApplication.js create mode 100644 src/routes/copilotOpportunityApply/create.js diff --git a/migrations/umzug/migrations/20250411182312-copilot_opportunity_apply.js b/migrations/umzug/migrations/20250411182312-copilot_opportunity_apply.js new file mode 100644 index 00000000..f07f8ede --- /dev/null +++ b/migrations/umzug/migrations/20250411182312-copilot_opportunity_apply.js @@ -0,0 +1,56 @@ +'use strict'; + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.createTable('copilot_applications', { + id: { + type: Sequelize.BIGINT, + allowNull: false, + primaryKey: true, + autoIncrement: true, + }, + userId: { + type: Sequelize.BIGINT, + allowNull: false, + }, + opportunityId: { + type: Sequelize.BIGINT, + allowNull: false, + references: { + model: 'copilot_opportunities', + key: 'id', + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL', + }, + deletedAt: { + type: Sequelize.DATE, + allowNull: true, + }, + createdAt: { + type: Sequelize.DATE, + allowNull: true, + }, + updatedAt: { + type: Sequelize.DATE, + allowNull: true, + }, + deletedBy: { + type: Sequelize.BIGINT, + allowNull: true, + }, + createdBy: { + type: Sequelize.BIGINT, + allowNull: false, + }, + updatedBy: { + type: Sequelize.BIGINT, + allowNull: false, + }, + }); + }, + + down: async (queryInterface) => { + await queryInterface.dropTable('copilot_applications'); + } +}; diff --git a/src/models/copilotApplication.js b/src/models/copilotApplication.js new file mode 100644 index 00000000..a17c14e5 --- /dev/null +++ b/src/models/copilotApplication.js @@ -0,0 +1,30 @@ +import _ from 'lodash'; +import { COPILOT_OPPORTUNITY_STATUS, COPILOT_OPPORTUNITY_TYPE } from '../constants'; + +module.exports = function defineCopilotOpportunity(sequelize, DataTypes) { + const CopilotApplication = sequelize.define('CopilotApplication', { + id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, + opportunityId: { type: DataTypes.BIGINT, allowNull: false }, + userId: { type: DataTypes.BIGINT, allowNull: false }, + deletedAt: { type: DataTypes.DATE, allowNull: true }, + createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + updatedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + deletedBy: { type: DataTypes.INTEGER, allowNull: true }, + createdBy: { type: DataTypes.INTEGER, allowNull: false }, + updatedBy: { type: DataTypes.INTEGER, allowNull: false }, + }, { + tableName: 'copilot_opportunities', + paranoid: true, + timestamps: true, + updatedAt: 'updatedAt', + createdAt: 'createdAt', + deletedAt: 'deletedAt', + indexes: [], + }); + + CopilotApplication.associate = (models) => { + CopilotApplication.belongsTo(models.CopilotOpportunity, { as: 'copilotOpportunity', foreignKey: 'copilotOpportunityId' }); + }; + + return CopilotApplication; +}; diff --git a/src/models/copilotOpportunity.js b/src/models/copilotOpportunity.js index 7ce395c3..acb8f152 100644 --- a/src/models/copilotOpportunity.js +++ b/src/models/copilotOpportunity.js @@ -38,6 +38,7 @@ module.exports = function defineCopilotOpportunity(sequelize, DataTypes) { CopilotOpportunity.associate = (models) => { CopilotOpportunity.belongsTo(models.CopilotRequest, { as: 'copilotRequest', foreignKey: 'copilotRequestId' }); CopilotOpportunity.belongsTo(models.Project, { as: 'project', foreignKey: 'projectId' }); + CopilotOpportunity.belongsTo(models.CopilotApplication, { as: 'copilotApplication', foreignKey: 'copilotOpportunityId' }); }; return CopilotOpportunity; diff --git a/src/permissions/constants.js b/src/permissions/constants.js index 17585d30..a7f647cd 100644 --- a/src/permissions/constants.js +++ b/src/permissions/constants.js @@ -265,6 +265,18 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export scopes: SCOPES_PROJECTS_WRITE, }, + APPLY_COPILOT_OPPORTUNITY: { + meta: { + title: 'Apply copilot opportunity', + group: 'Apply Copilot', + description: 'Who can apply for copilot opportunity.', + }, + topcoderRoles: [ + USER_ROLE.COPILOT, + ], + scopes: SCOPES_PROJECTS_WRITE, + }, + MANAGE_PROJECT_BILLING_ACCOUNT_ID: { meta: { title: 'Manage Project property "billingAccountId"', diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js new file mode 100644 index 00000000..f961b4c0 --- /dev/null +++ b/src/routes/copilotOpportunityApply/create.js @@ -0,0 +1,57 @@ +import validate from 'express-validation'; +import _ from 'lodash'; +import Joi from 'joi'; + +import models from '../../models'; +import util from '../../util'; +import { PERMISSION } from '../../permissions/constants'; + +const addCopilotApplicationValidations = { + body: Joi.object().keys({ + data: Joi.object() + .keys({ + opportunityId: Joi.number().required(), + }) + .required(), + }), +}; + +module.exports = [ + validate(addCopilotApplicationValidations), + async (req, res, next) => { + const { data } = req.body; + if (!util.hasPermissionByReq(PERMISSION.APPLY_COPILOT_OPPORTUNITY, req)) { + const err = new Error('Unable to apply for copilot opportunity'); + _.assign(err, { + details: JSON.stringify({ message: 'You do not have permission to apply for copilot opportunity' }), + status: 403, + }); + return next(err); + } + // default values + _.assign(data, { + userId: req.authUser.userId, + createdBy: req.authUser.userId, + updatedBy: req.authUser.userId, + }); + + return models.CopilotOpportunity.findOne({ + where: { + id: data.opportunityId, + }, + }).then((opportunity) => { + if (!opportunity) { + const err = new Error('No opportunity found'); + err.status = 404; + return next(err); + } + + return models.CopilotApplication.create(data).catch((err) => { + util.handleError('Error creating copilot application', err, req, next); + return next(err); + }); + }).catch((e) => { + util.handleError('Error finding the copilot opportunity', err, req, next); + }); + }, +]; diff --git a/src/routes/index.js b/src/routes/index.js index b0022ad8..66b753f6 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -404,6 +404,10 @@ router.route('/v5/projects/copilots/opportunities') router.route('/v5/projects/copilot/opportunity/:id(\\d+)') .get(require('./copilotOpportunity/get')); +// Project copilot opportunity apply +router.route('/v5/projects/copilots/opportunity/:id(\\d+)/apply') + .post(require('./copilotOpportunityApply/create')); + // Project Estimation Items router.route('/v5/projects/:projectId(\\d+)/estimations/:estimationId(\\d+)/items') .get(require('./projectEstimationItems/list')); From 0372f83d2f3e02643cf414e0e99a9f5ed71b3395 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 14 Apr 2025 22:51:08 +0200 Subject: [PATCH 002/166] deploy pr branch to develo[ --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1acd4a4c..3dd6a457 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup'] + only: ['develop', 'migration-setup', 'PM-577'] - deployProd: context : org-global filters: From 06280ae2bc69c66e087445d13c7ace4d4552d26c Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Mon, 21 Apr 2025 10:35:06 +0530 Subject: [PATCH 003/166] add migration --- ...0549-add_copilotOpportunityId_to_request.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 migrations/umzug/migrations/20250417160549-add_copilotOpportunityId_to_request.js diff --git a/migrations/umzug/migrations/20250417160549-add_copilotOpportunityId_to_request.js b/migrations/umzug/migrations/20250417160549-add_copilotOpportunityId_to_request.js new file mode 100644 index 00000000..f21f18a9 --- /dev/null +++ b/migrations/umzug/migrations/20250417160549-add_copilotOpportunityId_to_request.js @@ -0,0 +1,18 @@ +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.addColumn('copilot_requests', 'copilotOpportunityId', { + type: Sequelize.BIGINT, + allowNull: false, + references: { + model: 'copilot_opportunities', + key: 'id', + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL', + }); + }, + + down: async (queryInterface) => { + await queryInterface.removeColumn('copilot_requests', 'copilotOpportunityId'); + }, +}; From 561906e004dfc4719416dff02632a8f870baa6e1 Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Mon, 21 Apr 2025 15:16:05 +0530 Subject: [PATCH 004/166] deploy to dev --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1acd4a4c..e6699829 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup'] + only: ['develop', 'migration-setup','PM-589-add-migration'] - deployProd: context : org-global filters: From 5ac2a16f8a1b2f2732bccd356af8adccdb4dc03a Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Tue, 22 Apr 2025 16:56:20 +0530 Subject: [PATCH 005/166] Fix migration script --- .../20250417160549-add_copilotOpportunityId_to_request.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/umzug/migrations/20250417160549-add_copilotOpportunityId_to_request.js b/migrations/umzug/migrations/20250417160549-add_copilotOpportunityId_to_request.js index f21f18a9..76c3a897 100644 --- a/migrations/umzug/migrations/20250417160549-add_copilotOpportunityId_to_request.js +++ b/migrations/umzug/migrations/20250417160549-add_copilotOpportunityId_to_request.js @@ -2,7 +2,7 @@ module.exports = { up: async (queryInterface, Sequelize) => { await queryInterface.addColumn('copilot_requests', 'copilotOpportunityId', { type: Sequelize.BIGINT, - allowNull: false, + allowNull: true, references: { model: 'copilot_opportunities', key: 'id', From 99a5053db2690d5239b7bc6ed5971150a5ed368d Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 22 Apr 2025 22:26:25 +0200 Subject: [PATCH 006/166] fix: permission issue to delete copilot invite --- .circleci/config.yml | 2 +- src/permissions/constants.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1acd4a4c..5e064aff 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup'] + only: ['develop', 'migration-setup', 'pm-1077'] - deployProd: context : org-global filters: diff --git a/src/permissions/constants.js b/src/permissions/constants.js index 17585d30..92b32dd4 100644 --- a/src/permissions/constants.js +++ b/src/permissions/constants.js @@ -585,6 +585,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export topcoderRoles: [ ...TOPCODER_ROLES_ADMINS, USER_ROLE.COPILOT_MANAGER, + USER_ROLE.PROJECT_MANAGER, ], projectRoles: PROJECT_ROLES_MANAGEMENT, scopes: SCOPES_PROJECT_INVITES_WRITE, From 0b40100d2e514f0c42cae1b25b41eaebf490d237 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 22 Apr 2025 22:56:48 +0200 Subject: [PATCH 007/166] fix: permission issue to delete copilot invite --- src/permissions/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/permissions/constants.js b/src/permissions/constants.js index 92b32dd4..b12c299f 100644 --- a/src/permissions/constants.js +++ b/src/permissions/constants.js @@ -585,7 +585,6 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export topcoderRoles: [ ...TOPCODER_ROLES_ADMINS, USER_ROLE.COPILOT_MANAGER, - USER_ROLE.PROJECT_MANAGER, ], projectRoles: PROJECT_ROLES_MANAGEMENT, scopes: SCOPES_PROJECT_INVITES_WRITE, @@ -600,6 +599,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export topcoderRoles: [ ...TOPCODER_ROLES_ADMINS, USER_ROLE.COPILOT_MANAGER, + USER_ROLE.PROJECT_MANAGER, ], scopes: SCOPES_PROJECT_INVITES_WRITE, }, From fef6473ddff1a41f92dd2eadc2d31cff35ce7a31 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 23 Apr 2025 00:39:21 +0200 Subject: [PATCH 008/166] fix: removed circle ci config --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5e064aff..1acd4a4c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup', 'pm-1077'] + only: ['develop', 'migration-setup'] - deployProd: context : org-global filters: From 5907e42dc092106bf40e1424315026d48fef82b6 Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Wed, 23 Apr 2025 18:00:02 +0530 Subject: [PATCH 009/166] Make copilot opportunity details route public --- src/routes/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/index.js b/src/routes/index.js index b0022ad8..f171dac3 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -26,7 +26,7 @@ router.get(`/${apiVersion}/projects/health`, (req, res) => { // List of public routes const publicRoutes = [ `/${apiVersion}/projects/copilots/opportunities`, - `/${apiVersion}/projects/copilot/opportunities/:id(\\d+)`, + `/${apiVersion}/projects/copilot/opportunity/:id(\\d+)`, ]; // All project service endpoints need authentication From 4adf1e9afb0b28b40c7b8c53c2153dd4880b09b5 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 23 Apr 2025 19:34:58 +0200 Subject: [PATCH 010/166] removed circle config --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e6699829..1acd4a4c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup','PM-589-add-migration'] + only: ['develop', 'migration-setup'] - deployProd: context : org-global filters: From cffc8c0c6a58a32c8f844d38b850f7cca24c944a Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 23 Apr 2025 20:51:27 +0200 Subject: [PATCH 011/166] updated swagger doc --- docs/swagger.yaml | 38 ++++++++++++++++++-- src/routes/copilotOpportunityApply/create.js | 6 ++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index efe6e6d7..d52ba5e5 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -411,7 +411,7 @@ paths: "/projects/copilots/opportunities": get: tags: - - projects copilot opportunities + - projects copilot opportunity operationId: getAllCopilotOpportunities security: - Bearer: [] @@ -444,7 +444,7 @@ paths: description: "Internal Server Error" schema: $ref: "#/definitions/ErrorModel" - "/projects/copilots/opportunities/{copilotOpportunityId}": + "/projects/copilots/opportunity/{copilotOpportunityId}": get: tags: - projects copilot opportunity @@ -471,6 +471,33 @@ paths: description: "Internal Server Error" schema: $ref: "#/definitions/ErrorModel" + "/projects/copilots/opportunity/{copilotOpportunityId}/apply": + post: + tags: + - projects copilot opportunity + operationId: applyCopilotOpportunity + security: + - Bearer: [] + description: "Retrieve a specific copilot opportunity." + parameters: + - $ref: "#/parameters/copilotOpportunityIdParam" + responses: + "200": + description: "The copilot opportunity" + schema: + $ref: "#/definitions/CopilotOpportunity" + "401": + description: "Unauthorized" + schema: + $ref: "#/definitions/ErrorModel" + "403": + description: "Forbidden - User does not have permission" + schema: + $ref: "#/definitions/ErrorModel" + "500": + description: "Internal Server Error" + schema: + $ref: "#/definitions/ErrorModel" "/projects/{projectId}/attachments": get: tags: @@ -5448,6 +5475,13 @@ parameters: required: true type: integer format: int64 + copilotOpportunityIdParam: + name: copilotOpportunityId + in: path + description: copilot opportunity identifier + required: true + type: integer + format: int64 phaseIdParam: name: phaseId in: path diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index f961b4c0..9daf2e40 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -19,7 +19,8 @@ const addCopilotApplicationValidations = { module.exports = [ validate(addCopilotApplicationValidations), async (req, res, next) => { - const { data } = req.body; + const data = {}; + const copilotOpportunityId = _.parseInt(req.params.id); if (!util.hasPermissionByReq(PERMISSION.APPLY_COPILOT_OPPORTUNITY, req)) { const err = new Error('Unable to apply for copilot opportunity'); _.assign(err, { @@ -33,11 +34,12 @@ module.exports = [ userId: req.authUser.userId, createdBy: req.authUser.userId, updatedBy: req.authUser.userId, + opportunityId: copilotOpportunityId, }); return models.CopilotOpportunity.findOne({ where: { - id: data.opportunityId, + id: copilotOpportunityId, }, }).then((opportunity) => { if (!opportunity) { From bf83bcb8fb9bef5774eae710ec5d7b6a10169459 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 23 Apr 2025 23:30:51 +0200 Subject: [PATCH 012/166] fix: removed validation --- src/routes/copilotOpportunityApply/create.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index 9daf2e40..94978d9e 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -1,23 +1,10 @@ -import validate from 'express-validation'; import _ from 'lodash'; -import Joi from 'joi'; import models from '../../models'; import util from '../../util'; import { PERMISSION } from '../../permissions/constants'; -const addCopilotApplicationValidations = { - body: Joi.object().keys({ - data: Joi.object() - .keys({ - opportunityId: Joi.number().required(), - }) - .required(), - }), -}; - module.exports = [ - validate(addCopilotApplicationValidations), async (req, res, next) => { const data = {}; const copilotOpportunityId = _.parseInt(req.params.id); From dc392192349a43e750c18f6347dcc0a2728dfdc1 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 24 Apr 2025 00:39:04 +0200 Subject: [PATCH 013/166] fix: reference error --- src/routes/copilotOpportunityApply/create.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index 94978d9e..f25e988c 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -40,7 +40,7 @@ module.exports = [ return next(err); }); }).catch((e) => { - util.handleError('Error finding the copilot opportunity', err, req, next); + util.handleError('Error applying for copilot opportunity', e, req, next); }); }, ]; From 22cc47435255c48505039d081014a0a6fa51dbd8 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 24 Apr 2025 01:04:29 +0200 Subject: [PATCH 014/166] fix: model --- src/models/copilotApplication.js | 2 +- src/models/copilotOpportunity.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/models/copilotApplication.js b/src/models/copilotApplication.js index a17c14e5..ed72a8bc 100644 --- a/src/models/copilotApplication.js +++ b/src/models/copilotApplication.js @@ -23,7 +23,7 @@ module.exports = function defineCopilotOpportunity(sequelize, DataTypes) { }); CopilotApplication.associate = (models) => { - CopilotApplication.belongsTo(models.CopilotOpportunity, { as: 'copilotOpportunity', foreignKey: 'copilotOpportunityId' }); + CopilotApplication.belongsTo(models.CopilotOpportunity, { as: 'copilotOpportunity', foreignKey: 'opportunityId' }); }; return CopilotApplication; diff --git a/src/models/copilotOpportunity.js b/src/models/copilotOpportunity.js index acb8f152..cf2c8f8d 100644 --- a/src/models/copilotOpportunity.js +++ b/src/models/copilotOpportunity.js @@ -38,7 +38,7 @@ module.exports = function defineCopilotOpportunity(sequelize, DataTypes) { CopilotOpportunity.associate = (models) => { CopilotOpportunity.belongsTo(models.CopilotRequest, { as: 'copilotRequest', foreignKey: 'copilotRequestId' }); CopilotOpportunity.belongsTo(models.Project, { as: 'project', foreignKey: 'projectId' }); - CopilotOpportunity.belongsTo(models.CopilotApplication, { as: 'copilotApplication', foreignKey: 'copilotOpportunityId' }); + CopilotOpportunity.hasMany(models.CopilotApplication, { as: 'copilotApplication', foreignKey: 'opportunityId' }); }; return CopilotOpportunity; From 472acdfeff5e9bf48ea9695e7b2b3491677ed674 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 24 Apr 2025 14:30:05 +0200 Subject: [PATCH 015/166] fix: model --- src/models/copilotApplication.js | 2 +- src/models/copilotOpportunity.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/models/copilotApplication.js b/src/models/copilotApplication.js index ed72a8bc..719c3a0d 100644 --- a/src/models/copilotApplication.js +++ b/src/models/copilotApplication.js @@ -23,7 +23,7 @@ module.exports = function defineCopilotOpportunity(sequelize, DataTypes) { }); CopilotApplication.associate = (models) => { - CopilotApplication.belongsTo(models.CopilotOpportunity, { as: 'copilotOpportunity', foreignKey: 'opportunityId' }); + CopilotApplication.hasMany(models.CopilotOpportunity, { as: 'copilotOpportunity', foreignKey: 'opportunityId' }); }; return CopilotApplication; diff --git a/src/models/copilotOpportunity.js b/src/models/copilotOpportunity.js index cf2c8f8d..df3d7bf6 100644 --- a/src/models/copilotOpportunity.js +++ b/src/models/copilotOpportunity.js @@ -38,7 +38,7 @@ module.exports = function defineCopilotOpportunity(sequelize, DataTypes) { CopilotOpportunity.associate = (models) => { CopilotOpportunity.belongsTo(models.CopilotRequest, { as: 'copilotRequest', foreignKey: 'copilotRequestId' }); CopilotOpportunity.belongsTo(models.Project, { as: 'project', foreignKey: 'projectId' }); - CopilotOpportunity.hasMany(models.CopilotApplication, { as: 'copilotApplication', foreignKey: 'opportunityId' }); + CopilotOpportunity.belongsTo(models.CopilotApplication, { as: 'copilotApplication', foreignKey: 'opportunityId' }); }; return CopilotOpportunity; From 5a0b44fd46183afbeaf0d0063b152454c7dff1e5 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 24 Apr 2025 15:17:37 +0200 Subject: [PATCH 016/166] fix: model --- src/models/copilotOpportunity.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/models/copilotOpportunity.js b/src/models/copilotOpportunity.js index df3d7bf6..7ce395c3 100644 --- a/src/models/copilotOpportunity.js +++ b/src/models/copilotOpportunity.js @@ -38,7 +38,6 @@ module.exports = function defineCopilotOpportunity(sequelize, DataTypes) { CopilotOpportunity.associate = (models) => { CopilotOpportunity.belongsTo(models.CopilotRequest, { as: 'copilotRequest', foreignKey: 'copilotRequestId' }); CopilotOpportunity.belongsTo(models.Project, { as: 'project', foreignKey: 'projectId' }); - CopilotOpportunity.belongsTo(models.CopilotApplication, { as: 'copilotApplication', foreignKey: 'opportunityId' }); }; return CopilotOpportunity; From 7c637115e0cd4987107f25cdaca7efc98d6bfd5a Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 24 Apr 2025 15:59:58 +0200 Subject: [PATCH 017/166] fix: model --- src/models/copilotApplication.js | 4 ---- src/models/copilotOpportunity.js | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/models/copilotApplication.js b/src/models/copilotApplication.js index 719c3a0d..d71c95cf 100644 --- a/src/models/copilotApplication.js +++ b/src/models/copilotApplication.js @@ -22,9 +22,5 @@ module.exports = function defineCopilotOpportunity(sequelize, DataTypes) { indexes: [], }); - CopilotApplication.associate = (models) => { - CopilotApplication.hasMany(models.CopilotOpportunity, { as: 'copilotOpportunity', foreignKey: 'opportunityId' }); - }; - return CopilotApplication; }; diff --git a/src/models/copilotOpportunity.js b/src/models/copilotOpportunity.js index 7ce395c3..81355750 100644 --- a/src/models/copilotOpportunity.js +++ b/src/models/copilotOpportunity.js @@ -38,6 +38,7 @@ module.exports = function defineCopilotOpportunity(sequelize, DataTypes) { CopilotOpportunity.associate = (models) => { CopilotOpportunity.belongsTo(models.CopilotRequest, { as: 'copilotRequest', foreignKey: 'copilotRequestId' }); CopilotOpportunity.belongsTo(models.Project, { as: 'project', foreignKey: 'projectId' }); + CopilotOpportunity.hasMany(models.CopilotApplication, { as: 'copilotApplications', foreignKey: 'copilotOpportunityId' }); }; return CopilotOpportunity; From f0d083f27835af48a470aa2ac265ebcc62da2b6e Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 24 Apr 2025 16:04:42 +0200 Subject: [PATCH 018/166] fix: model --- src/models/copilotApplication.js | 4 ++++ src/models/copilotOpportunity.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/models/copilotApplication.js b/src/models/copilotApplication.js index d71c95cf..ed72a8bc 100644 --- a/src/models/copilotApplication.js +++ b/src/models/copilotApplication.js @@ -22,5 +22,9 @@ module.exports = function defineCopilotOpportunity(sequelize, DataTypes) { indexes: [], }); + CopilotApplication.associate = (models) => { + CopilotApplication.belongsTo(models.CopilotOpportunity, { as: 'copilotOpportunity', foreignKey: 'opportunityId' }); + }; + return CopilotApplication; }; diff --git a/src/models/copilotOpportunity.js b/src/models/copilotOpportunity.js index 81355750..446cab81 100644 --- a/src/models/copilotOpportunity.js +++ b/src/models/copilotOpportunity.js @@ -38,7 +38,7 @@ module.exports = function defineCopilotOpportunity(sequelize, DataTypes) { CopilotOpportunity.associate = (models) => { CopilotOpportunity.belongsTo(models.CopilotRequest, { as: 'copilotRequest', foreignKey: 'copilotRequestId' }); CopilotOpportunity.belongsTo(models.Project, { as: 'project', foreignKey: 'projectId' }); - CopilotOpportunity.hasMany(models.CopilotApplication, { as: 'copilotApplications', foreignKey: 'copilotOpportunityId' }); + CopilotOpportunity.hasMany(models.CopilotApplication, { as: 'copilotApplications', foreignKey: 'opportunityId' }); }; return CopilotOpportunity; From aa606699fe723c68647ea278f28bf5de37c376c9 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 24 Apr 2025 16:41:03 +0200 Subject: [PATCH 019/166] fix: model --- new | 12 ++++++++++++ src/models/copilotApplication.js | 13 +++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 new diff --git a/new b/new new file mode 100644 index 00000000..586be550 --- /dev/null +++ b/new @@ -0,0 +1,12 @@ +## +# Host Database +# +# localhost is used to configure the loopback interface +# when the system is booting. Do not change this entry. +## +127.0.0.1 dockerhost +127.0.0.1 localhost +255.255.255.255 broadcasthost +::1 localhost +127.0.0.1 local.topcoder-dev.com +127.0.0.1 local.topcoder.com diff --git a/src/models/copilotApplication.js b/src/models/copilotApplication.js index ed72a8bc..d50e22d0 100644 --- a/src/models/copilotApplication.js +++ b/src/models/copilotApplication.js @@ -4,7 +4,16 @@ import { COPILOT_OPPORTUNITY_STATUS, COPILOT_OPPORTUNITY_TYPE } from '../constan module.exports = function defineCopilotOpportunity(sequelize, DataTypes) { const CopilotApplication = sequelize.define('CopilotApplication', { id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, - opportunityId: { type: DataTypes.BIGINT, allowNull: false }, + opportunityId: { + type: DataTypes.BIGINT, + allowNull: false, + references: { + model: 'copilot_opportunities', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, userId: { type: DataTypes.BIGINT, allowNull: false }, deletedAt: { type: DataTypes.DATE, allowNull: true }, createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, @@ -13,7 +22,7 @@ module.exports = function defineCopilotOpportunity(sequelize, DataTypes) { createdBy: { type: DataTypes.INTEGER, allowNull: false }, updatedBy: { type: DataTypes.INTEGER, allowNull: false }, }, { - tableName: 'copilot_opportunities', + tableName: 'copilot_applications', paranoid: true, timestamps: true, updatedAt: 'updatedAt', From 47664c866f7d5daf84ec4e7ff7a11acb28b8110d Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 24 Apr 2025 17:17:35 +0200 Subject: [PATCH 020/166] fix: model --- src/routes/copilotOpportunityApply/create.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index f25e988c..79d4e673 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -35,7 +35,12 @@ module.exports = [ return next(err); } - return models.CopilotApplication.create(data).catch((err) => { + return models.CopilotApplication.create(data) + .then((result) => { + res.status(201).json(result); + return Promise.resolve(); + }) + .catch((err) => { util.handleError('Error creating copilot application', err, req, next); return next(err); }); From 1978a22971e7004015b9e5bcbd158ec66026dcac Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 24 Apr 2025 17:21:54 +0200 Subject: [PATCH 021/166] fix: validations --- src/routes/copilotOpportunityApply/create.js | 22 +++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index 79d4e673..016a25fb 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -3,6 +3,7 @@ import _ from 'lodash'; import models from '../../models'; import util from '../../util'; import { PERMISSION } from '../../permissions/constants'; +import { COPILOT_OPPORTUNITY_STATUS } from '../../constants'; module.exports = [ async (req, res, next) => { @@ -28,12 +29,31 @@ module.exports = [ where: { id: copilotOpportunityId, }, - }).then((opportunity) => { + }).then(async (opportunity) => { if (!opportunity) { const err = new Error('No opportunity found'); err.status = 404; return next(err); } + + if (opportunity.status !== COPILOT_OPPORTUNITY_STATUS.ACTIVE) { + const err = new Error('Opportunity is not active'); + err.status = 400; + return next(err); + } + + const existingApplication = await models.CopilotApplication.findOne({ + where: { + opportunityId: opportunity.id, + userId: req.authUser.userId, + }, + }); + + if (existingApplication) { + const err = new Error('User already applied for this opportunity'); + err.status = 400; + return next(err); + } return models.CopilotApplication.create(data) .then((result) => { From 3cac3253416c46434ef2fee9048b6cede122ba4d Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 24 Apr 2025 17:42:06 +0200 Subject: [PATCH 022/166] removed unnecessary file --- new | 12 ------------ src/models/copilotApplication.js | 1 - 2 files changed, 13 deletions(-) delete mode 100644 new diff --git a/new b/new deleted file mode 100644 index 586be550..00000000 --- a/new +++ /dev/null @@ -1,12 +0,0 @@ -## -# Host Database -# -# localhost is used to configure the loopback interface -# when the system is booting. Do not change this entry. -## -127.0.0.1 dockerhost -127.0.0.1 localhost -255.255.255.255 broadcasthost -::1 localhost -127.0.0.1 local.topcoder-dev.com -127.0.0.1 local.topcoder.com diff --git a/src/models/copilotApplication.js b/src/models/copilotApplication.js index d50e22d0..15366a92 100644 --- a/src/models/copilotApplication.js +++ b/src/models/copilotApplication.js @@ -1,5 +1,4 @@ import _ from 'lodash'; -import { COPILOT_OPPORTUNITY_STATUS, COPILOT_OPPORTUNITY_TYPE } from '../constants'; module.exports = function defineCopilotOpportunity(sequelize, DataTypes) { const CopilotApplication = sequelize.define('CopilotApplication', { From 9ba39a36e5539077cbf4a31de62f872e7e899b5b Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Thu, 24 Apr 2025 18:59:40 +0300 Subject: [PATCH 023/166] enable AI Review buddy --- .github/workflows/code_reviewer.yml | 22 +++++ fix.txt | 143 ++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 .github/workflows/code_reviewer.yml create mode 100644 fix.txt diff --git a/.github/workflows/code_reviewer.yml b/.github/workflows/code_reviewer.yml new file mode 100644 index 00000000..333b5bab --- /dev/null +++ b/.github/workflows/code_reviewer.yml @@ -0,0 +1,22 @@ +name: AI PR Reviewer + +on: + pull_request: + types: + - opened + - synchronize +permissions: + pull-requests: write +jobs: + tc-ai-pr-review: + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + + - name: TC AI PR Reviewer + uses: topcoder-platform/tc-ai-pr-reviewer@master + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # The GITHUB_TOKEN is there by default so you just need to keep it like it is and not necessarily need to add it as secret as it will throw an error. [More Details](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret) + LAB45_API_KEY: ${{ secrets.LAB45_API_KEY }} + exclude: '**/*.json, **/*.md, **/*.jpg, **/*.png, **/*.jpeg, **/*.bmp, **/*.webp' # Optional: exclude patterns separated by commas diff --git a/fix.txt b/fix.txt new file mode 100644 index 00000000..e56da10a --- /dev/null +++ b/fix.txt @@ -0,0 +1,143 @@ +commit 4b5eecc32d6cf2e5b8f4bc00d1b56b27447e88ba +Author: himaniraghav3 +Date: Thu Feb 27 14:33:45 2025 +0530 + + Add pagination to copilot opportunities response + +diff --git a/src/routes/copilotOpportunity/get.js b/src/routes/copilotOpportunity/get.js +index 9ceb6a57..a968af13 100644 +--- a/src/routes/copilotOpportunity/get.js ++++ b/src/routes/copilotOpportunity/get.js +@@ -1,9 +1,5 @@ +-import _ from 'lodash'; +- + import models from '../../models'; +-import { ADMIN_ROLES } from '../../constants'; + import util from '../../util'; +-import { PERMISSION } from '../../permissions/constants'; + + module.exports = [ + (req, res, next) => { +@@ -13,26 +9,26 @@ module.exports = [ + return util.handleError('Invalid opportunity ID', null, req, next, 400); + } + +- models.CopilotOpportunity.findOne({ +- where: { id }, +- include: [ +- { +- model: models.CopilotRequest, +- as: 'copilotRequest', +- }, +- { +- model: models.Project, +- as: 'project', +- attributes: ['name'], +- } +- ], ++ return models.CopilotOpportunity.findOne({ ++ where: { id }, ++ include: [ ++ { ++ model: models.CopilotRequest, ++ as: 'copilotRequest', ++ }, ++ { ++ model: models.Project, ++ as: 'project', ++ attributes: ['name'], ++ }, ++ ], + }) + .then((copilotOpportunity) => { +- const plainOpportunity = copilotOpportunity.get({ plain: true }); +- const formattedOpportunity = Object.assign({}, plainOpportunity, +- plainOpportunity.copilotRequest ? plainOpportunity.copilotRequest.data : {}, +- { copilotRequest: undefined } +- ); ++ const plainOpportunity = copilotOpportunity.get({ plain: true }); ++ const formattedOpportunity = Object.assign({}, plainOpportunity, ++ plainOpportunity.copilotRequest ? plainOpportunity.copilotRequest.data : {}, ++ { copilotRequest: undefined }, ++ ); + res.json(formattedOpportunity); + }) + .catch((err) => { +diff --git a/src/routes/copilotOpportunity/list.js b/src/routes/copilotOpportunity/list.js +index 1f1e003f..f8d45bde 100644 +--- a/src/routes/copilotOpportunity/list.js ++++ b/src/routes/copilotOpportunity/list.js +@@ -1,9 +1,7 @@ + import _ from 'lodash'; + + import models from '../../models'; +-import { ADMIN_ROLES } from '../../constants'; + import util from '../../util'; +-import { PERMISSION } from '../../permissions/constants'; + + module.exports = [ + (req, res, next) => { +@@ -17,29 +15,42 @@ module.exports = [ + } + const sortParams = sort.split(' '); + +- models.CopilotOpportunity.findAll({ +- include: [ +- { +- model: models.CopilotRequest, +- as: 'copilotRequest', +- }, +- { +- model: models.Project, +- as: 'project', +- attributes: ['name'], +- } +- ], +- order: [[sortParams[0], sortParams[1]]], ++ // Extract pagination parameters ++ const page = parseInt(req.query.page, 10) || 1; ++ const pageSize = parseInt(req.query.pageSize, 10) || 10; ++ const offset = (page - 1) * pageSize; ++ const limit = pageSize; ++ ++ return models.CopilotOpportunity.findAll({ ++ include: [ ++ { ++ model: models.CopilotRequest, ++ as: 'copilotRequest', ++ }, ++ { ++ model: models.Project, ++ as: 'project', ++ attributes: ['name'], ++ }, ++ ], ++ order: [[sortParams[0], sortParams[1]]], ++ limit, ++ offset, + }) +- .then(copilotOpportunities => { +- const formattedOpportunities = copilotOpportunities.map(opportunity => { +- const plainOpportunity = opportunity.get({ plain: true }); +- return Object.assign({}, plainOpportunity, +- plainOpportunity.copilotRequest ? plainOpportunity.copilotRequest.data : {}, +- { copilotRequest: undefined } +- ); ++ .then((copilotOpportunities) => { ++ const formattedOpportunities = copilotOpportunities.map((opportunity) => { ++ const plainOpportunity = opportunity.get({ plain: true }); ++ return Object.assign({}, plainOpportunity, ++ plainOpportunity.copilotRequest ? plainOpportunity.copilotRequest.data : {}, ++ { copilotRequest: undefined }, ++ ); ++ }); ++ return util.setPaginationHeaders(req, res, { ++ count: copilotOpportunities.count, ++ rows: formattedOpportunities, ++ page, ++ pageSize, + }); +- return res.json(formattedOpportunities); + }) + .catch((err) => { + util.handleError('Error fetching copilot opportunities', err, req, next); From 7ad57ecae2e8351f007291ef56efc3935f2dffc3 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 28 Apr 2025 17:20:07 +0200 Subject: [PATCH 024/166] fix: added notes column --- .../migrations/20250411182312-copilot_opportunity_apply.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/migrations/umzug/migrations/20250411182312-copilot_opportunity_apply.js b/migrations/umzug/migrations/20250411182312-copilot_opportunity_apply.js index f07f8ede..7d29919e 100644 --- a/migrations/umzug/migrations/20250411182312-copilot_opportunity_apply.js +++ b/migrations/umzug/migrations/20250411182312-copilot_opportunity_apply.js @@ -23,6 +23,10 @@ module.exports = { onUpdate: 'CASCADE', onDelete: 'SET NULL', }, + notes: { + type: Sequelize.TEXT, + allowNull: true, + }, deletedAt: { type: Sequelize.DATE, allowNull: true, From 8dfcdf0333e517df3f37f93f35864104b4455235 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 28 Apr 2025 17:23:34 +0200 Subject: [PATCH 025/166] fix: added notes from request --- src/routes/copilotOpportunityApply/create.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index 016a25fb..3e96bb9d 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -1,13 +1,24 @@ import _ from 'lodash'; +import validate from 'express-validation'; import models from '../../models'; import util from '../../util'; import { PERMISSION } from '../../permissions/constants'; import { COPILOT_OPPORTUNITY_STATUS } from '../../constants'; +const applyCopilotRequestValidations = { + body: Joi.object().keys({ + data: Joi.object() + .keys({ + notes: Joi.string(), + }), + }), +}; + module.exports = [ + validate(applyCopilotRequestValidations), async (req, res, next) => { - const data = {}; + const data = req.body; const copilotOpportunityId = _.parseInt(req.params.id); if (!util.hasPermissionByReq(PERMISSION.APPLY_COPILOT_OPPORTUNITY, req)) { const err = new Error('Unable to apply for copilot opportunity'); From 4a13b50867fce1062a08b967494d93c4992e915f Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 28 Apr 2025 17:29:03 +0200 Subject: [PATCH 026/166] updated swagger file --- docs/swagger.yaml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index d52ba5e5..e38fbe25 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -11,10 +11,10 @@ info: You can also set a custom page size up to 100 with the `perPage` parameter. Pagination response data is included in http headers. By Default, the response header contains links with `next`, `last`, `first`, `prev` resource links. -host: "localhost:3000" +host: "api.topcoder-dev.com" basePath: /v5 schemes: - - http + - https produces: - application/json consumes: @@ -481,6 +481,10 @@ paths: description: "Retrieve a specific copilot opportunity." parameters: - $ref: "#/parameters/copilotOpportunityIdParam" + - in: body + name: body + schema: + $ref: "#/definitions/ApplyCopilotOpportunity" responses: "200": description: "The copilot opportunity" @@ -6218,6 +6222,13 @@ definitions: - customer - manager - copilot + ApplyCopilotOpportunity: + title: Apply copilot CopilotOpportunity + type: object + properties: + notes: + type: string + description: notes about applying copilot opportunity NewProjectAttachment: title: Project attachment request type: object From 350d155902431fab16036a387f2ee13c13323594 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 28 Apr 2025 17:41:02 +0200 Subject: [PATCH 027/166] debug logs --- src/routes/copilotOpportunityApply/create.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index 3e96bb9d..0df91020 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -1,5 +1,6 @@ import _ from 'lodash'; import validate from 'express-validation'; +import Joi from 'joi'; import models from '../../models'; import util from '../../util'; @@ -19,6 +20,7 @@ module.exports = [ validate(applyCopilotRequestValidations), async (req, res, next) => { const data = req.body; + console.log(data, 'debug data'); const copilotOpportunityId = _.parseInt(req.params.id); if (!util.hasPermissionByReq(PERMISSION.APPLY_COPILOT_OPPORTUNITY, req)) { const err = new Error('Unable to apply for copilot opportunity'); From c922d0c7daf96e3384b357b9cc762068f77a7b4f Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 29 Apr 2025 19:17:49 +0200 Subject: [PATCH 028/166] added notes to model --- src/models/copilotApplication.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/models/copilotApplication.js b/src/models/copilotApplication.js index 15366a92..c472da60 100644 --- a/src/models/copilotApplication.js +++ b/src/models/copilotApplication.js @@ -13,6 +13,10 @@ module.exports = function defineCopilotOpportunity(sequelize, DataTypes) { onUpdate: 'CASCADE', onDelete: 'CASCADE' }, + notes: { + type: DataTypes.TEXT, + allowNull: true + }, userId: { type: DataTypes.BIGINT, allowNull: false }, deletedAt: { type: DataTypes.DATE, allowNull: true }, createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, From c3e8fca971a14c2fb7247794298442d8ae5b547c Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 29 Apr 2025 20:22:06 +0200 Subject: [PATCH 029/166] sanitize notes --- src/routes/copilotOpportunityApply/create.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index 0df91020..157649e0 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -9,18 +9,14 @@ import { COPILOT_OPPORTUNITY_STATUS } from '../../constants'; const applyCopilotRequestValidations = { body: Joi.object().keys({ - data: Joi.object() - .keys({ - notes: Joi.string(), - }), + notes: Joi.string(), }), }; module.exports = [ validate(applyCopilotRequestValidations), async (req, res, next) => { - const data = req.body; - console.log(data, 'debug data'); + const { notes } = req.body; const copilotOpportunityId = _.parseInt(req.params.id); if (!util.hasPermissionByReq(PERMISSION.APPLY_COPILOT_OPPORTUNITY, req)) { const err = new Error('Unable to apply for copilot opportunity'); @@ -36,8 +32,11 @@ module.exports = [ createdBy: req.authUser.userId, updatedBy: req.authUser.userId, opportunityId: copilotOpportunityId, + notes: notes ? req.sanitize(notes) : null, }); + console.log(data, 'debug data data'); + return models.CopilotOpportunity.findOne({ where: { id: copilotOpportunityId, From 5329b81dae72bac4cf511e0989064ecba9f52dd8 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 29 Apr 2025 20:41:39 +0200 Subject: [PATCH 030/166] sanitize notes --- src/routes/copilotOpportunityApply/create.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index 157649e0..51365269 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -26,14 +26,14 @@ module.exports = [ }); return next(err); } - // default values - _.assign(data, { + + const data = { userId: req.authUser.userId, createdBy: req.authUser.userId, updatedBy: req.authUser.userId, opportunityId: copilotOpportunityId, notes: notes ? req.sanitize(notes) : null, - }); + }; console.log(data, 'debug data data'); From ca59b3302b6016d6e220a66217f4a90cbec67235 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 29 Apr 2025 21:42:53 +0200 Subject: [PATCH 031/166] removed console log --- src/routes/copilotOpportunityApply/create.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index 51365269..dcf2cbe5 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -35,8 +35,6 @@ module.exports = [ notes: notes ? req.sanitize(notes) : null, }; - console.log(data, 'debug data data'); - return models.CopilotOpportunity.findOne({ where: { id: copilotOpportunityId, From f0e40c1dcaa090bf930082dc34f16aff19c394dd Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Wed, 30 Apr 2025 17:11:08 +0530 Subject: [PATCH 032/166] PM-1068 refactor: allow PMs to view all copilot requests --- src/routes/copilotRequest/list.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/routes/copilotRequest/list.js b/src/routes/copilotRequest/list.js index 66f36606..a36a3d7b 100644 --- a/src/routes/copilotRequest/list.js +++ b/src/routes/copilotRequest/list.js @@ -1,7 +1,6 @@ import _ from 'lodash'; import models from '../../models'; -import { ADMIN_ROLES } from '../../constants'; import util from '../../util'; import { PERMISSION } from '../../permissions/constants'; @@ -16,9 +15,6 @@ module.exports = [ return next(err); } - const isAdmin = util.hasRoles(req, ADMIN_ROLES); - - const userId = req.authUser.userId; const projectId = _.parseInt(req.params.projectId); let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'createdAt desc'; @@ -31,11 +27,7 @@ module.exports = [ } const sortParams = sort.split(' '); - // Admin can see all requests and the PM can only see requests created by them - const whereCondition = _.assign({}, - isAdmin ? {} : { createdBy: userId }, - projectId ? { projectId } : {}, - ); + const whereCondition = projectId ? { projectId } : {}; return models.CopilotRequest.findAll({ where: whereCondition, From a0e0d8bd32f7e485b9026c95351adc9aab454258 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Fri, 2 May 2025 10:30:20 +0200 Subject: [PATCH 033/166] fix: return existing application instead of throwing an error --- docs/swagger.yaml | 44 +++++++++++++++++++- src/routes/copilotOpportunityApply/create.js | 5 +-- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index e38fbe25..dbadf9e6 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -487,9 +487,9 @@ paths: $ref: "#/definitions/ApplyCopilotOpportunity" responses: "200": - description: "The copilot opportunity" + description: "The copilot opportunity application" schema: - $ref: "#/definitions/CopilotOpportunity" + $ref: "#/definitions/CopilotOpportunityApplication" "401": description: "Unauthorized" schema: @@ -6013,6 +6013,46 @@ definitions: format: int64 description: READ-ONLY. User that last updated this task readOnly: true + CopilotOpportunityApplication: + type: object + properties: + id: + description: unique identifier + type: integer + format: int64 + notes: + description: notes regarding the application + type: string + opportunityId: + description: copilot request id + type: integer + createdAt: + type: string + description: Datetime (GMT) when task was created + readOnly: true + createdBy: + type: integer + format: int64 + description: READ-ONLY. User who created this task + readOnly: true + updatedAt: + type: string + description: READ-ONLY. Datetime (GMT) when task was updated + readOnly: true + updatedBy: + type: integer + format: int64 + description: READ-ONLY. User that last updated this task + readOnly: true + deletedAt: + type: string + description: READ-ONLY. Datetime (GMT) when task was deleted + readOnly: true + deletedBy: + type: integer + format: int64 + description: READ-ONLY. User that deleted this task + readOnly: true Project: type: object properties: diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index dcf2cbe5..0093a217 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -60,9 +60,8 @@ module.exports = [ }); if (existingApplication) { - const err = new Error('User already applied for this opportunity'); - err.status = 400; - return next(err); + res.status(200).json(existingApplication); + return Promise.resolve(); } return models.CopilotApplication.create(data) From 7be3b140103bf7c8a01fd5630cd5fc9459400313 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Fri, 2 May 2025 10:30:42 +0200 Subject: [PATCH 034/166] fix: return existing application instead of throwing an error --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3dd6a457..6b4560c1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup', 'PM-577'] + only: ['develop', 'migration-setup', 'pm-577_1'] - deployProd: context : org-global filters: From 1bd520d41794b81db27a2a2346bd8a4eacbadcd7 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Fri, 2 May 2025 17:48:46 +0200 Subject: [PATCH 035/166] removed circle branch config --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6b4560c1..1acd4a4c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup', 'pm-577_1'] + only: ['develop', 'migration-setup'] - deployProd: context : org-global filters: From 93c1ca9cb8d625b8da8b90a192c0f1e595b92d51 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Fri, 2 May 2025 23:54:04 +0200 Subject: [PATCH 036/166] feat: list copilot applications --- src/permissions/constants.js | 15 ++++++ src/permissions/copilotApplications.view.js | 55 +++++++++++++++++++++ src/permissions/index.js | 4 ++ src/routes/copilotOpportunityApply/list.js | 47 ++++++++++++++++++ src/routes/index.js | 2 + src/util.js | 17 +++++++ 6 files changed, 140 insertions(+) create mode 100644 src/permissions/copilotApplications.view.js create mode 100644 src/routes/copilotOpportunityApply/list.js diff --git a/src/permissions/constants.js b/src/permissions/constants.js index 4395021e..85c38465 100644 --- a/src/permissions/constants.js +++ b/src/permissions/constants.js @@ -277,6 +277,21 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export scopes: SCOPES_PROJECTS_WRITE, }, + LIST_COPILOT_OPPORTUNITY: { + meta: { + title: 'Apply copilot opportunity', + group: 'Apply Copilot', + description: 'Who can apply for copilot opportunity.', + }, + topcoderRoles: [ + USER_ROLE.TOPCODER_ADMIN, + ], + projectRoles: [ + USER_ROLE.PROJECT_MANAGER, + ], + scopes: SCOPES_PROJECTS_WRITE, + }, + MANAGE_PROJECT_BILLING_ACCOUNT_ID: { meta: { title: 'Manage Project property "billingAccountId"', diff --git a/src/permissions/copilotApplications.view.js b/src/permissions/copilotApplications.view.js new file mode 100644 index 00000000..0fb14bf4 --- /dev/null +++ b/src/permissions/copilotApplications.view.js @@ -0,0 +1,55 @@ + +import _ from 'lodash'; +import util from '../util'; +import models from '../models'; + +/** + * Topcoder admin and Project managers who are part of the project can view the copilot applications in it + * Also, users who had an application will have access to view it. + * @param {Object} freq the express request instance + * @return {Promise} Returns a promise + */ +module.exports = freq => new Promise((resolve, reject) => { + console.log("start permission check"); + const opportunityId = _.parseInt(freq.params.id); + const currentUserId = freq.authUser.userId; + return models.CopilotOpportunity.find({ + where: { + id: opportunityId, + }, + }) + .then((opportunity) => { + const req = freq; + req.context = req.context || {}; + req.context.currentOpportunity = opportunity; + const projectId = opportunity.projectId; + const isProjectManager = util.hasProjectManagerRole(req); + + console.log("got opportunity", opportunityId); + return models.ProjectMember.getActiveProjectMembers(projectId) + .then((members) => { + + console.log("got active members", projectId); + return models.CopilotApplications.findOne({ + where: { + opportunityId: opportunityId, + userId: currentUserId, + }, + }).then((copilotApplication) => { + const isPartOfProject = isProjectManager && members.find(member => member.userId === currentUserId); + // check if auth user has acecss to this project + const hasAccess = util.hasAdminRole(req) || isPartOfProject || !!copilotApplication; + console.log("got assigned application", hasAccess); + return Promise.resolve(hasAccess); + }) + }) + }) + .then((hasAccess) => { + if (!hasAccess) { + const errorMessage = 'You do not have permissions to perform this action'; + // user is not an admin nor is a registered project member + return reject(new Error(errorMessage)); + } + return resolve(true); + }); +}); diff --git a/src/permissions/index.js b/src/permissions/index.js index edb9cb6f..01cb7799 100644 --- a/src/permissions/index.js +++ b/src/permissions/index.js @@ -9,6 +9,7 @@ const copilotAndAbove = require('./copilotAndAbove'); const workManagementPermissions = require('./workManagementForTemplate'); const projectSettingEdit = require('./projectSetting.edit'); const customerPaymentConfirm = require('./customerPayment.confirm'); +const viewCopilotApplications = require('./copilotApplications.view'); const generalPermission = require('./generalPermission'); const { PERMISSION } = require('./constants'); @@ -199,4 +200,7 @@ module.exports = () => { Authorizer.setPolicy('customerPayment.view', generalPermission(PERMISSION.VIEW_CUSTOMER_PAYMENT)); Authorizer.setPolicy('customerPayment.edit', generalPermission(PERMISSION.UPDATE_CUSTOMER_PAYMENT)); Authorizer.setPolicy('customerPayment.confirm', customerPaymentConfirm); + + // Copilot opportunity + Authorizer.setPolicy('copilotApplications.view', viewCopilotApplications); }; diff --git a/src/routes/copilotOpportunityApply/list.js b/src/routes/copilotOpportunityApply/list.js new file mode 100644 index 00000000..b7bb0cc7 --- /dev/null +++ b/src/routes/copilotOpportunityApply/list.js @@ -0,0 +1,47 @@ +import _ from 'lodash'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; + +import models from '../../models'; +import { ADMIN_ROLES } from '../../constants'; +import util from '../../util'; + +const permissions = tcMiddleware.permissions; + +module.exports = [ + permissions('copilotApplications.view'), + (req, res, next) => { + + console.log("start list operation"); + const isAdmin = util.hasRoles(req, ADMIN_ROLES); + + let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'createdAt desc'; + if (sort.indexOf(' ') === -1) { + sort += ' asc'; + } + const sortableProps = ['createdAt asc', 'createdAt desc']; + if (_.indexOf(sortableProps, sort) < 0) { + return util.handleError('Invalid sort criteria', null, req, next); + } + const sortParams = sort.split(' '); + + // Admin can see all requests and the PM can only see requests created by them + const whereCondition = _.assign({}, + isAdmin ? {} : { createdBy: userId }, + ); + + return models.CopilotApplication.findAll({ + where: whereCondition, + include: [ + { + model: models.CopilotOpportunity, + as: 'copilotOpportunity', + }, + ], + order: [[sortParams[0], sortParams[1]]], + }) + .then(copilotApplications => res.json(copilotApplications)) + .catch((err) => { + util.handleError('Error fetching copilot applications', err, req, next); + }); + }, +]; diff --git a/src/routes/index.js b/src/routes/index.js index 137c3759..97ba56ea 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -407,6 +407,8 @@ router.route('/v5/projects/copilot/opportunity/:id(\\d+)') // Project copilot opportunity apply router.route('/v5/projects/copilots/opportunity/:id(\\d+)/apply') .post(require('./copilotOpportunityApply/create')); +router.route('/v5/projects/copilots/opportunity/:id(\\d+)/applications') + .post(require('./copilotOpportunityApply/list')); // Project Estimation Items router.route('/v5/projects/:projectId(\\d+)/estimations/:estimationId(\\d+)/items') diff --git a/src/util.js b/src/util.js index 711b76a8..43b3c092 100644 --- a/src/util.js +++ b/src/util.js @@ -225,6 +225,23 @@ const projectServiceUtils = { return _.intersection(roles, ADMIN_ROLES.map(r => r.toLowerCase())).length > 0; }, + /** + * Helper funtion to verify if user has project manager role + * @param {object} req Request object that should contain authUser + * @return {boolean} true/false + */ + hasProjectManagerRole: (req) => { + const isMachineToken = _.get(req, 'authUser.isMachine', false); + const tokenScopes = _.get(req, 'authUser.scopes', []); + if (isMachineToken) { + if (_.indexOf(tokenScopes, M2M_SCOPES.CONNECT_PROJECT_ADMIN) >= 0) return true; + return false; + } + let roles = _.get(req, 'authUser.roles', []); + roles = roles.map(s => s.toLowerCase()); + return roles.includes(USER_ROLE.PROJECT_MANAGER.toLowerCase()); + }, + /** * Parses query fields and groups them per table * @param {array} queryFields list of query fields From b6f6b8bc7824095d119bdd6aad50b8580c2b878b Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Fri, 2 May 2025 23:54:34 +0200 Subject: [PATCH 037/166] added circle config --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1acd4a4c..8e10e3e6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup'] + only: ['develop', 'migration-setup', 'pm-855'] - deployProd: context : org-global filters: From 45f8ff29fd330b24cca6a91c4db9ebf314694cf6 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Sun, 4 May 2025 00:13:21 +0200 Subject: [PATCH 038/166] changed route method --- docs/swagger.yaml | 58 +++++++++++++++++++++++++++++++++++++++++++++ src/routes/index.js | 2 +- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index dbadf9e6..ae0cd049 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -502,6 +502,64 @@ paths: description: "Internal Server Error" schema: $ref: "#/definitions/ErrorModel" + "/projects/copilots/opportunity/{copilotOpportunityId}/applications": + get: + tags: + - projects copilot opportunity applications + operationId: listCopilotOpportunity + security: + - Bearer: [] + description: "Retrieve the list copilot opportunity applications." + parameters: + - $ref: "#/parameters/copilotOpportunityIdParam" + - name: sort + required: false + description: > + sort projects by createdAt, updatedAt. Default + is createdAt asc + in: query + type: string + responses: + "200": + description: A list of projects + schema: + type: array + items: + $ref: "#/definitions/CopilotOpportunityApplication" + headers: + X-Next-Page: + type: integer + description: The index of the next page + X-Page: + type: integer + description: The index of the current page (starting at 1) + X-Per-Page: + type: integer + description: The number of items to list per page + X-Prev-Page: + type: integer + description: The index of the previous page + X-Total: + type: integer + description: The total number of items + X-Total-Pages: + type: integer + description: The total number of pages + Link: + type: string + description: Pagination link header. + "401": + description: "Unauthorized" + schema: + $ref: "#/definitions/ErrorModel" + "403": + description: "Forbidden - User does not have permission" + schema: + $ref: "#/definitions/ErrorModel" + "500": + description: "Internal Server Error" + schema: + $ref: "#/definitions/ErrorModel" "/projects/{projectId}/attachments": get: tags: diff --git a/src/routes/index.js b/src/routes/index.js index 97ba56ea..b07041ab 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -408,7 +408,7 @@ router.route('/v5/projects/copilot/opportunity/:id(\\d+)') router.route('/v5/projects/copilots/opportunity/:id(\\d+)/apply') .post(require('./copilotOpportunityApply/create')); router.route('/v5/projects/copilots/opportunity/:id(\\d+)/applications') - .post(require('./copilotOpportunityApply/list')); + .get(require('./copilotOpportunityApply/list')); // Project Estimation Items router.route('/v5/projects/:projectId(\\d+)/estimations/:estimationId(\\d+)/items') From 21edae949ee67c31c4bbd91c736ec0667d60ed53 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Sun, 4 May 2025 10:16:53 +0200 Subject: [PATCH 039/166] updated permission method --- src/permissions/copilotApplications.view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/permissions/copilotApplications.view.js b/src/permissions/copilotApplications.view.js index 0fb14bf4..f40167dc 100644 --- a/src/permissions/copilotApplications.view.js +++ b/src/permissions/copilotApplications.view.js @@ -13,7 +13,7 @@ module.exports = freq => new Promise((resolve, reject) => { console.log("start permission check"); const opportunityId = _.parseInt(freq.params.id); const currentUserId = freq.authUser.userId; - return models.CopilotOpportunity.find({ + return models.CopilotOpportunity.findOne({ where: { id: opportunityId, }, From 840f1555ad44c6053425f414d1c4d8bfd9f707fd Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Sun, 4 May 2025 11:20:17 +0200 Subject: [PATCH 040/166] updated permission method --- src/permissions/copilotApplications.view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/permissions/copilotApplications.view.js b/src/permissions/copilotApplications.view.js index f40167dc..fd35e539 100644 --- a/src/permissions/copilotApplications.view.js +++ b/src/permissions/copilotApplications.view.js @@ -30,7 +30,7 @@ module.exports = freq => new Promise((resolve, reject) => { .then((members) => { console.log("got active members", projectId); - return models.CopilotApplications.findOne({ + return models.CopilotApplication.findOne({ where: { opportunityId: opportunityId, userId: currentUserId, From d093fe5e853e2d3af45c2d5c45edc51be16331d2 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Sun, 4 May 2025 12:01:09 +0200 Subject: [PATCH 041/166] updated permission method --- src/routes/copilotOpportunityApply/list.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/copilotOpportunityApply/list.js b/src/routes/copilotOpportunityApply/list.js index b7bb0cc7..0348d3e0 100644 --- a/src/routes/copilotOpportunityApply/list.js +++ b/src/routes/copilotOpportunityApply/list.js @@ -13,6 +13,7 @@ module.exports = [ console.log("start list operation"); const isAdmin = util.hasRoles(req, ADMIN_ROLES); + const userId = req.authUser.userId; let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'createdAt desc'; if (sort.indexOf(' ') === -1) { From 84117313266e715a74fc5fcb0bbe563de06619e8 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Sun, 4 May 2025 22:11:25 +0200 Subject: [PATCH 042/166] added opp id to the where clause --- src/routes/copilotOpportunityApply/list.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunityApply/list.js b/src/routes/copilotOpportunityApply/list.js index 0348d3e0..0c0e7fe8 100644 --- a/src/routes/copilotOpportunityApply/list.js +++ b/src/routes/copilotOpportunityApply/list.js @@ -14,6 +14,7 @@ module.exports = [ console.log("start list operation"); const isAdmin = util.hasRoles(req, ADMIN_ROLES); const userId = req.authUser.userId; + const opportunityId = _.parseInt(req.params.id); let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'createdAt desc'; if (sort.indexOf(' ') === -1) { @@ -26,7 +27,9 @@ module.exports = [ const sortParams = sort.split(' '); // Admin can see all requests and the PM can only see requests created by them - const whereCondition = _.assign({}, + const whereCondition = _.assign({ + opportunityId, + }, isAdmin ? {} : { createdBy: userId }, ); From 43dc20c3d1af53700c537d8802779ba785831fa3 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Sun, 4 May 2025 23:57:44 +0200 Subject: [PATCH 043/166] show all applications for pm --- src/permissions/copilotApplications.view.js | 6 +----- src/routes/copilotOpportunityApply/list.js | 5 ++--- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/permissions/copilotApplications.view.js b/src/permissions/copilotApplications.view.js index fd35e539..9b0c917b 100644 --- a/src/permissions/copilotApplications.view.js +++ b/src/permissions/copilotApplications.view.js @@ -10,7 +10,6 @@ import models from '../models'; * @return {Promise} Returns a promise */ module.exports = freq => new Promise((resolve, reject) => { - console.log("start permission check"); const opportunityId = _.parseInt(freq.params.id); const currentUserId = freq.authUser.userId; return models.CopilotOpportunity.findOne({ @@ -25,11 +24,9 @@ module.exports = freq => new Promise((resolve, reject) => { const projectId = opportunity.projectId; const isProjectManager = util.hasProjectManagerRole(req); - console.log("got opportunity", opportunityId); return models.ProjectMember.getActiveProjectMembers(projectId) .then((members) => { - console.log("got active members", projectId); return models.CopilotApplication.findOne({ where: { opportunityId: opportunityId, @@ -37,9 +34,8 @@ module.exports = freq => new Promise((resolve, reject) => { }, }).then((copilotApplication) => { const isPartOfProject = isProjectManager && members.find(member => member.userId === currentUserId); - // check if auth user has acecss to this project + // check if auth user has access to this project const hasAccess = util.hasAdminRole(req) || isPartOfProject || !!copilotApplication; - console.log("got assigned application", hasAccess); return Promise.resolve(hasAccess); }) }) diff --git a/src/routes/copilotOpportunityApply/list.js b/src/routes/copilotOpportunityApply/list.js index 0c0e7fe8..80786aef 100644 --- a/src/routes/copilotOpportunityApply/list.js +++ b/src/routes/copilotOpportunityApply/list.js @@ -11,8 +11,7 @@ module.exports = [ permissions('copilotApplications.view'), (req, res, next) => { - console.log("start list operation"); - const isAdmin = util.hasRoles(req, ADMIN_ROLES); + const canAccessAllApplications = util.hasRoles(req, ADMIN_ROLES) || util.hasProjectManagerRole(req); const userId = req.authUser.userId; const opportunityId = _.parseInt(req.params.id); @@ -30,7 +29,7 @@ module.exports = [ const whereCondition = _.assign({ opportunityId, }, - isAdmin ? {} : { createdBy: userId }, + canAccessAllApplications ? {} : { createdBy: userId }, ); return models.CopilotApplication.findAll({ From c42c1d430dfb44e0a6655ef63aaa02236819c2e2 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 5 May 2025 15:42:03 +0200 Subject: [PATCH 044/166] fix: allow only copilots to apply for opportunity --- src/constants.js | 1 + src/permissions/constants.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/constants.js b/src/constants.js index 77bd3b20..8307b773 100644 --- a/src/constants.js +++ b/src/constants.js @@ -90,6 +90,7 @@ export const USER_ROLE = { PROJECT_MANAGER: 'Project Manager', TOPCODER_USER: 'Topcoder User', TG_ADMIN: 'tgadmin', + TC_COPILOT: 'copilot', }; export const ADMIN_ROLES = [USER_ROLE.CONNECT_ADMIN, USER_ROLE.TOPCODER_ADMIN, USER_ROLE.TG_ADMIN]; diff --git a/src/permissions/constants.js b/src/permissions/constants.js index 85c38465..03bcaf21 100644 --- a/src/permissions/constants.js +++ b/src/permissions/constants.js @@ -272,7 +272,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export description: 'Who can apply for copilot opportunity.', }, topcoderRoles: [ - USER_ROLE.COPILOT, + USER_ROLE.TC_COPILOT, ], scopes: SCOPES_PROJECTS_WRITE, }, From 7adb0ccf7e86fbad01b3df8aae1138bbe64679a7 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 5 May 2025 16:16:13 +0200 Subject: [PATCH 045/166] removed dev deployment circle config --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8e10e3e6..1acd4a4c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup', 'pm-855'] + only: ['develop', 'migration-setup'] - deployProd: context : org-global filters: From aeb7b48531211679b9b697359eaff9016ceb688e Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Sun, 11 May 2025 16:17:58 +0200 Subject: [PATCH 046/166] feat: assign copilot opportunity --- docs/swagger.yaml | 45 +++++++++ ...250511123109-copilot_application_status.js | 21 +++++ src/constants.js | 5 + src/models/copilotApplication.js | 10 ++ src/permissions/constants.js | 12 +++ src/routes/copilotOpportunity/assign.js | 94 +++++++++++++++++++ src/routes/index.js | 4 + 7 files changed, 191 insertions(+) create mode 100644 migrations/umzug/migrations/20250511123109-copilot_application_status.js create mode 100644 src/routes/copilotOpportunity/assign.js diff --git a/docs/swagger.yaml b/docs/swagger.yaml index ae0cd049..83931b5e 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -560,6 +560,37 @@ paths: description: "Internal Server Error" schema: $ref: "#/definitions/ErrorModel" + "/projects/copilots/opportunity/{copilotOpportunityId}/assign": + post: + tags: + - assign project copilot opportunity + operationId: assignCopilotOpportunity + security: + - Bearer: [] + description: "Retrieve a specific copilot opportunity." + parameters: + - $ref: "#/parameters/copilotOpportunityIdParam" + - in: body + name: body + schema: + $ref: "#/definitions/AssignCopilotOpportunity" + responses: + "200": + description: "The copilot opportunity application" + schema: + $ref: "#/definitions/CopilotOpportunityApplication" + "401": + description: "Unauthorized" + schema: + $ref: "#/definitions/ErrorModel" + "403": + description: "Forbidden - User does not have permission" + schema: + $ref: "#/definitions/ErrorModel" + "500": + description: "Internal Server Error" + schema: + $ref: "#/definitions/ErrorModel" "/projects/{projectId}/attachments": get: tags: @@ -6081,6 +6112,13 @@ definitions: notes: description: notes regarding the application type: string + status: + description: status of the application + type: string + enum: + - pending + - accepted + example: pending opportunityId: description: copilot request id type: integer @@ -6327,6 +6365,13 @@ definitions: notes: type: string description: notes about applying copilot opportunity + AssignCopilotOpportunity: + title: Assign copilot CopilotOpportunity + type: object + properties: + applicationId: + type: string + description: application id which has to be accepted NewProjectAttachment: title: Project attachment request type: object diff --git a/migrations/umzug/migrations/20250511123109-copilot_application_status.js b/migrations/umzug/migrations/20250511123109-copilot_application_status.js new file mode 100644 index 00000000..bbac7d2c --- /dev/null +++ b/migrations/umzug/migrations/20250511123109-copilot_application_status.js @@ -0,0 +1,21 @@ +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.addColumn('copilot_applications', 'status', { + type: Sequelize.STRING(16), + allowNull: true, + }); + + await queryInterface.sequelize.query( + `UPDATE copilot_applications SET status = 'pending' WHERE status IS NULL` + ); + + await queryInterface.changeColumn('copilot_applications', 'status', { + type: Sequelize.STRING(16), + allowNull: false, + }); + }, + + down: async (queryInterface) => { + await queryInterface.removeColumn('copilot_applications', 'status'); + }, +}; diff --git a/src/constants.js b/src/constants.js index 8307b773..d432c1c5 100644 --- a/src/constants.js +++ b/src/constants.js @@ -18,6 +18,11 @@ export const COPILOT_REQUEST_STATUS = { FULFILLED: 'fulfiled', }; +export const COPILOT_APPLICATION_STATUS = { + PENDING: 'pending', + ACCEPTED: 'accepted', +}; + export const COPILOT_OPPORTUNITY_STATUS = { ACTIVE: 'active', COMPLETED: 'completed', diff --git a/src/models/copilotApplication.js b/src/models/copilotApplication.js index c472da60..e8298125 100644 --- a/src/models/copilotApplication.js +++ b/src/models/copilotApplication.js @@ -1,4 +1,5 @@ import _ from 'lodash'; +import { COPILOT_APPLICATION_STATUS } from '../constants'; module.exports = function defineCopilotOpportunity(sequelize, DataTypes) { const CopilotApplication = sequelize.define('CopilotApplication', { @@ -17,6 +18,15 @@ module.exports = function defineCopilotOpportunity(sequelize, DataTypes) { type: DataTypes.TEXT, allowNull: true }, + status: { + type: DataTypes.STRING(16), + defaultValue: 'pending', + allowNull: false, + validate: { + isIn: [_.values(COPILOT_APPLICATION_STATUS)], + }, + allowNull: false, + }, userId: { type: DataTypes.BIGINT, allowNull: false }, deletedAt: { type: DataTypes.DATE, allowNull: true }, createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, diff --git a/src/permissions/constants.js b/src/permissions/constants.js index 03bcaf21..c32e16d4 100644 --- a/src/permissions/constants.js +++ b/src/permissions/constants.js @@ -276,6 +276,18 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export ], scopes: SCOPES_PROJECTS_WRITE, }, + ASSIGN_COPILOT_OPPORTUNITY: { + meta: { + title: 'Assign copilot to opportunity', + group: 'Assign Copilot', + description: 'Who can assign for 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/assign.js b/src/routes/copilotOpportunity/assign.js new file mode 100644 index 00000000..87803a5a --- /dev/null +++ b/src/routes/copilotOpportunity/assign.js @@ -0,0 +1,94 @@ +import _ from 'lodash'; +import validate from 'express-validation'; +import Joi from 'joi'; + +import models from '../../models'; +import util from '../../util'; +import { PERMISSION } from '../../permissions/constants'; +import { COPILOT_APPLICATION_STATUS, COPILOT_OPPORTUNITY_STATUS } from '../../constants'; + +const assignCopilotOpportunityValidations = { + body: Joi.object().keys({ + applicationId: Joi.string(), + }), +}; + +module.exports = [ + validate(assignCopilotOpportunityValidations), + async (req, res, next) => { + const { applicationId } = req.body; + const copilotOpportunityId = _.parseInt(req.params.id); + if (!util.hasPermissionByReq(PERMISSION.ASSIGN_COPILOT_OPPORTUNITY, req)) { + const err = new Error('Unable to assign copilot opportunity'); + _.assign(err, { + details: JSON.stringify({ message: 'You do not have permission to assign a copilot opportunity' }), + status: 403, + }); + return next(err); + } + + return models.sequelize.transaction(() => { + models.CopilotOpportunity.findOne({ + where: { + id: copilotOpportunityId, + }, + }).then(async (opportunity) => { + if (!opportunity) { + const err = new Error('No opportunity found'); + err.status = 404; + return next(err); + } + + if (opportunity.status !== COPILOT_OPPORTUNITY_STATUS.ACTIVE) { + const err = new Error('Opportunity is not active'); + err.status = 400; + return next(err); + } + + const application = models.CopilotApplication.findOne({ + where: { + id: applicationId, + }, + }); + + if (!application) { + const err = new Error('No such application available'); + err.status = 400; + return next(err); + } + + if (application.status === COPILOT_APPLICATION_STATUS.ACCEPTED) { + const err = new Error('Application already accepted'); + err.status = 400; + return next(err); + } + + const projectId = opportunity.projectId; + const userId = application.userId; + + const activeMembers = await models.ProjectMember.getActiveProjectMembers(projectId); + + const existingUser = activeMembers.find(item => item.userId === userId); + + if (existingUser) { + const err = new Error(`User is already part of the project as ${existingUser.role}`); + err.status = 400; + return next(err); + } + + + return opportunity.update({status: COPILOT_OPPORTUNITY_STATUS.COMPLETED}) + }) + .then(() => { + return models.CopilotApplication.update({ + status: 'ACCEPTED' + }, { + where: { + id: applicationId, + } + }) + }) + }) + .catch(err => next(err)); + }, +]; diff --git a/src/routes/index.js b/src/routes/index.js index b07041ab..01b2d1e6 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -410,6 +410,10 @@ router.route('/v5/projects/copilots/opportunity/:id(\\d+)/apply') router.route('/v5/projects/copilots/opportunity/:id(\\d+)/applications') .get(require('./copilotOpportunityApply/list')); +// Copilot opportunity assign +router.route('/v5/projects/copilots/opportunity/:id(\\d+)/assign') + .post(require('./copilotOpportunity/assign')); + // Project Estimation Items router.route('/v5/projects/:projectId(\\d+)/estimations/:estimationId(\\d+)/items') .get(require('./projectEstimationItems/list')); From af846ab660c3bbd8f8bcc968ac29cd48d878e7fe Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Sun, 11 May 2025 16:18:35 +0200 Subject: [PATCH 047/166] deploy feature branch to dev --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1acd4a4c..90930d0d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup'] + only: ['develop', 'migration-setup', 'pm-1168'] - deployProd: context : org-global filters: From 709601f1d8bc67e05e32f8186ab09a47ef2bff8c Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Sun, 11 May 2025 17:48:39 +0200 Subject: [PATCH 048/166] deploy feature branch to dev --- docs/swagger.yaml | 8 ++++---- src/models/copilotApplication.js | 1 - src/routes/copilotOpportunity/assign.js | 9 ++++++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 83931b5e..92663901 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -567,7 +567,7 @@ paths: operationId: assignCopilotOpportunity security: - Bearer: [] - description: "Retrieve a specific copilot opportunity." + description: "Assign a copilot opportunity with copilot." parameters: - $ref: "#/parameters/copilotOpportunityIdParam" - in: body @@ -6359,19 +6359,19 @@ definitions: - manager - copilot ApplyCopilotOpportunity: - title: Apply copilot CopilotOpportunity + title: Apply Copilot Opportunity type: object properties: notes: type: string description: notes about applying copilot opportunity AssignCopilotOpportunity: - title: Assign copilot CopilotOpportunity + title: Assign Copilot Opportunity type: object properties: applicationId: type: string - description: application id which has to be accepted + description: The ID of the application to be accepted for the copilot opportunity. NewProjectAttachment: title: Project attachment request type: object diff --git a/src/models/copilotApplication.js b/src/models/copilotApplication.js index e8298125..f68e6c60 100644 --- a/src/models/copilotApplication.js +++ b/src/models/copilotApplication.js @@ -21,7 +21,6 @@ module.exports = function defineCopilotOpportunity(sequelize, DataTypes) { status: { type: DataTypes.STRING(16), defaultValue: 'pending', - allowNull: false, validate: { isIn: [_.values(COPILOT_APPLICATION_STATUS)], }, diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index 87803a5a..40f13793 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -80,13 +80,16 @@ module.exports = [ return opportunity.update({status: COPILOT_OPPORTUNITY_STATUS.COMPLETED}) }) .then(() => { - return models.CopilotApplication.update({ - status: 'ACCEPTED' + const updatedApplication = models.CopilotApplication.update({ + status: COPILOT_APPLICATION_STATUS.ACCEPTED, }, { where: { id: applicationId, } - }) + }); + + res.status(200).send(updatedApplication); + return Promise.resolve() }) }) .catch(err => next(err)); From 4543e5e49e927a9232b39bdb5c94640fd5912036 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 12 May 2025 19:30:52 +0200 Subject: [PATCH 049/166] fix: return object --- src/routes/copilotOpportunity/assign.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index 40f13793..a889c5c7 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -79,8 +79,8 @@ module.exports = [ return opportunity.update({status: COPILOT_OPPORTUNITY_STATUS.COMPLETED}) }) - .then(() => { - const updatedApplication = models.CopilotApplication.update({ + .then(async () => { + const [, affected] = await models.CopilotApplication.update({ status: COPILOT_APPLICATION_STATUS.ACCEPTED, }, { where: { @@ -88,7 +88,7 @@ module.exports = [ } }); - res.status(200).send(updatedApplication); + res.status(200).send(affected[0]); return Promise.resolve() }) }) From 9f7dc4804c2608d4fef08255895c2f9dfdaf1280 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 12 May 2025 19:54:14 +0200 Subject: [PATCH 050/166] fix: return object --- src/routes/copilotOpportunity/assign.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index a889c5c7..213d4cfc 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -80,7 +80,7 @@ module.exports = [ return opportunity.update({status: COPILOT_OPPORTUNITY_STATUS.COMPLETED}) }) .then(async () => { - const [, affected] = await models.CopilotApplication.update({ + const updated = await models.CopilotApplication.update({ status: COPILOT_APPLICATION_STATUS.ACCEPTED, }, { where: { @@ -88,7 +88,7 @@ module.exports = [ } }); - res.status(200).send(affected[0]); + res.status(200).send(updated); return Promise.resolve() }) }) From 7f40cdb1cd75594150f26ae410c7fd914deb95a7 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 12 May 2025 20:33:23 +0200 Subject: [PATCH 051/166] fix: return object --- docs/swagger.yaml | 9 ++++++++- src/routes/copilotOpportunity/assign.js | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 92663901..5ac22e74 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -578,7 +578,7 @@ paths: "200": description: "The copilot opportunity application" schema: - $ref: "#/definitions/CopilotOpportunityApplication" + $ref: "#/definitions/CopilotOpportunityAssignResponse" "401": description: "Unauthorized" schema: @@ -6149,6 +6149,13 @@ definitions: format: int64 description: READ-ONLY. User that deleted this task readOnly: true + CopilotOpportunityAssignResponse: + type: object + properties: + id: + description: unique identifier + type: integer + format: int64 Project: type: object properties: diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index 213d4cfc..f41026ee 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -80,7 +80,7 @@ module.exports = [ return opportunity.update({status: COPILOT_OPPORTUNITY_STATUS.COMPLETED}) }) .then(async () => { - const updated = await models.CopilotApplication.update({ + await models.CopilotApplication.update({ status: COPILOT_APPLICATION_STATUS.ACCEPTED, }, { where: { @@ -88,7 +88,7 @@ module.exports = [ } }); - res.status(200).send(updated); + res.status(200).send({id: applicationId}); return Promise.resolve() }) }) From 8e2881d3016141eb5e9b6749cc922de7debe4d01 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 12 May 2025 20:56:29 +0200 Subject: [PATCH 052/166] updated swagger docs --- docs/swagger.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 5ac22e74..60fe8f75 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -576,7 +576,7 @@ paths: $ref: "#/definitions/AssignCopilotOpportunity" responses: "200": - description: "The copilot opportunity application" + description: "The response after assigning an copilot opportunity" schema: $ref: "#/definitions/CopilotOpportunityAssignResponse" "401": From f7d09906e953890033b70b6239b916453dbc7846 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 12 May 2025 21:17:40 +0200 Subject: [PATCH 053/166] fix: make notes optional --- .circleci/config.yml | 2 +- src/routes/copilotOpportunityApply/create.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1acd4a4c..dbec1b58 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup'] + only: ['develop', 'migration-setup', 'pm-578_1'] - deployProd: context : org-global filters: diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index 0093a217..bdd82fb4 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -9,7 +9,7 @@ import { COPILOT_OPPORTUNITY_STATUS } from '../../constants'; const applyCopilotRequestValidations = { body: Joi.object().keys({ - notes: Joi.string(), + notes: Joi.string().optional(), }), }; From 4753022f26f7153773ff6906da5fbf5d2b65b1c3 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 13 May 2025 00:41:56 +0200 Subject: [PATCH 054/166] removed circle config --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index dbec1b58..1acd4a4c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup', 'pm-578_1'] + only: ['develop', 'migration-setup'] - deployProd: context : org-global filters: From 2ee4f9b258e75dc82e6113f3a51aba7236c33bf5 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 13 May 2025 23:18:51 +0200 Subject: [PATCH 055/166] fix: lint --- ...0250411182312-copilot_opportunity_apply.js | 4 +- ...250511123109-copilot_application_status.js | 2 +- src/models/copilotApplication.js | 6 +- src/permissions/copilotApplications.view.js | 9 +- src/routes/copilotOpportunity/assign.js | 111 +++++++++--------- src/routes/copilotOpportunityApply/create.js | 8 +- src/routes/copilotOpportunityApply/list.js | 3 +- src/routes/projectMemberInvites/create.js | 13 +- 8 files changed, 74 insertions(+), 82 deletions(-) diff --git a/migrations/umzug/migrations/20250411182312-copilot_opportunity_apply.js b/migrations/umzug/migrations/20250411182312-copilot_opportunity_apply.js index 7d29919e..27910a56 100644 --- a/migrations/umzug/migrations/20250411182312-copilot_opportunity_apply.js +++ b/migrations/umzug/migrations/20250411182312-copilot_opportunity_apply.js @@ -1,4 +1,4 @@ -'use strict'; + module.exports = { up: async (queryInterface, Sequelize) => { @@ -56,5 +56,5 @@ module.exports = { down: async (queryInterface) => { await queryInterface.dropTable('copilot_applications'); - } + }, }; diff --git a/migrations/umzug/migrations/20250511123109-copilot_application_status.js b/migrations/umzug/migrations/20250511123109-copilot_application_status.js index bbac7d2c..2c03606b 100644 --- a/migrations/umzug/migrations/20250511123109-copilot_application_status.js +++ b/migrations/umzug/migrations/20250511123109-copilot_application_status.js @@ -6,7 +6,7 @@ module.exports = { }); await queryInterface.sequelize.query( - `UPDATE copilot_applications SET status = 'pending' WHERE status IS NULL` + 'UPDATE copilot_applications SET status = \'pending\' WHERE status IS NULL', ); await queryInterface.changeColumn('copilot_applications', 'status', { diff --git a/src/models/copilotApplication.js b/src/models/copilotApplication.js index f68e6c60..9a90881f 100644 --- a/src/models/copilotApplication.js +++ b/src/models/copilotApplication.js @@ -9,14 +9,14 @@ module.exports = function defineCopilotOpportunity(sequelize, DataTypes) { allowNull: false, references: { model: 'copilot_opportunities', - key: 'id' + key: 'id', }, onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', }, notes: { type: DataTypes.TEXT, - allowNull: true + allowNull: true, }, status: { type: DataTypes.STRING(16), diff --git a/src/permissions/copilotApplications.view.js b/src/permissions/copilotApplications.view.js index 9b0c917b..975846bb 100644 --- a/src/permissions/copilotApplications.view.js +++ b/src/permissions/copilotApplications.view.js @@ -25,11 +25,9 @@ module.exports = freq => new Promise((resolve, reject) => { const isProjectManager = util.hasProjectManagerRole(req); return models.ProjectMember.getActiveProjectMembers(projectId) - .then((members) => { - - return models.CopilotApplication.findOne({ + .then(members => models.CopilotApplication.findOne({ where: { - opportunityId: opportunityId, + opportunityId, userId: currentUserId, }, }).then((copilotApplication) => { @@ -37,8 +35,7 @@ module.exports = freq => new Promise((resolve, reject) => { // check if auth user has access to this project const hasAccess = util.hasAdminRole(req) || isPartOfProject || !!copilotApplication; return Promise.resolve(hasAccess); - }) - }) + })); }) .then((hasAccess) => { if (!hasAccess) { diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index f41026ee..be588ce3 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -5,7 +5,7 @@ import Joi from 'joi'; import models from '../../models'; import util from '../../util'; import { PERMISSION } from '../../permissions/constants'; -import { COPILOT_APPLICATION_STATUS, COPILOT_OPPORTUNITY_STATUS } from '../../constants'; +import { COPILOT_APPLICATION_STATUS, COPILOT_OPPORTUNITY_STATUS, COPILOT_REQUEST_STATUS } from '../../constants'; const assignCopilotOpportunityValidations = { body: Joi.object().keys({ @@ -27,71 +27,68 @@ module.exports = [ return next(err); } - return models.sequelize.transaction(() => { - models.CopilotOpportunity.findOne({ - where: { - id: copilotOpportunityId, - }, - }).then(async (opportunity) => { - if (!opportunity) { - const err = new Error('No opportunity found'); - err.status = 404; - return next(err); - } - - if (opportunity.status !== COPILOT_OPPORTUNITY_STATUS.ACTIVE) { - const err = new Error('Opportunity is not active'); - err.status = 400; - return next(err); - } + return models.sequelize.transaction(async (t) => { + const opportunity = await models.CopilotOpportunity.findOne({ + where: { id: copilotOpportunityId }, + transaction: t, + }); + + if (!opportunity) { + const err = new Error('No opportunity found'); + err.status = 404; + throw err; + } - const application = models.CopilotApplication.findOne({ - where: { - id: applicationId, - }, - }); + if (opportunity.status !== COPILOT_OPPORTUNITY_STATUS.ACTIVE) { + const err = new Error('Opportunity is not active'); + err.status = 400; + throw err; + } - if (!application) { - const err = new Error('No such application available'); - err.status = 400; - return next(err); - } + const application = await models.CopilotApplication.findOne({ + where: { id: applicationId }, + transaction: t, + }); - if (application.status === COPILOT_APPLICATION_STATUS.ACCEPTED) { - const err = new Error('Application already accepted'); - err.status = 400; - return next(err); - } + if (!application) { + const err = new Error('No such application available'); + err.status = 400; + throw err; + } - const projectId = opportunity.projectId; - const userId = application.userId; + if (application.status === COPILOT_APPLICATION_STATUS.ACCEPTED) { + const err = new Error('Application already accepted'); + err.status = 400; + throw err; + } - const activeMembers = await models.ProjectMember.getActiveProjectMembers(projectId); + const projectId = opportunity.projectId; + const userId = application.userId; + const activeMembers = await models.ProjectMember.getActiveProjectMembers(projectId); - const existingUser = activeMembers.find(item => item.userId === userId); + const existingUser = activeMembers.find(item => item.userId === userId); + if (existingUser) { + const err = new Error(`User is already part of the project as ${existingUser.role}`); + err.status = 400; + throw err; + } - if (existingUser) { - const err = new Error(`User is already part of the project as ${existingUser.role}`); - err.status = 400; - return next(err); - } + await models.CopilotRequest.update( + { status: COPILOT_REQUEST_STATUS.FULFILLED }, + { where: { id: opportunity.copilotRequestId }, transaction: t }, + ); + await opportunity.update( + { status: COPILOT_OPPORTUNITY_STATUS.COMPLETED }, + { transaction: t }, + ); - return opportunity.update({status: COPILOT_OPPORTUNITY_STATUS.COMPLETED}) - }) - .then(async () => { - await models.CopilotApplication.update({ - status: COPILOT_APPLICATION_STATUS.ACCEPTED, - }, { - where: { - id: applicationId, - } - }); + await models.CopilotApplication.update( + { status: COPILOT_APPLICATION_STATUS.ACCEPTED }, + { where: { id: applicationId }, transaction: t }, + ); - res.status(200).send({id: applicationId}); - return Promise.resolve() - }) - }) - .catch(err => next(err)); + res.status(200).send({ id: applicationId }); + }).catch(err => next(err)); }, ]; diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index 0093a217..e81840ff 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -63,16 +63,16 @@ module.exports = [ res.status(200).json(existingApplication); return Promise.resolve(); } - + return models.CopilotApplication.create(data) .then((result) => { res.status(201).json(result); return Promise.resolve(); }) .catch((err) => { - util.handleError('Error creating copilot application', err, req, next); - return next(err); - }); + util.handleError('Error creating copilot application', err, req, next); + return next(err); + }); }).catch((e) => { util.handleError('Error applying for copilot opportunity', e, req, next); }); diff --git a/src/routes/copilotOpportunityApply/list.js b/src/routes/copilotOpportunityApply/list.js index 80786aef..69aea8fe 100644 --- a/src/routes/copilotOpportunityApply/list.js +++ b/src/routes/copilotOpportunityApply/list.js @@ -10,7 +10,6 @@ const permissions = tcMiddleware.permissions; module.exports = [ permissions('copilotApplications.view'), (req, res, next) => { - const canAccessAllApplications = util.hasRoles(req, ADMIN_ROLES) || util.hasProjectManagerRole(req); const userId = req.authUser.userId; const opportunityId = _.parseInt(req.params.id); @@ -29,7 +28,7 @@ module.exports = [ const whereCondition = _.assign({ opportunityId, }, - canAccessAllApplications ? {} : { createdBy: userId }, + canAccessAllApplications ? {} : { createdBy: userId }, ); return models.CopilotApplication.findAll({ diff --git a/src/routes/projectMemberInvites/create.js b/src/routes/projectMemberInvites/create.js index ed32e2c3..02ce50c9 100644 --- a/src/routes/projectMemberInvites/create.js +++ b/src/routes/projectMemberInvites/create.js @@ -199,7 +199,7 @@ const buildCreateInvitePromises = (req, inviteEmails, inviteUserIds, invites, da }; const sendInviteEmail = (req, projectId, invite) => { - req.log.debug(`Sending invite email: ${JSON.stringify(req.body)}, ${projectId}, ${JSON.stringify(invite)}`) + req.log.debug(`Sending invite email: ${JSON.stringify(req.body)}, ${projectId}, ${JSON.stringify(invite)}`); req.log.debug(req.authUser); const emailEventType = CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_EMAIL_INVITE_CREATED; const promises = [ @@ -295,13 +295,12 @@ module.exports = [ // whom we are inviting, because Member Service has a loose search logic and may return // users with handles whom we didn't search for .then((foundUsers) => { - if(invite.handles) { + if (invite.handles) { const lowerCaseHandles = invite.handles.map(handle => handle.toLowerCase()); return foundUsers.filter(foundUser => _.includes(lowerCaseHandles, foundUser.handleLower)); } - else { - return [] - } + + return []; }) .then((inviteUsers) => { const members = req.context.currentProjectMembers; @@ -414,7 +413,7 @@ module.exports = [ RESOURCES.PROJECT_MEMBER_INVITE, v.toJSON()); - req.log.debug(`V: ${JSON.stringify(v)}`) + req.log.debug(`V: ${JSON.stringify(v)}`); // send email invite (async) if (v.email && !v.userId && v.status === INVITE_STATUS.PENDING) { sendInviteEmail(req, projectId, v); @@ -443,7 +442,7 @@ module.exports = [ } }); }).catch((err) => { - console.log(err) + console.log(err); if (failed.length) { res.status(403).json(_.assign({}, { success: [] }, { failed })); } else next(err); From 2fd1652571aca1f3dd7e97c1f039a2f4ca89185d Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 13 May 2025 23:22:52 +0200 Subject: [PATCH 056/166] fix: check if the user is already a copilot --- src/routes/copilotOpportunity/assign.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index be588ce3..81de6e83 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -67,8 +67,8 @@ module.exports = [ const activeMembers = await models.ProjectMember.getActiveProjectMembers(projectId); const existingUser = activeMembers.find(item => item.userId === userId); - if (existingUser) { - const err = new Error(`User is already part of the project as ${existingUser.role}`); + if (existingUser && existingUser.role === 'copilot') { + const err = new Error(`User is already a copilot of this project`); err.status = 400; throw err; } From d5296d3a3d47b3fd83c3a64add64be5385634a7a Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 13 May 2025 23:53:48 +0200 Subject: [PATCH 057/166] fix: allow project managers to view applications of opportunity not belonging to their projects --- src/permissions/copilotApplications.view.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/permissions/copilotApplications.view.js b/src/permissions/copilotApplications.view.js index 9b0c917b..326a44f6 100644 --- a/src/permissions/copilotApplications.view.js +++ b/src/permissions/copilotApplications.view.js @@ -33,9 +33,8 @@ module.exports = freq => new Promise((resolve, reject) => { userId: currentUserId, }, }).then((copilotApplication) => { - const isPartOfProject = isProjectManager && members.find(member => member.userId === currentUserId); // check if auth user has access to this project - const hasAccess = util.hasAdminRole(req) || isPartOfProject || !!copilotApplication; + const hasAccess = util.hasAdminRole(req) || isProjectManager || !!copilotApplication; return Promise.resolve(hasAccess); }) }) From 4350c1f7389dfbb9e3e60271ccac3df52c52ef89 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 13 May 2025 23:55:26 +0200 Subject: [PATCH 058/166] fix: allow project managers to view applications of opportunity not belonging to their projects --- src/permissions/copilotApplications.view.js | 23 ++++++++------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/permissions/copilotApplications.view.js b/src/permissions/copilotApplications.view.js index 326a44f6..b333cc3c 100644 --- a/src/permissions/copilotApplications.view.js +++ b/src/permissions/copilotApplications.view.js @@ -21,22 +21,17 @@ module.exports = freq => new Promise((resolve, reject) => { const req = freq; req.context = req.context || {}; req.context.currentOpportunity = opportunity; - const projectId = opportunity.projectId; const isProjectManager = util.hasProjectManagerRole(req); - return models.ProjectMember.getActiveProjectMembers(projectId) - .then((members) => { - - return models.CopilotApplication.findOne({ - where: { - opportunityId: opportunityId, - userId: currentUserId, - }, - }).then((copilotApplication) => { - // check if auth user has access to this project - const hasAccess = util.hasAdminRole(req) || isProjectManager || !!copilotApplication; - return Promise.resolve(hasAccess); - }) + return models.CopilotApplication.findOne({ + where: { + opportunityId: opportunityId, + userId: currentUserId, + }, + }).then((copilotApplication) => { + // check if auth user has access to this project + const hasAccess = util.hasAdminRole(req) || isProjectManager || !!copilotApplication; + return Promise.resolve(hasAccess); }) }) .then((hasAccess) => { From 3a64f1d93b8784b60da1208ac8fc7db41760cdb4 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 13 May 2025 23:57:25 +0200 Subject: [PATCH 059/166] fix: allow project managers to view applications of opportunity not belonging to their projects --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1acd4a4c..bf41b433 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup'] + only: ['develop', 'migration-setup', 'pm-1067_1'] - deployProd: context : org-global filters: From cfbbd5669b70cbb921d6ed349ad91f913ebe8aab Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 14 May 2025 17:16:04 +0200 Subject: [PATCH 060/166] removed not needed circle config --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bf41b433..1acd4a4c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup', 'pm-1067_1'] + only: ['develop', 'migration-setup'] - deployProd: context : org-global filters: From c7d8d376be048e2a5eda00c358a3732904560bf0 Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Mon, 19 May 2025 19:08:37 +0530 Subject: [PATCH 061/166] PM-1207 Make copilot oppportunity details api public --- src/routes/index.js | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/routes/index.js b/src/routes/index.js index 01b2d1e6..15064b7e 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -25,8 +25,8 @@ router.get(`/${apiVersion}/projects/health`, (req, res) => { // List of public routes const publicRoutes = [ - `/${apiVersion}/projects/copilots/opportunities`, - `/${apiVersion}/projects/copilot/opportunity/:id(\\d+)`, + new RegExp(`^/${apiVersion}/projects/copilots/opportunities$`), + new RegExp(`^/${apiVersion}/projects/copilot/opportunity/\\d+$`), ]; // All project service endpoints need authentication @@ -35,7 +35,7 @@ const jwtAuth = require('tc-core-library-js').middleware.jwtAuthenticator; router.all( RegExp(`\\/${apiVersion}\\/(copilots|projects|timelines|orgConfig|customer-payments)(?!\\/health).*`), (req, res, next) => { - if (publicRoutes.some(route => req.path.match(new RegExp(`^${route}$`)))) { + if (publicRoutes.some(routeRegex => routeRegex.test(req.path))) { return next(); } // JWT authentication @@ -404,16 +404,6 @@ router.route('/v5/projects/copilots/opportunities') router.route('/v5/projects/copilot/opportunity/:id(\\d+)') .get(require('./copilotOpportunity/get')); -// Project copilot opportunity apply -router.route('/v5/projects/copilots/opportunity/:id(\\d+)/apply') - .post(require('./copilotOpportunityApply/create')); -router.route('/v5/projects/copilots/opportunity/:id(\\d+)/applications') - .get(require('./copilotOpportunityApply/list')); - -// Copilot opportunity assign -router.route('/v5/projects/copilots/opportunity/:id(\\d+)/assign') - .post(require('./copilotOpportunity/assign')); - // Project Estimation Items router.route('/v5/projects/:projectId(\\d+)/estimations/:estimationId(\\d+)/items') .get(require('./projectEstimationItems/list')); From d2aa3e9fa6013ce30b8dece2035510e2220b0aac Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Mon, 19 May 2025 19:11:40 +0530 Subject: [PATCH 062/166] Fix accidentally removed code --- src/routes/index.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/routes/index.js b/src/routes/index.js index 15064b7e..54df9280 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -404,6 +404,16 @@ router.route('/v5/projects/copilots/opportunities') router.route('/v5/projects/copilot/opportunity/:id(\\d+)') .get(require('./copilotOpportunity/get')); +// Project copilot opportunity apply +router.route('/v5/projects/copilots/opportunity/:id(\\d+)/apply') + .post(require('./copilotOpportunityApply/create')); +router.route('/v5/projects/copilots/opportunity/:id(\\d+)/applications') + .get(require('./copilotOpportunityApply/list')); + +// Copilot opportunity assign +router.route('/v5/projects/copilots/opportunity/:id(\\d+)/assign') + .post(require('./copilotOpportunity/assign')); + // Project Estimation Items router.route('/v5/projects/:projectId(\\d+)/estimations/:estimationId(\\d+)/items') .get(require('./projectEstimationItems/list')); From 69eb97a771a3d2e0d2ee78aa2fa298ea13ba4a8f Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 19 May 2025 19:59:31 +0200 Subject: [PATCH 063/166] fix: invite user when assigning as copilot --- .circleci/config.yml | 2 +- src/routes/copilotOpportunity/assign.js | 55 ++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 90930d0d..b26090c5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup', 'pm-1168'] + only: ['develop', 'migration-setup', 'pm-1168_1'] - deployProd: context : org-global filters: diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index 81de6e83..98d6efc2 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -1,11 +1,13 @@ import _ from 'lodash'; import validate from 'express-validation'; import Joi from 'joi'; +import config from 'config'; import models from '../../models'; import util from '../../util'; import { PERMISSION } from '../../permissions/constants'; -import { COPILOT_APPLICATION_STATUS, COPILOT_OPPORTUNITY_STATUS, COPILOT_REQUEST_STATUS } from '../../constants'; +import { createEvent } from '../../services/busApi'; +import { CONNECT_NOTIFICATION_EVENT, COPILOT_APPLICATION_STATUS, COPILOT_OPPORTUNITY_STATUS, COPILOT_REQUEST_STATUS, EVENT, INVITE_STATUS, PROJECT_MEMBER_ROLE, RESOURCES } from '../../constants'; const assignCopilotOpportunityValidations = { body: Joi.object().keys({ @@ -73,6 +75,57 @@ module.exports = [ throw err; } + const project = await models.Project.findOne({ + where: { + id: projectId, + }, + }); + + const invite = await models.ProjectMemberInvite.create({ + status: INVITE_STATUS.PENDING, + role: PROJECT_MEMBER_ROLE.COPILOT, + userId, + email: existingUser.email, + }) + + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, + RESOURCES.PROJECT_MEMBER_INVITE, + invite.toJSON()); + + const initiator = await util.getMemberDetailsByUserIds([req.authUser.userId], req.log, req.id); + + const emailEventType = CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_EMAIL_INVITE_CREATED; + await createEvent(emailEventType, { + data: { + workManagerUrl: config.get('workManagerUrl'), + accountsAppURL: config.get('accountsAppUrl'), + subject: config.get('inviteEmailSubject'), + projects: [{ + name: project.name, + projectId, + sections: [ + { + EMAIL_INVITES: true, + title: config.get('inviteEmailSectionTitle'), + projectName: project.name, + projectId, + initiator, + isSSO: util.isSSO(project), + }, + ], + }], + }, + recipients: [invite.email], + version: 'v3', + from: { + name: config.get('EMAIL_INVITE_FROM_NAME'), + email: config.get('EMAIL_INVITE_FROM_EMAIL'), + }, + categories: [`${process.env.NODE_ENV}:${emailEventType}`.toLowerCase()], + }, req.log); + await models.CopilotRequest.update( { status: COPILOT_REQUEST_STATUS.FULFILLED }, { where: { id: opportunity.copilotRequestId }, transaction: t }, From 675858b60228207bfe8ef6809867ad8efeec9249 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 19 May 2025 23:32:34 +0200 Subject: [PATCH 064/166] fix: invite user when assigning as copilot --- src/routes/copilotOpportunity/assign.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index 98d6efc2..dd8433e3 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -88,6 +88,8 @@ module.exports = [ email: existingUser.email, }) + console.log("aftr create", invite) + util.sendResourceToKafkaBus( req, EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, @@ -117,7 +119,7 @@ module.exports = [ ], }], }, - recipients: [invite.email], + recipients: [existingUser.email], version: 'v3', from: { name: config.get('EMAIL_INVITE_FROM_NAME'), From 613defe820d04f1b8988c1dfc2844ffba9838270 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 20 May 2025 17:04:20 +0200 Subject: [PATCH 065/166] fix: invite user when assigning as copilot --- src/routes/copilotOpportunity/assign.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index dd8433e3..81626666 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -81,6 +81,8 @@ module.exports = [ }, }); + req.log.info("before create", existingUser) + const invite = await models.ProjectMemberInvite.create({ status: INVITE_STATUS.PENDING, role: PROJECT_MEMBER_ROLE.COPILOT, @@ -88,7 +90,7 @@ module.exports = [ email: existingUser.email, }) - console.log("aftr create", invite) + req.log.info("aftr create", invite) util.sendResourceToKafkaBus( req, From e3303fe58f40134c480741d954dbeb8756400bfd Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 20 May 2025 17:12:04 +0200 Subject: [PATCH 066/166] fix: invite user when assigning as copilot --- src/routes/copilotOpportunity/assign.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index 81626666..6e0e0429 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -81,13 +81,15 @@ module.exports = [ }, }); - req.log.info("before create", existingUser) + const applicationUser = await util.getMemberDetailsByUserIds([userId], req.log, req.id)[0]; + + req.log.info("before create", applicationUser) const invite = await models.ProjectMemberInvite.create({ status: INVITE_STATUS.PENDING, role: PROJECT_MEMBER_ROLE.COPILOT, userId, - email: existingUser.email, + email: applicationUser.email, }) req.log.info("aftr create", invite) @@ -121,7 +123,7 @@ module.exports = [ ], }], }, - recipients: [existingUser.email], + recipients: [applicationUser.email], version: 'v3', from: { name: config.get('EMAIL_INVITE_FROM_NAME'), From d83097b46115e8cc290ed5bc5d504e84e1120042 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 20 May 2025 18:34:01 +0200 Subject: [PATCH 067/166] fix: invite user when assigning as copilot --- src/routes/copilotOpportunity/assign.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index 6e0e0429..49811e3e 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -81,9 +81,9 @@ module.exports = [ }, }); - const applicationUser = await util.getMemberDetailsByUserIds([userId], req.log, req.id)[0]; + const applicationUser = await util.getMemberDetailsByUserIds([userId], req.log, req.id); - req.log.info("before create", applicationUser) + req.log.info("before create", applicationUser, userId) const invite = await models.ProjectMemberInvite.create({ status: INVITE_STATUS.PENDING, From 68fdf7b2b301ad2e68db25f7be239c7c7cfb6221 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 20 May 2025 19:28:50 +0200 Subject: [PATCH 068/166] fix: invite user when assigning as copilot --- src/routes/copilotOpportunity/assign.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index 49811e3e..db79dad1 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -83,13 +83,15 @@ module.exports = [ const applicationUser = await util.getMemberDetailsByUserIds([userId], req.log, req.id); - req.log.info("before create", applicationUser, userId) + req.log.info("before create", applicationUser, userId, applicationUser[0]) const invite = await models.ProjectMemberInvite.create({ status: INVITE_STATUS.PENDING, role: PROJECT_MEMBER_ROLE.COPILOT, userId, - email: applicationUser.email, + email: applicationUser[0].email, + createdBy: req.authUser.userId, + createdAt: new Date(), }) req.log.info("aftr create", invite) @@ -123,7 +125,7 @@ module.exports = [ ], }], }, - recipients: [applicationUser.email], + recipients: [applicationUser[0].email], version: 'v3', from: { name: config.get('EMAIL_INVITE_FROM_NAME'), From 7383c7092100b0dd60e5fa8cc9c21d2a9cf13c2b Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 20 May 2025 20:09:11 +0200 Subject: [PATCH 069/166] fix: invite user when assigning as copilot --- src/routes/copilotOpportunity/assign.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index db79dad1..6360edd9 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -83,7 +83,7 @@ module.exports = [ const applicationUser = await util.getMemberDetailsByUserIds([userId], req.log, req.id); - req.log.info("before create", applicationUser, userId, applicationUser[0]) + req.log.info("before create", applicationUser, userId, applicationUser[0].email) const invite = await models.ProjectMemberInvite.create({ status: INVITE_STATUS.PENDING, @@ -92,6 +92,8 @@ module.exports = [ email: applicationUser[0].email, createdBy: req.authUser.userId, createdAt: new Date(), + updatedBy: req.authUser.userId, + updatedAt: new Date(), }) req.log.info("aftr create", invite) From aaea12c86d598ec7fe8a49583977f2f71a26dd60 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 20 May 2025 22:05:42 +0200 Subject: [PATCH 070/166] fix: invite user when assigning as copilot --- src/routes/copilotOpportunity/assign.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index 6360edd9..f6f6fbd3 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -83,28 +83,27 @@ module.exports = [ const applicationUser = await util.getMemberDetailsByUserIds([userId], req.log, req.id); - req.log.info("before create", applicationUser, userId, applicationUser[0].email) - const invite = await models.ProjectMemberInvite.create({ status: INVITE_STATUS.PENDING, role: PROJECT_MEMBER_ROLE.COPILOT, userId, + projectId, email: applicationUser[0].email, createdBy: req.authUser.userId, createdAt: new Date(), updatedBy: req.authUser.userId, updatedAt: new Date(), + }, { + transaction: t, }) - req.log.info("aftr create", invite) - util.sendResourceToKafkaBus( req, EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, RESOURCES.PROJECT_MEMBER_INVITE, invite.toJSON()); - const initiator = await util.getMemberDetailsByUserIds([req.authUser.userId], req.log, req.id); + const authUserDetails = await util.getMemberDetailsByUserIds([req.authUser.userId], req.log, req.id); const emailEventType = CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_EMAIL_INVITE_CREATED; await createEvent(emailEventType, { @@ -121,7 +120,7 @@ module.exports = [ title: config.get('inviteEmailSectionTitle'), projectName: project.name, projectId, - initiator, + initiator: authUserDetails[0], isSSO: util.isSSO(project), }, ], From 35d27e06ddacda3e394f663f86bf6f1fd7b457ef Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 21 May 2025 15:14:31 +0200 Subject: [PATCH 071/166] fix: removed circle config --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b26090c5..1acd4a4c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup', 'pm-1168_1'] + only: ['develop', 'migration-setup'] - deployProd: context : org-global filters: From 1102fdc729b22427559a19db8ee8243473a1438a Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 21 May 2025 23:21:12 +0200 Subject: [PATCH 072/166] fix: added migration to set application id in project member invite --- ...1211708-copilot_invite_from_application.js | 18 +++++++++++ src/routes/copilotOpportunity/assign.js | 30 +++++++++---------- src/routes/projectMemberInvites/update.js | 4 ++- 3 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 migrations/umzug/migrations/20250521211708-copilot_invite_from_application.js diff --git a/migrations/umzug/migrations/20250521211708-copilot_invite_from_application.js b/migrations/umzug/migrations/20250521211708-copilot_invite_from_application.js new file mode 100644 index 00000000..487d324e --- /dev/null +++ b/migrations/umzug/migrations/20250521211708-copilot_invite_from_application.js @@ -0,0 +1,18 @@ +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.addColumn('project_member_invites', 'applicationId', { + type: Sequelize.BIGINT, + allowNull: true, + references: { + model: 'copilot_applications', + key: 'id', + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL', + }); + }, + + down: async (queryInterface) => { + await queryInterface.removeColumn('project_member_invites', 'applicationId'); + }, +}; diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index f6f6fbd3..cf2d8c47 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -81,6 +81,21 @@ module.exports = [ }, }); + const existingInvite = await models.ProjectMemberInvite.findAll({ + where: { + userId, + projectId, + role: PROJECT_MEMBER_ROLE.COPILOT, + status: INVITE_STATUS.PENDING, + }, + }); + + if (existingInvite) { + const err = new Error(`User already has an pending invite to the project`); + err.status = 400; + throw err; + } + const applicationUser = await util.getMemberDetailsByUserIds([userId], req.log, req.id); const invite = await models.ProjectMemberInvite.create({ @@ -135,21 +150,6 @@ module.exports = [ categories: [`${process.env.NODE_ENV}:${emailEventType}`.toLowerCase()], }, req.log); - await models.CopilotRequest.update( - { status: COPILOT_REQUEST_STATUS.FULFILLED }, - { where: { id: opportunity.copilotRequestId }, transaction: t }, - ); - - await opportunity.update( - { status: COPILOT_OPPORTUNITY_STATUS.COMPLETED }, - { transaction: t }, - ); - - await models.CopilotApplication.update( - { status: COPILOT_APPLICATION_STATUS.ACCEPTED }, - { where: { id: applicationId }, transaction: t }, - ); - res.status(200).send({ id: applicationId }); }).catch(err => next(err)); }, diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index ccb7e657..f7780a2b 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -119,7 +119,9 @@ module.exports = [ }; return util .addUserToProject(req, member) - .then(() => res.json(util.postProcessInvites('$.email', updatedInvite, req))) + .then(async () => { + return res.json(util.postProcessInvites('$.email', updatedInvite, req)) + }) .catch(err => next(err)); }); } From fe085cfc1965ce9d5b66be0d05847040688df615 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 21 May 2025 23:24:39 +0200 Subject: [PATCH 073/166] fix: added migration to set application id in project member invite --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1acd4a4c..b26090c5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup'] + only: ['develop', 'migration-setup', 'pm-1168_1'] - deployProd: context : org-global filters: From 42485c51f831cae6fd4dc5453d803425dc40304f Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 22 May 2025 00:13:10 +0200 Subject: [PATCH 074/166] fix: complete the copilot application on accepting invite --- src/models/projectMember.js | 3 +- src/models/projectMemberInvite.js | 10 ++++ src/routes/copilotOpportunity/assign.js | 5 +- src/routes/projectMemberInvites/update.js | 58 +++++++++++++++++++---- 4 files changed, 66 insertions(+), 10 deletions(-) diff --git a/src/models/projectMember.js b/src/models/projectMember.js index d1ed93fb..bf213a3d 100644 --- a/src/models/projectMember.js +++ b/src/models/projectMember.js @@ -44,11 +44,12 @@ module.exports = function defineProjectMember(sequelize, DataTypes) { }) .then(res => _.without(_.map(res, 'projectId'), null)); - ProjectMember.getActiveProjectMembers = projectId => ProjectMember.findAll({ + ProjectMember.getActiveProjectMembers = (projectId, t) => ProjectMember.findAll({ where: { deletedAt: { $eq: null }, projectId, }, + transaction: t, raw: true, }); diff --git a/src/models/projectMemberInvite.js b/src/models/projectMemberInvite.js index d19db4cf..dcae4cce 100644 --- a/src/models/projectMemberInvite.js +++ b/src/models/projectMemberInvite.js @@ -13,6 +13,16 @@ module.exports = function defineProjectMemberInvite(sequelize, DataTypes) { isEmail: true, }, }, + applicationId: { + type: DataTypes.BIGINT, + allowNull: true, + references: { + model: 'copilot_applications', + key: 'id', + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE', + }, role: { type: DataTypes.STRING, allowNull: false, diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index cf2d8c47..6013762f 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -66,7 +66,7 @@ module.exports = [ const projectId = opportunity.projectId; const userId = application.userId; - const activeMembers = await models.ProjectMember.getActiveProjectMembers(projectId); + const activeMembers = await models.ProjectMember.getActiveProjectMembers(projectId, t); const existingUser = activeMembers.find(item => item.userId === userId); if (existingUser && existingUser.role === 'copilot') { @@ -79,6 +79,7 @@ module.exports = [ where: { id: projectId, }, + transaction: t, }); const existingInvite = await models.ProjectMemberInvite.findAll({ @@ -88,6 +89,7 @@ module.exports = [ role: PROJECT_MEMBER_ROLE.COPILOT, status: INVITE_STATUS.PENDING, }, + transaction: t, }); if (existingInvite) { @@ -103,6 +105,7 @@ module.exports = [ role: PROJECT_MEMBER_ROLE.COPILOT, userId, projectId, + applicationId: application.id, email: applicationUser[0].email, createdBy: req.authUser.userId, createdAt: new Date(), diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index f7780a2b..7706811f 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -4,7 +4,7 @@ import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; -import { INVITE_STATUS, EVENT, RESOURCES } from '../../constants'; +import { INVITE_STATUS, EVENT, RESOURCES, COPILOT_APPLICATION_STATUS } from '../../constants'; import { PERMISSION } from '../../permissions/constants'; /** @@ -94,7 +94,7 @@ module.exports = [ if (updatedInvite.status === INVITE_STATUS.ACCEPTED || updatedInvite.status === INVITE_STATUS.REQUEST_APPROVED) { return models.ProjectMember.getActiveProjectMembers(projectId) - .then((members) => { + .then(async (members) => { req.context = req.context || {}; req.context.currentProjectMembers = members; let userId = updatedInvite.userId; @@ -117,12 +117,54 @@ module.exports = [ createdBy: req.authUser.userId, updatedBy: req.authUser.userId, }; - return util - .addUserToProject(req, member) - .then(async () => { - return res.json(util.postProcessInvites('$.email', updatedInvite, req)) - }) - .catch(err => next(err)); + const t = await models.sequelize.transaction(); + try { + await util.addUserToProject(req, member, t); + if (invite.applicationId) { + const application = await models.CopilotApplication.findOne({ + where: { + id: invite.applicationId, + }, + transaction: t, + }); + + await application.update({ status: COPILOT_APPLICATION_STATUS.ACCEPTED }, { + transaction: t + }); + + const opportunity = await models.CopilotOpportunity.findOne({ + where: { + id: application.opportunityId, + }, + transaction: t, + }); + + await opportunity.update({ + status: COPILOT_OPPORTUNITY_STATUS.COMPLETED + }, { + transaction: t, + }); + + const request = await models.CopilotRequest.findOne({ + where: { + id: opportunity.copilotRequestId, + }, + transaction: t, + }); + + await request.update({ + status: COPILOT_REQUEST_STATUS.FULFILLED + }, { + transaction: t, + }); + } + + await t.commit(); + return res.json(util.postProcessInvites('$.email', updatedInvite, req)); + } catch (e) { + await t.rollback(); + return next(e); + } }); } return res.json(util.postProcessInvites('$.email', updatedInvite, req)); From 2d20d167760261ba5415f5a2e94b9e2bfc17e996 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 22 May 2025 17:05:31 +0200 Subject: [PATCH 075/166] fix: complete the copilot application on accepting invite --- src/routes/copilotOpportunity/assign.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index 6013762f..77acb2a7 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -92,7 +92,9 @@ module.exports = [ transaction: t, }); - if (existingInvite) { + console.log(existingInvite, 'existingInvite asdas') + + if (existingInvite && existingInvite.length) { const err = new Error(`User already has an pending invite to the project`); err.status = 400; throw err; From 3ec8ebdd50235723c8fa35b246705c032b6c0544 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 22 May 2025 17:10:19 +0200 Subject: [PATCH 076/166] fix: complete the copilot application on accepting invite --- src/routes/copilotOpportunity/assign.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index 77acb2a7..08e75f5e 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -92,7 +92,7 @@ module.exports = [ transaction: t, }); - console.log(existingInvite, 'existingInvite asdas') + req.log.info(existingInvite, 'existingInvite asdas') if (existingInvite && existingInvite.length) { const err = new Error(`User already has an pending invite to the project`); From 364e74b4d690130ef399d907cf3d96cc2ca9944e Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 22 May 2025 17:51:27 +0200 Subject: [PATCH 077/166] fix: complete the copilot application on accepting invite --- src/routes/projectMemberInvites/update.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index 7706811f..438b722e 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -4,7 +4,7 @@ import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; -import { INVITE_STATUS, EVENT, RESOURCES, COPILOT_APPLICATION_STATUS } from '../../constants'; +import { INVITE_STATUS, EVENT, RESOURCES, COPILOT_APPLICATION_STATUS, COPILOT_OPPORTUNITY_STATUS, COPILOT_REQUEST_STATUS } from '../../constants'; import { PERMISSION } from '../../permissions/constants'; /** From d691100757b0947fcef17281abd7ced07f32ee55 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 22 May 2025 17:51:46 +0200 Subject: [PATCH 078/166] fix: complete the copilot application on accepting invite --- src/routes/copilotOpportunity/assign.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index 08e75f5e..3e8a4d88 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -92,8 +92,6 @@ module.exports = [ transaction: t, }); - req.log.info(existingInvite, 'existingInvite asdas') - if (existingInvite && existingInvite.length) { const err = new Error(`User already has an pending invite to the project`); err.status = 400; From 3a4fc53a52bf00f52fe31262a27983f700b7ac2d Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 22 May 2025 21:25:25 +0200 Subject: [PATCH 079/166] fix: added new application status --- .circleci/config.yml | 2 +- src/constants.js | 1 + src/routes/copilotOpportunity/assign.js | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b26090c5..1acd4a4c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup', 'pm-1168_1'] + only: ['develop', 'migration-setup'] - deployProd: context : org-global filters: diff --git a/src/constants.js b/src/constants.js index d432c1c5..93326428 100644 --- a/src/constants.js +++ b/src/constants.js @@ -20,6 +20,7 @@ export const COPILOT_REQUEST_STATUS = { export const COPILOT_APPLICATION_STATUS = { PENDING: 'pending', + INVITED: 'invited', ACCEPTED: 'accepted', }; diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index 3e8a4d88..46cb6c78 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -153,6 +153,12 @@ module.exports = [ categories: [`${process.env.NODE_ENV}:${emailEventType}`.toLowerCase()], }, req.log); + await application.update({ + status: COPILOT_APPLICATION_STATUS.INVITED, + }, { + transaction: t, + }); + res.status(200).send({ id: applicationId }); }).catch(err => next(err)); }, From d6bd47e6b08c66a26d7103f3ae72c251280bbd6d Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Fri, 23 May 2025 20:14:42 +0200 Subject: [PATCH 080/166] fix: QA feedbacks for assigning copilots --- .circleci/config.yml | 2 +- src/constants.js | 2 +- src/routes/copilotOpportunity/assign.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1acd4a4c..3fc9e254 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup'] + only: ['develop', 'migration-setup', 'pm-1168_2'] - deployProd: context : org-global filters: diff --git a/src/constants.js b/src/constants.js index 93326428..e9362976 100644 --- a/src/constants.js +++ b/src/constants.js @@ -15,7 +15,7 @@ export const COPILOT_REQUEST_STATUS = { REJECTED: 'rejected', SEEKING: 'seeking', CANCELED: 'canceled', - FULFILLED: 'fulfiled', + FULFILLED: 'fulfilled', }; export const COPILOT_APPLICATION_STATUS = { diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index 46cb6c78..31c35697 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -48,7 +48,7 @@ module.exports = [ } const application = await models.CopilotApplication.findOne({ - where: { id: applicationId }, + where: { id: applicationId, opportunityId: copilotOpportunityId }, transaction: t, }); From 07973aa8577d0495a3ee90489e7e3130a592a0f0 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Fri, 23 May 2025 20:31:28 +0200 Subject: [PATCH 081/166] fix: move application to pending when all invites are rejected --- src/models/projectMemberInvite.js | 8 ++++++++ src/routes/projectMemberInvites/delete.js | 22 ++++++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/models/projectMemberInvite.js b/src/models/projectMemberInvite.js index dcae4cce..29bd895c 100644 --- a/src/models/projectMemberInvite.js +++ b/src/models/projectMemberInvite.js @@ -65,6 +65,14 @@ module.exports = function defineProjectMemberInvite(sequelize, DataTypes) { raw: true, }); + ProjectMemberInvite.getPendingInvitesForApplication = applicationId => ProjectMemberInvite.findAll({ + where: { + applicationId, + status: INVITE_STATUS.PENDING, + }, + raw: true, + }); + ProjectMemberInvite.getPendingAndReguestedInvitesForProject = projectId => ProjectMemberInvite.findAll({ where: { projectId, diff --git a/src/routes/projectMemberInvites/delete.js b/src/routes/projectMemberInvites/delete.js index d8c8be91..5bc2e30d 100644 --- a/src/routes/projectMemberInvites/delete.js +++ b/src/routes/projectMemberInvites/delete.js @@ -2,7 +2,7 @@ import _ from 'lodash'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; -import { PROJECT_MEMBER_ROLE, INVITE_STATUS, EVENT, RESOURCES } from '../../constants'; +import { PROJECT_MEMBER_ROLE, INVITE_STATUS, EVENT, RESOURCES, COPILOT_APPLICATION_STATUS } from '../../constants'; import { PERMISSION } from '../../permissions/constants'; /** @@ -74,7 +74,7 @@ module.exports = [ .update({ status: INVITE_STATUS.CANCELED, }) - .then((updatedInvite) => { + .then(async (updatedInvite) => { // emit the event util.sendResourceToKafkaBus( req, @@ -82,6 +82,24 @@ module.exports = [ RESOURCES.PROJECT_MEMBER_INVITE, updatedInvite.toJSON()); + // update the application if the invite + // originated from copilot opportunity + if (invite.applicationId) { + const allPendingInvitesForApplication = await models.Sequelize.ProjectMemberInvite.getPendingInvitesForApplication(invite.applicationId); + + // If only the current invite is the open one's + // then the application status has to be moved to pending status + if (allPendingInvitesForApplication.length === 1) { + await models.Sequelize.CopilotApplication.update({ + status: COPILOT_APPLICATION_STATUS.PENDING, + }, { + where: { + id: invite.applicationId, + }, + }); + } + } + res.status(204).end(); }); }) From c43ace7810f6da025140af99c33ef3c33a5227b6 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Fri, 23 May 2025 20:33:08 +0200 Subject: [PATCH 082/166] fix: move application to pending when all invites are rejected --- src/routes/projectMemberInvites/delete.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/projectMemberInvites/delete.js b/src/routes/projectMemberInvites/delete.js index 5bc2e30d..e6ef9f65 100644 --- a/src/routes/projectMemberInvites/delete.js +++ b/src/routes/projectMemberInvites/delete.js @@ -85,8 +85,10 @@ module.exports = [ // update the application if the invite // originated from copilot opportunity if (invite.applicationId) { + req.log.info("Invite originated from the application id", invite.applicationId); const allPendingInvitesForApplication = await models.Sequelize.ProjectMemberInvite.getPendingInvitesForApplication(invite.applicationId); + req.log.info("All pending invites which are open", allPendingInvitesForApplication); // If only the current invite is the open one's // then the application status has to be moved to pending status if (allPendingInvitesForApplication.length === 1) { From 9ba2c671797210e604b7ec849f7af159c8662c7b Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Sat, 24 May 2025 00:36:14 +0200 Subject: [PATCH 083/166] fix: move application to pending when all invites are rejected --- src/routes/projectMemberInvites/update.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index 438b722e..11821d18 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -81,7 +81,7 @@ module.exports = [ .update({ status: newStatus, }) - .then((updatedInvite) => { + .then(async (updatedInvite) => { // emit the event util.sendResourceToKafkaBus( req, @@ -166,6 +166,26 @@ module.exports = [ return next(e); } }); + } else if (updatedInvite.status === INVITE_STATUS.REFUSED) { + // update the application if the invite + // originated from copilot opportunity + if (updatedInvite.applicationId) { + req.log.info("Invite originated from the application id", invite.applicationId); + const allPendingInvitesForApplication = await models.Sequelize.ProjectMemberInvite.getPendingInvitesForApplication(invite.applicationId); + + req.log.info("All pending invites which are open", allPendingInvitesForApplication); + // If only the current invite is the open one's + // then the application status has to be moved to pending status + if (allPendingInvitesForApplication.length === 1) { + await models.Sequelize.CopilotApplication.update({ + status: COPILOT_APPLICATION_STATUS.PENDING, + }, { + where: { + id: updatedInvite.applicationId, + }, + }); + } + } } return res.json(util.postProcessInvites('$.email', updatedInvite, req)); }); From 4b1e2b38acef504dd57fb2355264e9d00eb6e2f3 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Sun, 25 May 2025 23:41:16 +0200 Subject: [PATCH 084/166] fix: move application to pending when all invites are rejected --- src/routes/projectMemberInvites/delete.js | 4 ++-- src/routes/projectMemberInvites/update.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/routes/projectMemberInvites/delete.js b/src/routes/projectMemberInvites/delete.js index e6ef9f65..5d50901b 100644 --- a/src/routes/projectMemberInvites/delete.js +++ b/src/routes/projectMemberInvites/delete.js @@ -86,13 +86,13 @@ module.exports = [ // originated from copilot opportunity if (invite.applicationId) { req.log.info("Invite originated from the application id", invite.applicationId); - const allPendingInvitesForApplication = await models.Sequelize.ProjectMemberInvite.getPendingInvitesForApplication(invite.applicationId); + const allPendingInvitesForApplication = await models.ProjectMemberInvite.getPendingInvitesForApplication(invite.applicationId); req.log.info("All pending invites which are open", allPendingInvitesForApplication); // If only the current invite is the open one's // then the application status has to be moved to pending status if (allPendingInvitesForApplication.length === 1) { - await models.Sequelize.CopilotApplication.update({ + await models.CopilotApplication.update({ status: COPILOT_APPLICATION_STATUS.PENDING, }, { where: { diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index 11821d18..651acc93 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -171,13 +171,13 @@ module.exports = [ // originated from copilot opportunity if (updatedInvite.applicationId) { req.log.info("Invite originated from the application id", invite.applicationId); - const allPendingInvitesForApplication = await models.Sequelize.ProjectMemberInvite.getPendingInvitesForApplication(invite.applicationId); + const allPendingInvitesForApplication = await models.ProjectMemberInvite.getPendingInvitesForApplication(invite.applicationId); req.log.info("All pending invites which are open", allPendingInvitesForApplication); // If only the current invite is the open one's // then the application status has to be moved to pending status if (allPendingInvitesForApplication.length === 1) { - await models.Sequelize.CopilotApplication.update({ + await models.CopilotApplication.update({ status: COPILOT_APPLICATION_STATUS.PENDING, }, { where: { From 4defcf7174d09fdee343f377670d957fbaa052c7 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 26 May 2025 00:10:18 +0200 Subject: [PATCH 085/166] fix: move application to pending when all invites are rejected --- src/routes/projectMemberInvites/update.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index 651acc93..6dade7c7 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -176,7 +176,7 @@ module.exports = [ req.log.info("All pending invites which are open", allPendingInvitesForApplication); // If only the current invite is the open one's // then the application status has to be moved to pending status - if (allPendingInvitesForApplication.length === 1) { + if (allPendingInvitesForApplication.length === 0) { await models.CopilotApplication.update({ status: COPILOT_APPLICATION_STATUS.PENDING, }, { From cc46a746046a1bcf669c0bc182c57a7e2ea8cddb Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 26 May 2025 00:11:08 +0200 Subject: [PATCH 086/166] fix: move application to pending when all invites are rejected --- src/routes/projectMemberInvites/delete.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/projectMemberInvites/delete.js b/src/routes/projectMemberInvites/delete.js index 5d50901b..dafe5938 100644 --- a/src/routes/projectMemberInvites/delete.js +++ b/src/routes/projectMemberInvites/delete.js @@ -91,7 +91,7 @@ module.exports = [ req.log.info("All pending invites which are open", allPendingInvitesForApplication); // If only the current invite is the open one's // then the application status has to be moved to pending status - if (allPendingInvitesForApplication.length === 1) { + if (allPendingInvitesForApplication.length === 0) { await models.CopilotApplication.update({ status: COPILOT_APPLICATION_STATUS.PENDING, }, { From 3bf61b2d1df82989c7cad2387e48414fbe9a0cbf Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 26 May 2025 00:37:16 +0200 Subject: [PATCH 087/166] fix: move application to pending when all invites are rejected --- src/routes/projectMemberInvites/delete.js | 3 --- src/routes/projectMemberInvites/update.js | 3 --- 2 files changed, 6 deletions(-) diff --git a/src/routes/projectMemberInvites/delete.js b/src/routes/projectMemberInvites/delete.js index dafe5938..6eab2167 100644 --- a/src/routes/projectMemberInvites/delete.js +++ b/src/routes/projectMemberInvites/delete.js @@ -85,10 +85,7 @@ module.exports = [ // update the application if the invite // originated from copilot opportunity if (invite.applicationId) { - req.log.info("Invite originated from the application id", invite.applicationId); const allPendingInvitesForApplication = await models.ProjectMemberInvite.getPendingInvitesForApplication(invite.applicationId); - - req.log.info("All pending invites which are open", allPendingInvitesForApplication); // If only the current invite is the open one's // then the application status has to be moved to pending status if (allPendingInvitesForApplication.length === 0) { diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index 6dade7c7..71d2a47d 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -170,10 +170,7 @@ module.exports = [ // update the application if the invite // originated from copilot opportunity if (updatedInvite.applicationId) { - req.log.info("Invite originated from the application id", invite.applicationId); const allPendingInvitesForApplication = await models.ProjectMemberInvite.getPendingInvitesForApplication(invite.applicationId); - - req.log.info("All pending invites which are open", allPendingInvitesForApplication); // If only the current invite is the open one's // then the application status has to be moved to pending status if (allPendingInvitesForApplication.length === 0) { From e19a9d1d49c2a468d59f10cc146c346d053d4724 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 26 May 2025 00:37:33 +0200 Subject: [PATCH 088/166] fix: move application to pending when all invites are rejected --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3fc9e254..1acd4a4c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup', 'pm-1168_2'] + only: ['develop', 'migration-setup'] - deployProd: context : org-global filters: From d4d37907b0444a851dc381a13ca2f5718cd236f9 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 26 May 2025 16:03:14 +0200 Subject: [PATCH 089/166] debug --- .circleci/config.yml | 2 +- src/routes/copilotOpportunity/assign.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1acd4a4c..3fc9e254 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup'] + only: ['develop', 'migration-setup', 'pm-1168_2'] - deployProd: context : org-global filters: diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index 31c35697..8244eedd 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -99,6 +99,7 @@ module.exports = [ } const applicationUser = await util.getMemberDetailsByUserIds([userId], req.log, req.id); + console.log(applicationUser, 'applicationUser'); const invite = await models.ProjectMemberInvite.create({ status: INVITE_STATUS.PENDING, From 3a8a1fc9c5bddddc82c584328c8d5395fec98cf7 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 26 May 2025 16:03:34 +0200 Subject: [PATCH 090/166] debug --- src/routes/copilotOpportunity/assign.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index 8244eedd..01d797fa 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -99,7 +99,7 @@ module.exports = [ } const applicationUser = await util.getMemberDetailsByUserIds([userId], req.log, req.id); - console.log(applicationUser, 'applicationUser'); + req.log.info(applicationUser, 'applicationUser'); const invite = await models.ProjectMemberInvite.create({ status: INVITE_STATUS.PENDING, From ea2ac33b7eda8466104fb447553cb6484120c651 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 26 May 2025 16:55:54 +0200 Subject: [PATCH 091/166] debug --- src/routes/copilotOpportunity/assign.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index 01d797fa..ba066191 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -99,7 +99,7 @@ module.exports = [ } const applicationUser = await util.getMemberDetailsByUserIds([userId], req.log, req.id); - req.log.info(applicationUser, 'applicationUser'); + req.log.info(applicationUser, 'applicationUser asdsd', userId); const invite = await models.ProjectMemberInvite.create({ status: INVITE_STATUS.PENDING, From d31503ec39b3f90646eeb82863d539889c0c2f42 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 27 May 2025 18:56:49 +0200 Subject: [PATCH 092/166] fix: don't show applied on for completed project --- src/routes/copilotOpportunity/assign.js | 3 ++- src/routes/projectMemberInvites/create.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index ba066191..654f331e 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -99,7 +99,6 @@ module.exports = [ } const applicationUser = await util.getMemberDetailsByUserIds([userId], req.log, req.id); - req.log.info(applicationUser, 'applicationUser asdsd', userId); const invite = await models.ProjectMemberInvite.create({ status: INVITE_STATUS.PENDING, @@ -116,6 +115,8 @@ module.exports = [ transaction: t, }) + console.log(invite, 'invite askjdhasd') + util.sendResourceToKafkaBus( req, EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, diff --git a/src/routes/projectMemberInvites/create.js b/src/routes/projectMemberInvites/create.js index 02ce50c9..29ea525a 100644 --- a/src/routes/projectMemberInvites/create.js +++ b/src/routes/projectMemberInvites/create.js @@ -406,6 +406,7 @@ module.exports = [ req, invite.emails, inviteUserIds, invites, data, failed, members, inviteUsers)) .then((values) => { values.forEach((v) => { + console.log(v, 'v checking') // emit the event util.sendResourceToKafkaBus( req, From 8507660c7245287a93a89bff53384d0b7db44fde Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 27 May 2025 22:17:17 +0200 Subject: [PATCH 093/166] fix: don't show applied on for completed project --- src/routes/copilotOpportunity/assign.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index 654f331e..37f4dcf1 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -106,7 +106,6 @@ module.exports = [ userId, projectId, applicationId: application.id, - email: applicationUser[0].email, createdBy: req.authUser.userId, createdAt: new Date(), updatedBy: req.authUser.userId, From 09249e6108c59789de83d1bdaf05376958afec96 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 27 May 2025 22:17:45 +0200 Subject: [PATCH 094/166] fix: don't show applied on for completed project --- src/routes/copilotOpportunity/assign.js | 2 +- src/routes/projectMemberInvites/create.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index 37f4dcf1..a36cd1ca 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -114,7 +114,7 @@ module.exports = [ transaction: t, }) - console.log(invite, 'invite askjdhasd') + console.log(invite.toJSON(), 'invite askjdhasd') util.sendResourceToKafkaBus( req, diff --git a/src/routes/projectMemberInvites/create.js b/src/routes/projectMemberInvites/create.js index 29ea525a..d162a951 100644 --- a/src/routes/projectMemberInvites/create.js +++ b/src/routes/projectMemberInvites/create.js @@ -406,7 +406,7 @@ module.exports = [ req, invite.emails, inviteUserIds, invites, data, failed, members, inviteUsers)) .then((values) => { values.forEach((v) => { - console.log(v, 'v checking') + console.log(v.toJSON(), 'v checking') // emit the event util.sendResourceToKafkaBus( req, From d10aa020e700db435cc98825d061c654e85ce77a Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 27 May 2025 23:00:32 +0200 Subject: [PATCH 095/166] fix: don't show applied on for completed project --- src/events/busApi.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/events/busApi.js b/src/events/busApi.js index ccea766a..0ddea3b2 100644 --- a/src/events/busApi.js +++ b/src/events/busApi.js @@ -992,12 +992,14 @@ module.exports = (app, logger) => { /* Send event for Notification Service */ - const projectId = _.parseInt(req.params.projectId); + const projectId = resource.projectId; const userId = resource.userId; const email = resource.email; const status = resource.status; const role = resource.role; + console.log(projectId, 'projectId asjdasd') + models.Project.findOne({ where: { id: projectId }, }) From 4f2ea1a70da4866412e13c89459ce0b23448b6a5 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 27 May 2025 23:24:26 +0200 Subject: [PATCH 096/166] fix: don't show applied on for completed project --- src/routes/copilotOpportunity/assign.js | 47 +---------------------- src/routes/projectMemberInvites/create.js | 1 - 2 files changed, 1 insertion(+), 47 deletions(-) diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index a36cd1ca..fa5f1446 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -1,13 +1,11 @@ import _ from 'lodash'; import validate from 'express-validation'; import Joi from 'joi'; -import config from 'config'; import models from '../../models'; import util from '../../util'; import { PERMISSION } from '../../permissions/constants'; -import { createEvent } from '../../services/busApi'; -import { CONNECT_NOTIFICATION_EVENT, COPILOT_APPLICATION_STATUS, COPILOT_OPPORTUNITY_STATUS, COPILOT_REQUEST_STATUS, EVENT, INVITE_STATUS, PROJECT_MEMBER_ROLE, RESOURCES } from '../../constants'; +import { COPILOT_APPLICATION_STATUS, COPILOT_OPPORTUNITY_STATUS, COPILOT_REQUEST_STATUS, EVENT, INVITE_STATUS, PROJECT_MEMBER_ROLE, RESOURCES } from '../../constants'; const assignCopilotOpportunityValidations = { body: Joi.object().keys({ @@ -75,13 +73,6 @@ module.exports = [ throw err; } - const project = await models.Project.findOne({ - where: { - id: projectId, - }, - transaction: t, - }); - const existingInvite = await models.ProjectMemberInvite.findAll({ where: { userId, @@ -98,8 +89,6 @@ module.exports = [ throw err; } - const applicationUser = await util.getMemberDetailsByUserIds([userId], req.log, req.id); - const invite = await models.ProjectMemberInvite.create({ status: INVITE_STATUS.PENDING, role: PROJECT_MEMBER_ROLE.COPILOT, @@ -114,46 +103,12 @@ module.exports = [ transaction: t, }) - console.log(invite.toJSON(), 'invite askjdhasd') - util.sendResourceToKafkaBus( req, EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, RESOURCES.PROJECT_MEMBER_INVITE, invite.toJSON()); - const authUserDetails = await util.getMemberDetailsByUserIds([req.authUser.userId], req.log, req.id); - - const emailEventType = CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_EMAIL_INVITE_CREATED; - await createEvent(emailEventType, { - data: { - workManagerUrl: config.get('workManagerUrl'), - accountsAppURL: config.get('accountsAppUrl'), - subject: config.get('inviteEmailSubject'), - projects: [{ - name: project.name, - projectId, - sections: [ - { - EMAIL_INVITES: true, - title: config.get('inviteEmailSectionTitle'), - projectName: project.name, - projectId, - initiator: authUserDetails[0], - isSSO: util.isSSO(project), - }, - ], - }], - }, - recipients: [applicationUser[0].email], - version: 'v3', - from: { - name: config.get('EMAIL_INVITE_FROM_NAME'), - email: config.get('EMAIL_INVITE_FROM_EMAIL'), - }, - categories: [`${process.env.NODE_ENV}:${emailEventType}`.toLowerCase()], - }, req.log); - await application.update({ status: COPILOT_APPLICATION_STATUS.INVITED, }, { diff --git a/src/routes/projectMemberInvites/create.js b/src/routes/projectMemberInvites/create.js index d162a951..02ce50c9 100644 --- a/src/routes/projectMemberInvites/create.js +++ b/src/routes/projectMemberInvites/create.js @@ -406,7 +406,6 @@ module.exports = [ req, invite.emails, inviteUserIds, invites, data, failed, members, inviteUsers)) .then((values) => { values.forEach((v) => { - console.log(v.toJSON(), 'v checking') // emit the event util.sendResourceToKafkaBus( req, From 5caaf0d1f578bd40d51a91eb4040cddc6d9d6373 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 27 May 2025 23:27:40 +0200 Subject: [PATCH 097/166] fix: don't show applied on for completed project --- src/events/busApi.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/events/busApi.js b/src/events/busApi.js index 0ddea3b2..eb8b0f28 100644 --- a/src/events/busApi.js +++ b/src/events/busApi.js @@ -998,8 +998,6 @@ module.exports = (app, logger) => { const status = resource.status; const role = resource.role; - console.log(projectId, 'projectId asjdasd') - models.Project.findOne({ where: { id: projectId }, }) From cf0236d5713cfb9acdd592ba7651261c23d3c1e1 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 28 May 2025 00:16:57 +0200 Subject: [PATCH 098/166] removed circle config --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3fc9e254..1acd4a4c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup', 'pm-1168_2'] + only: ['develop', 'migration-setup'] - deployProd: context : org-global filters: From aa20437b5a133bfd93667b9a0bfd98beb67bd3ba Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Wed, 28 May 2025 09:08:05 +1000 Subject: [PATCH 099/166] DB schema config parameter --- config/custom-environment-variables.json | 3 ++- src/models/index.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/config/custom-environment-variables.json b/config/custom-environment-variables.json index 5d7f5192..2d1d9475 100644 --- a/config/custom-environment-variables.json +++ b/config/custom-environment-variables.json @@ -30,7 +30,8 @@ "dbConfig": { "masterUrl": "DB_MASTER_URL", "maxPoolSize": "DB_MAX_POOL_SIZE", - "minPoolSize": "DB_MIN_POOL_SIZE" + "minPoolSize": "DB_MIN_POOL_SIZE", + "schema": "DB_SCHEMA_NAME" }, "kafkaConfig": { "groupId": "KAFKA_GROUP_ID", diff --git a/src/models/index.js b/src/models/index.js index 0acc3b27..036e30ae 100644 --- a/src/models/index.js +++ b/src/models/index.js @@ -29,6 +29,7 @@ const operatorsAliases = { const sequelize = new Sequelize(config.get('dbConfig.masterUrl'), { operatorsAliases, + schema: config.get('dbConfig.schema'), logging: false, dialectOptions: { ssl: false, From bbb0d6b8ad3e366ad802c66df69ebb154b32ea9c Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Wed, 28 May 2025 09:34:03 +1000 Subject: [PATCH 100/166] Add schema to umzug config as well --- .circleci/config.yml | 9 +++++++++ migrations/umzug/index.js | 1 + 2 files changed, 10 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1acd4a4c..e97bc479 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,6 +15,13 @@ install_deploysuite: &install_deploysuite cp ./../buildscript/master_deploy.sh . cp ./../buildscript/buildenv.sh . cp ./../buildscript/awsconfiguration.sh . +restore_cache_settings_for_build: &restore_cache_settings_for_build + key: docker-node-modules-v4-{{ checksum "package-lock.json" }} + +save_cache_settings: &save_cache_settings + key: docker-node-modules-v4-{{ checksum "package-lock.json" }} + paths: + - node_modules # Instructions of deployment deploy_steps: &deploy_steps @@ -24,6 +31,8 @@ deploy_steps: &deploy_steps - run: *install_awscli - run: *install_deploysuite - setup_remote_docker + # Restoration of node_modules from cache. + - restore_cache: *restore_cache_settings_for_build - run: docker build -t ${APPNAME}:latest . - deploy: name: "Running Masterscript - deploy tc-project-service " diff --git a/migrations/umzug/index.js b/migrations/umzug/index.js index 7a807c26..40401ea7 100644 --- a/migrations/umzug/index.js +++ b/migrations/umzug/index.js @@ -5,6 +5,7 @@ const { Umzug, SequelizeStorage } = require('umzug'); // Initialize Sequelize const sequelize = new Sequelize(config.get('dbConfig.masterUrl'), { dialect: 'postgres', + schema: config.get('dbConfig.schema'), }); // Initialize Umzug From dea4e18a2a0083fb5ae8a72144491b1cd8407870 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Wed, 28 May 2025 09:34:56 +1000 Subject: [PATCH 101/166] Back out circle ci test --- .circleci/config.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e97bc479..1acd4a4c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,13 +15,6 @@ install_deploysuite: &install_deploysuite cp ./../buildscript/master_deploy.sh . cp ./../buildscript/buildenv.sh . cp ./../buildscript/awsconfiguration.sh . -restore_cache_settings_for_build: &restore_cache_settings_for_build - key: docker-node-modules-v4-{{ checksum "package-lock.json" }} - -save_cache_settings: &save_cache_settings - key: docker-node-modules-v4-{{ checksum "package-lock.json" }} - paths: - - node_modules # Instructions of deployment deploy_steps: &deploy_steps @@ -31,8 +24,6 @@ deploy_steps: &deploy_steps - run: *install_awscli - run: *install_deploysuite - setup_remote_docker - # Restoration of node_modules from cache. - - restore_cache: *restore_cache_settings_for_build - run: docker build -t ${APPNAME}:latest . - deploy: name: "Running Masterscript - deploy tc-project-service " From 160c4d868bc99e31a67df1214727f86fe4707173 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 28 May 2025 16:39:40 +0200 Subject: [PATCH 102/166] fix: send source as part of project member invite --- src/routes/copilotOpportunity/assign.js | 5 ++++- src/routes/projectMemberInvites/create.js | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index fa5f1446..17c03085 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -107,7 +107,10 @@ module.exports = [ req, EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, RESOURCES.PROJECT_MEMBER_INVITE, - invite.toJSON()); + { + ...invite.toJSON(), + source: 'copilot_portal', + }); await application.update({ status: COPILOT_APPLICATION_STATUS.INVITED, diff --git a/src/routes/projectMemberInvites/create.js b/src/routes/projectMemberInvites/create.js index 02ce50c9..44b9b50f 100644 --- a/src/routes/projectMemberInvites/create.js +++ b/src/routes/projectMemberInvites/create.js @@ -411,7 +411,10 @@ module.exports = [ req, EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, RESOURCES.PROJECT_MEMBER_INVITE, - v.toJSON()); + { + ...v.toJSON(), + source: 'work_manager', + }); req.log.debug(`V: ${JSON.stringify(v)}`); // send email invite (async) From db388bccfa3e424e4aada63177299bcfb71f9e6b Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 28 May 2025 16:40:34 +0200 Subject: [PATCH 103/166] fix: send source as part of project member invite --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1acd4a4c..753c7b1e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup'] + only: ['develop', 'migration-setup', 'pm-1169'] - deployProd: context : org-global filters: From ded8a7cd3915154fa11fdc6918ecdea30352cdeb Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 28 May 2025 16:54:47 +0200 Subject: [PATCH 104/166] fix: send source as part of project member invite --- src/routes/copilotOpportunity/assign.js | 6 +++--- src/routes/projectMemberInvites/create.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index 17c03085..5a3d0b3f 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -107,10 +107,10 @@ module.exports = [ req, EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, RESOURCES.PROJECT_MEMBER_INVITE, - { - ...invite.toJSON(), + Object.assign({}, invite.toJSON(), { source: 'copilot_portal', - }); + }), + ); await application.update({ status: COPILOT_APPLICATION_STATUS.INVITED, diff --git a/src/routes/projectMemberInvites/create.js b/src/routes/projectMemberInvites/create.js index 44b9b50f..7c5a836d 100644 --- a/src/routes/projectMemberInvites/create.js +++ b/src/routes/projectMemberInvites/create.js @@ -411,10 +411,10 @@ module.exports = [ req, EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, RESOURCES.PROJECT_MEMBER_INVITE, - { - ...v.toJSON(), + Object.assign({}, v.toJSON(), { source: 'work_manager', - }); + }), + ); req.log.debug(`V: ${JSON.stringify(v)}`); // send email invite (async) From 7c299935518babe49e2b6fb18f9fb23ca1e7a9a5 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 28 May 2025 18:43:17 +0200 Subject: [PATCH 105/166] fix: send source as part of project member invite --- src/events/busApi.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/events/busApi.js b/src/events/busApi.js index eb8b0f28..c3e0b58d 100644 --- a/src/events/busApi.js +++ b/src/events/busApi.js @@ -997,6 +997,7 @@ module.exports = (app, logger) => { const email = resource.email; const status = resource.status; const role = resource.role; + const source = resource.source; models.Project.findOne({ where: { id: projectId }, @@ -1011,6 +1012,7 @@ module.exports = (app, logger) => { role, initiatorUserId: req.authUser.userId, isSSO: util.isSSO(project), + source, }, logger); } else { // send event to bus api @@ -1021,6 +1023,7 @@ module.exports = (app, logger) => { role, initiatorUserId: req.authUser.userId, isSSO: util.isSSO(project), + source, }); createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_CREATED, { projectId, @@ -1029,6 +1032,7 @@ module.exports = (app, logger) => { role, initiatorUserId: req.authUser.userId, isSSO: util.isSSO(project), + source, }, logger); } }).catch(err => logger.error(err)); // eslint-disable-line no-unused-vars From cc922784676411e8654b9bb8757e55b524a59212 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Fri, 30 May 2025 16:14:07 +0200 Subject: [PATCH 106/166] fix: update copilot request based on the source of invite --- src/constants.js | 6 ++++++ src/routes/projectMemberInvites/update.js | 21 +++++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/constants.js b/src/constants.js index e9362976..ce2d90bb 100644 --- a/src/constants.js +++ b/src/constants.js @@ -22,6 +22,7 @@ export const COPILOT_APPLICATION_STATUS = { PENDING: 'pending', INVITED: 'invited', ACCEPTED: 'accepted', + CANCELED: 'canceled', }; export const COPILOT_OPPORTUNITY_STATUS = { @@ -360,6 +361,11 @@ export const INVITE_STATUS = { CANCELED: 'canceled', }; +export const INVITE_SOURCE = { + WORK_MANAGER: "work_manager", + COPILOT_PORTAL: "copilot_portal", +}; + export const SCOPE_CHANGE_REQ_STATUS = { PENDING: 'pending', APPROVED: 'approved', diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index 71d2a47d..a92a9c63 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -4,7 +4,7 @@ import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; -import { INVITE_STATUS, EVENT, RESOURCES, COPILOT_APPLICATION_STATUS, COPILOT_OPPORTUNITY_STATUS, COPILOT_REQUEST_STATUS } from '../../constants'; +import { INVITE_STATUS, EVENT, RESOURCES, COPILOT_APPLICATION_STATUS, COPILOT_OPPORTUNITY_STATUS, COPILOT_REQUEST_STATUS, INVITE_SOURCE } from '../../constants'; import { PERMISSION } from '../../permissions/constants'; /** @@ -19,6 +19,9 @@ const updateMemberValidations = { status: Joi.any() .valid(_.values(INVITE_STATUS)) .required(), + source: Joi.any() + .valid(_.values(INVITE_SOURCE)) + .default(INVITE_SOURCE.WORK_MANAGER), }) .required(), }; @@ -29,6 +32,7 @@ module.exports = [ permissions('projectMemberInvite.edit'), (req, res, next) => { const newStatus = req.body.status; + const source = req.body.source; if (newStatus === INVITE_STATUS.CANCELED) { const err = new Error('Cannot change invite status to “canceled”. Please, delete the invite instead.'); err.status = 400; @@ -121,6 +125,15 @@ module.exports = [ try { await util.addUserToProject(req, member, t); if (invite.applicationId) { + let nextApplicationStatus = COPILOT_APPLICATION_STATUS.CANCELED; + let nextOpportunityStatus = COPILOT_OPPORTUNITY_STATUS.CANCELED; + let nextOpportunityRequestStatus = COPILOT_REQUEST_STATUS.CANCELED; + if (source === 'copilot_portal') { + nextApplicationStatus = COPILOT_APPLICATION_STATUS.ACCEPTED; + nextOpportunityStatus = COPILOT_OPPORTUNITY_STATUS.COMPLETED; + nextOpportunityRequestStatus = COPILOT_REQUEST_STATUS.FULFILLED; + } + const application = await models.CopilotApplication.findOne({ where: { id: invite.applicationId, @@ -128,7 +141,7 @@ module.exports = [ transaction: t, }); - await application.update({ status: COPILOT_APPLICATION_STATUS.ACCEPTED }, { + await application.update({ status: nextApplicationStatus }, { transaction: t }); @@ -140,7 +153,7 @@ module.exports = [ }); await opportunity.update({ - status: COPILOT_OPPORTUNITY_STATUS.COMPLETED + status: nextOpportunityStatus, }, { transaction: t, }); @@ -153,7 +166,7 @@ module.exports = [ }); await request.update({ - status: COPILOT_REQUEST_STATUS.FULFILLED + status: nextOpportunityRequestStatus, }, { transaction: t, }); From dc019e35d5d7d466a57f372912184661c95692a6 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 2 Jun 2025 13:12:55 +0200 Subject: [PATCH 107/166] fix: send source to accept or decline invite API --- src/routes/projectMemberInvites/update.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index a92a9c63..9dfbfdf0 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -19,7 +19,7 @@ const updateMemberValidations = { status: Joi.any() .valid(_.values(INVITE_STATUS)) .required(), - source: Joi.any() + source: Joi.string() .valid(_.values(INVITE_SOURCE)) .default(INVITE_SOURCE.WORK_MANAGER), }) From 79fdf6fb27b9d13ab25cbc38d3a07d08a8e8fd74 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 2 Jun 2025 16:48:14 +0200 Subject: [PATCH 108/166] feat: get roles by role name --- .../copilotRequest/approveRequest.service.js | 7 +++++++ src/util.js | 21 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/routes/copilotRequest/approveRequest.service.js b/src/routes/copilotRequest/approveRequest.service.js index b5164092..e0980b04 100644 --- a/src/routes/copilotRequest/approveRequest.service.js +++ b/src/routes/copilotRequest/approveRequest.service.js @@ -2,6 +2,7 @@ import _ from 'lodash'; import models from '../../models'; import { COPILOT_REQUEST_STATUS } from '../../constants'; +import util from '../../util'; const resolveTransaction = (transaction, callback) => { if (transaction) { @@ -52,6 +53,12 @@ module.exports = (data, existingTransaction) => { return models.CopilotOpportunity .create(data, { transaction }); })) + .then((opportunity) => { + console.log(opportunity); + const roles = util.getRolesByRoleName('copilot'); + console.log(roles, 'roles by copilot'); + return opportunity; + }) .catch((err) => { transaction.rollback(); return Promise.reject(err); diff --git a/src/util.js b/src/util.js index 43b3c092..5962f419 100644 --- a/src/util.js +++ b/src/util.js @@ -815,6 +815,27 @@ const projectServiceUtils = { } }, + getRolesByRoleName: Promise.coroutine(function* (roleName, logger, requestId) { // eslint-disable-line func-names + try { + const token = yield this.getM2MToken(); + const httpClient = this.getHttpClient({ id: requestId, log: logger }); + return httpClient.get(`${config.identityServiceEndpoint}roles`, { + params: { + filter: `roleName=${roleName}`, + }, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + }).then((res) => { + logger.debug(`Roles by ${roleName}: ${JSON.stringify(res.data.result.content)}`); + return _.get(res, 'data.result.content', []).map(r => r.roleName); + }); + } catch (err) { + return Promise.reject(err); + } + }), + /** * Retrieve member details from userIds */ From 4b8c9f71049dafd3ff3f2c0405470421a7fa0219 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 2 Jun 2025 16:54:22 +0200 Subject: [PATCH 109/166] deploy feature branch --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1acd4a4c..5b58794d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup'] + only: ['develop', 'migration-setup', 'pm-1173'] - deployProd: context : org-global filters: From 0c54ecf674f59efc9eceb2b9c286aacb0696fde1 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 2 Jun 2025 17:04:33 +0200 Subject: [PATCH 110/166] deploy feature branch --- src/constants.js | 3 +++ .../copilotRequest/approveRequest.service.js | 4 +++- src/util.js | 23 ++++++++++++++++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/constants.js b/src/constants.js index e9362976..4782f307 100644 --- a/src/constants.js +++ b/src/constants.js @@ -301,6 +301,9 @@ export const CONNECT_NOTIFICATION_EVENT = { TOPIC_UPDATED: 'connect.notification.project.topic.updated', POST_CREATED: 'connect.notification.project.post.created', POST_UPDATED: 'connect.notification.project.post.edited', + + // Copilot events + COPILOT_OPPORTUNITY_CREATED: 'connect.notification.project.copilot.opportunity.created', }; export const REGEX = { diff --git a/src/routes/copilotRequest/approveRequest.service.js b/src/routes/copilotRequest/approveRequest.service.js index e0980b04..578cd925 100644 --- a/src/routes/copilotRequest/approveRequest.service.js +++ b/src/routes/copilotRequest/approveRequest.service.js @@ -56,7 +56,9 @@ module.exports = (data, existingTransaction) => { .then((opportunity) => { console.log(opportunity); const roles = util.getRolesByRoleName('copilot'); - console.log(roles, 'roles by copilot'); + const roleInfo = util.getRoleInfo(roles[0]); + console.log(roles, roleInfo, 'roles by copilot'); + return opportunity; }) .catch((err) => { diff --git a/src/util.js b/src/util.js index 5962f419..4b1fce7e 100644 --- a/src/util.js +++ b/src/util.js @@ -815,6 +815,27 @@ const projectServiceUtils = { } }, + getRoleInfo: Promise.coroutine(function* (roleId, logger, requestId) { // eslint-disable-line func-names + try { + const token = yield this.getM2MToken(); + const httpClient = this.getHttpClient({ id: requestId, log: logger }); + return httpClient.get(`${config.identityServiceEndpoint}roles/${roleId}`, { + params: { + fields: 'subjects' + }, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + }).then((res) => { + logger.debug(`Role info by ${roleId}: ${JSON.stringify(res.data.result.content)}`); + return _.get(res, 'data.result.content', []); + }); + } catch (err) { + return Promise.reject(err); + } + }), + getRolesByRoleName: Promise.coroutine(function* (roleName, logger, requestId) { // eslint-disable-line func-names try { const token = yield this.getM2MToken(); @@ -829,7 +850,7 @@ const projectServiceUtils = { }, }).then((res) => { logger.debug(`Roles by ${roleName}: ${JSON.stringify(res.data.result.content)}`); - return _.get(res, 'data.result.content', []).map(r => r.roleName); + return _.get(res, 'data.result.content', []).map(r => r.id); }); } catch (err) { return Promise.reject(err); From 223b7bd8d9ba61b083ef60efd255344c7631515e Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 2 Jun 2025 22:54:39 +0200 Subject: [PATCH 111/166] deploy feature branch --- src/routes/copilotRequest/approveRequest.service.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/copilotRequest/approveRequest.service.js b/src/routes/copilotRequest/approveRequest.service.js index 578cd925..b9da5b50 100644 --- a/src/routes/copilotRequest/approveRequest.service.js +++ b/src/routes/copilotRequest/approveRequest.service.js @@ -53,10 +53,10 @@ module.exports = (data, existingTransaction) => { return models.CopilotOpportunity .create(data, { transaction }); })) - .then((opportunity) => { + .then(async (opportunity) => { console.log(opportunity); - const roles = util.getRolesByRoleName('copilot'); - const roleInfo = util.getRoleInfo(roles[0]); + const roles = await util.getRolesByRoleName('copilot'); + const roleInfo = await util.getRoleInfo(roles[0]); console.log(roles, roleInfo, 'roles by copilot'); return opportunity; From 82dd7978ff0607399c56cef81c3d30ec8d47d2e6 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 2 Jun 2025 23:42:05 +0200 Subject: [PATCH 112/166] deploy feature branch --- src/routes/copilotRequest/approveRequest.js | 2 +- src/routes/copilotRequest/approveRequest.service.js | 6 +++--- src/routes/copilotRequest/create.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/routes/copilotRequest/approveRequest.js b/src/routes/copilotRequest/approveRequest.js index 12fd6a8b..c1371671 100644 --- a/src/routes/copilotRequest/approveRequest.js +++ b/src/routes/copilotRequest/approveRequest.js @@ -35,7 +35,7 @@ module.exports = [ updatedBy: req.authUser.userId, }); - return approveRequest(data) + return approveRequest(req, data) .then(_newCopilotOpportunity => res.status(201).json(_newCopilotOpportunity)) .catch((err) => { if (err.message) { diff --git a/src/routes/copilotRequest/approveRequest.service.js b/src/routes/copilotRequest/approveRequest.service.js index b9da5b50..eb818722 100644 --- a/src/routes/copilotRequest/approveRequest.service.js +++ b/src/routes/copilotRequest/approveRequest.service.js @@ -12,7 +12,7 @@ const resolveTransaction = (transaction, callback) => { return models.sequelize.transaction(callback); }; -module.exports = (data, existingTransaction) => { +module.exports = (req, data, existingTransaction) => { const { projectId, copilotRequestId } = data; return resolveTransaction(existingTransaction, transaction => @@ -55,8 +55,8 @@ module.exports = (data, existingTransaction) => { })) .then(async (opportunity) => { console.log(opportunity); - const roles = await util.getRolesByRoleName('copilot'); - const roleInfo = await util.getRoleInfo(roles[0]); + const roles = await util.getRolesByRoleName('copilot', req.log, req.id); + const roleInfo = await util.getRoleInfo(roles[0], req.log, req.id); console.log(roles, roleInfo, 'roles by copilot'); return opportunity; diff --git a/src/routes/copilotRequest/create.js b/src/routes/copilotRequest/create.js index 95fa45f2..2b05f524 100644 --- a/src/routes/copilotRequest/create.js +++ b/src/routes/copilotRequest/create.js @@ -98,7 +98,7 @@ module.exports = [ updatedBy: req.authUser.userId, type: copilotRequest.data.projectType, }); - return approveRequest(approveData, transaction).then(() => copilotRequest); + return approveRequest(req, approveData, transaction).then(() => copilotRequest); }).then(copilotRequest => res.status(201).json(copilotRequest)) .catch((err) => { try { From 9793dcc61549c31e6048fcab3bfa9c44171a9530 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 3 Jun 2025 00:46:29 +0200 Subject: [PATCH 113/166] fix: get role by role name --- config/default.json | 1 + config/development.json | 1 + config/production.json | 1 + .../copilotRequest/approveRequest.service.js | 30 ++++++++++++++++--- src/util.js | 4 ++- 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/config/default.json b/config/default.json index 43353496..d65b6383 100644 --- a/config/default.json +++ b/config/default.json @@ -54,6 +54,7 @@ "inviteEmailSubject": "You are invited to Topcoder", "inviteEmailSectionTitle": "Project Invitation", "workManagerUrl": "https://challenges.topcoder-dev.com", + "copilotPortalUrl": "https://copilots.topcoder-dev.com", "accountsAppUrl": "https://accounts.topcoder-dev.com", "MAX_REVISION_NUMBER": 100, "UNIQUE_GMAIL_VALIDATION": false, diff --git a/config/development.json b/config/development.json index 5874ef2c..878bc669 100644 --- a/config/development.json +++ b/config/development.json @@ -3,6 +3,7 @@ "pubsubExchangeName": "dev.projects", "attachmentsS3Bucket": "topcoder-dev-media", "workManagerUrl": "https://challenges.topcoder-dev.com", + "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/", diff --git a/config/production.json b/config/production.json index d784a55e..73399edf 100644 --- a/config/production.json +++ b/config/production.json @@ -1,6 +1,7 @@ { "authDomain": "topcoder.com", "workManagerUrl": "https://challenges.topcoder.com", + "copilotPortalUrl": "https://copilots.topcoder.com", "sfdcBillingAccountNameField": "Billing_Account_name__c", "sfdcBillingAccountMarkupField": "Mark_up__c", "sfdcBillingAccountActiveField": "Active__c" diff --git a/src/routes/copilotRequest/approveRequest.service.js b/src/routes/copilotRequest/approveRequest.service.js index eb818722..dc7e5445 100644 --- a/src/routes/copilotRequest/approveRequest.service.js +++ b/src/routes/copilotRequest/approveRequest.service.js @@ -1,8 +1,10 @@ import _ from 'lodash'; +import config from 'config'; import models from '../../models'; -import { COPILOT_REQUEST_STATUS } from '../../constants'; +import { CONNECT_NOTIFICATION_EVENT, COPILOT_REQUEST_STATUS } from '../../constants'; import util from '../../util'; +import { createEvent } from '../../services/busApi'; const resolveTransaction = (transaction, callback) => { if (transaction) { @@ -54,11 +56,31 @@ module.exports = (req, data, existingTransaction) => { .create(data, { transaction }); })) .then(async (opportunity) => { - console.log(opportunity); const roles = await util.getRolesByRoleName('copilot', req.log, req.id); - const roleInfo = await util.getRoleInfo(roles[0], req.log, req.id); - console.log(roles, roleInfo, 'roles by copilot'); + const { subjects = [] } = await util.getRoleInfo(roles[0], req.log, req.id); + req.log.info("getting subjects for roles", roles[0]); + const emailEventType = CONNECT_NOTIFICATION_EVENT.COPILOT_OPPORTUNITY_CREATED; + const copilotPortalUrl = config.get('copilotPortalUrl'); + req.log.info("Sending emails to all copilots about new opportunity"); + subjects.forEach(subject => { + req.log.info("Each copilot members", subject); + createEvent(emailEventType, { + data: { + handle: subject.handle, + opportunityDetailsUrl: `${copilotPortalUrl}/opportunity/${opportunity.id}`, + }, + recipients: [subject.email], + version: 'v3', + from: { + name: config.get('EMAIL_INVITE_FROM_NAME'), + email: config.get('EMAIL_INVITE_FROM_EMAIL'), + }, + categories: [`${process.env.NODE_ENV}:${emailEventType}`.toLowerCase()], + }, req.log); + }); + req.log.info("Finished sending emails to copilots"); + return opportunity; }) .catch((err) => { diff --git a/src/util.js b/src/util.js index 4b1fce7e..80de04a1 100644 --- a/src/util.js +++ b/src/util.js @@ -850,7 +850,9 @@ const projectServiceUtils = { }, }).then((res) => { logger.debug(`Roles by ${roleName}: ${JSON.stringify(res.data.result.content)}`); - return _.get(res, 'data.result.content', []).map(r => r.id); + return _.get(res, 'data.result.content', []) + .filter(item => item.roleName === roleName) + .map(r => r.id); }); } catch (err) { return Promise.reject(err); From 5b29f65fd50dd55a93f5f570adbc8da2933b03e1 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 3 Jun 2025 01:16:58 +0200 Subject: [PATCH 114/166] modified timeout --- src/util.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/util.js b/src/util.js index 80de04a1..299da69f 100644 --- a/src/util.js +++ b/src/util.js @@ -840,6 +840,7 @@ const projectServiceUtils = { try { const token = yield this.getM2MToken(); const httpClient = this.getHttpClient({ id: requestId, log: logger }); + httpClient.defaults.timeout = 6000; return httpClient.get(`${config.identityServiceEndpoint}roles`, { params: { filter: `roleName=${roleName}`, From a282f0b7f82289c01efd35463bf07366d79338b4 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 3 Jun 2025 16:57:41 +0200 Subject: [PATCH 115/166] fix: cancel all copilot requests for a project when manually copilot is added --- src/routes/projectMemberInvites/update.js | 52 +++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index 9dfbfdf0..ccba79cf 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -6,6 +6,7 @@ import models from '../../models'; import util from '../../util'; import { INVITE_STATUS, EVENT, RESOURCES, COPILOT_APPLICATION_STATUS, COPILOT_OPPORTUNITY_STATUS, COPILOT_REQUEST_STATUS, INVITE_SOURCE } from '../../constants'; import { PERMISSION } from '../../permissions/constants'; +import { Op } from 'sequelize'; /** * API to update invite member to project. @@ -170,6 +171,57 @@ module.exports = [ }, { transaction: t, }); + } else { + const allCopilotRequestsByProjectId = await models.CopilotRequest.findAll({ + where: { + projectId: invite.projectId, + }, + transaction: t, + }); + + const requestIds = allCopilotRequestsByProjectId.map(item => item.id); + + await models.CopilotRequest.update({ + status: COPILOT_REQUEST_STATUS.CANCELED, + }, { + where: { + id: { + [Op.in]: requestIds, + } + }, + transaction: t, + }); + + const allCopilotOpportunityByRequestIds = await models.CopilotOpportunity.findAll({ + where: { + copilotRequestId: { + [Op.in]: requestIds, + }, + }, + transaction: t, + }); + + await models.CopilotOpportunity.update({ + status: COPILOT_OPPORTUNITY_STATUS.CANCELED, + }, { + where: { + copilotRequestId: { + [Op.in]: allCopilotOpportunityByRequestIds, + }, + }, + transaction: t, + }); + + await models.CopilotApplication.update({ + status: COPILOT_APPLICATION_STATUS.CANCELED, + }, { + where: { + opportunityId: { + [Op.in]: allCopilotOpportunityByRequestIds.map(item => item.id), + }, + }, + transaction: t, + }); } await t.commit(); From ff6f62a4adfa0f3addeaef2ff159723db7ab5bbe Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 3 Jun 2025 16:58:00 +0200 Subject: [PATCH 116/166] fix: cancel all copilot requests for a project when manually copilot is added --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 753c7b1e..d3aab5b3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup', 'pm-1169'] + only: ['develop', 'migration-setup', 'pm-1169_1'] - deployProd: context : org-global filters: From 0a3317aaaa46b7fd257d9de97a1761b395111f3b Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 3 Jun 2025 17:02:11 +0200 Subject: [PATCH 117/166] fix: cancel all copilot requests for a project when manually copilot is added --- src/routes/projectMemberInvites/update.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index ccba79cf..864b9bd2 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -171,7 +171,7 @@ module.exports = [ }, { transaction: t, }); - } else { + } else if (source === INVITE_SOURCE.WORK_MANAGER) { const allCopilotRequestsByProjectId = await models.CopilotRequest.findAll({ where: { projectId: invite.projectId, From aa5cc622913aa9896541c23f7cc77fd62e5177e2 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 3 Jun 2025 17:03:08 +0200 Subject: [PATCH 118/166] fix: cancel all copilot requests for a project when manually copilot is added --- src/routes/projectMemberInvites/update.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index 864b9bd2..88137695 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -1,12 +1,13 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; +import { Op } from 'sequelize'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; import { INVITE_STATUS, EVENT, RESOURCES, COPILOT_APPLICATION_STATUS, COPILOT_OPPORTUNITY_STATUS, COPILOT_REQUEST_STATUS, INVITE_SOURCE } from '../../constants'; import { PERMISSION } from '../../permissions/constants'; -import { Op } from 'sequelize'; + /** * API to update invite member to project. From f7579a00b45ab5a1fae40bb748b68d0c412ce76b Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 3 Jun 2025 22:53:05 +0200 Subject: [PATCH 119/166] fix: cancel all copilot requests for a project when manually copilot is added --- src/routes/projectMemberInvites/update.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index 88137695..37f4731b 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -173,6 +173,7 @@ module.exports = [ transaction: t, }); } else if (source === INVITE_SOURCE.WORK_MANAGER) { + req.log.info("cancelling all existing requests", source); const allCopilotRequestsByProjectId = await models.CopilotRequest.findAll({ where: { projectId: invite.projectId, @@ -181,6 +182,7 @@ module.exports = [ }); const requestIds = allCopilotRequestsByProjectId.map(item => item.id); + req.log.info("requestIds", requestIds); await models.CopilotRequest.update({ status: COPILOT_REQUEST_STATUS.CANCELED, @@ -193,6 +195,8 @@ module.exports = [ transaction: t, }); + req.log.info("updated copilot request"); + const allCopilotOpportunityByRequestIds = await models.CopilotOpportunity.findAll({ where: { copilotRequestId: { @@ -202,6 +206,8 @@ module.exports = [ transaction: t, }); + req.log.info("allCopilotOpportunityByRequestIds", allCopilotOpportunityByRequestIds.length); + await models.CopilotOpportunity.update({ status: COPILOT_OPPORTUNITY_STATUS.CANCELED, }, { @@ -213,6 +219,8 @@ module.exports = [ transaction: t, }); + req.log.info("updated copilot opportunity"); + await models.CopilotApplication.update({ status: COPILOT_APPLICATION_STATUS.CANCELED, }, { @@ -223,6 +231,8 @@ module.exports = [ }, transaction: t, }); + + req.log.info("updated CopilotApplication"); } await t.commit(); From 206547869f894812776e5f8fde84b8f33a89066b Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 3 Jun 2025 23:45:16 +0200 Subject: [PATCH 120/166] fix: cancel all copilot requests for a project when manually copilot is added --- src/routes/projectMemberInvites/update.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index 37f4731b..843c82be 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -212,8 +212,8 @@ module.exports = [ status: COPILOT_OPPORTUNITY_STATUS.CANCELED, }, { where: { - copilotRequestId: { - [Op.in]: allCopilotOpportunityByRequestIds, + id: { + [Op.in]: allCopilotOpportunityByRequestIds.map(item => item.id), }, }, transaction: t, From 724d0281f2ee910eeba14ea601986b786f09703e Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 4 Jun 2025 00:12:27 +0200 Subject: [PATCH 121/166] fix: cancel all copilot requests for a project when manually copilot is added --- src/routes/projectMemberInvites/update.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index 843c82be..3c3d71be 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -173,7 +173,6 @@ module.exports = [ transaction: t, }); } else if (source === INVITE_SOURCE.WORK_MANAGER) { - req.log.info("cancelling all existing requests", source); const allCopilotRequestsByProjectId = await models.CopilotRequest.findAll({ where: { projectId: invite.projectId, @@ -182,7 +181,6 @@ module.exports = [ }); const requestIds = allCopilotRequestsByProjectId.map(item => item.id); - req.log.info("requestIds", requestIds); await models.CopilotRequest.update({ status: COPILOT_REQUEST_STATUS.CANCELED, @@ -195,8 +193,6 @@ module.exports = [ transaction: t, }); - req.log.info("updated copilot request"); - const allCopilotOpportunityByRequestIds = await models.CopilotOpportunity.findAll({ where: { copilotRequestId: { @@ -206,8 +202,6 @@ module.exports = [ transaction: t, }); - req.log.info("allCopilotOpportunityByRequestIds", allCopilotOpportunityByRequestIds.length); - await models.CopilotOpportunity.update({ status: COPILOT_OPPORTUNITY_STATUS.CANCELED, }, { @@ -219,8 +213,6 @@ module.exports = [ transaction: t, }); - req.log.info("updated copilot opportunity"); - await models.CopilotApplication.update({ status: COPILOT_APPLICATION_STATUS.CANCELED, }, { @@ -231,8 +223,6 @@ module.exports = [ }, transaction: t, }); - - req.log.info("updated CopilotApplication"); } await t.commit(); From 21b079bbf5fd9f2d7980288b24322a35f5f4d828 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 4 Jun 2025 00:57:12 +0200 Subject: [PATCH 122/166] fix: cancel all copilot requests for a project when manually copilot is added --- src/routes/projectMemberInvites/update.js | 26 +++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index 3c3d71be..7869f7e3 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -213,16 +213,38 @@ module.exports = [ transaction: t, }); + const copilotApplications = await models.CopilotApplication.findAll({ + where: { + opportunityId: { + [Op.in]: allCopilotOpportunityByRequestIds.map(item => item.id), + }, + }, + transaction: t, + }); + await models.CopilotApplication.update({ status: COPILOT_APPLICATION_STATUS.CANCELED, }, { where: { - opportunityId: { - [Op.in]: allCopilotOpportunityByRequestIds.map(item => item.id), + id: { + [Op.in]: copilotApplications.map(item => item.id), }, }, transaction: t, }); + + // Cancel the existing invites which are opened via + // applications + await models.ProjectMemberInvite.update({ + status: INVITE_STATUS.CANCELED, + }, { + where: { + applicationId: { + [Op.in]: copilotApplications.map(item => item.id), + } + }, + transaction: t, + }); } await t.commit(); From deaed3a9ff7061ebca875ffb84cb5df0b88e9e5b Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 4 Jun 2025 14:45:29 +0200 Subject: [PATCH 123/166] rebased from develop branch --- src/routes/copilotRequest/approveRequest.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/copilotRequest/approveRequest.service.js b/src/routes/copilotRequest/approveRequest.service.js index dc7e5445..34d3cbf2 100644 --- a/src/routes/copilotRequest/approveRequest.service.js +++ b/src/routes/copilotRequest/approveRequest.service.js @@ -57,8 +57,8 @@ module.exports = (req, data, existingTransaction) => { })) .then(async (opportunity) => { const roles = await util.getRolesByRoleName('copilot', req.log, req.id); - const { subjects = [] } = await util.getRoleInfo(roles[0], req.log, req.id); req.log.info("getting subjects for roles", roles[0]); + const { subjects = [] } = await util.getRoleInfo(roles[0], req.log, req.id); const emailEventType = CONNECT_NOTIFICATION_EVENT.COPILOT_OPPORTUNITY_CREATED; const copilotPortalUrl = config.get('copilotPortalUrl'); req.log.info("Sending emails to all copilots about new opportunity"); From d4ed7a9fb3899e238a47126f9a8dfbb2aeef7a3e Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 4 Jun 2025 16:44:13 +0200 Subject: [PATCH 124/166] rebased from develop branch --- src/util.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/util.js b/src/util.js index 299da69f..8408654d 100644 --- a/src/util.js +++ b/src/util.js @@ -819,6 +819,7 @@ const projectServiceUtils = { try { const token = yield this.getM2MToken(); const httpClient = this.getHttpClient({ id: requestId, log: logger }); + httpClient.defaults.timeout = 6000; return httpClient.get(`${config.identityServiceEndpoint}roles/${roleId}`, { params: { fields: 'subjects' From 91f23ab9b77505b5c2fbf506e3eaf62ede04b864 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 4 Jun 2025 17:06:37 +0200 Subject: [PATCH 125/166] rebased from develop branch --- src/util.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/util.js b/src/util.js index 8408654d..049ef68a 100644 --- a/src/util.js +++ b/src/util.js @@ -820,10 +820,8 @@ 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"); return httpClient.get(`${config.identityServiceEndpoint}roles/${roleId}`, { - params: { - fields: 'subjects' - }, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, @@ -833,6 +831,7 @@ const projectServiceUtils = { return _.get(res, 'data.result.content', []); }); } catch (err) { + logger.debug(err, "error on getting role info"); return Promise.reject(err); } }), From b7f5bf5768a5beb0c1ca206e41186b985c7e80d9 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 5 Jun 2025 01:07:53 +0200 Subject: [PATCH 126/166] fix: sync issue between db and es --- .circleci/config.yml | 2 +- src/routes/projectMemberInvites/update.js | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d3aab5b3..a0a5164f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup', 'pm-1169_1'] + only: ['develop', 'migration-setup', 'pm-1169_2'] - deployProd: context : org-global filters: diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index 7869f7e3..8e4e9d7d 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -233,6 +233,15 @@ module.exports = [ transaction: t, }); + const invitesToBeUpdated = await models.ProjectMemberInvite.findAll({ + where: { + applicationId: { + [Op.in]: copilotApplications.map(item => item.id), + } + }, + transaction: t, + }); + // Cancel the existing invites which are opened via // applications await models.ProjectMemberInvite.update({ @@ -245,6 +254,15 @@ module.exports = [ }, transaction: t, }); + + invitesToBeUpdated.forEach((inviteToBeUpdated) => { + req.log.info(inviteToBeUpdated.toJSON(), 'invite to be updated') + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED, + RESOURCES.PROJECT_MEMBER_INVITE, + inviteToBeUpdated.toJSON()); + }) } await t.commit(); From 4c45e27eb1ac67394b90df624a59a5056119ef12 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 5 Jun 2025 01:47:55 +0200 Subject: [PATCH 127/166] fix: sync issue --- .circleci/config.yml | 2 +- src/routes/projectMemberInvites/update.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a0a5164f..1acd4a4c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup', 'pm-1169_2'] + only: ['develop', 'migration-setup'] - deployProd: context : org-global filters: diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index 8e4e9d7d..e5005c4a 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -256,7 +256,6 @@ module.exports = [ }); invitesToBeUpdated.forEach((inviteToBeUpdated) => { - req.log.info(inviteToBeUpdated.toJSON(), 'invite to be updated') util.sendResourceToKafkaBus( req, EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED, From 31a2b66230ea4b669c9c8c64e9b22757ed1a4678 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 5 Jun 2025 17:52:10 +0200 Subject: [PATCH 128/166] fix: sync issue --- src/util.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/util.js b/src/util.js index 049ef68a..60c71d57 100644 --- a/src/util.js +++ b/src/util.js @@ -822,6 +822,9 @@ const projectServiceUtils = { httpClient.defaults.timeout = 6000; logger.debug(`${config.identityServiceEndpoint}roles/${roleId}`, "fetching role info"); return httpClient.get(`${config.identityServiceEndpoint}roles/${roleId}`, { + params: { + fields: `subjects`, + }, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, From ae5952d9effabded8f703594530c3cb9b9e2f64c Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 5 Jun 2025 21:19:10 +0200 Subject: [PATCH 129/166] fix: added external action email kaufka type --- src/constants.js | 4 ++-- src/routes/copilotRequest/approveRequest.service.js | 12 ++++-------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/constants.js b/src/constants.js index f35d1a89..7e20ef93 100644 --- a/src/constants.js +++ b/src/constants.js @@ -303,8 +303,8 @@ export const CONNECT_NOTIFICATION_EVENT = { POST_CREATED: 'connect.notification.project.post.created', POST_UPDATED: 'connect.notification.project.post.edited', - // Copilot events - COPILOT_OPPORTUNITY_CREATED: 'connect.notification.project.copilot.opportunity.created', + // External action email + EXTERNAL_ACTION_EMAIL: 'external.action.email', }; export const REGEX = { diff --git a/src/routes/copilotRequest/approveRequest.service.js b/src/routes/copilotRequest/approveRequest.service.js index 34d3cbf2..a2dfb81a 100644 --- a/src/routes/copilotRequest/approveRequest.service.js +++ b/src/routes/copilotRequest/approveRequest.service.js @@ -59,23 +59,19 @@ module.exports = (req, data, existingTransaction) => { const roles = await util.getRolesByRoleName('copilot', req.log, req.id); req.log.info("getting subjects for roles", roles[0]); const { subjects = [] } = await util.getRoleInfo(roles[0], req.log, req.id); - const emailEventType = CONNECT_NOTIFICATION_EVENT.COPILOT_OPPORTUNITY_CREATED; + const emailEventType = CONNECT_NOTIFICATION_EVENT.EXTERNAL_ACTION_EMAIL; const copilotPortalUrl = config.get('copilotPortalUrl'); req.log.info("Sending emails to all copilots about new opportunity"); subjects.forEach(subject => { req.log.info("Each copilot members", subject); createEvent(emailEventType, { data: { - handle: subject.handle, + user_name: subject.handle, opportunityDetailsUrl: `${copilotPortalUrl}/opportunity/${opportunity.id}`, }, + sendgrid_template_id: "d-3efdc91da580479d810c7acd50a4c17f", recipients: [subject.email], - version: 'v3', - from: { - name: config.get('EMAIL_INVITE_FROM_NAME'), - email: config.get('EMAIL_INVITE_FROM_EMAIL'), - }, - categories: [`${process.env.NODE_ENV}:${emailEventType}`.toLowerCase()], + version: '433b1688-c543-4656-a295-efcbea57444d', }, req.log); }); From c16d06ee7596707d82186f0c8a64e3c76e08768c Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 5 Jun 2025 21:56:25 +0200 Subject: [PATCH 130/166] fix: added external action email kaufka type --- src/routes/copilotRequest/approveRequest.service.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/copilotRequest/approveRequest.service.js b/src/routes/copilotRequest/approveRequest.service.js index a2dfb81a..3b79401e 100644 --- a/src/routes/copilotRequest/approveRequest.service.js +++ b/src/routes/copilotRequest/approveRequest.service.js @@ -62,6 +62,7 @@ module.exports = (req, data, existingTransaction) => { const emailEventType = CONNECT_NOTIFICATION_EVENT.EXTERNAL_ACTION_EMAIL; const copilotPortalUrl = config.get('copilotPortalUrl'); req.log.info("Sending emails to all copilots about new opportunity"); + req.log.info(`${copilotPortalUrl}/opportunity/${opportunity.id}`, '`${copilotPortalUrl}/opportunity/${opportunity.id}`'); subjects.forEach(subject => { req.log.info("Each copilot members", subject); createEvent(emailEventType, { From 190c04c3b14f2357a32f50ef1bc9262792ad8e33 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 9 Jun 2025 03:54:03 +0530 Subject: [PATCH 131/166] fix: added external action email kaufka type --- src/routes/copilotRequest/approveRequest.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/copilotRequest/approveRequest.service.js b/src/routes/copilotRequest/approveRequest.service.js index 3b79401e..e992dd72 100644 --- a/src/routes/copilotRequest/approveRequest.service.js +++ b/src/routes/copilotRequest/approveRequest.service.js @@ -67,7 +67,7 @@ module.exports = (req, data, existingTransaction) => { req.log.info("Each copilot members", subject); createEvent(emailEventType, { data: { - user_name: subject.handle, + userName: subject.handle, opportunityDetailsUrl: `${copilotPortalUrl}/opportunity/${opportunity.id}`, }, sendgrid_template_id: "d-3efdc91da580479d810c7acd50a4c17f", From 4c98fe7c655cac3ae3cab776c5391e6588b7fda8 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 10 Jun 2025 06:58:30 +0530 Subject: [PATCH 132/166] fix: added external action email kaufka type --- src/routes/copilotRequest/approveRequest.service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/copilotRequest/approveRequest.service.js b/src/routes/copilotRequest/approveRequest.service.js index e992dd72..7bab8e1c 100644 --- a/src/routes/copilotRequest/approveRequest.service.js +++ b/src/routes/copilotRequest/approveRequest.service.js @@ -67,8 +67,8 @@ module.exports = (req, data, existingTransaction) => { req.log.info("Each copilot members", subject); createEvent(emailEventType, { data: { - userName: subject.handle, - opportunityDetailsUrl: `${copilotPortalUrl}/opportunity/${opportunity.id}`, + user_name: subject.handle, + opportunity_details_url: `${copilotPortalUrl}/opportunity/${opportunity.id}`, }, sendgrid_template_id: "d-3efdc91da580479d810c7acd50a4c17f", recipients: [subject.email], From 0ad22c31eeed02676967e7b587b17e1f2d4b91c2 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 10 Jun 2025 07:02:32 +0530 Subject: [PATCH 133/166] fix: added external action email kaufka type --- src/routes/copilotRequest/approveRequest.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/copilotRequest/approveRequest.service.js b/src/routes/copilotRequest/approveRequest.service.js index 7bab8e1c..0562c9e2 100644 --- a/src/routes/copilotRequest/approveRequest.service.js +++ b/src/routes/copilotRequest/approveRequest.service.js @@ -72,7 +72,7 @@ module.exports = (req, data, existingTransaction) => { }, sendgrid_template_id: "d-3efdc91da580479d810c7acd50a4c17f", recipients: [subject.email], - version: '433b1688-c543-4656-a295-efcbea57444d', + version: 'v3', }, req.log); }); From cc5a28d95be2cea5df2dae1bcee89e14b2ab31bf Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 10 Jun 2025 09:25:50 +0530 Subject: [PATCH 134/166] fix: send emails to PMs and creator --- src/routes/copilotOpportunityApply/create.js | 34 +++++++++++++++++-- .../copilotRequest/approveRequest.service.js | 2 -- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index 33daaddb..04867900 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -1,11 +1,12 @@ import _ from 'lodash'; import validate from 'express-validation'; import Joi from 'joi'; +import config from 'config'; import models from '../../models'; import util from '../../util'; import { PERMISSION } from '../../permissions/constants'; -import { COPILOT_OPPORTUNITY_STATUS } from '../../constants'; +import { CONNECT_NOTIFICATION_EVENT, COPILOT_OPPORTUNITY_STATUS } from '../../constants'; const applyCopilotRequestValidations = { body: Joi.object().keys({ @@ -65,7 +66,36 @@ module.exports = [ } return models.CopilotApplication.create(data) - .then((result) => { + .then(async (result) => { + const pmRole = await util.getRolesByRoleName('Project Manager', req.log, req.id); + const { subjects = [] } = await util.getRoleInfo(pmRole[0], req.log, req.id); + + const creator = await util.getMemberDetailsByUserIds([req.authUser.userId], req.log, req.id); + const listOfSubjects = subjects; + if (creator) { + const isCreatorPartofSubjects = subjects.find(item => item.email === creator[0].email); + if (!isCreatorPartofSubjects) { + listOfSubjects.push({ + email: creator.email, + handle: creator.handle, + }); + } + } + + const emailEventType = CONNECT_NOTIFICATION_EVENT.EXTERNAL_ACTION_EMAIL; + const copilotPortalUrl = config.get('copilotPortalUrl'); + listOfSubjects.forEach((subject) => { + createEvent(emailEventType, { + data: { + user_name: subject.handle, + opportunity_details_url: `${copilotPortalUrl}/opportunity/${opportunity.id}#applications`, + }, + sendgrid_template_id: "d-d7c1f48628654798a05c8e09e52db14f", + recipients: [subject.email], + version: 'v3', + }, req.log); + }); + res.status(201).json(result); return Promise.resolve(); }) diff --git a/src/routes/copilotRequest/approveRequest.service.js b/src/routes/copilotRequest/approveRequest.service.js index 0562c9e2..8b46154c 100644 --- a/src/routes/copilotRequest/approveRequest.service.js +++ b/src/routes/copilotRequest/approveRequest.service.js @@ -57,12 +57,10 @@ module.exports = (req, data, existingTransaction) => { })) .then(async (opportunity) => { const roles = await util.getRolesByRoleName('copilot', req.log, req.id); - req.log.info("getting subjects for roles", roles[0]); 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"); - req.log.info(`${copilotPortalUrl}/opportunity/${opportunity.id}`, '`${copilotPortalUrl}/opportunity/${opportunity.id}`'); subjects.forEach(subject => { req.log.info("Each copilot members", subject); createEvent(emailEventType, { From b52b16e44dd8b05834163e69464bd8e3d6db9640 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 10 Jun 2025 09:28:53 +0530 Subject: [PATCH 135/166] fix: send emails to PMs and creator --- src/routes/copilotOpportunityApply/create.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index 04867900..b94a8833 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -70,7 +70,7 @@ module.exports = [ const pmRole = await util.getRolesByRoleName('Project Manager', req.log, req.id); const { subjects = [] } = await util.getRoleInfo(pmRole[0], req.log, req.id); - const creator = await util.getMemberDetailsByUserIds([req.authUser.userId], req.log, req.id); + const creator = await util.getMemberDetailsByUserIds([opportunity.userId], req.log, req.id); const listOfSubjects = subjects; if (creator) { const isCreatorPartofSubjects = subjects.find(item => item.email === creator[0].email); From 5fbd1974af3b870d471e55a24460e70c5c0799c3 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 10 Jun 2025 14:20:40 +0530 Subject: [PATCH 136/166] fix: send emails to PMs and creator --- src/routes/copilotOpportunityApply/create.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index b94a8833..b7e263ba 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -7,6 +7,7 @@ import models from '../../models'; import util from '../../util'; import { PERMISSION } from '../../permissions/constants'; import { CONNECT_NOTIFICATION_EVENT, COPILOT_OPPORTUNITY_STATUS } from '../../constants'; +import { createEvent } from '../../services/busApi'; const applyCopilotRequestValidations = { body: Joi.object().keys({ @@ -76,8 +77,8 @@ module.exports = [ const isCreatorPartofSubjects = subjects.find(item => item.email === creator[0].email); if (!isCreatorPartofSubjects) { listOfSubjects.push({ - email: creator.email, - handle: creator.handle, + email: creator[0].email, + handle: creator[0].handle, }); } } From 49356d6e77fd32c7057a5ce8b86ffae82b1292b4 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 10 Jun 2025 16:33:58 +0530 Subject: [PATCH 137/166] fix: review comments --- src/constants.js | 4 ++++ src/routes/copilotOpportunityApply/create.js | 8 ++++---- src/routes/copilotRequest/approveRequest.service.js | 7 +++---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/constants.js b/src/constants.js index 7e20ef93..9c96a746 100644 --- a/src/constants.js +++ b/src/constants.js @@ -307,6 +307,10 @@ export const CONNECT_NOTIFICATION_EVENT = { EXTERNAL_ACTION_EMAIL: 'external.action.email', }; +export const TEMPLATE_IDS = { + APPLY_COPILOT: 'd-d7c1f48628654798a05c8e09e52db14f', + CREATE_REQUEST: 'd-3efdc91da580479d810c7acd50a4c17f' +} export const REGEX = { URL: /^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,15})+(\:[0-9]{2,5})?(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=;]*)?$/, // eslint-disable-line }; diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index b7e263ba..49e92c2f 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -6,7 +6,7 @@ import config from 'config'; import models from '../../models'; import util from '../../util'; import { PERMISSION } from '../../permissions/constants'; -import { CONNECT_NOTIFICATION_EVENT, COPILOT_OPPORTUNITY_STATUS } from '../../constants'; +import { CONNECT_NOTIFICATION_EVENT, COPILOT_OPPORTUNITY_STATUS, TEMPLATE_IDS, USER_ROLE } from '../../constants'; import { createEvent } from '../../services/busApi'; const applyCopilotRequestValidations = { @@ -68,13 +68,13 @@ module.exports = [ return models.CopilotApplication.create(data) .then(async (result) => { - const pmRole = await util.getRolesByRoleName('Project Manager', req.log, req.id); + const pmRole = await util.getRolesByRoleName(USER_ROLE.PROJECT_MANAGER, req.log, req.id); const { subjects = [] } = await util.getRoleInfo(pmRole[0], req.log, req.id); const creator = await util.getMemberDetailsByUserIds([opportunity.userId], req.log, req.id); const listOfSubjects = subjects; if (creator) { - const isCreatorPartofSubjects = subjects.find(item => item.email === creator[0].email); + const isCreatorPartofSubjects = subjects.find(item => item.email.toLowerCase() === creator[0].email.toLowerCase()); if (!isCreatorPartofSubjects) { listOfSubjects.push({ email: creator[0].email, @@ -91,7 +91,7 @@ module.exports = [ user_name: subject.handle, opportunity_details_url: `${copilotPortalUrl}/opportunity/${opportunity.id}#applications`, }, - sendgrid_template_id: "d-d7c1f48628654798a05c8e09e52db14f", + sendgrid_template_id: TEMPLATE_IDS.APPLY_COPILOT, recipients: [subject.email], version: 'v3', }, req.log); diff --git a/src/routes/copilotRequest/approveRequest.service.js b/src/routes/copilotRequest/approveRequest.service.js index 8b46154c..d4a2665d 100644 --- a/src/routes/copilotRequest/approveRequest.service.js +++ b/src/routes/copilotRequest/approveRequest.service.js @@ -2,7 +2,7 @@ import _ from 'lodash'; import config from 'config'; import models from '../../models'; -import { CONNECT_NOTIFICATION_EVENT, COPILOT_REQUEST_STATUS } from '../../constants'; +import { CONNECT_NOTIFICATION_EVENT, COPILOT_REQUEST_STATUS, TEMPLATE_IDS, USER_ROLE } from '../../constants'; import util from '../../util'; import { createEvent } from '../../services/busApi'; @@ -56,19 +56,18 @@ module.exports = (req, data, existingTransaction) => { .create(data, { transaction }); })) .then(async (opportunity) => { - const roles = await util.getRolesByRoleName('copilot', req.log, req.id); + 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"); subjects.forEach(subject => { - req.log.info("Each copilot members", subject); createEvent(emailEventType, { data: { user_name: subject.handle, opportunity_details_url: `${copilotPortalUrl}/opportunity/${opportunity.id}`, }, - sendgrid_template_id: "d-3efdc91da580479d810c7acd50a4c17f", + sendgrid_template_id: TEMPLATE_IDS.CREATE_REQUEST, recipients: [subject.email], version: 'v3', }, req.log); From e34f785116019a2183a60d5ca7b2e97b73a82734 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 10 Jun 2025 16:35:06 +0530 Subject: [PATCH 138/166] fix: review comments --- src/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants.js b/src/constants.js index 9c96a746..02a8db53 100644 --- a/src/constants.js +++ b/src/constants.js @@ -309,7 +309,7 @@ export const CONNECT_NOTIFICATION_EVENT = { export const TEMPLATE_IDS = { APPLY_COPILOT: 'd-d7c1f48628654798a05c8e09e52db14f', - CREATE_REQUEST: 'd-3efdc91da580479d810c7acd50a4c17f' + CREATE_REQUEST: 'd-3efdc91da580479d810c7acd50a4c17f', } export const REGEX = { URL: /^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,15})+(\:[0-9]{2,5})?(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=;]*)?$/, // eslint-disable-line From 024b70f24991302a98434ebf21d480ba0e76e4da Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 16 Jun 2025 11:43:58 +0530 Subject: [PATCH 139/166] fix: added work manager url --- src/routes/copilotOpportunityApply/create.js | 1 + src/routes/copilotRequest/approveRequest.service.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index 49e92c2f..4dbda3bb 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -90,6 +90,7 @@ module.exports = [ data: { user_name: subject.handle, opportunity_details_url: `${copilotPortalUrl}/opportunity/${opportunity.id}#applications`, + workManagerUrl: config.get('workManagerUrl'), }, sendgrid_template_id: TEMPLATE_IDS.APPLY_COPILOT, recipients: [subject.email], diff --git a/src/routes/copilotRequest/approveRequest.service.js b/src/routes/copilotRequest/approveRequest.service.js index d4a2665d..ff7c7a13 100644 --- a/src/routes/copilotRequest/approveRequest.service.js +++ b/src/routes/copilotRequest/approveRequest.service.js @@ -66,6 +66,7 @@ module.exports = (req, data, existingTransaction) => { data: { user_name: subject.handle, opportunity_details_url: `${copilotPortalUrl}/opportunity/${opportunity.id}`, + workManagerUrl: config.get('workManagerUrl'), }, sendgrid_template_id: TEMPLATE_IDS.CREATE_REQUEST, recipients: [subject.email], From 1a5016fcebd45e6cec2f55f55030884b4ee1fd42 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 16 Jun 2025 11:52:23 +0530 Subject: [PATCH 140/166] fix: retain query param --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5b58794d..d4c591c5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup', 'pm-1173'] + only: ['develop', 'migration-setup', 'pm-1173_1'] - deployProd: context : org-global filters: From 24d2fba25a9bdcd8ad704e2916b3735a6dc0dbd9 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 16 Jun 2025 12:57:52 +0530 Subject: [PATCH 141/166] fix: removed validation for source as default is work manager --- src/routes/projectMemberInvites/update.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index e5005c4a..9f21b1c8 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -22,7 +22,6 @@ const updateMemberValidations = { .valid(_.values(INVITE_STATUS)) .required(), source: Joi.string() - .valid(_.values(INVITE_SOURCE)) .default(INVITE_SOURCE.WORK_MANAGER), }) .required(), From a26bd8f202d76058501d78658460a51576f2916f Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 16 Jun 2025 14:12:48 +0530 Subject: [PATCH 142/166] fix: removed validation for source as default is work manager --- src/routes/copilotOpportunityApply/create.js | 2 +- src/routes/copilotRequest/approveRequest.service.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index 4dbda3bb..829eec36 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -90,7 +90,7 @@ module.exports = [ data: { user_name: subject.handle, opportunity_details_url: `${copilotPortalUrl}/opportunity/${opportunity.id}#applications`, - workManagerUrl: config.get('workManagerUrl'), + work_manager_url: config.get('workManagerUrl'), }, sendgrid_template_id: TEMPLATE_IDS.APPLY_COPILOT, recipients: [subject.email], diff --git a/src/routes/copilotRequest/approveRequest.service.js b/src/routes/copilotRequest/approveRequest.service.js index ff7c7a13..fc0663d5 100644 --- a/src/routes/copilotRequest/approveRequest.service.js +++ b/src/routes/copilotRequest/approveRequest.service.js @@ -66,7 +66,7 @@ module.exports = (req, data, existingTransaction) => { data: { user_name: subject.handle, opportunity_details_url: `${copilotPortalUrl}/opportunity/${opportunity.id}`, - workManagerUrl: config.get('workManagerUrl'), + work_manager_url: config.get('workManagerUrl'), }, sendgrid_template_id: TEMPLATE_IDS.CREATE_REQUEST, recipients: [subject.email], From 339c8ab1a8020cdc8940be4672d4922ab489943f Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 16 Jun 2025 18:49:38 +0530 Subject: [PATCH 143/166] debug apply copilot --- .circleci/config.yml | 2 +- src/routes/copilotOpportunityApply/create.js | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d4c591c5..235c89cf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup', 'pm-1173_1'] + only: ['develop', 'migration-setup', 'pm-1175_2'] - deployProd: context : org-global filters: diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index 829eec36..0cedb55b 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -71,10 +71,16 @@ module.exports = [ const pmRole = await util.getRolesByRoleName(USER_ROLE.PROJECT_MANAGER, req.log, req.id); const { subjects = [] } = await util.getRoleInfo(pmRole[0], req.log, req.id); + req.log.debug(subjects, 'all manager subjects'); + const creator = await util.getMemberDetailsByUserIds([opportunity.userId], req.log, req.id); + + req.log.debug(subjects, 'creator'); + const listOfSubjects = subjects; if (creator) { const isCreatorPartofSubjects = subjects.find(item => item.email.toLowerCase() === creator[0].email.toLowerCase()); + req.log.debug(isCreatorPartofSubjects, 'isCreatorPartofSubjects'); if (!isCreatorPartofSubjects) { listOfSubjects.push({ email: creator[0].email, @@ -82,6 +88,8 @@ module.exports = [ }); } } + + req.log.debug(listOfSubjects, 'final list of subjects'); const emailEventType = CONNECT_NOTIFICATION_EVENT.EXTERNAL_ACTION_EMAIL; const copilotPortalUrl = config.get('copilotPortalUrl'); From 9be8c5ff85f6344c2c87ca27bb4be97082e6af1f Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 16 Jun 2025 19:14:03 +0530 Subject: [PATCH 144/166] debug apply copilot --- src/routes/copilotOpportunityApply/create.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index 0cedb55b..c1650516 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -75,7 +75,7 @@ module.exports = [ const creator = await util.getMemberDetailsByUserIds([opportunity.userId], req.log, req.id); - req.log.debug(subjects, 'creator'); + req.log.debug(creator, 'creator'); const listOfSubjects = subjects; if (creator) { From d95af0dd02a280c6ddfa227f199fdb2fa9f0138e Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 16 Jun 2025 20:48:28 +0530 Subject: [PATCH 145/166] fix: use correct field --- src/routes/copilotOpportunityApply/create.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index c1650516..cbe02e4d 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -73,7 +73,7 @@ module.exports = [ req.log.debug(subjects, 'all manager subjects'); - const creator = await util.getMemberDetailsByUserIds([opportunity.userId], req.log, req.id); + const creator = await util.getMemberDetailsByUserIds([opportunity.createdBy], req.log, req.id); req.log.debug(creator, 'creator'); From 126ea0b9590bb50650b25506ba32994a518a680e Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 16 Jun 2025 20:48:46 +0530 Subject: [PATCH 146/166] fix: use correct field --- src/routes/copilotOpportunityApply/create.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index cbe02e4d..cee025fc 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -75,7 +75,7 @@ module.exports = [ const creator = await util.getMemberDetailsByUserIds([opportunity.createdBy], req.log, req.id); - req.log.debug(creator, 'creator'); + req.log.debug(creator, 'creator', opportunity.createdBy); const listOfSubjects = subjects; if (creator) { From 3875c3e5cbce7ab73fc2d5c7ec8e50053784af7c Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 16 Jun 2025 21:10:18 +0530 Subject: [PATCH 147/166] fix: lowercase null exception --- src/routes/copilotOpportunityApply/create.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index cee025fc..b62e9bf7 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -74,12 +74,17 @@ module.exports = [ req.log.debug(subjects, 'all manager subjects'); const creator = await util.getMemberDetailsByUserIds([opportunity.createdBy], req.log, req.id); - req.log.debug(creator, 'creator', opportunity.createdBy); const listOfSubjects = subjects; - if (creator) { - const isCreatorPartofSubjects = subjects.find(item => item.email.toLowerCase() === creator[0].email.toLowerCase()); + if (creator && creator[0] && creator[0].email) { + const isCreatorPartofSubjects = subjects.find(item => { + if (!item.email) { + return false; + } + + return item.email.toLowerCase() === creator[0].email.toLowerCase(); + }); req.log.debug(isCreatorPartofSubjects, 'isCreatorPartofSubjects'); if (!isCreatorPartofSubjects) { listOfSubjects.push({ From 92ee79e2fff32fdc3a0e226a578ff8b648d21e5e Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 16 Jun 2025 22:15:46 +0530 Subject: [PATCH 148/166] removed debug logs --- src/routes/copilotOpportunityApply/create.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/routes/copilotOpportunityApply/create.js b/src/routes/copilotOpportunityApply/create.js index b62e9bf7..4bfbea9f 100644 --- a/src/routes/copilotOpportunityApply/create.js +++ b/src/routes/copilotOpportunityApply/create.js @@ -71,10 +71,7 @@ module.exports = [ const pmRole = await util.getRolesByRoleName(USER_ROLE.PROJECT_MANAGER, req.log, req.id); const { subjects = [] } = await util.getRoleInfo(pmRole[0], req.log, req.id); - req.log.debug(subjects, 'all manager subjects'); - const creator = await util.getMemberDetailsByUserIds([opportunity.createdBy], req.log, req.id); - req.log.debug(creator, 'creator', opportunity.createdBy); const listOfSubjects = subjects; if (creator && creator[0] && creator[0].email) { @@ -85,7 +82,6 @@ module.exports = [ return item.email.toLowerCase() === creator[0].email.toLowerCase(); }); - req.log.debug(isCreatorPartofSubjects, 'isCreatorPartofSubjects'); if (!isCreatorPartofSubjects) { listOfSubjects.push({ email: creator[0].email, @@ -93,8 +89,6 @@ module.exports = [ }); } } - - req.log.debug(listOfSubjects, 'final list of subjects'); const emailEventType = CONNECT_NOTIFICATION_EVENT.EXTERNAL_ACTION_EMAIL; const copilotPortalUrl = config.get('copilotPortalUrl'); From 70e8e405437f1f4935db2ece53949ddf73979a7c Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 16 Jun 2025 22:16:07 +0530 Subject: [PATCH 149/166] removed circle ci configs --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 235c89cf..1acd4a4c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup', 'pm-1175_2'] + only: ['develop', 'migration-setup'] - deployProd: context : org-global filters: From 12fab3ab7bc96ee68451e8ffc3914b8a2e3af434 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 16 Jun 2025 23:52:11 +0530 Subject: [PATCH 150/166] fix: send project member invite email via external email event --- .circleci/config.yml | 2 +- src/constants.js | 1 + src/routes/projectMemberInvites/create.js | 9 +++------ 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1acd4a4c..bc40edcd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup'] + only: ['develop', 'migration-setup', 'pm-1356'] - deployProd: context : org-global filters: diff --git a/src/constants.js b/src/constants.js index 02a8db53..bfc93281 100644 --- a/src/constants.js +++ b/src/constants.js @@ -310,6 +310,7 @@ export const CONNECT_NOTIFICATION_EVENT = { export const TEMPLATE_IDS = { APPLY_COPILOT: 'd-d7c1f48628654798a05c8e09e52db14f', CREATE_REQUEST: 'd-3efdc91da580479d810c7acd50a4c17f', + PROJECT_MEMBER_INVITED: 'd-b47a25b103604bc28fc0ce77e77fb681', } export const REGEX = { URL: /^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,15})+(\:[0-9]{2,5})?(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=;]*)?$/, // eslint-disable-line diff --git a/src/routes/projectMemberInvites/create.js b/src/routes/projectMemberInvites/create.js index 7c5a836d..99f4334a 100644 --- a/src/routes/projectMemberInvites/create.js +++ b/src/routes/projectMemberInvites/create.js @@ -14,6 +14,7 @@ import { RESOURCES, MAX_PARALLEL_REQUEST_QTY, CONNECT_NOTIFICATION_EVENT, + TEMPLATE_IDS, } from '../../constants'; import { createEvent } from '../../services/busApi'; import { PERMISSION, PROJECT_TO_TOPCODER_ROLES_MATRIX } from '../../permissions/constants'; @@ -201,7 +202,7 @@ const buildCreateInvitePromises = (req, inviteEmails, inviteUserIds, invites, da const sendInviteEmail = (req, projectId, invite) => { req.log.debug(`Sending invite email: ${JSON.stringify(req.body)}, ${projectId}, ${JSON.stringify(invite)}`); req.log.debug(req.authUser); - const emailEventType = CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_EMAIL_INVITE_CREATED; + const emailEventType = CONNECT_NOTIFICATION_EVENT.EXTERNAL_ACTION_EMAIL; const promises = [ models.Project.findOne({ where: { id: projectId }, @@ -237,13 +238,9 @@ const sendInviteEmail = (req, projectId, invite) => { ], }], }, + sendgrid_template_id: TEMPLATE_IDS.PROJECT_MEMBER_INVITED, recipients: [invite.email], version: 'v3', - from: { - name: config.get('EMAIL_INVITE_FROM_NAME'), - email: config.get('EMAIL_INVITE_FROM_EMAIL'), - }, - categories: [`${process.env.NODE_ENV}:${emailEventType}`.toLowerCase()], }, req.log); }).catch((error) => { req.log.error(error); From f8a6786660f2a4eadc8b446d1b015a390ba72c42 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 18 Jun 2025 14:47:51 +0530 Subject: [PATCH 151/166] fix: send members as part of opportunity --- src/routes/copilotOpportunity/get.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunity/get.js b/src/routes/copilotOpportunity/get.js index a968af13..145d90a8 100644 --- a/src/routes/copilotOpportunity/get.js +++ b/src/routes/copilotOpportunity/get.js @@ -19,7 +19,7 @@ module.exports = [ { model: models.Project, as: 'project', - attributes: ['name'], + attributes: ['name', 'members'], }, ], }) From 596d9b13cb02853fa105e67b765733b9a2245e7f Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 18 Jun 2025 14:49:30 +0530 Subject: [PATCH 152/166] fix: send members as part of opportunity --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bc40edcd..8c07e37e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup', 'pm-1356'] + only: ['develop', 'migration-setup', 'pm-1273'] - deployProd: context : org-global filters: From 9d7010402713f57a5dce9fb02986a41dec4defd5 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 18 Jun 2025 16:22:47 +0530 Subject: [PATCH 153/166] fix: send members as part of opportunity --- src/routes/copilotOpportunity/get.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunity/get.js b/src/routes/copilotOpportunity/get.js index 145d90a8..11949adb 100644 --- a/src/routes/copilotOpportunity/get.js +++ b/src/routes/copilotOpportunity/get.js @@ -19,7 +19,14 @@ module.exports = [ { model: models.Project, as: 'project', - attributes: ['name', 'members'], + attributes: ['name'], + include: [ + { + model: models.ProjectMember, + as: 'members', + attributes: ['id', 'userId', 'role'], + }, + ] }, ], }) From 2eb2d21e02059625a7f96fd60cbbbf91a00f866c Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 18 Jun 2025 16:24:27 +0530 Subject: [PATCH 154/166] fix: send members as part of opportunity --- src/models/projectMember.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/models/projectMember.js b/src/models/projectMember.js index bf213a3d..627f9531 100644 --- a/src/models/projectMember.js +++ b/src/models/projectMember.js @@ -34,6 +34,10 @@ module.exports = function defineProjectMember(sequelize, DataTypes) { ], }); + ProjectMember.associate = (models) => { + ProjectMember.belongsTo(models.Project, { foreignKey: 'projectId' }); + }; + ProjectMember.getProjectIdsForUser = userId => ProjectMember.findAll({ where: { deletedAt: { $eq: null }, From f6138fe492409c2830e75a2a39006dc6731d2d90 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 18 Jun 2025 17:40:33 +0530 Subject: [PATCH 155/166] fix: send members as part of opportunity --- src/routes/copilotOpportunity/get.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunity/get.js b/src/routes/copilotOpportunity/get.js index 11949adb..37f36837 100644 --- a/src/routes/copilotOpportunity/get.js +++ b/src/routes/copilotOpportunity/get.js @@ -32,7 +32,16 @@ module.exports = [ }) .then((copilotOpportunity) => { const plainOpportunity = copilotOpportunity.get({ plain: true }); - const formattedOpportunity = Object.assign({}, plainOpportunity, + let canApplyAsCopilot = false; + if (plainOpportunity && plainOpportunity.project && plainOpportunity.project.members && req.authUser) { + const existingMember = plainOpportunity.project.members.find(item => item.userId === req.authUser.userId); + canApplyAsCopilot = !!!existingMember; + } + // This shouldn't be exposed to the clientside + delete plainOpportunity.project.members; + const formattedOpportunity = Object.assign({ + canApplyAsCopilot, + }, plainOpportunity, plainOpportunity.copilotRequest ? plainOpportunity.copilotRequest.data : {}, { copilotRequest: undefined }, ); From 8612e0281dce4890f9f3ed816d2d0a87ae8cd296 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 18 Jun 2025 18:04:08 +0530 Subject: [PATCH 156/166] fix: send members as part of opportunity --- src/routes/copilotOpportunity/get.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/routes/copilotOpportunity/get.js b/src/routes/copilotOpportunity/get.js index 37f36837..c574b26d 100644 --- a/src/routes/copilotOpportunity/get.js +++ b/src/routes/copilotOpportunity/get.js @@ -33,8 +33,11 @@ module.exports = [ .then((copilotOpportunity) => { const plainOpportunity = copilotOpportunity.get({ plain: true }); let canApplyAsCopilot = false; + req.log.info(plainOpportunity.project.members); + req.log.info(req.authUser, 'authuser'); if (plainOpportunity && plainOpportunity.project && plainOpportunity.project.members && req.authUser) { const existingMember = plainOpportunity.project.members.find(item => item.userId === req.authUser.userId); + req.log.info(existingMember, 'existingMember'); canApplyAsCopilot = !!!existingMember; } // This shouldn't be exposed to the clientside From 81effa61744eebed8bcd5e60aa55fe03d17da253 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 18 Jun 2025 18:51:35 +0530 Subject: [PATCH 157/166] fix: send members as part of opportunity --- src/routes/copilotOpportunity/get.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/routes/copilotOpportunity/get.js b/src/routes/copilotOpportunity/get.js index c574b26d..061d403b 100644 --- a/src/routes/copilotOpportunity/get.js +++ b/src/routes/copilotOpportunity/get.js @@ -32,18 +32,11 @@ module.exports = [ }) .then((copilotOpportunity) => { const plainOpportunity = copilotOpportunity.get({ plain: true }); - let canApplyAsCopilot = false; - req.log.info(plainOpportunity.project.members); - req.log.info(req.authUser, 'authuser'); - if (plainOpportunity && plainOpportunity.project && plainOpportunity.project.members && req.authUser) { - const existingMember = plainOpportunity.project.members.find(item => item.userId === req.authUser.userId); - req.log.info(existingMember, 'existingMember'); - canApplyAsCopilot = !!!existingMember; - } + const memberIds = plainOpportunity.project.members.map((member) => member.userId); // This shouldn't be exposed to the clientside delete plainOpportunity.project.members; const formattedOpportunity = Object.assign({ - canApplyAsCopilot, + members: memberIds, }, plainOpportunity, plainOpportunity.copilotRequest ? plainOpportunity.copilotRequest.data : {}, { copilotRequest: undefined }, From 25b4d80a9e31aa9090b33a53618d4ad3bd9ad696 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 18 Jun 2025 22:16:28 +0530 Subject: [PATCH 158/166] debug auth user --- src/routes/copilotOpportunity/get.js | 1 + src/routes/index.js | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/routes/copilotOpportunity/get.js b/src/routes/copilotOpportunity/get.js index 061d403b..2d6af5fa 100644 --- a/src/routes/copilotOpportunity/get.js +++ b/src/routes/copilotOpportunity/get.js @@ -32,6 +32,7 @@ module.exports = [ }) .then((copilotOpportunity) => { const plainOpportunity = copilotOpportunity.get({ plain: true }); + req.log.info("authUser", req.authUser); const memberIds = plainOpportunity.project.members.map((member) => member.userId); // This shouldn't be exposed to the clientside delete plainOpportunity.project.members; diff --git a/src/routes/index.js b/src/routes/index.js index 54df9280..ab3b6308 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -35,9 +35,14 @@ const jwtAuth = require('tc-core-library-js').middleware.jwtAuthenticator; router.all( RegExp(`\\/${apiVersion}\\/(copilots|projects|timelines|orgConfig|customer-payments)(?!\\/health).*`), (req, res, next) => { - if (publicRoutes.some(routeRegex => routeRegex.test(req.path))) { + let token + if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') { + token = req.headers.authorization.split(' ')[1] + } + if (publicRoutes.some(routeRegex => routeRegex.test(req.path)) && !token) { return next(); } + req.log.info("token available", token); // JWT authentication return jwtAuth(config)(req, res, next); }, From 6080e8e4f1c98e4d4fc785de587593c1894c71ba Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 18 Jun 2025 23:15:26 +0530 Subject: [PATCH 159/166] debug auth user --- src/routes/copilotOpportunity/get.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/copilotOpportunity/get.js b/src/routes/copilotOpportunity/get.js index 2d6af5fa..a87a6f98 100644 --- a/src/routes/copilotOpportunity/get.js +++ b/src/routes/copilotOpportunity/get.js @@ -34,10 +34,12 @@ module.exports = [ const plainOpportunity = copilotOpportunity.get({ plain: true }); req.log.info("authUser", req.authUser); const memberIds = plainOpportunity.project.members.map((member) => member.userId); + const canApplyAsCopilot = !memberIds.includes(req.authUser.userId) // This shouldn't be exposed to the clientside delete plainOpportunity.project.members; const formattedOpportunity = Object.assign({ members: memberIds, + canApplyAsCopilot, }, plainOpportunity, plainOpportunity.copilotRequest ? plainOpportunity.copilotRequest.data : {}, { copilotRequest: undefined }, From 6557460df9bbc7ee24714caa47220985e1c18444 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 19 Jun 2025 09:27:33 +0530 Subject: [PATCH 160/166] debug auth user --- src/routes/copilotOpportunity/get.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/routes/copilotOpportunity/get.js b/src/routes/copilotOpportunity/get.js index a87a6f98..2fd1856c 100644 --- a/src/routes/copilotOpportunity/get.js +++ b/src/routes/copilotOpportunity/get.js @@ -33,8 +33,11 @@ module.exports = [ .then((copilotOpportunity) => { const plainOpportunity = copilotOpportunity.get({ plain: true }); req.log.info("authUser", req.authUser); - const memberIds = plainOpportunity.project.members.map((member) => member.userId); - const canApplyAsCopilot = !memberIds.includes(req.authUser.userId) + const memberIds = plainOpportunity.project.members && plainOpportunity.project.members.map((member) => member.userId); + let canApplyAsCopilot = false; + if (req.authUser) { + canApplyAsCopilot = !memberIds.includes(req.authUser.userId) + } // This shouldn't be exposed to the clientside delete plainOpportunity.project.members; const formattedOpportunity = Object.assign({ From 8d2840c6702c6015fc4235e729db41d818f09250 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 19 Jun 2025 19:33:58 +0530 Subject: [PATCH 161/166] 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 162/166] 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 163/166] 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 164/166] 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 165/166] 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 166/166] 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, }, {