diff --git a/README.md b/README.md index 57fb6ad8..7d06f1dd 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Microservice to manage CRUD operations for all things Projects. ### Local Development * We use docker-compose for running dependencies locally. Instructions for Docker compose setup - https://docs.docker.com/compose/install/ -* Nodejs 5.10.1 - consider using [nvm](https://github.com/creationix/nvm) or equivalent to manage your node version +* Nodejs 6.9.4 - consider using [nvm](https://github.com/creationix/nvm) or equivalent to manage your node version * Install [libpg](https://www.npmjs.com/package/pg-native) * Install node dependencies `npm install | ./node_modules/.bin/bunyan` @@ -25,6 +25,8 @@ npm run -s build > NODE_ENV=development node -e "require('./dist/models').default.sequelize.sync({force: true}).then((res)=> console.log('Success: ', res)).catch((err)=> console.log('Failed: ', err));" ``` +Other simple approach to create tables is to run `npm run sync` from root of project. This additional script is added to make the above task simpler. + #### Redis Docker compose command will start a local redis instance as well. You should be able to connect to this instance using url `$(docker-machine ip):6379` @@ -51,5 +53,8 @@ You may replace 172.17.0.1 with your docker0 IP. You can paste **swagger.yaml** to [swagger editor](http://editor.swagger.io/) or import **postman.json** to verify endpoints. +#### Deploying without docker +If you don't want to use docker to deploy to localhost. You can simply run `npm run start` from root of project. This should start the server on default port `3000`. + ### Deployment Using awsebcli - http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/eb-cli3-install.html diff --git a/package.json b/package.json index d0fee265..76477cd9 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "prestart": "npm run -s build", "seed": "babel-node src/tests/seed.js --presets es2015", "direct": "babel-node src/mocks/direct.js --presets es2015", + "sync": "node sync.js", "lint": "./node_modules/.bin/eslint src" }, "repository": { diff --git a/postman.json b/postman.json index 41c30408..ab4617db 100644 --- a/postman.json +++ b/postman.json @@ -1,525 +1,1123 @@ { - "id": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "name": "tc-project-service ", - "description": "", - "order": [], - "folders": [ + "variables": [], + "info": { + "name": "tc-project-service ", + "_postman_id": "85538c36-6562-9d05-bec5-b04dc8f33ef9", + "description": "", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" + }, + "item": [ { - "id": "a56012d4-9aaf-1df7-4fe7-6cef64d91a22", - "name": "bookmarks", - "description": "", - "order": [ - "e146bef2-7ea0-aebb-aa27-54785b64765d", - "53d2c743-75a3-da8b-5da6-a2c123291b73", - "e798a2e0-09c3-160a-5401-30748e2066b0", - "bfb954da-8007-81f6-50e0-33570ac72320", - "db09f872-d175-4066-52c4-77ee81aca14f", - "9ca83b2c-eb99-0a21-774f-ebbd32862db6", - "60784e18-9bcb-e5f8-3ce5-7ef440aa5ad4", - "de9780e3-b6c1-9a78-eb3e-6bb9fb26aeea" - ], - "owner": 0 - }, - { - "id": "a555eab3-f75e-6d88-daba-7d5a5f92c56f", - "name": "issue1", - "description": "", - "order": [ - "9db67567-033c-3d07-cdb5-6e5db4bde1c9" - ], - "owner": 0, - "collectionId": "160ba566-2572-1f51-d620-c7615f3411e6" - }, - { - "id": "434cd818-a881-e83f-0e92-4f8545a43942", - "name": "issue10", - "description": "", - "order": [ - "018056b4-f539-e267-e895-2fee42a75f88", - "cc4c20b8-b153-57c9-0e8b-55a01b080e82" - ], - "owner": 0, - "collectionId": "160ba566-2572-1f51-d620-c7615f3411e6" - }, - { - "id": "c44adfb1-dfd4-fa39-e11d-82d239e82292", - "name": "issue5", - "description": "", - "order": [ - "0e98aa1a-e66f-de89-e273-79b54a29ba72", - "6a963b29-f7aa-5ed1-d6f9-b315090af2d4", - "662b20de-1060-9e37-77e8-b54946e7c3a2" - ], - "owner": 0 - }, - { - "id": "47bf002c-126b-0994-8439-b1108b7d049a", - "name": "issue8", - "description": "", - "order": [ - "37f501b2-e28e-de3e-2ad0-9cf5ad8c415b", - "6fb063f5-2316-f835-c0ea-34326343e9a2", - "0ec296ec-0cff-125e-8b82-a85895f714f1", - "7a07ab7f-3142-c2fc-24a6-7de562084767", - "f3a6be9f-9c57-5c48-3019-5f3381fcbbb2", - "378b26c1-6d02-98e0-3b58-53d4b817391b", - "3537eb17-9a0a-02f0-d48e-457e628a4cc3" - ], - "owner": 0, - "collectionId": "160ba566-2572-1f51-d620-c7615f3411e6" - } - ], - "timestamp": 1470532482194, - "owner": 0, - "public": false, - "published": false, - "requests": [ - { - "id": "018056b4-f539-e267-e895-2fee42a75f88", - "headers": "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts\nContent-Type: application/json\n", - "url": "http://localhost:3000/v4/projects/3/members/5", - "preRequestScript": null, - "pathVariables": {}, - "method": "PATCH", - "data": [], - "dataMode": "raw", - "version": 2, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "time": 1470657192407, - "name": "wrong role", - "description": "", - "collectionId": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "responses": [], - "rawModeData": " {\n \"param\": {\n \"role\": \"wrong\"\n }\n } ", - "folder": "434cd818-a881-e83f-0e92-4f8545a43942" - }, - { - "id": "0e98aa1a-e66f-de89-e273-79b54a29ba72", - "headers": "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts\nContent-Type: application/json\n", - "url": "http://localhost:3000/v4/projects/1", - "preRequestScript": null, - "pathVariables": {}, - "method": "PATCH", - "data": [], - "dataMode": "raw", - "version": 2, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "time": 1470655466654, - "name": "launch a project by topcoder managers ", - "description": "", - "collectionId": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "responses": [], - "rawModeData": "{\n \n \"param\":{\n \"name\": \"updatedProject name\",\n \"status\": \"active\"\n }\n}", - "folder": "c44adfb1-dfd4-fa39-e11d-82d239e82292" - }, - { - "id": "0ec296ec-0cff-125e-8b82-a85895f714f1", - "headers": "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts\nContent-Type: application/json\n", - "url": "http://localhost:3000/v4/projects/1/members", - "preRequestScript": null, - "pathVariables": {}, - "method": "POST", - "data": [], - "dataMode": "raw", - "version": 2, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "time": 1470656347172, - "name": "Response error from direct project service", - "description": "", - "collectionId": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "responses": [], - "rawModeData": "{\n \"param\": {\n \"userId\": 2, \n \"role\": \"copilot\"\n }\n}", - "folder": "47bf002c-126b-0994-8439-b1108b7d049a" - }, - { - "id": "3537eb17-9a0a-02f0-d48e-457e628a4cc3", - "headers": "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts\nContent-Type: application/json\n", - "url": "http://localhost:3000/v4/projects/2/members/4", - "preRequestScript": null, - "pathVariables": {}, - "method": "DELETE", - "data": [], - "dataMode": "raw", - "version": 2, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "time": 1470656184824, - "name": "Delete co-pilot when a co-pilot is removed from a project", - "description": "", - "collectionId": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "responses": [], - "rawModeData": "", - "folder": "47bf002c-126b-0994-8439-b1108b7d049a" - }, - { - "id": "378b26c1-6d02-98e0-3b58-53d4b817391b", - "headers": "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts\nContent-Type: application/json\n", - "url": "http://localhost:3000/v4/projects/2", - "preRequestScript": null, - "pathVariables": {}, - "method": "PATCH", - "data": [], - "dataMode": "raw", - "version": 2, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "time": 1470656225362, - "name": " Sync billing account id with direct", - "description": "", - "collectionId": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "responses": [], - "rawModeData": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name\"\n }\n}", - "folder": "47bf002c-126b-0994-8439-b1108b7d049a" - }, - { - "id": "37f501b2-e28e-de3e-2ad0-9cf5ad8c415b", - "headers": "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts\nContent-Type: application/json\n", - "url": "https://localhost:8443/v3/direct/projects", - "preRequestScript": null, - "pathVariables": {}, - "method": "GET", - "data": [], - "dataMode": "raw", - "version": 2, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "time": 1470615101332, - "name": "mock direct projects", - "description": "", - "collectionId": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "responses": [], - "rawModeData": " {\n \"param\": {\n \"role\": \"copilot\",\n \"isPrimary\": true\n }\n } ", - "folder": "47bf002c-126b-0994-8439-b1108b7d049a" - }, - { - "id": "53d2c743-75a3-da8b-5da6-a2c123291b73", - "headers": "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts\nContent-Type: application/json\n", - "url": "http://localhost:3000/v4/projects", - "pathVariables": {}, - "preRequestScript": null, - "method": "POST", - "collectionId": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "data": [], - "dataMode": "raw", - "name": " Create project with valid bookmarks", - "description": "", - "descriptionFormat": "html", - "time": 1471670842546, - "version": 2, - "responses": [], - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "folder": "a56012d4-9aaf-1df7-4fe7-6cef64d91a22", - "rawModeData": "{\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}" - }, - { - "id": "60784e18-9bcb-e5f8-3ce5-7ef440aa5ad4", - "headers": "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts\nContent-Type: application/json\n", - "url": "http://localhost:3000/v4/projects/2", - "pathVariables": {}, - "preRequestScript": null, - "method": "PATCH", - "collectionId": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "data": [], - "dataMode": "raw", - "name": "Update project with invalid bookmarks", - "description": "", - "descriptionFormat": "html", - "time": 1471670874088, - "version": 2, - "responses": [], - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "folder": "a56012d4-9aaf-1df7-4fe7-6cef64d91a22", - "rawModeData": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name2\",\n \"bookmarks\":3\n }\n}" - }, - { - "id": "662b20de-1060-9e37-77e8-b54946e7c3a2", - "headers": "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJjb3BpbG90Il0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMiIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.tY_eE9fjtKQ_Hp9XPwmhwMaaTdOYKoR09tdGgvZ8RLw\nContent-Type: application/json\n", - "url": "http://localhost:3000/v4/projects/1", - "preRequestScript": null, - "pathVariables": {}, - "method": "PATCH", - "data": [], - "dataMode": "raw", - "version": 2, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "time": 1470561983805, - "name": "launch a project by copilot", - "description": "", - "collectionId": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "responses": [], - "rawModeData": "{\n \n \"param\":{\n \"name\": \"updatedProject name\",\n \"status\": \"active\"\n }\n}", - "folder": "c44adfb1-dfd4-fa39-e11d-82d239e82292" - }, - { - "id": "6a963b29-f7aa-5ed1-d6f9-b315090af2d4", - "headers": "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6W10sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMSIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.p13tStpp0A1RJjYJ2axSKCTx7lyWIS3kYtCvs8u88WM\nContent-Type: application/json\n", - "url": "http://localhost:3000/v4/projects/1", - "preRequestScript": null, - "pathVariables": {}, - "method": "PATCH", - "data": [], - "dataMode": "raw", - "version": 2, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "time": 1470574753479, - "name": "launch a project by member", - "description": "", - "collectionId": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "responses": [], - "rawModeData": "{\n \n \"param\":{\n \"name\": \"updatedProject name\",\n \"status\": \"active\"\n }\n}", - "folder": "c44adfb1-dfd4-fa39-e11d-82d239e82292" - }, - { - "id": "6fb063f5-2316-f835-c0ea-34326343e9a2", - "headers": "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts\nContent-Type: application/json\n", - "url": "http://localhost:3000/v4/projects", - "preRequestScript": null, - "pathVariables": {}, - "method": "POST", - "data": [], - "dataMode": "raw", - "version": 2, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "time": 1470625658104, - "name": " Create direct project when a new project is successfully created", - "description": "", - "collectionId": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "responses": [], - "rawModeData": "{\n \"param\": {\n \"type\": \"generic\",\n \"description\": \"test project\",\n \"details\": {},\n \"billingAccountId\": 123,\n \"name\": \"test project1\"\n }\n}", - "folder": "47bf002c-126b-0994-8439-b1108b7d049a" - }, - { - "id": "7a07ab7f-3142-c2fc-24a6-7de562084767", - "headers": "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts\nContent-Type: application/json\n", - "url": "http://localhost:3000/v4/projects/2/members", - "preRequestScript": null, - "pathVariables": {}, - "method": "POST", - "data": [], - "dataMode": "raw", - "version": 2, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "time": 1470655602176, - "name": " Add co-pilot when a co-pilot is added to a project", - "description": "", - "collectionId": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "responses": [], - "rawModeData": "{\n \"param\": {\n \"userId\": 2, \n \"role\": \"copilot\"\n }\n}", - "folder": "47bf002c-126b-0994-8439-b1108b7d049a" - }, - { - "id": "9ca83b2c-eb99-0a21-774f-ebbd32862db6", - "headers": "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts\nContent-Type: application/json\n", - "url": "http://localhost:3000/v4/projects/2", - "pathVariables": {}, - "preRequestScript": null, - "method": "PATCH", - "collectionId": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "data": [], - "dataMode": "raw", - "name": "Delete project all bookmarks null", + "name": "Project Members", "description": "", - "descriptionFormat": "html", - "time": 1471670868007, - "version": 2, - "responses": [], - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "folder": "a56012d4-9aaf-1df7-4fe7-6cef64d91a22", - "rawModeData": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name2\",\n \"bookmarks\":null\n }\n}" + "item": [ + { + "name": "Create project member with no payload", + "request": { + "url": "http://localhost:3000/v4/projects/1/members", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.yG7pd9_UUFGo6XFH7-H6Wd5wWzRivkeZTCyet7IXFso", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "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", + "request": { + "url": "http://localhost:3000/v4/projects/1/members", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.yG7pd9_UUFGo6XFH7-H6Wd5wWzRivkeZTCyet7IXFso", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"role\": \"copilot\"\n}" + }, + "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", + "request": { + "url": "http://localhost:3000/v4/projects/1/members", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.yG7pd9_UUFGo6XFH7-H6Wd5wWzRivkeZTCyet7IXFso", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "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}" + }, + "description": "If the request payload is valid, than project member should be created." + }, + "response": [] + }, + { + "name": "Create project member, if user already registered", + "request": { + "url": "http://localhost:3000/v4/projects/1/members", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.yG7pd9_UUFGo6XFH7-H6Wd5wWzRivkeZTCyet7IXFso", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "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}" + }, + "description": "If the request payload is valid and user is already registered with the specified role than this should result in 400." + }, + "response": [] + }, + { + "name": "Create project manager with valid values", + "request": { + "url": "http://localhost:3000/v4/projects/1/members", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.yG7pd9_UUFGo6XFH7-H6Wd5wWzRivkeZTCyet7IXFso", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "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}" + }, + "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": "Update project member", + "request": { + "url": "http://localhost:3000/v4/projects/1/members/1", + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.yG7pd9_UUFGo6XFH7-H6Wd5wWzRivkeZTCyet7IXFso", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"param\": {\n\t\t\"role\": \"copilot\",\n\t\t\"isPrimary\": true\n\t}\n}" + }, + "description": "Update a project's member." + }, + "response": [] + }, + { + "name": "Delete project member", + "request": { + "url": "http://localhost:3000/v4/projects/2/members/2", + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.yG7pd9_UUFGo6XFH7-H6Wd5wWzRivkeZTCyet7IXFso", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "Delete a project's member" + }, + "response": [] + } + ] }, { - "id": "9db67567-033c-3d07-cdb5-6e5db4bde1c9", - "headers": "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBjb3BpbG90Il0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOjQwMDUxMzMyLCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwiaWF0IjoxNDcwNjIwMDQ0fQ.HUhRM1G6jwBt8Kv2slPYndhVUdYXXBAH174fPzqpFME\n", - "url": "http://localhost:3000/v4/projects", - "preRequestScript": null, - "pathVariables": {}, - "method": "GET", - "data": null, - "dataMode": "params", - "version": 2, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "time": 1470655431639, - "name": "get projects with copilot token", - "description": "", - "collectionId": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "responses": [], - "folder": "a555eab3-f75e-6d88-daba-7d5a5f92c56f" - }, - { - "id": "bfb954da-8007-81f6-50e0-33570ac72320", - "headers": "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts\nContent-Type: application/json\n", - "url": "http://localhost:3000/v4/projects/2", - "pathVariables": {}, - "preRequestScript": null, - "method": "GET", - "collectionId": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "data": [], - "dataMode": "raw", - "name": "get project", - "description": "", - "descriptionFormat": "html", - "time": 1471670855697, - "version": 2, - "responses": [], - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "folder": "a56012d4-9aaf-1df7-4fe7-6cef64d91a22", - "rawModeData": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name\"\n }\n}" + "name": "Projects", + "description": "Requests for all things projects.", + "item": [ + { + "name": "Create project without payload", + "request": { + "url": "http://localhost:3000/v4/projects", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiQ29ubmVjdCBNYW5hZ2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzNCIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.amNzJ6PknW8V1ZDrVxJAd-SxcyfYhTf80IBc1sH-vF8", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\n}" + }, + "description": "Request body is mandatory while creating project. If invalid request body is supplied this should return 422 status code." + }, + "response": [] + }, + { + "name": "Create project without valid name", + "request": { + "url": "http://localhost:3000/v4/projects", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiQ29ubmVjdCBNYW5hZ2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzNCIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.amNzJ6PknW8V1ZDrVxJAd-SxcyfYhTf80IBc1sH-vF8", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"param\": {\n\t}\n}" + }, + "description": "Certain fields are mandatory while creating project. If invalid request body is supplied this should return 422 status code." + }, + "response": [] + }, + { + "name": "Create project with valid values", + "request": { + "url": "http://localhost:3000/v4/projects", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiQ29ubmVjdCBNYW5hZ2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzNCIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.amNzJ6PknW8V1ZDrVxJAd-SxcyfYhTf80IBc1sH-vF8", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "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}" + }, + "description": "Valid request body. Project should be created successfully." + }, + "response": [] + }, + { + "name": "Get project by id", + "request": { + "url": "http://localhost:3000/v4/projects/1", + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.yG7pd9_UUFGo6XFH7-H6Wd5wWzRivkeZTCyet7IXFso", + "description": "" + } + ], + "body": {}, + "description": "Get a project by id. project members and attachments should also be returned." + }, + "response": [] + }, + { + "name": "Get project by id and request specific fields", + "request": { + "url": "http://localhost:3000/v4/projects/1?fields=id,name,description,members.id,members.projectId", + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.yG7pd9_UUFGo6XFH7-H6Wd5wWzRivkeZTCyet7IXFso", + "description": "" + } + ], + "body": {}, + "description": "Get a project by id. project members and attachments should also be returned. Only those fields which are specified should be returned." + }, + "response": [] + }, + { + "name": "List projects", + "request": { + "url": "http://localhost:3000/v4/projects", + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.yG7pd9_UUFGo6XFH7-H6Wd5wWzRivkeZTCyet7IXFso", + "description": "" + } + ], + "body": {}, + "description": "List all the project with no filter. Default sort and limits are applied." + }, + "response": [] + }, + { + "name": "List projects with limit and offset", + "request": { + "url": "http://localhost:3000/v4/projects?limit=1&offset=1", + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.yG7pd9_UUFGo6XFH7-H6Wd5wWzRivkeZTCyet7IXFso", + "description": "" + } + ], + "body": {}, + "description": "List all the project with no filter. Limit of 1 and offset of 1 is applied" + }, + "response": [] + }, + { + "name": "List projects with filters applied", + "request": { + "url": "http://localhost:3000/v4/projects?filter=type%3Dgeneric", + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.yG7pd9_UUFGo6XFH7-H6Wd5wWzRivkeZTCyet7IXFso", + "description": "" + } + ], + "body": {}, + "description": "List all the project with filters applied. The filter string should be url encoded. Default limit and offset is applicable" + }, + "response": [] + }, + { + "name": "List projects with sort applied", + "request": { + "url": "http://localhost:3000/v4/projects?sort=type%20desc", + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.yG7pd9_UUFGo6XFH7-H6Wd5wWzRivkeZTCyet7IXFso", + "description": "" + } + ], + "body": {}, + "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": [] + }, + { + "name": "List projects and return specific fields", + "request": { + "url": "http://localhost:3000/v4/projects?fields=id,name,description", + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.yG7pd9_UUFGo6XFH7-H6Wd5wWzRivkeZTCyet7IXFso", + "description": "" + } + ], + "body": {}, + "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": [] + }, + { + "name": "DELETE project by id", + "request": { + "url": "http://localhost:3000/v4/projects/3", + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.yG7pd9_UUFGo6XFH7-H6Wd5wWzRivkeZTCyet7IXFso", + "description": "" + } + ], + "body": {}, + "description": "Delete a project by id" + }, + "response": [] + }, + { + "name": "Update project", + "request": { + "url": "http://localhost:3000/v4/projects/1", + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiQ29ubmVjdCBNYW5hZ2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzNCIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.amNzJ6PknW8V1ZDrVxJAd-SxcyfYhTf80IBc1sH-vF8", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"param\": {\n\t\t\"name\": \"project name updated\"\n\t}\n}" + }, + "description": "Update the project name. Name should be updated successfully." + }, + "response": [] + }, + { + "name": "Update project 403", + "request": { + "url": "http://localhost:3000/v4/projects/2", + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMSIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.IgPq3dcPH-WJXQAytjF_4fJbx3gtsee1U3vmqGIGoUA", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"param\": {\n\t\t\"name\": \"project name updated\"\n\t}\n}" + }, + "description": "Update the project name. If user don't have permission to the project than it should return 403." + }, + "response": [] + }, + { + "name": "Update project 404", + "request": { + "url": "http://localhost:3000/v4/projects/10", + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiQ29ubmVjdCBNYW5hZ2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzNCIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.amNzJ6PknW8V1ZDrVxJAd-SxcyfYhTf80IBc1sH-vF8", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"param\": {\n\t\t\"name\": \"project name updated\"\n\t}\n}" + }, + "description": "Update the project name. If project is not found than this result in 404 status code." + }, + "response": [] + }, + { + "name": "Update project status to cancelled", + "request": { + "url": "http://localhost:3000/v4/projects/1", + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiQ29ubmVjdCBNYW5hZ2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzNCIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.amNzJ6PknW8V1ZDrVxJAd-SxcyfYhTf80IBc1sH-vF8", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"param\": {\n\t\t\"status\": \"cancelled\"\n\t}\n}" + }, + "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", + "request": { + "url": "http://localhost:3000/v4/projects/1", + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiQ29ubmVjdCBNYW5hZ2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzNCIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.amNzJ6PknW8V1ZDrVxJAd-SxcyfYhTf80IBc1sH-vF8", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"param\": {\n\t\t\"status\": \"cancelled\",\n\t\t\"cancelReason\": \"price/cost\"\n\t}\n}" + }, + "description": "Update the project status. While cancelling the project `cancelReason` is mandatory." + }, + "response": [] + }, + { + "name": "Move project out of cancel state.", + "request": { + "url": "http://localhost:3000/v4/projects/1", + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiQ29ubmVjdCBNYW5hZ2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzNCIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.amNzJ6PknW8V1ZDrVxJAd-SxcyfYhTf80IBc1sH-vF8", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"param\": {\n\t\t\"status\": \"active\"\n\t}\n}" + }, + "description": "Move a project out of cancel state. Only admin and manager is allowed to do so." + }, + "response": [] + }, + { + "name": "Move project out of cancel state 403", + "request": { + "url": "http://localhost:3000/v4/projects/1", + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMSIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.IgPq3dcPH-WJXQAytjF_4fJbx3gtsee1U3vmqGIGoUA", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"param\": {\n\t\t\"status\": \"active\"\n\t}\n}" + }, + "description": "Move a project out of cancel state. Only admin and manager is allowed to do so." + }, + "response": [] + } + ] }, { - "id": "cc4c20b8-b153-57c9-0e8b-55a01b080e82", - "headers": "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts\nContent-Type: application/json\n", - "url": "http://localhost:3000/v4/projects/3/members/5", - "preRequestScript": null, - "pathVariables": {}, - "method": "PATCH", - "data": [], - "dataMode": "raw", - "version": 2, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "time": 1470657179934, - "name": "editing project member roles & primary option", - "description": "", - "collectionId": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "responses": [], - "rawModeData": " {\n \"param\": {\n \"role\": \"manager\",\n \"isPrimary\": true\n }\n } ", - "folder": "434cd818-a881-e83f-0e92-4f8545a43942" - }, - { - "id": "db09f872-d175-4066-52c4-77ee81aca14f", - "headers": "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts\nContent-Type: application/json\n", - "url": "http://localhost:3000/v4/projects/2", - "pathVariables": {}, - "preRequestScript": null, - "method": "PATCH", - "collectionId": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "data": [], - "dataMode": "raw", - "name": "Update project with bookmarks", + "name": "bookmarks", "description": "", - "descriptionFormat": "html", - "time": 1471670861583, - "version": 2, - "responses": [], - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "folder": "a56012d4-9aaf-1df7-4fe7-6cef64d91a22", - "rawModeData": "{\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}" + "item": [ + { + "name": " Create project without bookmarks", + "request": { + "url": "http://localhost:3000/v4/projects", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "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": "" + }, + "response": [] + }, + { + "name": " Create project with valid bookmarks", + "request": { + "url": "http://localhost:3000/v4/projects", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "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": "" + }, + "response": [] + }, + { + "name": " Create project with invalid bookmarks", + "request": { + "url": "http://localhost:3000/v4/projects", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "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": "" + }, + "response": [] + }, + { + "name": "get project", + "request": { + "url": "http://localhost:3000/v4/projects/2", + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name\"\n }\n}" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Update project with bookmarks", + "request": { + "url": "http://localhost:3000/v4/projects/2", + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "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": "" + }, + "response": [] + }, + { + "name": "Delete project all bookmarks null", + "request": { + "url": "http://localhost:3000/v4/projects/2", + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name2\",\n \"bookmarks\":null\n }\n}" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Update project with invalid bookmarks", + "request": { + "url": "http://localhost:3000/v4/projects/2", + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name2\",\n \"bookmarks\":3\n }\n}" + }, + "description": "" + }, + "response": [] + }, + { + "name": "get projects with admin token", + "request": { + "url": "http://localhost:3000/v4/projects", + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts", + "description": "" + } + ], + "body": {}, + "description": "" + }, + "response": [] + } + ] }, { - "id": "de9780e3-b6c1-9a78-eb3e-6bb9fb26aeea", - "headers": "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts\n", - "url": "http://localhost:3000/v4/projects", - "pathVariables": {}, - "preRequestScript": null, - "method": "GET", - "collectionId": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "data": null, - "dataMode": "params", - "name": "get projects with admin token", + "name": "issue1", "description": "", - "descriptionFormat": "html", - "time": 1471671128898, - "version": 2, - "responses": [], - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "folder": "a56012d4-9aaf-1df7-4fe7-6cef64d91a22" + "item": [ + { + "name": "get projects with copilot token", + "request": { + "url": "http://localhost:3000/v4/projects", + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBjb3BpbG90Il0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOjQwMDUxMzMyLCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwiaWF0IjoxNDcwNjIwMDQ0fQ.HUhRM1G6jwBt8Kv2slPYndhVUdYXXBAH174fPzqpFME", + "description": "" + } + ], + "body": {}, + "description": "" + }, + "response": [] + } + ] }, { - "id": "e146bef2-7ea0-aebb-aa27-54785b64765d", - "headers": "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts\nContent-Type: application/json\n", - "url": "http://localhost:3000/v4/projects", - "pathVariables": {}, - "preRequestScript": null, - "method": "POST", - "collectionId": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "data": [], - "dataMode": "raw", - "name": " Create project without bookmarks", + "name": "issue10", "description": "", - "descriptionFormat": "html", - "time": 1471670834661, - "version": 2, - "responses": [], - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "folder": "a56012d4-9aaf-1df7-4fe7-6cef64d91a22", - "rawModeData": "{\n \"param\": {\n \"type\": \"generic\",\n \"description\": \"test project\",\n \"details\": {},\n \"billingAccountId\": 123,\n \"name\": \"test project1\"\n }\n}" + "item": [ + { + "name": "wrong role", + "request": { + "url": "http://localhost:3000/v4/projects/3/members/5", + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": " {\n \"param\": {\n \"role\": \"wrong\"\n }\n } " + }, + "description": "" + }, + "response": [] + }, + { + "name": "editing project member roles & primary option", + "request": { + "url": "http://localhost:3000/v4/projects/1/members/1", + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": " {\n \"param\": {\n \"role\": \"manager\",\n \"isPrimary\": true\n }\n } " + }, + "description": "" + }, + "response": [] + } + ] }, { - "id": "e798a2e0-09c3-160a-5401-30748e2066b0", - "headers": "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts\nContent-Type: application/json\n", - "url": "http://localhost:3000/v4/projects", - "pathVariables": {}, - "preRequestScript": null, - "method": "POST", - "collectionId": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "data": [], - "dataMode": "raw", - "name": " Create project with invalid bookmarks", + "name": "issue5", "description": "", - "descriptionFormat": "html", - "time": 1471670849223, - "version": 2, - "responses": [], - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "folder": "a56012d4-9aaf-1df7-4fe7-6cef64d91a22", - "rawModeData": "{\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}" + "item": [ + { + "name": "launch a project by topcoder managers ", + "request": { + "url": "http://localhost:3000/v4/projects/1", + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \n \"param\":{\n \"name\": \"updatedProject name\",\n \"status\": \"active\"\n }\n}" + }, + "description": "" + }, + "response": [] + }, + { + "name": "launch a project by member", + "request": { + "url": "http://localhost:3000/v4/projects/1", + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6W10sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMSIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.p13tStpp0A1RJjYJ2axSKCTx7lyWIS3kYtCvs8u88WM", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \n \"param\":{\n \"name\": \"updatedProject name\",\n \"status\": \"active\"\n }\n}" + }, + "description": "" + }, + "response": [] + }, + { + "name": "launch a project by copilot", + "request": { + "url": "http://localhost:3000/v4/projects/1", + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJjb3BpbG90Il0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMiIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.tY_eE9fjtKQ_Hp9XPwmhwMaaTdOYKoR09tdGgvZ8RLw", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \n \"param\":{\n \"name\": \"updatedProject name\",\n \"status\": \"active\"\n }\n}" + }, + "description": "" + }, + "response": [] + } + ] }, { - "id": "f3a6be9f-9c57-5c48-3019-5f3381fcbbb2", - "headers": "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts\nContent-Type: application/json\n", - "url": "http://localhost:3000/v4/projects/2/members/4", - "preRequestScript": null, - "pathVariables": {}, - "method": "PATCH", - "data": [], - "dataMode": "raw", - "version": 2, - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "time": 1470657251192, - "name": "remove copilot from direct project when editing project member role", + "name": "issue8", "description": "", - "collectionId": "170de2c0-0caa-c77b-d761-336f4d4fbd28", - "responses": [], - "rawModeData": " {\n \"param\": {\n \"role\": \"customer\",\n \"isPrimary\": true\n }\n } ", - "folder": "47bf002c-126b-0994-8439-b1108b7d049a" + "item": [ + { + "name": "mock direct projects", + "request": { + "url": "https://localhost:8443/v3/direct/projects", + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": " {\n \"param\": {\n \"role\": \"copilot\",\n \"isPrimary\": true\n }\n } " + }, + "description": "" + }, + "response": [] + }, + { + "name": " Create direct project when a new project is successfully created", + "request": { + "url": "http://localhost:3000/v4/projects", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "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": "" + }, + "response": [] + }, + { + "name": "Response error from direct project service", + "request": { + "url": "http://localhost:3000/v4/projects/1/members", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"param\": {\n \"userId\": 2, \n \"role\": \"copilot\"\n }\n}" + }, + "description": "" + }, + "response": [] + }, + { + "name": " Add co-pilot when a co-pilot is added to a project", + "request": { + "url": "http://localhost:3000/v4/projects/2/members", + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"param\": {\n \"userId\": 2, \n \"role\": \"copilot\"\n }\n}" + }, + "description": "" + }, + "response": [] + }, + { + "name": "remove copilot from direct project when editing project member role", + "request": { + "url": "http://localhost:3000/v4/projects/2/members/4", + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": " {\n \"param\": {\n \"role\": \"customer\",\n \"isPrimary\": true\n }\n } " + }, + "description": "" + }, + "response": [] + }, + { + "name": " Sync billing account id with direct", + "request": { + "url": "http://localhost:3000/v4/projects/2", + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name\"\n }\n}" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Delete co-pilot when a co-pilot is removed from a project", + "request": { + "url": "http://localhost:3000/v4/projects/2/members/4", + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRlc3QxIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzMyIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.uiZHiDXF-_KysU5tq-G82oBTYBR0gV_w-svLX_2O6ts", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "description": "" + }, + "response": [] + } + ] } ] -} +} \ No newline at end of file diff --git a/src/events/projectMembers/index.js b/src/events/projectMembers/index.js index 5b0e0602..0ffb1750 100644 --- a/src/events/projectMembers/index.js +++ b/src/events/projectMembers/index.js @@ -44,7 +44,54 @@ const projectMemberAddedHandler = (logger, msg, channel) => { }) } else { logger.info('project not associated with a direct project, skipping') - ack(msg) + channel.ack(msg) + } + }) + .catch(err => { + // if the message has been redelivered dont attempt to reprocess it + logger.error('Error retrieving project', err, msg) + channel.nack(msg, false, !msg.fields.redelivered) + }) + } else if (newMember.role === PROJECT_MEMBER_ROLE.MANAGER) { + // if manager is added we have to add the manager to direct project + return models.Project.getDirectProjectId(newMember.projectId) + .then(directProjectId => { + if (directProjectId) { + // retrieve system user token + return util.getSystemUserToken(logger) + .then(token => { + const req = { + id: origRequestId, + log: logger, + headers: { + authorization: `Bearer ${token}` + } + } + return directProject.editProjectPermissions(req, directProjectId, { + permissions: [ + { + userId: newMember.userId, + permissionType: { + permissionTypeId: 3, + name: 'project_full' + }, + studio: false + } + ] + }) + .then(resp => { + logger.debug('added manager to direct') + // acknowledge + channel.ack(msg) + }) + }) + .catch(err => { + logger.error('Error caught while adding manager from direct', err) + channel.nack(msg, false, false) + }) + } else { + logger.info('project not associated with a direct project, skipping') + channel.ack(msg) } }) .catch(err => { @@ -63,7 +110,7 @@ const projectMemberRemovedHandler = (logger, msg, channel) => { const member = JSON.parse(msg.content.toString()) if (member.role === PROJECT_MEMBER_ROLE.COPILOT) { - // Add co-pilot when a co-pilot is added to a project + // Delete co-pilot when a co-pilot is deleted from a project return models.Project.getDirectProjectId(member.projectId) .then(directProjectId => { if (directProjectId) { @@ -100,6 +147,54 @@ const projectMemberRemovedHandler = (logger, msg, channel) => { logger.error('Error retrieving project', err, msg) channel.nack(msg, false, !msg.fields.redelivered) }) + } else if (member.role === PROJECT_MEMBER_ROLE.MANAGER) { + // when a manager is removed from direct project we have to remove manager from direct + return models.Project.getDirectProjectId(member.projectId) + .then(directProjectId => { + if (directProjectId) { + // retrieve system user token + return util.getSystemUserToken(logger) + .then(token => { + const req = { + id: origRequestId, + log: logger, + headers: { + authorization: `Bearer ${token}` + } + } + return directProject.editProjectPermissions(req, directProjectId, { + permissions: [ + { + userId: member.userId, + resourceId: directProjectId, + permissionType: { + permissionTypeId: '', + name: 'project_full' + }, + studio: false + } + ] + }) + .then(resp => { + logger.debug('removed manager from direct') + // acknowledge + channel.ack(msg) + }) + }) + .catch(err => { + logger.error('Error caught while adding manager from direct', err) + channel.nack(msg, false, false) + }) + } else { + logger.info('project not associated with a direct project, skipping') + channel.ack(msg) + } + }) + .catch(err => { + // if the message has been redelivered dont attempt to reprocess it + logger.error('Error retrieving project', err, msg) + channel.nack(msg, false, !msg.fields.redelivered) + }) } else { // nothing to do channel.ack(msg) diff --git a/src/mocks/direct.js b/src/mocks/direct.js index b16697aa..6f8dd5cc 100644 --- a/src/mocks/direct.js +++ b/src/mocks/direct.js @@ -96,6 +96,17 @@ router.route('/v3/direct/projects/:projectId(\\d+)/copilot') } }) +router.route('/v3/direct/projects/:projectId(\\d+)/permissions') + .post((req, res)=>{ + const projectId = req.params.projectId + app.logger.info({body: req.body, projectId: projectId }, 'add permissions to Project') + if(projects[projectId]) { + res.json() + } else { + res.json(util.wrapErrorResponse(req.id, 404, `Cannot find direct project ${projectId}`)) + } + }) + app.use(router) // ======================= diff --git a/src/models/project.js b/src/models/project.js index 93bd1fa4..ef728ee8 100644 --- a/src/models/project.js +++ b/src/models/project.js @@ -35,6 +35,7 @@ module.exports = function(sequelize, DataTypes) { }, details: { type: DataTypes.JSON }, challengeEligibility: DataTypes.JSON, + cancelReason: DataTypes.STRING, deletedAt: { type: DataTypes.DATE, allowNull: true }, createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, updatedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, diff --git a/src/models/projectHistory.js b/src/models/projectHistory.js new file mode 100644 index 00000000..b9edfa22 --- /dev/null +++ b/src/models/projectHistory.js @@ -0,0 +1,22 @@ +'use strict' + +module.exports = function(sequelize, DataTypes) { + var ProjectHistory = sequelize.define('ProjectHistory', { + id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, + projectId: { type: DataTypes.BIGINT, allowNull: false }, + status: { type: DataTypes.STRING, allowNull: false }, + cancelReason: { type: DataTypes.STRING, allowNull: true }, + createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + updatedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + updatedBy: { type: DataTypes.INTEGER, allowNull: false } + }, { + tableName: 'project_history', + paranoid: false, + timestamps: true, + updatedAt: 'updatedAt', + createdAt: 'createdAt', + indexes: [] + }) + + return ProjectHistory +} diff --git a/src/routes/attachments/create.js b/src/routes/attachments/create.js index 3d30d347..3a9abe19 100644 --- a/src/routes/attachments/create.js +++ b/src/routes/attachments/create.js @@ -25,7 +25,7 @@ const addAttachmentValidations = { filePath: Joi.string().required(), s3Bucket: Joi.string().required(), contentType: Joi.string().required() - }) + }).required() } } diff --git a/src/routes/projectMembers/create.js b/src/routes/projectMembers/create.js index 54880488..94f7c884 100644 --- a/src/routes/projectMembers/create.js +++ b/src/routes/projectMembers/create.js @@ -21,7 +21,7 @@ const addMemberValidations = { userId: Joi.number().required(), isPrimary: Joi.boolean(), role: Joi.any().valid(PROJECT_MEMBER_ROLE.CUSTOMER, PROJECT_MEMBER_ROLE.MANAGER, PROJECT_MEMBER_ROLE.COPILOT).required() - }) + }).required() } } diff --git a/src/routes/projectMembers/delete.js b/src/routes/projectMembers/delete.js index 309f5b99..b1387dd7 100644 --- a/src/routes/projectMembers/delete.js +++ b/src/routes/projectMembers/delete.js @@ -4,7 +4,7 @@ import _ from 'lodash' import models from '../../models' import { middleware as tcMiddleware } from 'tc-core-library-js' -import { EVENT } from '../../constants' +import { EVENT, PROJECT_MEMBER_ROLE } from '../../constants' /** * API to delete a project member. @@ -23,29 +23,65 @@ module.exports = [ return models.ProjectMember.findOne({ where: {id: memberRecordId, projectId: projectId} }) - .then((member) => { - if (!member) { - let err = new Error('Record not found') - err.status = 404 - return Promise.reject(err) - } - return member.destroy({logging: console.log}) - }) - .then(member => { - return member.save() - }) - .then(member => { - // fire event - member = member.get({plain:true}) - req.app.services.pubsub.publish( - EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, - member, - { correlationId: req.id } - ) - req.app.emit(EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, { req, member }) - res.status(204).json({}) - }) - .catch(err => next(err)) - }) + .then((member) => { + if (!member) { + let err = new Error('Record not found') + err.status = 404 + return Promise.reject(err) + } + return member.destroy({logging: console.log}) + }) + .then(member => { + return member.save() + }) + // if primary co-pilot is removed promote the next co-pilot to primary #43 + .then((member) => { + return new Promise((accept, reject) => { + if (member.role === PROJECT_MEMBER_ROLE.COPILOT && member.isPrimary) { + // find the next copilot + models.ProjectMember.findAll({ + limit: 1, + // return only non-deleted records + paranoid: true, + where: { + projectId: projectId, + role: PROJECT_MEMBER_ROLE.COPILOT + }, + order: [['createdAt', 'ASC']] + }).then((members) => { + if (members && members.length > 0) { + // mark the copilot as primary + const nextMember = members[0] + nextMember.set({ isPrimary: true }) + nextMember.save().then(() => { + accept(member) + }).catch((err) => { + reject(err) + }) + } else { + // no copilot found nothing to do + accept(member) + } + }).catch((err) => { + reject(err) + }) + } else { + // nothing to do + accept(member) + } + }) + }) + }).then((member) => { + // only return the response after transaction is committed + // fire event + member = member.get({plain:true}) + req.app.services.pubsub.publish( + EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, + member, + { correlationId: req.id } + ) + req.app.emit(EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, { req, member }) + res.status(204).json({}) + }).catch((err) => next(err)) } ] diff --git a/src/routes/projectMembers/delete.spec.js b/src/routes/projectMembers/delete.spec.js index 8fb74636..a0d17c16 100644 --- a/src/routes/projectMembers/delete.spec.js +++ b/src/routes/projectMembers/delete.spec.js @@ -118,7 +118,149 @@ describe('Project members delete', () => { }) }) - it('should return 204 if copilot user is trying to remove a manager', done => { + it('should return 204 if copilot is removed (promote the next copilot to primary)', done => { + models.ProjectMember.bulkCreate([{ + userId: 40051331, + projectId: project1.id, + role: 'copilot', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + createdAt: '2016-06-30 00:33:07+00', + updatedAt: '2016-06-30 00:33:07+00' + }, { + userId: 40051333, + projectId: project1.id, + role: 'copilot', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + createdAt: '2016-07-30 00:33:07+00', + updatedAt: '2016-07-30 00:33:07+00' + }, { + userId: 40051335, + projectId: project1.id, + role: 'copilot', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + createdAt: '2016-08-30 00:33:07+00', + updatedAt: '2016-08-30 00:33:07+00' + }]).then(() => { + request(server) + .delete('/v4/projects/' + project1.id + '/members/' + member1.id) + .set({ + 'Authorization': 'Bearer ' + testUtil.jwts.copilot + }) + .expect(204) + .end(function(err) { + if (err) { + return done(err) + } + var removedMember = { + projectId: project1.id, + userId: 40051332, + role: 'copilot', + isPrimary: true + } + server.services.pubsub.publish.calledWith('project.member.removed', sinon.match(removedMember)).should.be.true + // validate the primary copilot + models.ProjectMember.findAll({ paranoid: true, where: { projectId: project1.id, role: 'copilot', isPrimary: true }}) + .then((members) => { + should.exist(members) + members.length.should.equal(1) + const plain = members[0].get({ plain: true }) + plain.role.should.equal('copilot') + plain.isPrimary.should.equal(true) + plain.userId.should.equal(40051331) + done() + }) + }) + }) + }) + + it('should return 204 if manager is removed from the project', done => { + var mockHttpClient = _.merge(testUtil.mockHttpClient, { + post: () => Promise.resolve({ + status: 200, + data: { + id: 'requesterId', + version: 'v3', + result: { + success: true, + status: 200, + content: { } + } + } + }) + }) + var postSpy = sinon.spy(mockHttpClient, 'post') + sandbox.stub(util, 'getHttpClient', () => mockHttpClient) + request(server) + .delete('/v4/projects/' + project1.id + '/members/' + member2.id) + .set({ + 'Authorization': 'Bearer ' + testUtil.jwts.manager + }) + .expect(204) + .end(function(err) { + if (err) { + return done(err) + } + var removedMember = { + projectId: project1.id, + userId: 40051334, + role: 'manager', + isPrimary: true + } + server.services.pubsub.publish.calledWith('project.member.removed', sinon.match(removedMember)).should.be.true + postSpy.should.have.been.calledOnce + done() + }) + }) + + it('should return 204 if manager is removed from the project (without direct project id)', done => { + var mockHttpClient = _.merge(testUtil.mockHttpClient, { + post: () => Promise.resolve({ + status: 200, + data: { + id: 'requesterId', + version: 'v3', + result: { + success: true, + status: 200, + content: { } + } + } + }) + }) + var postSpy = sinon.spy(mockHttpClient, 'post') + sandbox.stub(util, 'getHttpClient', () => mockHttpClient) + models.Project.update({ directProjectId: null}, {where: {id: project1.id}}) + .then(() => { + request(server) + .delete('/v4/projects/' + project1.id + '/members/' + member2.id) + .set({ + 'Authorization': 'Bearer ' + testUtil.jwts.manager + }) + .expect(204) + .end(function(err) { + if (err) { + return done(err) + } + var removedMember = { + projectId: project1.id, + userId: 40051334, + role: 'manager', + isPrimary: true + } + server.services.pubsub.publish.calledWith('project.member.removed', sinon.match(removedMember)).should.be.true + postSpy.should.not.have.been.calledOnce + done() + }) + }) + }) + + it('should return 403 if copilot user is trying to remove a manager', done => { request(server) .delete('/v4/projects/' + project1.id + '/members/' + member2.id) .set({ diff --git a/src/routes/projectMembers/update.spec.js b/src/routes/projectMembers/update.spec.js index bb0d6e14..8e2a149a 100644 --- a/src/routes/projectMembers/update.spec.js +++ b/src/routes/projectMembers/update.spec.js @@ -11,7 +11,7 @@ import testUtil from '../../tests/util' const should = chai.should() describe('Project members update', () => { - let project1, member1, member2 + let project1, member1, member2, member3 beforeEach(done => { testUtil.clearDb() .then(() => { @@ -38,7 +38,7 @@ describe('Project members update', () => { createdAt: "2016-06-30 00:33:07+00", updatedAt: "2016-06-30 00:33:07+00" }).then((pm) => { - member1 = pm + member1 = pm.get({ plain: true }) models.ProjectMember.create({ userId: 40051332, projectId: project1.id, @@ -48,10 +48,21 @@ describe('Project members update', () => { updatedBy: 1, createdAt: "2016-06-30 00:33:07+00", updatedAt: "2016-06-30 00:33:07+00" - }).then((pm2) => pm2.reload()) - .then((pm2) => { + }).then((pm2) => { member2 = pm2.get({plain: true}) - done() + models.ProjectMember.create({ + userId: 40051330, + projectId: project1.id, + role: 'copilot', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + createdAt: "2016-06-30 00:33:07+00", + updatedAt: "2016-06-30 00:33:07+00" + }).then((pm3) => { + member3 = pm3.get({ plain: true }) + done() + }) }) }) }) @@ -168,7 +179,6 @@ describe('Project members update', () => { should.exist(resJson) resJson.role.should.equal('customer') resJson.isPrimary.should.be.true - new Date(resJson.updatedAt).valueOf().should.be.equal(new Date(member2.updatedAt).valueOf()) resJson.updatedBy.should.equal(40051332) server.services.pubsub.publish.calledWith('project.member.updated').should.be.true done() @@ -271,6 +281,99 @@ describe('Project members update', () => { }) }) + it('should return 200 if valid user(become manager) and data', done => { + var mockHttpClient = _.merge(testUtil.mockHttpClient, { + post: () => Promise.resolve({ + status: 200, + data: { + id: 'requesterId', + version: 'v3', + result: { + success: true, + status: 200, + content: { } + } + } + }) + }) + var postSpy = sinon.spy(mockHttpClient, 'post') + sandbox.stub(util, 'getHttpClient', () => mockHttpClient) + request(server) + .patch('/v4/projects/' + project1.id + '/members/' + member3.id) + .set({ + 'Authorization': 'Bearer ' + testUtil.jwts.manager + }) + .send({ + param: { + "role": "manager", + "isPrimary": false + } + }) + .expect('Content-Type', /json/) + .expect(200) + .end(function (err, res) { + if (err) { + return done(err) + } + const resJson = res.body.result.content + should.exist(resJson) + resJson.role.should.equal('manager') + resJson.isPrimary.should.be.false + resJson.updatedAt.should.not.equal("2016-06-30 00:33:07+00") + resJson.updatedBy.should.equal(40051334) + postSpy.should.have.been.calledOnce + done() + }) + }) + + it('should return 200 if valid user(become manager) and data (without directProjectId)', done => { + models.Project.update({ directProjectId: null}, {where: {id: project1.id}}) + .then(() => { + var mockHttpClient = _.merge(testUtil.mockHttpClient, { + post: () => Promise.resolve({ + status: 200, + data: { + id: 'requesterId', + version: 'v3', + result: { + success: true, + status: 200, + content: { } + } + } + }) + }) + var postSpy = sinon.spy(mockHttpClient, 'post') + sandbox.stub(util, 'getHttpClient', () => mockHttpClient) + request(server) + .patch('/v4/projects/' + project1.id + '/members/' + member3.id) + .set({ + 'Authorization': 'Bearer ' + testUtil.jwts.manager + }) + .send({ + param: { + "role": "manager", + "isPrimary": false + } + }) + .expect('Content-Type', /json/) + .expect(200) + .end(function (err, res) { + if (err) { + return done(err) + } + const resJson = res.body.result.content + should.exist(resJson) + resJson.role.should.equal('manager') + resJson.isPrimary.should.be.false + resJson.updatedAt.should.not.equal("2016-06-30 00:33:07+00") + resJson.updatedBy.should.equal(40051334) + postSpy.should.not.have.been.calledOnce + done() + }) + }) + }) + it('should return 200 if valid user(become copilot) and data', done => { var mockHttpClient = _.merge(testUtil.mockHttpClient, { post: () => Promise.resolve({ diff --git a/src/routes/projects/create.js b/src/routes/projects/create.js index 6e407516..6aabcfcf 100644 --- a/src/routes/projects/create.js +++ b/src/routes/projects/create.js @@ -42,14 +42,14 @@ const createProjectValdiations = { data: Joi.string().max(300) // TODO - restrict length }).allow(null), // TODO - add more types - type: Joi.any().valid(_.values(PROJECT_TYPE)), + type: Joi.any().valid(_.values(PROJECT_TYPE)).required(), details: Joi.any(), challengeEligibility: Joi.array().items(Joi.object().keys({ role: Joi.string().valid('submitter', 'reviewer', 'copilot'), users: Joi.array().items(Joi.number().positive()), groups: Joi.array().items(Joi.number().positive()) })).allow(null) - }) + }).required() } } diff --git a/src/routes/projects/list.js b/src/routes/projects/list.js index 2ce1aa88..84bb5d59 100755 --- a/src/routes/projects/list.js +++ b/src/routes/projects/list.js @@ -111,7 +111,7 @@ module.exports = [ ] if (!util.isValidFilter(filters, ['id', 'status', 'type', 'memberOnly', 'keyword']) || (sort && _.indexOf(sortableProps, sort) < 0)) { - util.handleError('Invalid filters or sort', null, req, next) + return util.handleError('Invalid filters or sort', null, req, next) } // check if user only wants to retrieve projects where he/she is a member const memberOnly = _.get(filters, 'memberOnly', false) diff --git a/src/routes/projects/update.js b/src/routes/projects/update.js index 4f40681c..35d1e494 100644 --- a/src/routes/projects/update.js +++ b/src/routes/projects/update.js @@ -3,7 +3,7 @@ import validate from 'express-validation' import _ from 'lodash' import Joi from 'joi' import models from '../../models' -import { PROJECT_TYPE, PROJECT_STATUS, PROJECT_MEMBER_ROLE, EVENT } from '../../constants' +import { PROJECT_TYPE, PROJECT_STATUS, PROJECT_MEMBER_ROLE, EVENT, USER_ROLE } from '../../constants' import util from '../../util' import directProject from '../../services/directProject' import { middleware as tcMiddleware } from 'tc-core-library-js' @@ -54,7 +54,13 @@ const updateProjectValdiations = { role: Joi.string().valid('submitter', 'reviewer', 'copilot'), users: Joi.array().items(Joi.number().positive()), groups: Joi.array().items(Joi.number().positive()) - })).allow(null) + })).allow(null), + // cancel reason is mandatory when project status is cancelled + cancelReason: Joi.when('status', { + is: PROJECT_STATUS.CANCELLED, + then: Joi.string().required(), + otherwise: Joi.string().optional() + }) }) } } @@ -64,7 +70,6 @@ var validateUpdates = (existingProject, updatedProject) => { var errors = [] switch (existingProject.status) { case PROJECT_STATUS.COMPLETED: - case PROJECT_STATUS.CANCELLED: errors.push(`cannot update a project that is in ${existingProject.status}' state`) break // disabling this check for now. @@ -82,7 +87,7 @@ var validateUpdates = (existingProject, updatedProject) => { module.exports = [ // handles request validations - // validate(updateProjectValdiations), + validate(updateProjectValdiations), permissions('project.edit'), /** * POST projects/ @@ -120,13 +125,15 @@ module.exports = [ }) return Promise.reject(err) } - // Only project manager (user with manager role assigned) should be allowed to transition project status to 'active'. + // Only project manager (user with manager role assigned) or topcoder admin should be allowed + // to transition project status to 'active'. const members = req.context.currentProjectMembers const validRoles = [PROJECT_MEMBER_ROLE.MANAGER, PROJECT_MEMBER_ROLE.MANAGER].map(x => x.toLowerCase()) const matchRole = (role) => _.indexOf(validRoles, role.toLowerCase()) >= 0 - if(updatedProps.status === PROJECT_STATUS.ACTIVE && + if (updatedProps.status === PROJECT_STATUS.ACTIVE && + !util.hasRole(req, USER_ROLE.TOPCODER_ADMIN) && _.isUndefined(_.find(members, (m) => m.userId === req.authUser.userId && matchRole(m.role)))) { - let err = new Error('Only assigned topcoder-managers should be allowed to launch a project') + let err = new Error('Only assigned topcoder-managers or topcoder admins should be allowed to launch a project') err.status = 403 return Promise.reject(err) } @@ -158,6 +165,22 @@ module.exports = [ .then(() => { return project.reload(project.id) }) + // update project history + .then(() => { + return new Promise((accept, reject) => { + // we only want to have project history when project status is updated + if (updatedProps.status && (updatedProps.status !== previousValue.status)) { + models.ProjectHistory.create({ + projectId: project.id, + status: updatedProps.status, + cancelReason: updatedProps.cancelReason, + updatedBy: req.authUser.userId + }).then(() => accept()).catch((err) => reject(err)) + } else { + accept() + } + }) + }) .then(() => { project = project.get({plain: true}) project = _.omit(project, ['deletedAt']) @@ -179,11 +202,10 @@ module.exports = [ // get attachments return util.getProjectAttachments(req, project.id) }) - .then((attachments) => { - project.attachments = attachments - res.json(util.wrapResponse(req.id, project)) - }) - .catch((err) => next(err)) - }) + }).then((attachments) => { + // make sure we only send response after transaction is committed + project.attachments = attachments + res.json(util.wrapResponse(req.id, project)) + }).catch((err) => next(err)) } ] diff --git a/src/routes/projects/update.spec.js b/src/routes/projects/update.spec.js index f49a9f12..70751fed 100644 --- a/src/routes/projects/update.spec.js +++ b/src/routes/projects/update.spec.js @@ -8,6 +8,7 @@ import models from '../../models' import server from '../../app' import testUtil from '../../tests/util' import util from '../../util' +import { PROJECT_STATUS } from '../../constants' var should = chai.should() @@ -97,7 +98,7 @@ describe('Project', () => { request(server) .patch("/v4/projects/" + project1.id) .send(body) - .expect(403,done) + .expect(403, done) }) it('should return 400 if update completed project', done => { @@ -137,7 +138,8 @@ describe('Project', () => { var result = res.body.result result.success.should.be.false result.status.should.equal(403) - result.content.message.should.equal('Only assigned topcoder-managers should be allowed to launch a project') + result.content.message.should.equal('Only assigned topcoder-managers or topcoder admins' + + ' should be allowed to launch a project') done() }) }) @@ -187,6 +189,250 @@ describe('Project', () => { }) }) + it('should return 200 and project history should be updated (status is not set)', done => { + const sbody = _.cloneDeep(body) + // set project status to be updated + sbody.param.status = PROJECT_STATUS.IN_REVIEW + request(server) + .patch("/v4/projects/" + project1.id) + .set({"Authorization": "Bearer " + testUtil.jwts.copilot}) + .send(sbody) + .expect('Content-Type', /json/) + .expect(200) + .end(function (err,res) { + if (err) { + return done(err) + } + var resJson = res.body.result.content + should.exist(resJson) + resJson.name.should.equal('updatedProject name') + resJson.updatedAt.should.not.equal("2016-06-30 00:33:07+00") + resJson.updatedBy.should.equal(40051332) + server.services.pubsub.publish.calledWith('project.updated').should.be.true + // validate that project history is updated + models.ProjectHistory.findAll({ + limit: 1, + where: { projectId: project1.id }, + order: [['createdAt', 'DESC']] + }).then((histories) => { + should.exist(histories) + histories.length.should.equal(1) + const history = histories[0].get({ plain: true }) + history.status.should.equal(PROJECT_STATUS.IN_REVIEW) + history.projectId.should.equal(project1.id) + done() + }) + }) + }) + + it('should return 200 and project history should not be updated (status is not updated)', done => { + const sbody = _.cloneDeep(body) + sbody.param.status = PROJECT_STATUS.DRAFT + request(server) + .patch("/v4/projects/" + project1.id) + .set({"Authorization": "Bearer " + testUtil.jwts.copilot}) + .send(sbody) + .expect('Content-Type', /json/) + .expect(200) + .end(function (err,res) { + if (err) { + return done(err) + } + var resJson = res.body.result.content + should.exist(resJson) + resJson.name.should.equal('updatedProject name') + resJson.updatedAt.should.not.equal("2016-06-30 00:33:07+00") + resJson.updatedBy.should.equal(40051332) + server.services.pubsub.publish.calledWith('project.updated').should.be.true + // validate that project history is not updated + models.ProjectHistory.findAll({ + where: { projectId: project1.id } + }).then((histories) => { + should.exist(histories) + histories.length.should.equal(0) + done() + }) + }) + }) + + it('should return 422 as cancel reason is mandatory if project status is cancelled', done => { + const sbody = _.cloneDeep(body) + sbody.param.status = PROJECT_STATUS.CANCELLED + request(server) + .patch("/v4/projects/" + project1.id) + .set({"Authorization": "Bearer " + testUtil.jwts.copilot}) + .send(sbody) + .expect('Content-Type', /json/) + .expect(422) + .end(function (err,res) { + if (err) { + return done(err) + } + const result = res.body.result + result.success.should.be.false + result.status.should.equal(422) + done() + }) + }) + + it('should return 200 and project history should be updated for cancelled project', done => { + const sbody = _.cloneDeep(body) + sbody.param.status = PROJECT_STATUS.CANCELLED + sbody.param.cancelReason = 'price/cost' + request(server) + .patch("/v4/projects/" + project1.id) + .set({"Authorization": "Bearer " + testUtil.jwts.copilot}) + .send(sbody) + .expect('Content-Type', /json/) + .expect(200) + .end(function (err,res) { + if (err) { + return done(err) + } + var resJson = res.body.result.content + should.exist(resJson) + resJson.name.should.equal('updatedProject name') + resJson.updatedAt.should.not.equal("2016-06-30 00:33:07+00") + resJson.updatedBy.should.equal(40051332) + server.services.pubsub.publish.calledWith('project.updated').should.be.true + // validate that project history is updated + models.ProjectHistory.findAll({ + where: { projectId: project1.id } + }).then((histories) => { + should.exist(histories) + histories.length.should.equal(1) + const history = histories[0].get({ plain: true }) + history.status.should.equal(PROJECT_STATUS.CANCELLED) + history.projectId.should.equal(project1.id) + history.cancelReason.should.equal('price/cost') + done() + }) + }) + }) + + it('should return 200, manager is allowed to transition project out of cancel status', done => { + models.Project.update({ status: PROJECT_STATUS.CANCELLED}, {where: {id: project1.id}}) + .then(() => { + const sbody = _.cloneDeep(body) + sbody.param.status = PROJECT_STATUS.ACTIVE + request(server) + .patch("/v4/projects/" + project1.id) + .set({"Authorization": "Bearer " + testUtil.jwts.manager}) + .send(sbody) + .expect('Content-Type', /json/) + .expect(200) + .end(function (err,res) { + if (err) { + return done(err) + } + var resJson = res.body.result.content + should.exist(resJson) + resJson.name.should.equal('updatedProject name') + resJson.updatedAt.should.not.equal("2016-06-30 00:33:07+00") + resJson.updatedBy.should.equal(40051334) + server.services.pubsub.publish.calledWith('project.updated').should.be.true + // validate that project history is updated + models.ProjectHistory.findAll({ + where: { projectId: project1.id } + }).then((histories) => { + should.exist(histories) + histories.length.should.equal(1) + const history = histories[0].get({ plain: true }) + history.status.should.equal(PROJECT_STATUS.ACTIVE) + history.projectId.should.equal(project1.id) + done() + }) + }) + }) + }) + + it('should return 200, admin is allowed to transition project out of cancel status', done => { + models.Project.update({ status: PROJECT_STATUS.CANCELLED}, {where: {id: project1.id}}) + .then(() => { + const sbody = _.cloneDeep(body) + sbody.param.status = PROJECT_STATUS.ACTIVE + request(server) + .patch("/v4/projects/" + project1.id) + .set({"Authorization": "Bearer " + testUtil.jwts.admin}) + .send(sbody) + .expect('Content-Type', /json/) + .expect(200) + .end(function (err,res) { + if (err) { + return done(err) + } + var resJson = res.body.result.content + should.exist(resJson) + resJson.name.should.equal('updatedProject name') + resJson.updatedAt.should.not.equal("2016-06-30 00:33:07+00") + resJson.updatedBy.should.equal(40051333) + server.services.pubsub.publish.calledWith('project.updated').should.be.true + // validate that project history is updated + models.ProjectHistory.findAll({ + where: { projectId: project1.id } + }).then((histories) => { + should.exist(histories) + histories.length.should.equal(1) + const history = histories[0].get({ plain: true }) + history.status.should.equal(PROJECT_STATUS.ACTIVE) + history.projectId.should.equal(project1.id) + done() + }) + }) + }) + }) + + it('should return 403, copilot is not allowed to transition project out of cancel status', done => { + models.Project.update({ status: PROJECT_STATUS.CANCELLED}, {where: {id: project1.id}}) + .then(() => { + const sbody = _.cloneDeep(body) + sbody.param.status = PROJECT_STATUS.ACTIVE + request(server) + .patch("/v4/projects/" + project1.id) + .set({"Authorization": "Bearer " + testUtil.jwts.copilot}) + .send(sbody) + .expect('Content-Type', /json/) + .expect(403) + .end(function (err,res) { + if (err) { + return done(err) + } + const result = res.body.result + result.success.should.be.false + result.status.should.equal(403) + done() + }) + }) + }) + + it('should return 200 and project history should not be updated', done => { + request(server) + .patch("/v4/projects/" + project1.id) + .set({"Authorization": "Bearer " + testUtil.jwts.copilot}) + .send(body) + .expect('Content-Type', /json/) + .expect(200) + .end(function (err,res) { + if (err) { + return done(err) + } + var resJson = res.body.result.content + should.exist(resJson) + resJson.name.should.equal('updatedProject name') + resJson.updatedAt.should.not.equal("2016-06-30 00:33:07+00") + resJson.updatedBy.should.equal(40051332) + server.services.pubsub.publish.calledWith('project.updated').should.be.true + // validate that project history is not updated + models.ProjectHistory.findAll({ + where: { projectId: project1.id } + }).then((histories) => { + should.exist(histories) + histories.length.should.equal(0) + done() + }) + }) + }) + it('should return 500 if error to sync billing account id', done => { var mockHttpClient = _.merge(testUtil.mockHttpClient, { post: () => Promise.reject(new Error('error message')) diff --git a/src/services/directProject.js b/src/services/directProject.js index 1fb60dd6..9d54dfb8 100644 --- a/src/services/directProject.js +++ b/src/services/directProject.js @@ -64,4 +64,14 @@ export default { */ addBillingAccount: (req, directProjectId, body) => _getHttpClient(req) .post(`/projects/${directProjectId}/billingaccount`, body), + + /** + * Add/remove direct project permissions + * This can be used to add/remove direct project manager + * @param req the request + * @param directProjectId the id of direct project + * @param body the body contains permissions information + */ + editProjectPermissions: (req, directProjectId, body) => _getHttpClient(req) + .post(`/projects/${directProjectId}/permissions`, body), } diff --git a/swagger.yaml b/swagger.yaml index ff0b6b11..e3968d3f 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -26,12 +26,18 @@ paths: tags: - project operationId: findProjects + security: + - Bearer: [] description: Retreive projects that match the filter responses: '422': description: Invalid input schema: $ref: "#/definitions/ErrorModel" + '403': + description: No permission or wrong token + schema: + $ref: "#/definitions/ErrorModel" '200': description: A list of projects schema: @@ -45,14 +51,15 @@ paths: in: query description: | Url encoded list of Supported filters - - type + - id - status + - type - memberOnly - keyword - name: sort required: false description: | - sort projects by status, name + sort projects by status, name, type, createdAt, updatedAt. Default is createdAt asc in: query type: string post: @@ -83,24 +90,38 @@ paths: /projects/{projectId}: get: description: Retrieve project by id + security: + - Bearer: [] responses: '404': description: Not found schema: $ref: "#/definitions/ErrorModel" + '403': + description: No permission or wrong token + schema: + $ref: "#/definitions/ErrorModel" '200': description: a project schema: $ref: "#/definitions/ProjectResponse" parameters: - $ref: "#/parameters/projectIdParam" + - name: fields + required: false + type: string + in: query + description: | + Comma separated list of project fields to return. + Can also specify project_members, attachments to get project members and project attachments. + Sub fields of project members and project attachments are also allowed. operationId: getProject patch: operationId: updateProject security: - Bearer: [] - description: Update a project that user has access to. + description: Update a project that user has access to. Managers and admin are able to pull out a project from cancelled state. responses: '403': description: No permission or wrong token @@ -127,9 +148,28 @@ paths: - name: body in: body required: true + description: Only specify those properties that needs to be updated. `cancelReason` is mandatory if status is cancelled schema: $ref: "#/definitions/ProjectBodyParam" + delete: + description: remove an existing project + security: + - Bearer: [] + parameters: + - $ref: "#/parameters/projectIdParam" + responses: + '403': + description: No permission or wrong token + schema: + $ref: "#/definitions/ErrorModel" + '404': + description: If project is not found + schema: + $ref: "#/definitions/ErrorModel" + '204': + description: Project successfully removed + /projects/{projectId}/attachments: post: description: add a new project attachment @@ -148,7 +188,7 @@ paths: schema: $ref: "#/definitions/ErrorModel" '201': - description: Returns the newly created project + description: Returns the newly created project attachment schema: $ref: "#/definitions/NewProjectAttachmentResponse" '422': @@ -166,10 +206,12 @@ paths: - in: path name: id required: true + description: The id of attachment to update type: integer - in: body name: body required: true + description: Specify only those properties that needs to be updated schema: $ref: '#/definitions/NewProjectAttachmentBodyParam' responses: @@ -181,6 +223,10 @@ paths: description: Returns the newly created project schema: $ref: "#/definitions/NewProjectAttachmentResponse" + '404': + description: If project attachment is not found + schema: + $ref: "#/definitions/ErrorModel" '422': description: Invalid input schema: @@ -194,12 +240,17 @@ paths: - in: path name: id required: true + description: The id of attachment to delete type: integer responses: '403': description: No permission or wrong token schema: $ref: "#/definitions/ErrorModel" + '404': + description: If attachment is not found + schema: + $ref: "#/definitions/ErrorModel" '204': description: Attachment successfully removed @@ -231,7 +282,7 @@ paths: /projects/{projectId}/members/{id}: delete: - description: add a new project member + description: Delete a project member security: - Bearer: [] parameters: @@ -303,7 +354,7 @@ parameters: format: int32 limitParam: name: limit - description: "max records to return. Defaults to 10" + description: "max records to return. Defaults to 20" in: query required: false type: integer @@ -369,6 +420,19 @@ definitions: description: type: string description: Project description + billingAccountId: + type: number + format: long + description: the customer billing account id + estimatedPrice: + type: number + format: float + description: The estimated price of the project + terms: + type: array + items: + type: number + format: integer external: type: object description: READ-ONLY, OPTIONAL. Refernce to external task/issue. @@ -382,7 +446,7 @@ definitions: enum: [ "github", "jira", "asana", "other"] data: type: string - description: "250 Char length text blob for customer provided data" + description: "300 Char length text blob for customer provided data" type: type: string description: project type @@ -404,6 +468,8 @@ definitions: properties: campaign: type: string + medium: + type: string source: type: string @@ -420,7 +486,7 @@ definitions: properties: role: type: string - enum: ["submitter", "reviewer", "copilot", "observer"] + enum: ["submitter", "reviewer", "copilot"] users: type: array items: @@ -444,6 +510,33 @@ definitions: description: unique identifier in direct type: integer format: int64 + billingAccountId: + type: integer + format: int64 + description: The customer billing account id + utm: + description: READ-ONLY. Used for tracking + type: object + properties: + campaign: + type: string + medium: + type: string + source: + type: string + estimatedPrice: + type: number + format: float + description: The estimated price of the project + actualPrice: + type: number + format: float + description: The actual price of the project + terms: + type: array + items: + type: number + format: integer name: type: string description: project name @@ -464,20 +557,18 @@ definitions: enum: [ "github", "jira", "asana", "other"] data: type: string - description: "250 Char length text blob for customer provided data" + description: "300 Char length text blob for customer provided data" type: type: string description: project type - enum: ["design", "code", "...from product offerings..."] - tags: - description: Array of tags associated with this project. - type: array - items: - type: integer + enum: ["app_dev", "generic", "visual_prototype", "visual_design"] status: type: string description: current state of the task - enum: ["draft", "active", "paused", "cancelled", "completed"] + enum: ["draft", "in_review", "reviewed", "active", "paused", "cancelled", "completed"] + cancelReason: + type: string + description: If a project is cancelled, define the reason of cancellation challengeEligibility: description: List of eligibility criteria (one entry per role) type: array @@ -557,6 +648,9 @@ definitions: type: number format: int64 description: user identifier + isPrimary: + type: boolean + description: Flag to indicate this member is primary for specified role role: type: string description: member role on specified project @@ -593,11 +687,16 @@ definitions: type: object required: - filePath + - s3Bucket + - title - contentType properties: filePath: type: string description: path where file is stored + s3Bucket: + type: string + description: The s3 bucket of attachment contentType: type: string description: Uploaded file content type @@ -607,6 +706,13 @@ definitions: description: type: string description: Optional description for the attached file. + category: + type: string + description: Category of attachment + size: + type: number + format: float + description: The size of attachment NewProjectAttachmentBodyParam: type: object @@ -615,7 +721,7 @@ definitions: $ref: "#/definitions/NewProjectAttachment" NewProjectAttachmentResponse: - title: Project member object response + title: Project attachment object response type: object properties: id: @@ -641,9 +747,13 @@ definitions: id: type: number description: unique id for the attachment - filePath: + size: + type: number + format: float + description: The size of attachment + category: type: string - description: path where file is stored + description: The category of attachment contentType: type: string description: Uploaded file content type @@ -686,6 +796,9 @@ definitions: type: number format: int64 description: user identifier + isPrimary: + type: boolean + description: Flag to indicate this member is primary for specified role projectId: type: number format: int64 @@ -824,4 +937,4 @@ definitions: content: type: array items: - $ref: "#/definitions/Project" + $ref: "#/definitions/Project" \ No newline at end of file diff --git a/sync.js b/sync.js new file mode 100644 index 00000000..440e9023 --- /dev/null +++ b/sync.js @@ -0,0 +1,20 @@ +'use strict' + +/** + * Sync the database models to db tables. + */ + +/** + * Make sure we are in development mode + * @type {String} + */ +process.env.NODE_ENV = 'development' + +require('./dist/models').default.sequelize.sync({ force: true }) + .then(() => { + console.log('Database synced successfully') + process.exit() + }).catch((err) => { + console.error('Error syncing database', err) + process.exit(1) + })