From 64920f713eb4e1e47ef4797a4240de8f1f86b9c4 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Sun, 26 Jan 2025 20:16:48 +0100 Subject: [PATCH 01/14] feat: create copilot request endpoint --- docs/swagger.yaml | 73 ++++++++++++++++++++++++++++- src/constants.js | 9 ++++ src/models/copilotRequest.js | 26 ++++++++++ src/permissions/constants.js | 13 +++++ src/routes/copilotRequest/create.js | 62 ++++++++++++++++++++++++ src/routes/index.js | 6 ++- 6 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 src/models/copilotRequest.js create mode 100644 src/routes/copilotRequest/create.js diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 76edf443..5119662b 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -24,7 +24,7 @@ securityDefinitions: type: apiKey name: Authorization in: header -paths: +paths: "/projects": get: tags: @@ -244,6 +244,38 @@ paths: description: Internal Server Error schema: $ref: "#/definitions/ErrorModel" + "/projects/{projectId}/copilot-request": + post: + tags: + - projects copilot request + operationId: createCopilotRequest + security: + - Bearer: [] + description: "create copilot request" + responses: + "200": + description: Created copilot request + schema: + $ref: "#/definitions/Copilot" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/ErrorModel" + "400": + description: Bad request + schema: + $ref: "#/definitions/ErrorModel" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/ErrorModel" + parameters: + - $ref: "#/parameters/projectIdParam" + - name: body + in: body + required: true + schema: + $ref: "#/definitions/NewCopilotRequest" "/projects/{projectId}/attachments": get: tags: @@ -5459,6 +5491,14 @@ definitions: type: string address: type: string + NewCopilotRequest: + type: object + required: + - data + properties: + data: + type: object + description: copilot request data NewProject: type: object required: @@ -5581,6 +5621,37 @@ definitions: type: integer format: int64 + Copilot: + type: object + properties: + id: + description: unique identifier + type: integer + format: int64 + data: + description: copilot data + type: object + status: + description: status of the copilot request + type: string + 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 Project: type: object properties: diff --git a/src/constants.js b/src/constants.js index ac73a35b..3541aba7 100644 --- a/src/constants.js +++ b/src/constants.js @@ -9,6 +9,15 @@ export const PROJECT_STATUS = { CANCELLED: 'cancelled', }; +export const COPILOT_REQUEST_STATUS = { + NEW: 'new', + APPROVED: 'approved', + REJECTED: 'rejected', + SEEKING: 'seeking', + CANCELED: 'canceled', + FULFILLED: 'fulfiled', +}; + export const WORKSTREAM_STATUS = { DRAFT: 'draft', REVIEWED: 'reviewed', diff --git a/src/models/copilotRequest.js b/src/models/copilotRequest.js new file mode 100644 index 00000000..59ddde99 --- /dev/null +++ b/src/models/copilotRequest.js @@ -0,0 +1,26 @@ +module.exports = function defineCopilotRequest(sequelize, DataTypes) { + const CopliotRequest = sequelize.define('CopilotRequest', { + id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, + status: { type: DataTypes.STRING, defaultValue: 'new' }, + data: { type: DataTypes.JSON, defaultValue: {} }, + + createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + updatedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + createdBy: { type: DataTypes.INTEGER, allowNull: false }, + updatedBy: { type: DataTypes.INTEGER, allowNull: false }, + }, { + tableName: 'copilot_requests', + paranoid: true, + timestamps: true, + updatedAt: 'updatedAt', + createdAt: 'createdAt', + indexes: [], + }); + + CopliotRequest.associate = (models) => { + CopliotRequest.hasMany(models.Project, { as: 'projects', foreignKey: 'projectId' }); + }; + + + return CopliotRequest; +}; diff --git a/src/permissions/constants.js b/src/permissions/constants.js index 312a2d42..0957daa2 100644 --- a/src/permissions/constants.js +++ b/src/permissions/constants.js @@ -250,6 +250,19 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export scopes: SCOPES_PROJECTS_WRITE, }, + MANAGE_COPILOT_REQUEST: { + meta: { + title: 'Manage Copilot Request', + group: 'Copilot Request', + description: 'Who can create, update, delete copilot request.', + }, + topcoderRoles: [ + USER_ROLE.PROJECT_MANAGER, + USER_ROLE.TOPCODER_ADMIN, + ], + scopes: SCOPES_PROJECTS_WRITE, + }, + MANAGE_PROJECT_BILLING_ACCOUNT_ID: { meta: { title: 'Manage Project property "billingAccountId"', diff --git a/src/routes/copilotRequest/create.js b/src/routes/copilotRequest/create.js new file mode 100644 index 00000000..d939b3f5 --- /dev/null +++ b/src/routes/copilotRequest/create.js @@ -0,0 +1,62 @@ +import validate from 'express-validation'; +import _ from 'lodash'; +import Joi from 'joi'; + +import models from '../../models'; +import util from '../../util'; +import { COPILOT_REQUEST_STATUS } from '../../constants'; +import { PERMISSION } from '../../permissions/constants'; + +const addCopilotRequestValidations = { + body: Joi.object().keys({ + data: Joi.object().required(), + }), +}; + +module.exports = [ + validate(addCopilotRequestValidations), + (req, res, next) => { + const data = req.body; + if(!util.hasPermissionByReq(PERMISSION.MANAGE_COPILOT_REQUEST, req)) { + const err = new Error('Unable to create copilot request'); + _.assign(err, { + details: JSON.stringify({ message: 'You do not have permission to create copilot request' }), + status: 400, + }); + return Promise.reject(err); + } + // default values + const projectId = _.parseInt(req.params.projectId); + _.assign(data, { + projectId, + status: COPILOT_REQUEST_STATUS.NEW, + createdBy: req.authUser.userId, + updatedBy: req.authUser.userId, + }); + + models.sequelize.transaction((transaction) => { + req.log.debug('Create Copilot request transaction'); + return models.Project.findOne({ + where: { id: projectId, deletedAt: { $eq: null } }, + }) + .then((existingProject) => { + if (!existingProject) { + const err = new Error(`active project not found for project id ${projectId}`); + err.status = 404; + throw err; + } + return models.CopilotRequest + .create(data, { transaction }) + .then((_newCopilotRequest) => { + return res.status(201).json(_newCopilotRequest); + }); + }) + }) + .catch((err) => { + if (err.message) { + _.assign(err, { details: err.message }); + } + util.handleError('Error creating copilot request', err, req, next); + }); + }, +]; diff --git a/src/routes/index.js b/src/routes/index.js index 33b7c902..edfe1bd3 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -28,7 +28,7 @@ router.get(`/${apiVersion}/projects/health`, (req, res) => { const jwtAuth = require('tc-core-library-js').middleware.jwtAuthenticator; router.all( - RegExp(`\\/${apiVersion}\\/(projects|timelines|orgConfig|customer-payments)(?!\\/health).*`), (req, res, next) => ( + RegExp(`\\/${apiVersion}\\/(copilot-request|projects|timelines|orgConfig|customer-payments)(?!\\/health).*`), (req, res, next) => ( // JWT authentication jwtAuth(config)(req, res, next) ), @@ -378,6 +378,10 @@ router.route('/v5/projects/:projectId(\\d+)/settings') .get(require('./projectSettings/list')) .post(require('./projectSettings/create')); +// Project Copilot Request +router.route('/v5/projects/:projectId(\\d+)/copilot-request') + .post(require('./copilotRequest/create')); + // Project Estimation Items router.route('/v5/projects/:projectId(\\d+)/estimations/:estimationId(\\d+)/items') .get(require('./projectEstimationItems/list')); From 731514385bf6be3da6c2ab8a8225e47e19226ad3 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Sun, 26 Jan 2025 21:39:41 +0100 Subject: [PATCH 02/14] fix: associated copilot request to project --- src/models/copilotRequest.js | 5 ----- src/models/project.js | 1 + src/routes/copilotRequest/create.js | 20 +++++++++++++++----- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/models/copilotRequest.js b/src/models/copilotRequest.js index 59ddde99..697eec19 100644 --- a/src/models/copilotRequest.js +++ b/src/models/copilotRequest.js @@ -16,11 +16,6 @@ module.exports = function defineCopilotRequest(sequelize, DataTypes) { createdAt: 'createdAt', indexes: [], }); - - CopliotRequest.associate = (models) => { - CopliotRequest.hasMany(models.Project, { as: 'projects', foreignKey: 'projectId' }); - }; - return CopliotRequest; }; diff --git a/src/models/project.js b/src/models/project.js index b9191dcd..fac8ef18 100644 --- a/src/models/project.js +++ b/src/models/project.js @@ -74,6 +74,7 @@ module.exports = function defineProject(sequelize, DataTypes) { Project.hasMany(models.ProjectMemberInvite, { as: 'invites', foreignKey: 'projectId' }); Project.hasMany(models.ScopeChangeRequest, { as: 'scopeChangeRequests', foreignKey: 'projectId' }); Project.hasMany(models.WorkStream, { as: 'workStreams', foreignKey: 'projectId' }); + Project.hasMany(models.CopilotRequest, { as: 'copilotRequests', foreignKey: 'projectId' }); }; /** diff --git a/src/routes/copilotRequest/create.js b/src/routes/copilotRequest/create.js index d939b3f5..1bdb5b7a 100644 --- a/src/routes/copilotRequest/create.js +++ b/src/routes/copilotRequest/create.js @@ -45,11 +45,21 @@ module.exports = [ err.status = 404; throw err; } - return models.CopilotRequest - .create(data, { transaction }) - .then((_newCopilotRequest) => { - return res.status(201).json(_newCopilotRequest); - }); + return models.CopilotRequest.findOne({ + where: { + createdBy: req.authUser.userId, + projectId: projectId, + }, + }).then((existingCopilotRequest) => { + if (existingCopilotRequest) { + return res.status(200).json(existingCopilotRequest); + } + return models.CopilotRequest + .create(data, { transaction }) + .then((_newCopilotRequest) => { + return res.status(201).json(_newCopilotRequest); + }); + }) }) }) .catch((err) => { From f1021bf2a79a455b6fc38b7f8a89c5c08a736e92 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 27 Jan 2025 21:42:22 +0100 Subject: [PATCH 03/14] fix: review comments --- docs/swagger.yaml | 2 +- src/models/copilotRequest.js | 4 ++-- src/routes/copilotRequest/create.js | 8 ++++++-- src/routes/index.js | 4 ++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 5119662b..42173766 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -244,7 +244,7 @@ paths: description: Internal Server Error schema: $ref: "#/definitions/ErrorModel" - "/projects/{projectId}/copilot-request": + "/projects/{projectId}/copilots/request": post: tags: - projects copilot request diff --git a/src/models/copilotRequest.js b/src/models/copilotRequest.js index 697eec19..cbb61852 100644 --- a/src/models/copilotRequest.js +++ b/src/models/copilotRequest.js @@ -1,8 +1,8 @@ module.exports = function defineCopilotRequest(sequelize, DataTypes) { const CopliotRequest = sequelize.define('CopilotRequest', { id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, - status: { type: DataTypes.STRING, defaultValue: 'new' }, - data: { type: DataTypes.JSON, defaultValue: {} }, + status: { type: DataTypes.STRING(16), defaultValue: 'new', allowNull: false }, + data: { type: DataTypes.JSON, defaultValue: {}, allowNull: false }, createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, updatedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, diff --git a/src/routes/copilotRequest/create.js b/src/routes/copilotRequest/create.js index 1bdb5b7a..ca4dcac0 100644 --- a/src/routes/copilotRequest/create.js +++ b/src/routes/copilotRequest/create.js @@ -6,6 +6,7 @@ import models from '../../models'; import util from '../../util'; import { COPILOT_REQUEST_STATUS } from '../../constants'; import { PERMISSION } from '../../permissions/constants'; +import { Op } from 'sequelize'; const addCopilotRequestValidations = { body: Joi.object().keys({ @@ -21,7 +22,7 @@ module.exports = [ const err = new Error('Unable to create copilot request'); _.assign(err, { details: JSON.stringify({ message: 'You do not have permission to create copilot request' }), - status: 400, + status: 403, }); return Promise.reject(err); } @@ -35,7 +36,7 @@ module.exports = [ }); models.sequelize.transaction((transaction) => { - req.log.debug('Create Copilot request transaction'); + req.log.debug('Create Copilot request transaction', data); return models.Project.findOne({ where: { id: projectId, deletedAt: { $eq: null } }, }) @@ -49,6 +50,9 @@ module.exports = [ where: { createdBy: req.authUser.userId, projectId: projectId, + status: { + [Op.in] : [COPILOT_REQUEST_STATUS.NEW, COPILOT_REQUEST_STATUS.APPROVED, COPILOT_REQUEST_STATUS.SEEKING], + } }, }).then((existingCopilotRequest) => { if (existingCopilotRequest) { diff --git a/src/routes/index.js b/src/routes/index.js index edfe1bd3..be5ef802 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -28,7 +28,7 @@ router.get(`/${apiVersion}/projects/health`, (req, res) => { const jwtAuth = require('tc-core-library-js').middleware.jwtAuthenticator; router.all( - RegExp(`\\/${apiVersion}\\/(copilot-request|projects|timelines|orgConfig|customer-payments)(?!\\/health).*`), (req, res, next) => ( + RegExp(`\\/${apiVersion}\\/(copilots|projects|timelines|orgConfig|customer-payments)(?!\\/health).*`), (req, res, next) => ( // JWT authentication jwtAuth(config)(req, res, next) ), @@ -379,7 +379,7 @@ router.route('/v5/projects/:projectId(\\d+)/settings') .post(require('./projectSettings/create')); // Project Copilot Request -router.route('/v5/projects/:projectId(\\d+)/copilot-request') +router.route('/v5/projects/:projectId(\\d+)/copilots/request') .post(require('./copilotRequest/create')); // Project Estimation Items From 564a7cbbdfa2bb9155843236a0d658475c545f26 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 28 Jan 2025 18:00:08 +0100 Subject: [PATCH 04/14] fix: deploy feature branch to dev env --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4e6f3414..4033b1f9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'connect-performance-testing', 'feature/new-milestone-concept', 'permission_fixes'] + only: ['develop', 'connect-performance-testing', 'feature/new-milestone-concept', 'permission_fixes', 'pm-571'] - deployProd: context : org-global filters: From 6de161cd91da5183ca0ccc58930fef75a53b102d Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 28 Jan 2025 19:54:04 +0100 Subject: [PATCH 05/14] fix: added copilot requests migration --- migrations/20212902_copilot_requests.sql | 34 ++++++++++++++++++++++++ src/models/copilotRequest.js | 15 ++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 migrations/20212902_copilot_requests.sql diff --git a/migrations/20212902_copilot_requests.sql b/migrations/20212902_copilot_requests.sql new file mode 100644 index 00000000..654ac0a9 --- /dev/null +++ b/migrations/20212902_copilot_requests.sql @@ -0,0 +1,34 @@ +-- +-- CREATE NEW TABLE: +-- copilot_requests +-- +CREATE TABLE copilot_requests ( + id bigint NOT NULL, + "data" json NOT NULL, + "status" character varying(16) NOT NULL, + "projectId" bigint NOT NULL, + "deletedAt" timestamp with time zone, + "createdAt" timestamp with time zone, + "updatedAt" timestamp with time zone, + "deletedBy" bigint, + "createdBy" bigint NOT NULL, + "updatedBy" bigint NOT NULL +); + +CREATE SEQUENCE copilot_requests_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE copilot_requests_id_seq OWNED BY copilot_requests.id; + +ALTER TABLE copilot_requests + ALTER COLUMN id SET DEFAULT nextval('copilot_requests_id_seq'); + +ALTER TABLE ONLY copilot_requests + ADD CONSTRAINT "copilot_requests_pkey" PRIMARY KEY (id); + +ALTER TABLE ONLY copilot_requests + ADD CONSTRAINT "copilot_requests_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES projects(id) ON UPDATE CASCADE ON DELETE SET NULL; \ No newline at end of file diff --git a/src/models/copilotRequest.js b/src/models/copilotRequest.js index cbb61852..6be712e0 100644 --- a/src/models/copilotRequest.js +++ b/src/models/copilotRequest.js @@ -1,11 +1,23 @@ +import _ from 'lodash'; +import { COPILOT_REQUEST_STATUS } from '../constants'; + module.exports = function defineCopilotRequest(sequelize, DataTypes) { const CopliotRequest = sequelize.define('CopilotRequest', { id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, - status: { type: DataTypes.STRING(16), defaultValue: 'new', allowNull: false }, + status: { + type: DataTypes.STRING(16), + defaultValue: 'new', + allowNull: false, + validate: { + isIn: [_.values(COPILOT_REQUEST_STATUS)], + } + }, data: { type: DataTypes.JSON, defaultValue: {}, 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 }, }, { @@ -14,6 +26,7 @@ module.exports = function defineCopilotRequest(sequelize, DataTypes) { timestamps: true, updatedAt: 'updatedAt', createdAt: 'createdAt', + deletedAt: 'deletedAt', indexes: [], }); From 288279a16c73108065c9359593cd9cbec99b1744 Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Thu, 30 Jan 2025 17:13:25 +0530 Subject: [PATCH 06/14] umzug migration setup for copilot requests --- migrations/umzug/README.md | 89 +++++++++++++++++++ migrations/umzug/index.js | 41 +++++++++ .../20250130094419-create-copilot-requests.js | 60 +++++++++++++ package.json | 5 ++ 4 files changed, 195 insertions(+) create mode 100644 migrations/umzug/README.md create mode 100644 migrations/umzug/index.js create mode 100644 migrations/umzug/migrations/20250130094419-create-copilot-requests.js diff --git a/migrations/umzug/README.md b/migrations/umzug/README.md new file mode 100644 index 00000000..faca1dfd --- /dev/null +++ b/migrations/umzug/README.md @@ -0,0 +1,89 @@ +# Migration Guide + +This project uses **Sequelize** with **Umzug** for managing database migrations. + +## **📌 How to Add a New Migration** + +1. **Generate a new migration file** + + cd into `migrations/umzug/` directory and run: + + ```sh + npx sequelize-cli migration:generate --name your_migration_name + ``` + + This will create a new migration file inside `umzug/migrations/`. + +2. **Modify the generated migration file** + + - Open the file inside `umzug/migrations/`. + - Define the required table changes inside the `up` method. + - Define how to revert the changes in the `down` method. + + **Example:** Creating a `users` table + + ```javascript + module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.createTable("users", { + id: { + type: Sequelize.BIGINT, + allowNull: false, + primaryKey: true, + autoIncrement: true, + }, + name: { + type: Sequelize.STRING, + allowNull: false, + }, + createdAt: { + type: Sequelize.DATE, + allowNull: true, + }, + updatedAt: { + type: Sequelize.DATE, + allowNull: true, + } + }); + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.dropTable("users"); + } + }; + ``` + +3. **Test Migrations** + + ```sh + npm run migrate + ``` + + This will apply all pending migrations. + +4. **Rollback Migrations (If Needed)** + + ```sh + npm run migrate:down + ``` + + This will revert the last applied migration. + +5. **Revert All Migrations (If Needed)** + + If you need to revert all applied migrations, run: + + ```sh + npm run migrate:reset + ``` + +This will undo all migrations in reverse order. + +--- + +## **📌 How Migrations Work in This Project** + +- All migration files are stored in `umzug/migrations/`. +- The migration runner is inside `umzug/index.js`. +- After installing dependencies (`npm install`), migrations will **automatically run** via `postinstall`. + +--- diff --git a/migrations/umzug/index.js b/migrations/umzug/index.js new file mode 100644 index 00000000..7532c114 --- /dev/null +++ b/migrations/umzug/index.js @@ -0,0 +1,41 @@ +const config = require('config'); +const { Sequelize } = require("sequelize"); +const { Umzug, SequelizeStorage } = require("umzug"); + +// Initialize Sequelize +const sequelize = new Sequelize(config.get('dbConfig.masterUrl'), { + dialect: "postgres", +}); + +console.log(__dirname); + +// Initialize Umzug +const umzug = new Umzug({ + migrations: { + glob: "__dirname/migrations/*.js", + resolve: ({ name, path, context }) => { + const migration = require(path); + return { + name, + up: async () => migration.up(context, Sequelize), + down: async () => migration.down(context, Sequelize), + }; + }, + }, + context: sequelize.getQueryInterface(), + storage: new SequelizeStorage({ sequelize }), + logger: console, +}); + +// Run migrations +if (require.main === module) { + umzug.up().then(() => { + console.log("Migrations executed successfully"); + process.exit(0); + }).catch((err) => { + console.error("Migration failed", err); + process.exit(1); + }); +} + +module.exports = umzug; diff --git a/migrations/umzug/migrations/20250130094419-create-copilot-requests.js b/migrations/umzug/migrations/20250130094419-create-copilot-requests.js new file mode 100644 index 00000000..72bc7e77 --- /dev/null +++ b/migrations/umzug/migrations/20250130094419-create-copilot-requests.js @@ -0,0 +1,60 @@ +'use strict'; + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.createTable("copilot_requests", { + id: { + type: Sequelize.BIGINT, + allowNull: false, + primaryKey: true, + autoIncrement: true, + }, + data: { + type: Sequelize.JSON, + allowNull: false, + }, + status: { + type: Sequelize.STRING(16), + allowNull: false, + }, + projectId: { + type: Sequelize.BIGINT, + allowNull: false, + references: { + model: "projects", + 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, Sequelize) => { + await queryInterface.dropTable("copilot_requests"); + } +}; diff --git a/package.json b/package.json index 6e0fcd59..e212c459 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,9 @@ "test:watch": "cross-env NODE_ENV=test mocha -w --require babel-core/register --require ./src/tests \"./src/**/*.spec.js*\" ", "reset:db": "npm run babel-node-script -- migrations/sync.js", "reset:es": "npm run babel-node-script -- migrations/elasticsearch_sync.js", + "migrate": "node migrations/umzug/index.js", + "migrate:down": "node migrations/umzug/index.js down", + "migrate:reset": "node migrations/umzug/index.js down --all", "import-from-api": "env-cmd npm run babel-node-script -- scripts/import-from-api", "es-db-compare": "env-cmd npm run babel-node-script -- scripts/es-db-compare", "data:export": "cross-env NODE_ENV=development LOG_LEVEL=info env-cmd npm run babel-node-script -- scripts/data/export", @@ -42,6 +45,7 @@ }, "homepage": "https://github.com/appirio-tech/tc-projects-service#readme", "dependencies": { + "ajv": "^8.17.1", "analytics-node": "^2.1.1", "app-module-path": "^1.0.7", "aws-sdk": "^2.610.0", @@ -76,6 +80,7 @@ "swagger-ui-express": "^4.0.6", "tc-core-library-js": "github:appirio-tech/tc-core-library-js#v2.6.6", "traverse": "^0.6.6", + "umzug": "^3.8.2", "urlencode": "^1.1.0", "yamljs": "^0.3.0" }, From 4e36664dd2591a986ba52cc2621362632b606035 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Fri, 31 Jan 2025 06:52:37 +0200 Subject: [PATCH 07/14] hook migrations before start --- .circleci/config.yml | 3 ++- package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4033b1f9..bc0195b7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -39,6 +39,7 @@ deploy_steps: &deploy_steps ./buildenv.sh -e $DEPLOY_ENV -b ${LOGICAL_ENV}-${APPNAME}-consumers-deployvar source buildenvvar ./master_deploy.sh -d ECS -e $DEPLOY_ENV -t latest -s ${LOGICAL_ENV}-global-appvar,${LOGICAL_ENV}-${APPNAME}-appvar -i ${APPNAME} + jobs: UnitTests: @@ -149,7 +150,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'connect-performance-testing', 'feature/new-milestone-concept', 'permission_fixes', 'pm-571'] + only: ['develop', 'migration-setup'] - deployProd: context : org-global filters: diff --git a/package.json b/package.json index e212c459..6a27dfe2 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "lint": "eslint .", "lint:fix": "eslint . --fix || true", "build": "babel src -d dist --presets es2015 --copy-files", - "start": "node dist", + "start": "npm run migrate && node dist", "start:dev": "cross-env NODE_ENV=development PORT=3000 nodemon -w src --exec \"./node_modules/.bin/env-cmd npm run babel-node-script -- src\" | bunyan", "startKafkaConsumers": "node dist/index-kafka.js", "startKafkaConsumers:dev": "cross-env NODE_ENV=development nodemon -w src --exec \"./node_modules/.bin/env-cmd npm run babel-node-script src/index-kafka.js\" | bunyan", From 51d5d1d4c1c0f9d142e2059da1d6edcb518e5001 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Fri, 31 Jan 2025 07:00:54 +0200 Subject: [PATCH 08/14] Update config.yml --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bc0195b7..37724805 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -39,7 +39,6 @@ deploy_steps: &deploy_steps ./buildenv.sh -e $DEPLOY_ENV -b ${LOGICAL_ENV}-${APPNAME}-consumers-deployvar source buildenvvar ./master_deploy.sh -d ECS -e $DEPLOY_ENV -t latest -s ${LOGICAL_ENV}-global-appvar,${LOGICAL_ENV}-${APPNAME}-appvar -i ${APPNAME} - jobs: UnitTests: From 2e8460abefbf72416733677ffbedff197b654e81 Mon Sep 17 00:00:00 2001 From: Gunasekar-K Date: Fri, 31 Jan 2025 11:14:40 +0530 Subject: [PATCH 09/14] Update config.yml- build issue CORE-1768 --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 37724805..1acd4a4c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,7 @@ version: 2.1 python_env: &python_env docker: - - image: cimg/python:3.11.7-browsers + - image: cimg/python:3.11.11-browsers install_awscli: &install_awscli name: "Install awscli" command: | @@ -32,13 +32,13 @@ deploy_steps: &deploy_steps source awsenvconf ./buildenv.sh -e $DEPLOY_ENV -b ${LOGICAL_ENV}-${APPNAME}-deployvar source buildenvvar - ./master_deploy.sh -d ECS -e $DEPLOY_ENV -t latest -s ${LOGICAL_ENV}-global-appvar,${LOGICAL_ENV}-${APPNAME}-appvar -i ${APPNAME} + ./master_deploy.sh -d ECS -e $DEPLOY_ENV -t latest -s ${LOGICAL_ENV}-global-appvar,${LOGICAL_ENV}-${APPNAME}-appvar -i ${APPNAME} -p FARGATE echo "======= Running Masterscript - deploy projects-api-consumers ===========" if [ -e ${LOGICAL_ENV}-${APPNAME}-appvar.json ]; then sudo rm -vf ${LOGICAL_ENV}-${APPNAME}-appvar.json; fi ./buildenv.sh -e $DEPLOY_ENV -b ${LOGICAL_ENV}-${APPNAME}-consumers-deployvar source buildenvvar - ./master_deploy.sh -d ECS -e $DEPLOY_ENV -t latest -s ${LOGICAL_ENV}-global-appvar,${LOGICAL_ENV}-${APPNAME}-appvar -i ${APPNAME} + ./master_deploy.sh -d ECS -e $DEPLOY_ENV -t latest -s ${LOGICAL_ENV}-global-appvar,${LOGICAL_ENV}-${APPNAME}-appvar -i ${APPNAME} -p FARGATE jobs: UnitTests: From cef47393d9267d26a7bdc2920a6bdbbc3da856d9 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Fri, 31 Jan 2025 08:15:53 +0200 Subject: [PATCH 10/14] fix path for migration files --- migrations/umzug/index.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/migrations/umzug/index.js b/migrations/umzug/index.js index 7532c114..f2d863b0 100644 --- a/migrations/umzug/index.js +++ b/migrations/umzug/index.js @@ -1,18 +1,18 @@ const config = require('config'); -const { Sequelize } = require("sequelize"); -const { Umzug, SequelizeStorage } = require("umzug"); +const { Sequelize } = require('sequelize'); +const { SequelizeStorage } = require('umzug'); // Initialize Sequelize const sequelize = new Sequelize(config.get('dbConfig.masterUrl'), { - dialect: "postgres", + dialect: 'postgres', }); -console.log(__dirname); +console.log('Umzug migrations running in:', __dirname); // Initialize Umzug const umzug = new Umzug({ migrations: { - glob: "__dirname/migrations/*.js", + glob: 'migrations/*.js', resolve: ({ name, path, context }) => { const migration = require(path); return { @@ -29,13 +29,16 @@ const umzug = new Umzug({ // Run migrations if (require.main === module) { - umzug.up().then(() => { - console.log("Migrations executed successfully"); - process.exit(0); - }).catch((err) => { - console.error("Migration failed", err); - process.exit(1); - }); + umzug + .up() + .then(() => { + console.log('Migrations executed successfully'); + process.exit(0); + }) + .catch((err) => { + console.error('Migration failed', err); + process.exit(1); + }); } module.exports = umzug; From 408e66e44c7240c3becbfd0bdeebe4d5b781cbde Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Fri, 31 Jan 2025 09:14:47 +0200 Subject: [PATCH 11/14] fix umzug glob --- migrations/umzug/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/umzug/index.js b/migrations/umzug/index.js index f2d863b0..fd047bbc 100644 --- a/migrations/umzug/index.js +++ b/migrations/umzug/index.js @@ -12,7 +12,7 @@ console.log('Umzug migrations running in:', __dirname); // Initialize Umzug const umzug = new Umzug({ migrations: { - glob: 'migrations/*.js', + glob: __dirname + '/migrations/*.js', resolve: ({ name, path, context }) => { const migration = require(path); return { From 973ada62da31fccd3965bdcb38c2609fbda5d864 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Fri, 31 Jan 2025 09:32:45 +0200 Subject: [PATCH 12/14] umzug: resolve --- migrations/umzug/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/migrations/umzug/index.js b/migrations/umzug/index.js index fd047bbc..3413c5e8 100644 --- a/migrations/umzug/index.js +++ b/migrations/umzug/index.js @@ -7,13 +7,14 @@ const sequelize = new Sequelize(config.get('dbConfig.masterUrl'), { dialect: 'postgres', }); -console.log('Umzug migrations running in:', __dirname); +console.log('Umzug migration script:', __dirname); // Initialize Umzug const umzug = new Umzug({ migrations: { - glob: __dirname + '/migrations/*.js', + glob: '__dirname/migrations/*.js', resolve: ({ name, path, context }) => { + console.log('Loading migration:', name, path); const migration = require(path); return { name, From 83228dd58b49cf8cc0651b4c8dc10535a9dc6f19 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Fri, 31 Jan 2025 10:50:22 +0200 Subject: [PATCH 13/14] fix umzug undefined --- migrations/umzug/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/migrations/umzug/index.js b/migrations/umzug/index.js index 3413c5e8..48298851 100644 --- a/migrations/umzug/index.js +++ b/migrations/umzug/index.js @@ -1,6 +1,6 @@ const config = require('config'); const { Sequelize } = require('sequelize'); -const { SequelizeStorage } = require('umzug'); +const { Umzug, SequelizeStorage } = require('umzug'); // Initialize Sequelize const sequelize = new Sequelize(config.get('dbConfig.masterUrl'), { @@ -12,7 +12,7 @@ console.log('Umzug migration script:', __dirname); // Initialize Umzug const umzug = new Umzug({ migrations: { - glob: '__dirname/migrations/*.js', + glob: 'migrations/*.js', resolve: ({ name, path, context }) => { console.log('Loading migration:', name, path); const migration = require(path); From 082b71a551313ed71c08ef28e261fe4080e4d7b1 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Fri, 31 Jan 2025 11:13:03 +0200 Subject: [PATCH 14/14] fix glob path --- migrations/umzug/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/umzug/index.js b/migrations/umzug/index.js index 48298851..0d7d2593 100644 --- a/migrations/umzug/index.js +++ b/migrations/umzug/index.js @@ -12,7 +12,7 @@ console.log('Umzug migration script:', __dirname); // Initialize Umzug const umzug = new Umzug({ migrations: { - glob: 'migrations/*.js', + glob: 'migrations/umzug/migrations/*.js', resolve: ({ name, path, context }) => { console.log('Loading migration:', name, path); const migration = require(path);