diff --git a/README.md b/README.md index 112cc3fa..2a18f10c 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Copy config/sample.local.js as config/local.js, update the properties and accord Once you start your PostgreSQL database through docker, it will create a projectsDB. *To create tables - note this will drop tables if they already exist* ``` -NODE_ENV=development npm run sync +NODE_ENV=development npm run sync:db ``` #### Redis @@ -34,7 +34,7 @@ Docker compose includes elasticsearch instance as well. It will open ports 9200 There is a helper script to sync the indices and mappings with the elasticsearch. -Run `npm run elasticsearch:sync` from the root of project to execute the script. +Run `npm run sync:es` from the root of project to execute the script. > NOTE: This will first clear all the indices and than recreate them. So use with caution. diff --git a/config/custom-environment-variables.json b/config/custom-environment-variables.json index 1fb82ceb..36774d4d 100644 --- a/config/custom-environment-variables.json +++ b/config/custom-environment-variables.json @@ -36,5 +36,7 @@ }, "analyticsKey": "ANALYTICS_KEY", "validIssuers": "VALID_ISSUERS", - "jwksUri": "JWKS_URI" + "jwksUri": "JWKS_URI", + "busApiUrl": "BUS_API_URL", + "busApiToken": "BUS_API_TOKEN" } diff --git a/config/default.json b/config/default.json index 5e2bef12..dd753d8e 100644 --- a/config/default.json +++ b/config/default.json @@ -43,5 +43,7 @@ }, "analyticsKey": "", "validIssuers": "[\"https:\/\/topcoder-newauth.auth0.com\/\",\"https:\/\/api.topcoder-dev.com\"]", - "jwksUri": "" + "jwksUri": "", + "busApiUrl": "http://api.topcoder-dev.com", + "busApiToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoicHJvamVjdC1zZXJ2aWNlIiwiaWF0IjoxNTEyNzQ3MDgyLCJleHAiOjE1MjEzODcwODJ9.PHuNcFDaotGAL8RhQXQMdpL8yOKXxjB5DbBIodmt7RE" } diff --git a/config/sample.local.js b/config/sample.local.js index a6c0d1fb..47dbc0bd 100644 --- a/config/sample.local.js +++ b/config/sample.local.js @@ -25,7 +25,7 @@ if (process.env.NODE_ENV === 'test') { projectIdFieldId: '00N2C000000Vxxx', }, dbConfig: { - masterUrl: 'postgres://coder:mysecretpassword@dockerhost:5432/projectsdb', + masterUrl: 'postgres://coder:mysecretpassword@dockerhost:54321/projectsdb', maxPoolSize: 50, minPoolSize: 4, idleTimeout: 1000, diff --git a/package-lock.json b/package-lock.json index 6eb9d237..0eedd948 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,12 +14,12 @@ } }, "@types/body-parser": { - "version": "1.16.7", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.16.7.tgz", - "integrity": "sha512-Obn1/GG0sYsnlAlhhSR1hvYRGBpQT+fzSi2IlGN8emCE4iu6f6xIjaq499B1sa7N9iBLzxyOUBo5bzgJd16BvA==", + "version": "1.16.8", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.16.8.tgz", + "integrity": "sha512-BdN2PXxOFnTXFcyONPW6t0fHjz2fvRZHVMFpaS0wYr+Y8fWEaNOs4V8LEu/fpzQlMx+ahdndgTaGTwPC+J/EeA==", "requires": { "@types/express": "4.0.39", - "@types/node": "8.0.47" + "@types/node": "8.5.1" } }, "@types/express": { @@ -27,9 +27,9 @@ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.0.39.tgz", "integrity": "sha512-dBUam7jEjyuEofigUXCtublUHknRZvcRgITlGsTbFgPvnTwtQUt2NgLakbsf+PsGo/Nupqr3IXCYsOpBpofyrA==", "requires": { - "@types/body-parser": "1.16.7", - "@types/express-serve-static-core": "4.0.56", - "@types/serve-static": "1.13.0" + "@types/body-parser": "1.16.8", + "@types/express-serve-static-core": "4.0.57", + "@types/serve-static": "1.13.1" } }, "@types/express-jwt": { @@ -42,11 +42,11 @@ } }, "@types/express-serve-static-core": { - "version": "4.0.56", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.0.56.tgz", - "integrity": "sha512-/0nwIzF1Bd4KGwW4lhDZYi5StmCZG1DIXXMfQ/zjORzlm4+F1eRA4c6yJQrt4hqX//TDtPULpSlYwmSNyCMeMg==", + "version": "4.0.57", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.0.57.tgz", + "integrity": "sha512-QLAHjdLwEICm3thVbXSKRoisjfgMVI4xJH/HU8F385BR2HI7PmM6ax4ELXf8Du6sLmSpySXMYaI+xc//oQ/IFw==", "requires": { - "@types/node": "8.0.47" + "@types/node": "8.5.1" } }, "@types/express-unless": { @@ -68,16 +68,16 @@ "integrity": "sha512-A2TAGbTFdBw9azHbpVd+/FkdW2T6msN1uct1O9bH3vTerEHKZhTXJUQXy+hNq1B0RagfU8U+KBdqiZpxjhOUQA==" }, "@types/node": { - "version": "8.0.47", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.47.tgz", - "integrity": "sha512-kOwL746WVvt/9Phf6/JgX/bsGQvbrK5iUgzyfwZNcKVFcjAUVSpF9HxevLTld2SG9aywYHOILj38arDdY1r/iQ==" + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.5.1.tgz", + "integrity": "sha512-SrmAO+NhnsuG/6TychSl2VdxBZiw/d6V+8j+DFo8O3PwFi+QeYXWHhAw+b170aSc6zYab6/PjEWRZHIDN9mNUw==" }, "@types/serve-static": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.0.tgz", - "integrity": "sha512-wvQkePwCDZoyQPGb64DTl2TEeLw54CQFXjY+tznxYYxNcBb4LG40ezoVbMDa0epwE4yogB0f42jCaH0356x5Mg==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.1.tgz", + "integrity": "sha512-jDMH+3BQPtvqZVIcsH700Dfi8Q3MIcEx16g/VdxjoqiGR/NntekB10xdBpirMKnPe9z2C5cBmL0vte0YttOr3Q==", "requires": { - "@types/express-serve-static-core": "4.0.56", + "@types/express-serve-static-core": "4.0.57", "@types/mime": "2.0.0" } }, @@ -416,11 +416,12 @@ "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" }, "axios": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.12.0.tgz", - "integrity": "sha1-uQewIhzDTsHJ+sGOx/B935V4W6Q=", + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.17.1.tgz", + "integrity": "sha1-LY4+XQvb1zJ/kbyBT1xXZg+Bgk0=", "requires": { - "follow-redirects": "0.0.7" + "follow-redirects": "1.2.6", + "is-buffer": "1.1.6" } }, "babel-cli": { @@ -2182,6 +2183,15 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cors": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", + "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", + "requires": { + "object-assign": "4.1.1", + "vary": "1.1.2" + } + }, "create-error-class": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", @@ -3252,12 +3262,21 @@ } }, "follow-redirects": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-0.0.7.tgz", - "integrity": "sha1-NLkLqyqRGqNHVx2pDyK9NuzYqRk=", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.2.6.tgz", + "integrity": "sha512-FrMqZ/FONtHnbqO651UPpfRUVukIEwJhXMfdr/JWAmrDbeYBu773b1J6gdWDyRIj4hvvzQEHoEOTrdR8o6KLYA==", "requires": { - "debug": "2.6.9", - "stream-consume": "0.1.0" + "debug": "3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } } }, "for-in": { @@ -3806,14 +3825,14 @@ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "requires": { - "ajv": "5.3.0", + "ajv": "5.5.1", "har-schema": "2.0.0" }, "dependencies": { "ajv": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.3.0.tgz", - "integrity": "sha1-RBT/dKUIecII7l/cgm4ywwNUnto=", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.1.tgz", + "integrity": "sha1-s4u4h22ehr7plJVqBOch6IskjrI=", "requires": { "co": "4.6.0", "fast-deep-equal": "1.0.0", @@ -4059,8 +4078,7 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "is-builtin-module": { "version": "1.0.0", @@ -5519,8 +5537,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object.defaults": { "version": "1.1.0", @@ -7214,6 +7231,25 @@ "le_node": "1.7.1", "lodash": "4.17.4", "millisecond": "0.1.2" + }, + "dependencies": { + "axios": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.12.0.tgz", + "integrity": "sha1-uQewIhzDTsHJ+sGOx/B935V4W6Q=", + "requires": { + "follow-redirects": "0.0.7" + } + }, + "follow-redirects": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-0.0.7.tgz", + "integrity": "sha1-NLkLqyqRGqNHVx2pDyK9NuzYqRk=", + "requires": { + "debug": "2.6.9", + "stream-consume": "0.1.0" + } + } } }, "term-size": { diff --git a/package.json b/package.json index 93e3d565..25d80bc3 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "migrate:es": "./node_modules/.bin/babel-node migrations/seedElasticsearchIndex.js", "prestart": "npm run -s build", "start": "node dist", - "start:dev": "NODE_ENV=local PORT=8001 nodemon -w src --exec \"babel-node src --presets es2015\" | ./node_modules/.bin/bunyan", + "start:dev": "NODE_ENV=development PORT=8001 nodemon -w src --exec \"babel-node src --presets es2015\" | ./node_modules/.bin/bunyan", "test": "NODE_ENV=test npm run lint && NODE_ENV=test npm run sync:es && NODE_ENV=test ./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha -- --compilers js:babel-core/register $(find src -path '*spec.js*')", "test:watch": "NODE_ENV=test ./node_modules/.bin/mocha -w --compilers js:babel-core/register $(find src -path '*spec.js*')", "seed": "babel-node src/tests/seed.js --presets es2015" @@ -35,11 +35,13 @@ "analytics-node": "^2.1.1", "app-module-path": "^1.0.7", "aws-sdk": "^2.33.0", + "axios": "^0.17.1", "bluebird": "^3.4.1", "body-parser": "^1.15.0", "co": "^4.6.0", "config": "^1.20.1", "continuation-local-storage": "^3.1.7", + "cors": "^2.8.4", "elasticsearch": "^11.0.1", "express": "^4.13.4", "express-list-routes": "^0.1.4", diff --git a/postman.json b/postman.json index 13f6386b..807c4f87 100644 --- a/postman.json +++ b/postman.json @@ -1,87 +1,79 @@ { - "variables": [], "info": { "name": "tc-project-service ", - "_postman_id": "50ca5c4a-6999-510e-4cfb-54ccfa1ec1f1", + "_postman_id": "8f323d9c-63bd-5f2c-87f1-1e99083786f3", "description": "", "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" }, "item": [ { "name": "Project Attachments", - "description": "", "item": [ { - "name": "POST /v4/projects/5/attachments", + "name": "Upload attachment", "request": { - "url": "{{api-url}}/v4/projects/5/attachments", "method": "POST", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n\t\"param\": {\n\t\t\"title\": \"first attachment submission\",\n\t\t\"filePath\": \"asdjshdasdas/asdsadj/asdasd.png\",\n\t\t\"s3Bucket\": \"topcoder-project-service\",\n\t\t\"contentType\": \"application/png\"\n\t}\n}" }, + "url": "{{api-url}}/v4/projects/7/attachments", "description": "Create an project attachment" }, "response": [] }, { - "name": "PATCH /v4/projects/5/attachments/6", + "name": "Update attachment", "request": { - "url": "{{api-url}}/v4/projects/5/attachments/6", "method": "PATCH", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n\t\"param\": {\n\t\t\"title\": \"first attachment submission updated\",\n\t\t\"description\": \"updated project attachment\"\n\t}\n}" }, + "url": "{{api-url}}/v4/projects/7/attachments/2", "description": "Update project attachment" }, "response": [] }, { - "name": "DELETE /v4/projects/5/attachments/6", + "name": "Delete attachment", "request": { - "url": "{{api-url}}/v4/projects/5/attachments/6", "method": "DELETE", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, + "url": "{{api-url}}/v4/projects/7/attachments/2", "description": "Delete a project attachment" }, "response": [] @@ -90,79 +82,72 @@ }, { "name": "Project Members", - "description": "", "item": [ { "name": "Create project member with no payload", "request": { - "url": "{{api-url}}/v4/projects/1/members", "method": "POST", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, + "url": "{{api-url}}/v4/projects/1/members", "description": "Request payload is mandatory while creating project. If no request payload is specified this should result in 422 status code." }, "response": [] }, { - "name": "Create project member with invalid userId", + "name": "Create project copilot with invalid userId", "request": { - "url": "{{api-url}}/v4/projects/1/members", "method": "POST", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n\t\"role\": \"copilot\"\n}" }, + "url": "{{api-url}}/v4/projects/1/members", "description": "Certain fields are mandatory while creating project. If invalid fields are specified this should result in 422 status code." }, "response": [] }, { - "name": "Create project member with valid values", + "name": "Create project copilot with valid values", "request": { - "url": "{{api-url}}/v4/projects/1/members", "method": "POST", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n\t\"param\": {\n\t\t\"role\": \"copilot\",\n\t\t\"userId\": 40051331,\n\t\t\"isPrimary\": true\n\t}\n}" }, + "url": "{{api-url}}/v4/projects/7/members", "description": "If the request payload is valid, than project member should be created." }, "response": [] @@ -170,24 +155,22 @@ { "name": "Create project member, if user already registered", "request": { - "url": "{{api-url}}/v4/projects/1/members", "method": "POST", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n\t\"param\": {\n\t\t\"role\": \"copilot\",\n\t\t\"userId\": 40051331,\n\t\t\"isPrimary\": true\n\t}\n}" }, + "url": "{{api-url}}/v4/projects/1/members", "description": "If the request payload is valid and user is already registered with the specified role than this should result in 400." }, "response": [] @@ -195,49 +178,91 @@ { "name": "Create project manager with valid values", "request": { - "url": "{{api-url}}/v4/projects/1/members", "method": "POST", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n\t\"param\": {\n\t\t\"role\": \"manager\",\n\t\t\"userId\": 40051330,\n\t\t\"isPrimary\": true\n\t}\n}" }, + "url": "{{api-url}}/v4/projects/7/members", "description": "If the request payload is valid, than project manager should be added. This should sync with the direct project is project is associated with direct project." }, "response": [] }, + { + "name": "Create project customer with valid values", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"param\": {\n\t\t\"role\": \"customer\",\n\t\t\"userId\": 40051332,\n\t\t\"isPrimary\": true\n\t}\n}" + }, + "url": "{{api-url}}/v4/projects/7/members", + "description": "If the request payload is valid, than project customer should be added. This should sync with the direct project is project is associated with direct project." + }, + "response": [] + }, { "name": "Update project member", "request": { - "url": "{{api-url}}/v4/projects/1/members/1", "method": "PATCH", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n\t\"param\": {\n\t\t\"role\": \"copilot\",\n\t\t\"isPrimary\": true\n\t}\n}" }, + "url": "{{api-url}}/v4/projects/7/members/16", + "description": "Update a project's member." + }, + "response": [] + }, + { + "name": "Update project member with isPrimary False", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"param\": {\n\t\t\"role\": \"copilot\",\n\t\t\"isPrimary\": false\n\t}\n}" + }, + "url": "{{api-url}}/v4/projects/7/members/16", "description": "Update a project's member." }, "response": [] @@ -245,24 +270,22 @@ { "name": "Delete project member", "request": { - "url": "{{api-url}}/v4/projects/2/members/2", "method": "DELETE", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, + "url": "{{api-url}}/v4/projects/7/members/15", "description": "Delete a project's member" }, "response": [] @@ -276,24 +299,22 @@ { "name": "Create project without payload", "request": { - "url": "{{api-url}}/v4/projects", "method": "POST", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n\t\n}" }, + "url": "{{api-url}}/v4/projects", "description": "Request body is mandatory while creating project. If invalid request body is supplied this should return 422 status code." }, "response": [] @@ -301,24 +322,22 @@ { "name": "Create project without valid name", "request": { - "url": "{{api-url}}/v4/projects", "method": "POST", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n\t\"param\": {\n\t}\n}" }, + "url": "{{api-url}}/v4/projects", "description": "Certain fields are mandatory while creating project. If invalid request body is supplied this should return 422 status code." }, "response": [] @@ -326,24 +345,22 @@ { "name": "Create project with valid values", "request": { - "url": "{{api-url}}/v4/projects", "method": "POST", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project\",\n\t\t\"description\": \"Hello I am a test project\",\n\t\t\"type\": \"generic\"\n\t}\n}" }, + "url": "{{api-url}}/v4/projects", "description": "Valid request body. Project should be created successfully." }, "response": [] @@ -351,19 +368,18 @@ { "name": "Get project by id", "request": { - "url": "{{api-url}}/v4/projects/1", "method": "GET", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", "raw": "" }, + "url": "{{api-url}}/v4/projects/7", "description": "Get a project by id. project members and attachments should also be returned." }, "response": [] @@ -371,19 +387,34 @@ { "name": "Get project by id and request specific fields", "request": { - "url": "{{api-url}}/v4/projects/1?fields=id,name,description,members.id,members.projectId", "method": "GET", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", "raw": "" }, + "url": { + "raw": "{{api-url}}/v4/projects/1?fields=id,name,description,members.id,members.projectId", + "host": [ + "{{api-url}}" + ], + "path": [ + "v4", + "projects", + "1" + ], + "query": [ + { + "key": "fields", + "value": "id,name,description,members.id,members.projectId" + } + ] + }, "description": "Get a project by id. project members and attachments should also be returned. Only those fields which are specified should be returned." }, "response": [] @@ -391,19 +422,18 @@ { "name": "List projects", "request": { - "url": "{{api-url}}/v4/projects", "method": "GET", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", "raw": "" }, + "url": "{{api-url}}/v4/projects", "description": "List all the project with no filter. Default sort and limits are applied." }, "response": [] @@ -411,19 +441,37 @@ { "name": "List projects with limit and offset", "request": { - "url": "{{api-url}}/v4/projects?limit=1&offset=1", "method": "GET", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", "raw": "" }, + "url": { + "raw": "{{api-url}}/v4/projects?limit=1&offset=1", + "host": [ + "{{api-url}}" + ], + "path": [ + "v4", + "projects" + ], + "query": [ + { + "key": "limit", + "value": "1" + }, + { + "key": "offset", + "value": "1" + } + ] + }, "description": "List all the project with no filter. Limit of 1 and offset of 1 is applied" }, "response": [] @@ -431,19 +479,33 @@ { "name": "List projects with filters applied", "request": { - "url": "{{api-url}}/v4/projects?filter=type%3Dgeneric", "method": "GET", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", "raw": "" }, + "url": { + "raw": "{{api-url}}/v4/projects?filter=type%3Dgeneric", + "host": [ + "{{api-url}}" + ], + "path": [ + "v4", + "projects" + ], + "query": [ + { + "key": "filter", + "value": "type%3Dgeneric" + } + ] + }, "description": "List all the project with filters applied. The filter string should be url encoded. Default limit and offset is applicable" }, "response": [] @@ -451,19 +513,33 @@ { "name": "List projects with sort applied", "request": { - "url": "{{api-url}}/v4/projects?sort=type%20desc", "method": "GET", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", "raw": "" }, + "url": { + "raw": "{{api-url}}/v4/projects?sort=type%20desc", + "host": [ + "{{api-url}}" + ], + "path": [ + "v4", + "projects" + ], + "query": [ + { + "key": "sort", + "value": "type%20desc" + } + ] + }, "description": "List all the project with custom sort and no filter. Default sort and limits are applied. The sort string has to be url encoded. Sort is of type `key asc|desc`" }, "response": [] @@ -471,19 +547,33 @@ { "name": "List projects and return specific fields", "request": { - "url": "{{api-url}}/v4/projects?fields=id,name,description", "method": "GET", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", "raw": "" }, + "url": { + "raw": "{{api-url}}/v4/projects?fields=id,name,description", + "host": [ + "{{api-url}}" + ], + "path": [ + "v4", + "projects" + ], + "query": [ + { + "key": "fields", + "value": "id,name,description" + } + ] + }, "description": "List all the project with no filter. Default sort and limits are applied. The fields to return is specified as comma separated list. Only those fields should be returned." }, "response": [] @@ -491,19 +581,18 @@ { "name": "DELETE project by id", "request": { - "url": "{{api-url}}/v4/projects/3", "method": "DELETE", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", "raw": "" }, + "url": "{{api-url}}/v4/projects/3", "description": "Delete a project by id" }, "response": [] @@ -511,24 +600,22 @@ { "name": "Update project", "request": { - "url": "{{api-url}}/v4/projects/1", "method": "PATCH", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"name\": \"project name updated\"\n\t}\n}" + "raw": "{\n \"param\": {\n \"name\": \"project name updated\"\n }\n}" }, + "url": "{{api-url}}/v4/projects/13", "description": "Update the project name. Name should be updated successfully." }, "response": [] @@ -536,24 +623,22 @@ { "name": "Update project 403", "request": { - "url": "{{api-url}}/v4/projects/2", "method": "PATCH", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n\t\"param\": {\n\t\t\"name\": \"project name updated\"\n\t}\n}" }, + "url": "{{api-url}}/v4/projects/2", "description": "Update the project name. If user don't have permission to the project than it should return 403." }, "response": [] @@ -561,99 +646,183 @@ { "name": "Update project 404", "request": { - "url": "{{api-url}}/v4/projects/10", "method": "PATCH", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n\t\"param\": {\n\t\t\"name\": \"project name updated\"\n\t}\n}" }, + "url": "{{api-url}}/v4/projects/10", "description": "Update the project name. If project is not found than this result in 404 status code." }, "response": [] }, + { + "name": "Update project status to in review", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"param\": {\n \"status\": \"in_review\"\n }\n}" + }, + "url": "{{api-url}}/v4/projects/7", + "description": "Update the project status." + }, + "response": [] + }, + { + "name": "Update project status to reviewed", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"param\": {\n \"status\": \"reviewed\"\n }\n}" + }, + "url": "{{api-url}}/v4/projects/7", + "description": "Update the project status." + }, + "response": [] + }, + { + "name": "Update project status to paused", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"param\": {\n \"status\": \"paused\"\n }\n}" + }, + "url": "{{api-url}}/v4/projects/7", + "description": "Update the project status." + }, + "response": [] + }, + { + "name": "Update project status to cancelled with cancelReason", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"param\": {\n \"status\": \"cancelled\",\n \"cancelReason\": \"price/cost\"\n }\n}" + }, + "url": "{{api-url}}/v4/projects/7", + "description": "Update the project status. While cancelling the project `cancelReason` is mandatory." + }, + "response": [] + }, { "name": "Update project status to cancelled", "request": { - "url": "{{api-url}}/v4/projects/1", "method": "PATCH", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n\t\"param\": {\n\t\t\"status\": \"cancelled\"\n\t}\n}" }, + "url": "{{api-url}}/v4/projects/1", "description": "Update the project status. While cancelling the project `cancelReason` is mandatory. If no `cancelReason` is supplied this should result in 422 status code." }, "response": [] }, { - "name": "Update project status to cancelled with cancelReason", + "name": "Update project status to completed", "request": { - "url": "{{api-url}}/v4/projects/1", "method": "PATCH", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"status\": \"cancelled\",\n\t\t\"cancelReason\": \"price/cost\"\n\t}\n}" + "raw": "{\n \"param\": {\n \"status\": \"completed\"\n }\n}" }, - "description": "Update the project status. While cancelling the project `cancelReason` is mandatory." + "url": "{{api-url}}/v4/projects/7", + "description": "Update the project status." }, "response": [] }, { "name": "Move project out of cancel state.", "request": { - "url": "{{api-url}}/v4/projects/1", "method": "PATCH", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n\t\"param\": {\n\t\t\"status\": \"active\"\n\t}\n}" }, + "url": "{{api-url}}/v4/projects/1", "description": "Move a project out of cancel state. Only admin and manager is allowed to do so." }, "response": [] @@ -661,226 +830,246 @@ { "name": "Move project out of cancel state 403", "request": { - "url": "{{api-url}}/v4/projects/1", "method": "PATCH", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n\t\"param\": {\n\t\t\"status\": \"active\"\n\t}\n}" }, + "url": "{{api-url}}/v4/projects/1", "description": "Move a project out of cancel state. Only admin and manager is allowed to do so." }, "response": [] + }, + { + "name": "Update project details", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"param\": {\n \"details\": {\n \"summary\": \"project name updated\"\n }\n }\n}" + }, + "url": "{{api-url}}/v4/projects/8", + "description": "Update the project details. This should fire specification modified event" + }, + "response": [] + }, + { + "name": "Update project bookmarks", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"param\": {\n \"bookmarks\": [\n {\n \"title\": \"test\",\n \"address\": \"http://topcoder.com\"\n }\n \n ]\n }\n}" + }, + "url": "{{api-url}}/v4/projects/8", + "description": "Update the project bookmarks. This should fire project link created event" + }, + "response": [] } ] }, { "name": "bookmarks", - "description": "", "item": [ { "name": " Create project without bookmarks", "request": { - "url": "{{api-url}}/v4/projects", "method": "POST", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"param\": {\n \"type\": \"generic\",\n \"description\": \"test project\",\n \"details\": {},\n \"billingAccountId\": 123,\n \"name\": \"test project1\"\n }\n}" }, - "description": "" + "url": "{{api-url}}/v4/projects" }, "response": [] }, { "name": " Create project with valid bookmarks", "request": { - "url": "{{api-url}}/v4/projects", "method": "POST", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"param\": {\n \"type\": \"generic\",\n \"description\": \"test project\",\n \"details\": {},\n \"bookmarks\":[{\n \"title\":\"title1\",\n \"address\":\"address1\"\n },{\n \"title\":\"title2\",\n \"address\":\"address2\"\n }],\n \"billingAccountId\": 123,\n \"name\": \"test project1\"\n }\n}" }, - "description": "" + "url": "{{api-url}}/v4/projects" }, "response": [] }, { "name": " Create project with invalid bookmarks", "request": { - "url": "{{api-url}}/v4/projects", "method": "POST", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"param\": {\n \"type\": \"generic\",\n \"description\": \"test project\",\n \"details\": {},\n \"bookmarks\":[{\n \"title\":\"title1\",\n \"invalid\":3,\n \"address\":\"address1\"\n },{\n \"title\":\"title2\",\n \"address\":\"address2\"\n }],\n \"billingAccountId\": 123,\n \"name\": \"test project1\"\n }\n}" }, - "description": "" + "url": "{{api-url}}/v4/projects" }, "response": [] }, { "name": "get project", "request": { - "url": "{{api-url}}/v4/projects/2", "method": "GET", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name\"\n }\n}" }, - "description": "" + "url": "{{api-url}}/v4/projects/2" }, "response": [] }, { "name": "Update project with bookmarks", "request": { - "url": "{{api-url}}/v4/projects/2", "method": "PATCH", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name\",\n \"bookmarks\":[{\n \"title\":\"title1\",\n \"address\":\"address1\"\n },{\n \"title\":\"title2\",\n \"address\":\"address2\"\n }]\n }\n}" }, - "description": "" + "url": "{{api-url}}/v4/projects/2" }, "response": [] }, { "name": "Delete project all bookmarks null", "request": { - "url": "{{api-url}}/v4/projects/2", "method": "PATCH", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name2\",\n \"bookmarks\":null\n }\n}" }, - "description": "" + "url": "{{api-url}}/v4/projects/2" }, "response": [] }, { "name": "Update project with invalid bookmarks", "request": { - "url": "{{api-url}}/v4/projects/2", "method": "PATCH", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name2\",\n \"bookmarks\":3\n }\n}" }, - "description": "" + "url": "{{api-url}}/v4/projects/2" }, "response": [] }, { "name": "get projects with admin token", "request": { - "url": "{{api-url}}/v4/projects", "method": "GET", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{api-url}}/v4/projects" }, "response": [] } @@ -888,25 +1077,22 @@ }, { "name": "issue1", - "description": "", "item": [ { "name": "get projects with copilot token", "request": { - "url": "{{api-url}}/v4/projects", "method": "GET", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{api-url}}/v4/projects" }, "response": [] } @@ -914,55 +1100,48 @@ }, { "name": "issue10", - "description": "", "item": [ { "name": "wrong role", "request": { - "url": "{{api-url}}/v4/projects/3/members/5", "method": "PATCH", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": " {\n \"param\": {\n \"role\": \"wrong\"\n }\n } " }, - "description": "" + "url": "{{api-url}}/v4/projects/3/members/5" }, "response": [] }, { "name": "editing project member roles & primary option", "request": { - "url": "{{api-url}}/v4/projects/1/members/1", "method": "PATCH", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": " {\n \"param\": {\n \"role\": \"manager\",\n \"isPrimary\": true\n }\n } " }, - "description": "" + "url": "{{api-url}}/v4/projects/1/members/1" }, "response": [] } @@ -970,80 +1149,70 @@ }, { "name": "issue5", - "description": "", "item": [ { "name": "launch a project by topcoder managers ", "request": { - "url": "{{api-url}}/v4/projects/1", "method": "PATCH", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \n \"param\":{\n \"name\": \"updatedProject name\",\n \"status\": \"active\"\n }\n}" }, - "description": "" + "url": "{{api-url}}/v4/projects/1" }, "response": [] }, { "name": "launch a project by member", "request": { - "url": "{{api-url}}/v4/projects/1", "method": "PATCH", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \n \"param\":{\n \"name\": \"updatedProject name\",\n \"status\": \"active\"\n }\n}" }, - "description": "" + "url": "{{api-url}}/v4/projects/1" }, "response": [] }, { "name": "launch a project by copilot", "request": { - "url": "{{api-url}}/v4/projects/1", "method": "PATCH", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \n \"param\":{\n \"name\": \"updatedProject name\",\n \"status\": \"active\"\n }\n}" }, - "description": "" + "url": "{{api-url}}/v4/projects/1" }, "response": [] } @@ -1051,180 +1220,158 @@ }, { "name": "issue8", - "description": "", "item": [ { "name": "mock direct projects", "request": { - "url": "https://localhost:8443/v3/direct/projects", "method": "GET", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": " {\n \"param\": {\n \"role\": \"copilot\",\n \"isPrimary\": true\n }\n } " }, - "description": "" + "url": "https://localhost:8443/v3/direct/projects" }, "response": [] }, { "name": " Create direct project when a new project is successfully created", "request": { - "url": "{{api-url}}/v4/projects", "method": "POST", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"param\": {\n \"type\": \"generic\",\n \"description\": \"test project\",\n \"details\": {},\n \"billingAccountId\": 123,\n \"name\": \"test project1\"\n }\n}" }, - "description": "" + "url": "{{api-url}}/v4/projects" }, "response": [] }, { "name": "Response error from direct project service", "request": { - "url": "{{api-url}}/v4/projects/1/members", "method": "POST", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"param\": {\n \"userId\": 2, \n \"role\": \"copilot\"\n }\n}" }, - "description": "" + "url": "{{api-url}}/v4/projects/1/members" }, "response": [] }, { "name": " Add co-pilot when a co-pilot is added to a project", "request": { - "url": "{{api-url}}/v4/projects/2/members", "method": "POST", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"param\": {\n \"userId\": 2, \n \"role\": \"copilot\"\n }\n}" }, - "description": "" + "url": "{{api-url}}/v4/projects/2/members" }, "response": [] }, { "name": "remove copilot from direct project when editing project member role", "request": { - "url": "{{api-url}}/v4/projects/2/members/4", "method": "PATCH", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": " {\n \"param\": {\n \"role\": \"customer\",\n \"isPrimary\": true\n }\n } " }, - "description": "" + "url": "{{api-url}}/v4/projects/2/members/4" }, "response": [] }, { "name": " Sync billing account id with direct", "request": { - "url": "{{api-url}}/v4/projects/2", "method": "PATCH", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name\"\n }\n}" }, - "description": "" + "url": "{{api-url}}/v4/projects/2" }, "response": [] }, { "name": "Delete co-pilot when a co-pilot is removed from a project", "request": { - "url": "{{api-url}}/v4/projects/2/members/4", "method": "DELETE", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "description": "" + "value": "Bearer {{jwt-token}}" }, { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, - "description": "" + "url": "{{api-url}}/v4/projects/2/members/4" }, "response": [] } diff --git a/src/app.js b/src/app.js index 4b9144ef..dbb693ca 100644 --- a/src/app.js +++ b/src/app.js @@ -10,6 +10,7 @@ import router from './routes'; import permissions from './permissions'; import models from './models'; import analytics from './events/analytics'; +import busApi from './events/busApi'; const app = express(); @@ -75,6 +76,11 @@ if (!_.isEmpty(analyticsKey)) { analytics(analyticsKey, app, logger); } +// ======================= +// Event listener for Bus Api +// ======================= +busApi(app, logger); + // ======================== // Permissions // ======================== diff --git a/src/constants.js b/src/constants.js index 1d2c0d62..43cfb52b 100644 --- a/src/constants.js +++ b/src/constants.js @@ -48,3 +48,23 @@ export const EVENT = { PROJECT_DELETED: 'project.deleted', }, }; + +export const BUS_API_EVENT = { + PROJECT_CREATED: 'connect.project.created', + PROJECT_SUBMITTED_FOR_REVIEW: 'connect.project.submittedForReview', + PROJECT_APPROVED: 'connect.project.approved', + PROJECT_PAUSED: 'connect.project.paused', + PROJECT_COMPLETED: 'connect.project.completed', + PROJECT_CANCELED: 'connect.project.canceled', + + MEMBER_JOINED: 'connect.project.member.joined', + MEMBER_LEFT: 'connect.project.member.left', + MEMBER_REMOVED: 'connect.project.member.removed', + MEMBER_ASSIGNED_AS_OWNER: 'connect.project.member.assignedAsOwner', + MEMBER_JOINED_COPILOT: 'connect.project.member.copilotJoined', + MEMBER_JOINED_MANAGER: 'connect.project.member.managerJoined', + + PROJECT_LINK_CREATED: 'connect.project.linkCreated', + PROJECT_FILE_UPLOADED: 'connect.project.fileUploaded', + PROJECT_SPECIFICATION_MODIFIED: 'connect.project.specificationModified', +}; diff --git a/src/events/busApi.js b/src/events/busApi.js new file mode 100644 index 00000000..79230b2b --- /dev/null +++ b/src/events/busApi.js @@ -0,0 +1,170 @@ +import _ from 'lodash'; +import 'config'; +import { EVENT, BUS_API_EVENT, PROJECT_STATUS, PROJECT_MEMBER_ROLE } from '../constants'; +import { createEvent } from '../services/busApi'; +import models from '../models'; + +/** + * Map of project status and event name sent to bus api + */ +const mapEventTypes = { + [PROJECT_STATUS.DRAFT]: BUS_API_EVENT.PROJECT_CREATED, + [PROJECT_STATUS.IN_REVIEW]: BUS_API_EVENT.PROJECT_SUBMITTED_FOR_REVIEW, + [PROJECT_STATUS.REVIEWED]: BUS_API_EVENT.PROJECT_APPROVED, + [PROJECT_STATUS.COMPLETED]: BUS_API_EVENT.PROJECT_COMPLETED, + [PROJECT_STATUS.CANCELLED]: BUS_API_EVENT.PROJECT_CANCELED, + [PROJECT_STATUS.PAUSED]: BUS_API_EVENT.PROJECT_PAUSED, +}; + +module.exports = (app, logger) => { + /** + * PROJECT_DRAFT_CREATED + */ + app.on(EVENT.ROUTING_KEY.PROJECT_DRAFT_CREATED, ({ req, project }) => { + logger.debug('receive PROJECT_DRAFT_CREATED event'); + + // send event to bus api + createEvent(BUS_API_EVENT.PROJECT_CREATED, { + projectId: project.id, + projectName: project.name, + userId: req.authUser.userId, + }); + }); + + /** + * PROJECT_UPDATED + */ + app.on(EVENT.ROUTING_KEY.PROJECT_UPDATED, ({ req, original, updated }) => { + logger.debug('receive PROJECT_UPDATED event'); + + if (original.status !== updated.status) { + logger.debug(`project status is updated from ${original.status} to ${updated.status}`); + createEvent(mapEventTypes[updated.status], { + projectId: updated.id, + projectName: updated.name, + userId: req.authUser.userId, + }); + } else if ( + !_.isEqual(original.details, updated.details) || + !_.isEqual(original.name, updated.name) || + !_.isEqual(original.description, updated.description)) { + logger.debug('project spec is updated'); + createEvent(BUS_API_EVENT.PROJECT_SPECIFICATION_MODIFIED, { + projectId: updated.id, + projectName: updated.name, + userId: req.authUser.userId, + }); + } else if (!_.isEqual(original.bookmarks, updated.bookmarks)) { + logger.debug('project bookmarks is updated'); + createEvent(BUS_API_EVENT.PROJECT_LINK_CREATED, { + projectId: updated.id, + projectName: updated.name, + userId: req.authUser.userId, + }); + } + }); + + /** + * PROJECT_MEMBER_ADDED + */ + app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED, ({ req, member }) => { + logger.debug('receive PROJECT_MEMBER_ADDED event'); + + let eventType; + switch (member.role) { + case PROJECT_MEMBER_ROLE.MANAGER: + eventType = BUS_API_EVENT.MEMBER_JOINED_MANAGER; + break; + case PROJECT_MEMBER_ROLE.COPILOT: + eventType = BUS_API_EVENT.MEMBER_JOINED_COPILOT; + break; + default: + eventType = BUS_API_EVENT.MEMBER_JOINED; + break; + } + const projectId = _.parseInt(req.params.projectId); + + models.Project.findOne({ + where: { id: projectId }, + }) + .then((project) => { + createEvent(eventType, { + projectId, + projectName: project.name, + userId: member.userId, + }); + }).catch(err => null); // eslint-disable-line no-unused-vars + }); + + /** + * PROJECT_MEMBER_REMOVED + */ + app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, ({ req, member }) => { + logger.debug('receive PROJECT_MEMBER_REMOVED event'); + + let eventType; + if (member.userId === req.authUser.userId) { + eventType = BUS_API_EVENT.MEMBER_LEFT; + } else { + eventType = BUS_API_EVENT.MEMBER_REMOVED; + } + const projectId = _.parseInt(req.params.projectId); + + models.Project.findOne({ + where: { id: projectId }, + }) + .then((project) => { + if (project) { + createEvent(eventType, { + projectId, + projectName: project.name, + userId: member.userId, + }); + } + }).catch(err => null); // eslint-disable-line no-unused-vars + }); + + /** + * PROJECT_MEMBER_UPDATED + */ + app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_UPDATED, ({ req, original, updated }) => { // eslint-disable-line no-unused-vars + logger.debug('receive PROJECT_MEMBER_UPDATED event'); + + const projectId = _.parseInt(req.params.projectId); + if (updated.isPrimary && !original.isPrimary) { + models.Project.findOne({ + where: { id: projectId }, + }) + .then((project) => { + if (project) { + createEvent(BUS_API_EVENT.MEMBER_ASSIGNED_AS_OWNER, { + projectId, + projectName: project.name, + userId: updated.userId, + }); + } + }).catch(err => null); // eslint-disable-line no-unused-vars + } + }); + + /** + * PROJECT_ATTACHMENT_ADDED + */ + app.on(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED, ({ req, attachment }) => { + logger.debug('receive PROJECT_ATTACHMENT_ADDED event'); + + const projectId = _.parseInt(req.params.projectId); + + models.Project.findOne({ + where: { id: projectId }, + }) + .then((project) => { + createEvent(BUS_API_EVENT.PROJECT_FILE_UPLOADED, { + projectId, + projectName: project.name, + fileName: attachment.filePath.replace(/^.*[\\\/]/, ''), // eslint-disable-line + userId: req.authUser.userId, + }); + }).catch(err => null); // eslint-disable-line no-unused-vars + }); +}; diff --git a/src/routes/index.js b/src/routes/index.js index 4932602c..e8b0b857 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -25,6 +25,10 @@ const jwtAuth = require('tc-core-library-js').middleware.jwtAuthenticator; router.all('/v4/projects*', jwtAuth()); +router.get('/v4/projects/debug', (req, res) => { + res.status(200).json({ history: {}, env: process.env }); +}); + // Register all the routes router.route('/v4/projects') .post(require('./projects/create')) diff --git a/src/routes/projectMembers/update.js b/src/routes/projectMembers/update.js index 47626083..081fd614 100644 --- a/src/routes/projectMembers/update.js +++ b/src/routes/projectMembers/update.js @@ -121,6 +121,8 @@ module.exports = [ { original: previousValue, updated: projectMember }, { correlationId: req.id }, ); + req.app.emit(EVENT.ROUTING_KEY.PROJECT_MEMBER_UPDATED, + { req, original: previousValue, updated: projectMember }); req.log.debug('updated project member', projectMember); res.json(util.wrapResponse(req.id, projectMember)); }) diff --git a/src/services/busApi.js b/src/services/busApi.js new file mode 100644 index 00000000..933d43c4 --- /dev/null +++ b/src/services/busApi.js @@ -0,0 +1,54 @@ +import config from 'config'; + +const Promise = require('bluebird'); +const axios = require('axios'); + + +let client = null; + +/** + * Get Http client to bus api + * @return {Object} Http Client to bus api + */ +function getClient() { + if (client) return client; + const apiBusUrl = config.get('busApiUrl'); + const apiBusToken = config.get('busApiToken'); + + client = axios.create({ baseURL: apiBusUrl }); + + // Alter defaults after instance has been created + client.defaults.headers.common.Authorization = `Bearer ${apiBusToken}`; + + // Add a response interceptor + client.interceptors.response.use(function (res) { // eslint-disable-line + return res; + }, function (error) { // eslint-disable-line + // Ingore response errors + return Promise.resolve(); + }); + + return client; +} + +/** + * Creates a new event in Bus API + * Any errors will be simply ignored + * @param {String} type the event type, should be a dot separated fully qualitied name + * @param {Object} message the message, should be a JSON object + * @return {Promise} new event promise + */ +function createEvent(type, message) { + const body = JSON.stringify(message); + return getClient().post('/eventbus/events', { + type, + message: body, + }) + .then(resp => resp) + .catch(error => Promise.resolve()); // eslint-disable-line +} + + +module.exports = { + createEvent, +};