diff --git a/.circleci/config.yml b/.circleci/config.yml
index 84f1cf7c..02e01d86 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -2,19 +2,19 @@ version: 2
python_env: &python_env
docker:
- image: circleci/python:2.7-stretch-browsers
-
+
install_awscli: &install_awscli
name: "Install awscli"
command: |
sudo pip install awscli --upgrade
install_deploysuite: &install_deploysuite
- name: Installation of install_deploysuite.
- command: |
- git clone --branch master https://github.com/topcoder-platform/tc-deploy-scripts ../buildscript
- cp ./../buildscript/master_deploy.sh .
- cp ./../buildscript/buildenv.sh .
- cp ./../buildscript/awsconfiguration.sh .
-
+ name: Installation of install_deploysuite.
+ command: |
+ git clone --branch v1.4.1 https://github.com/topcoder-platform/tc-deploy-scripts ../buildscript
+ cp ./../buildscript/master_deploy.sh .
+ cp ./../buildscript/buildenv.sh .
+ cp ./../buildscript/awsconfiguration.sh .
+
# Instructions of deployment
deploy_steps: &deploy_steps
- checkout
@@ -23,22 +23,16 @@ deploy_steps: &deploy_steps
- run: *install_awscli
- run: *install_deploysuite
- setup_remote_docker
- - run: docker build -t tc-project-service:latest .
+ - run: docker build -t ${APPNAME}:latest .
- deploy:
- name: "Running Masterscript - deploy tc-project-service "
+ name: "Running Masterscript - deploy tc-project-service "
command: |
- ./awsconfiguration.sh $DEPLOY_ENV
+ ./awsconfiguration.sh $DEPLOY_ENV
source awsenvconf
- ./buildenv.sh -e $DEPLOY_ENV -b ${VAR_ENV}-tc-project-service-deployvar
+ ./buildenv.sh -e $DEPLOY_ENV -b ${LOGICAL_ENV}-${APPNAME}-deployvar
source buildenvvar
- ./master_deploy.sh -d ECS -e $DEPLOY_ENV -t latest -s ${VAR_ENV}-global-appvar,${VAR_ENV}-tc-project-service-appvar -i tc-project-service -p FARGATE
+ ./master_deploy.sh -d ECS -e $DEPLOY_ENV -t latest -s ${LOGICAL_ENV}-global-appvar,${LOGICAL_ENV}-${APPNAME}-appvar -i ${APPNAME}
- echo "======= Running Masterscript - deploy tc-project-service-consumers ==========="
- if [ -e ${VAR_ENV}-tc-project-service-appvar.json ]; then sudo rm -vf ${VAR_ENV}-tc-project-service-appvar.json; fi
- ./buildenv.sh -e $DEPLOY_ENV -b ${VAR_ENV}-tc-project-service-consumers-deployvar
- source buildenvvar
- ./master_deploy.sh -d ECS -e $DEPLOY_ENV -t latest -s ${VAR_ENV}-global-appvar,${VAR_ENV}-tc-project-service-appvar -i tc-project-service -p FARGATE
-
jobs:
test:
docker:
@@ -50,13 +44,20 @@ jobs:
- image: elasticsearch:2.3
- image: rabbitmq:3-management
environment:
- DB_MASTER_URL: postgres://circle_test:@127.0.0.1:5432/circle_test
- AUTH_SECRET: secret
- AUTH_DOMAIN: topcoder-dev.com
- LOG_LEVEL: debug
- APP_VERSION: v4
+ DEPLOY_ENV: "DEV"
+ LOGICAL_ENV: "dev"
+ APPNAME: "projects-api"
steps:
- checkout
+ - run:
+ name: "Install dependeency"
+ command: |
+ sudo apt update
+ sudo apt install curl
+ sudo apt install python-pip
+ - run: *install_awscli
+ - run: *install_deploysuite
+ - setup_remote_docker
- restore_cache:
key: test-node-modules-{{ checksum "package.json" }}
- run: npm install
@@ -65,39 +66,50 @@ jobs:
paths:
- node_modules
- run: npm run lint
- - run: npm run test
+ - run:
+ name: "Running Masterscript - deploy tc-project-service "
+ command: |
+ ./awsconfiguration.sh $DEPLOY_ENV
+ source awsenvconf
+ ./buildenv.sh -e $DEPLOY_ENV -b ${LOGICAL_ENV}-${APPNAME}-testvar
+ source buildenvvar
+ npm run test
+ rm -f buildenvvar
- run: npm run build
- persist_to_workspace:
root: .
paths:
- dist
-
+
deployProd:
<<: *python_env
environment:
DEPLOY_ENV: "PROD"
- VAR_ENV: "prod"
+ LOGICAL_ENV: "prod"
+ APPNAME: "projects-api"
steps: *deploy_steps
deployDev:
<<: *python_env
environment:
DEPLOY_ENV: "DEV"
- VAR_ENV: "dev"
- steps: *deploy_steps
-
+ LOGICAL_ENV: "dev"
+ APPNAME: "projects-api"
+ steps: *deploy_steps
+
workflows:
version: 2
build:
jobs:
- - test
+ - test:
+ context : org-global
- deployDev:
context : org-global
requires:
- test
filters:
branches:
- only: ['dev', 'dev-sts', 'feature/looker-api-integration']
+ only: ['develop']
- deployProd:
context : org-global
requires:
diff --git a/.nvmrc b/.nvmrc
index e51c059b..641c7df3 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-v8.2.1
+v8.9.4
diff --git a/README.md b/README.md
index 209b291a..20fedeea 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ Microservice to manage CRUD operations for all things Projects.
### Requirements
* [docker-compose](https://docs.docker.com/compose/install/) - We use docker-compose for running dependencies locally.
-* Nodejs 8.2.1 - consider using [nvm](https://github.com/creationix/nvm) or equivalent to manage your node version
+* Nodejs 8.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)
### Steps to run locally
@@ -54,6 +54,7 @@ Microservice to manage CRUD operations for all things Projects.
```
Alternatively, you may update `config/local.js` and replace `dockerhost` with your docker IP address.
You may try using command `docker-machine ip` to get your docker IP, but it works not for all systems.
+ Also, be sure to update `busApiUrl` if you are running `tc-bus-api` locally. (See below)
Explanation of configs:
- `config/mock.local.js` - Use local `mock-services` from docker to mock Identity and Member services instead of using deployed at Topcoder dev environment.
@@ -85,12 +86,66 @@ Microservice to manage CRUD operations for all things Projects.
The project service will be served on `http://localhost:8001`.
### Import sample metadata & projects
+
```bash
CONNECT_USER_TOKEN= npm run demo-data
```
-This command will create sample metadata entries in the DB (duplicate what is currently in development environment).
+To retrieve data from DEV env we have to provide a valid user token (`CONNECT_USER_TOKEN`). You may login to http://connect.topcoder-dev.com and find the Bearer token in the request headers using browser dev tools.
+
+This command for importing data uses API to create demo data. Which has a few pecularities:
+- data in DB would be for sure created
+- data in ElasticSearch Index (ES) would be only created if services [project-processor-es](https://github.com/topcoder-platform/project-processor-es) and [tc-bus-api](https://github.com/topcoder-platform/tc-bus-api) are also started locally. If you don't start them, then imported data wouldn't be indexed in ES, and would be only added to DB. You may start them locally separately, or better use `local/full/docker-compose.yml` as described [next section](#local-deployment-with-other-topcoder-services) which would start them automatically.
+ - **NOTE** During data importing a lot of records has to be indexed in ES, so you have to wait about 5-10 minutes after `npm run demo-data` is finished until imported data is indexed in ES. You may watch logs of `project-processor-es` to see if its done or no.
+
+### Local Deployment with other Topcoder Services.
+
+* There exists an alternate `docker-compose.yml` file that can be used to spawn containers for the following services:
+
+ | Service | Name | Port |
+ |----------|:-----:|:----:|
+ | PostGreSQL DB | db | 5432 |
+ | ElasticSearch | esearch | 9200,9300 |
+ | RabbitMQ | queue | 5672, 15672 |
+ | Zookeeper | zookeeper | 2181 |
+ | Kafka | kafka | 9092 |
+ | [tc-bus-api](https://github.com/topcoder-platform/tc-bus-api) | tc-bus-api | 8002 |
+ | [project-processor-es](https://github.com/topcoder-platform/project-processor-es) | project-processor-es | 5000 |
+ | [tc-notifications-api](https://github.com/topcoder-platform/tc-notifications) | tc-notifications-api | 4000 |
+ | [tc-notifications-processor](https://github.com/topcoder-platform/tc-notifications) | tc-notifications-processor | 4001 |
+
+* To have kafka create a list of desired topics on startup, there exists a file with the path `local/full/kafka-client/topics.txt`. Each line from the file will be added as a topic.
+* To run these services simply run the following commands:
+
+ ```bash
+ export AUTH0_CLIENT_ID=
+ export AUTH0_CLIENT_SECRET=
+ export AUTH0_URL=
+ export AUTH0_AUDIENCE=
+ export AUTH0_PROXY_SERVER_URL=
+
+ cd local/full
+ docker-compose up -d
+ ```
-To retrieve data from DEV env we need to provide a valid user token. You may login to http://connect.topcoder-dev.com and find the Bearer token in the request headers using browser dev tools.
+* The environment variables specified in the commands above will be passed onto the containers that have been configured to read them.
+* The above command will start all containers in the background.
+* To view the logs of any of the services use the following command, replacing "SERVICE_NAME" with the corresponding value under the "Name" column in the above table:
+
+ ```bash
+ cd local/full
+ docker-compose logs -f SERVICE_NAME
+ ```
+
+* The containers have been configured such that all Topcoder services will wait until all the topics listed in `local/full/kafka-client/topics.txt` have been created. To monitor the progress of topic creation, you can view the logs of the `kafka-client` service, which will exit when all topics have been created.
+
+* **WARNING**
+ After all the containers are started, make sure that `project-processor-es` service started successfully, as sometimes it doesn't start successfully as Kafka wasn't yet properly started at that moment. So run `docker-compose logs -f project-processor-es` to see its logs, you should see 3 lines with text `Subscribed to project.action.` like:
+ ```
+ project-processor-es_1 | 2019-12-18T11:10:12.849Z DEBUG no-kafka-client Subscribed to project.action.update:0 offset 0 leader 96e65c46c746:9092
+ project-processor-es_1 | 2019-12-18T11:10:12.851Z DEBUG no-kafka-client Subscribed to project.action.delete:0 offset 0 leader 96e65c46c746:9092
+ project-processor-es_1 | 2019-12-18T11:10:12.852Z DEBUG no-kafka-client Subscribed to project.action.create:0 offset 0 leader 96e65c46c746:9092
+ ```
+ If you don't see such lines, restart `project-processor-es` service ONLY by running `docker-compose restart project-processor-es`.
### Run Connect App with Project Service locally
@@ -99,7 +154,9 @@ To be able to run [Connect App](https://github.com/appirio-tech/connect-app) wit
```js
PROJECTS_API_URL: 'http://localhost:8001'
+ TC_NOTIFICATION_URL: 'http://localhost:4000/v5/notifications' # if tc-notfication-api has been locally deployed
```
+
2. Bypass token validation in Project Service.
In `tc-project-service/node_modules/tc-core-library-js/lib/auth/verifier.js` add this to line 23:
@@ -142,3 +199,33 @@ You can paste **swagger.yaml** to [swagger editor](http://editor.swagger.io/) o
#### Deploying without docker
If you don't want to use docker to deploy to localhost. You can simply run `npm run start:dev` from root of project. This should start the server on default port `8001`.
+
+### Kafka Commands
+
+If you've used `docker-compose` with the file `local/full/docker-compose.yml` to spawn kafka & zookeeper, you can use the following commands to manipulate kafka topics and messages:
+(Replace TOPIC_NAME with the name of the desired topic)
+
+**Create Topic**
+
+```bash
+docker exec tc-projects-kafka /usr/bin/kafka-topics --create --zookeeper zookeeper:2181 --partitions 1 --replication-factor 1 --topic TOPIC_NAME
+```
+
+**List Topics**
+
+```bash
+docker exec -it tc-projects-kafka /usr/bin/kafka-topics --list --zookeeper zookeeper:2181
+```
+
+**Watch Topic**
+
+```bash
+docker exec -it tc-projects-kafka /usr/bin/kafka-console-consumer --bootstrap-server localhost:9092 --zookeeper zookeeper:2181 --topic TOPIC_NAME
+```
+
+**Post Message to Topic**
+
+```bash
+docker exec -it tc-projects-kafka /usr/bin/kafka-console-producer --topic TOPIC_NAME --broker-list localhost:9092
+```
+The message can be passed using `stdin`
diff --git a/config/custom-environment-variables.json b/config/custom-environment-variables.json
index 886de47a..b927d972 100644
--- a/config/custom-environment-variables.json
+++ b/config/custom-environment-variables.json
@@ -10,9 +10,11 @@
"host": "PROJECTS_ES_URL",
"apiVersion": "2.3",
"indexName": "PROJECTS_ES_INDEX_NAME",
- "docType": "projectV4",
+ "docType": "PROJECTS_ES_DOC_TYPE",
"timelineIndexName": "TIMELINES_ES_INDEX_NAME",
- "timelineDocType": "TIMELINES_ES_DOC_TYPE"
+ "timelineDocType": "TIMELINES_ES_DOC_TYPE",
+ "metadataIndexName": "METADATA_ES_INDEX_NAME",
+ "metadataDocType": "METADATA_ES_DOC_TYPE"
},
"rabbitmqURL": "RABBITMQ_URL",
"pubsubQueueName": "PUBSUB_QUEUE_NAME",
@@ -50,6 +52,7 @@
"accountsAppUrl": "ACCOUNTS_APP_URL",
"inviteEmailSubject": "INVITE_EMAIL_SUBJECT",
"inviteEmailSectionTitle": "INVITE_EMAIL_SECTION_TITLE",
+ "pageSize": "PAGE_SIZE",
"SSO_REFCODES": "SSO_REFCODES",
"lookerConfig": {
"BASE_URL": "LOOKER_API_BASE_URL",
diff --git a/config/default.json b/config/default.json
index 3b336f4e..ccca74c6 100644
--- a/config/default.json
+++ b/config/default.json
@@ -1,8 +1,8 @@
{
- "apiVersion": "v4",
+ "apiVersion": "v5",
"AUTH_SECRET": "secret",
"logLevel": "info",
- "version": "v4",
+ "version": "v5",
"captureLogs": "false",
"enableFileUpload": "true",
"logentriesToken": "",
@@ -19,11 +19,13 @@
"projectAttachmentPathSuffix": "attachments",
"elasticsearchConfig": {
"host": "",
- "apiVersion": "2.3",
+ "apiVersion": "6.8",
"indexName": "projects",
- "docType": "projectV4",
+ "docType": "doc",
"timelineIndexName": "timelines",
- "timelineDocType": "timelineV4"
+ "timelineDocType": "doc",
+ "metadataIndexName": "metadata",
+ "metadataDocType": "doc"
},
"connectProjectUrl":"",
"dbConfig": {
@@ -44,13 +46,8 @@
"busApiToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoicHJvamVjdC1zZXJ2aWNlIiwiaWF0IjoxNTEyNzQ3MDgyLCJleHAiOjE1MjEzODcwODJ9.PHuNcFDaotGAL8RhQXQMdpL8yOKXxjB5DbBIodmt7RE",
"HEALTH_CHECK_URL": "_health",
"maxPhaseProductCount": 1,
- "AUTH0_CLIENT_ID": "",
- "AUTH0_CLIENT_SECRET": "",
- "AUTH0_AUDIENCE": "",
- "AUTH0_URL": "",
- "TOKEN_CACHE_TIME": "",
+ "TOKEN_CACHE_TIME": "86000",
"whitelistedOriginsForUserIdAuth": "[\"https:\/\/topcoder-newauth.auth0.com\/\",\"https:\/\/api.topcoder-dev.com\"]",
- "AUTH0_PROXY_SERVER_URL" : "",
"EMAIL_INVITE_FROM_NAME":"Topcoder",
"EMAIL_INVITE_FROM_EMAIL":"noreply@connect.topcoder.com",
"inviteEmailSubject": "You are invited to Topcoder",
@@ -59,8 +56,9 @@
"accountsAppUrl": "https://accounts.topcoder-dev.com",
"MAX_REVISION_NUMBER": 100,
"UNIQUE_GMAIL_VALIDATION": false,
- "SSO_REFCODES": "[]",
+ "pageSize": 20,
"VALID_STATUSES_BEFORE_PAUSED": "[\"active\"]",
+ "SSO_REFCODES": "[]",
"lookerConfig": {
"BASE_URL": "",
"CLIENT_ID": "",
diff --git a/config/m2m.local.js b/config/m2m.local.js
index 8fb26130..41677f37 100644
--- a/config/m2m.local.js
+++ b/config/m2m.local.js
@@ -5,6 +5,7 @@ if (process.env.NODE_ENV === 'test') {
config = require('./test.json');
} else {
config = {
+ busApiUrl: "http://localhost:8002/v5",
identityServiceEndpoint: "https://api.topcoder-dev.com/v3/",
authSecret: 'secret',
authDomain: 'topcoder-dev.com',
@@ -23,11 +24,7 @@ if (process.env.NODE_ENV === 'test') {
idleTimeout: 1000,
},
elasticsearchConfig: {
- host: 'dockerhost:9200',
- // target elasticsearch 2.3 version
- apiVersion: '2.3',
- indexName: 'projects',
- docType: 'projectV4'
+ host: 'dockerhost:9200'
},
whitelistedOriginsForUserIdAuth: "[\"\"]",
};
diff --git a/config/mock.local.js b/config/mock.local.js
index bec4dd70..14fbdba4 100644
--- a/config/mock.local.js
+++ b/config/mock.local.js
@@ -5,6 +5,7 @@ if (process.env.NODE_ENV === 'test') {
config = require('./test.json');
} else {
config = {
+ busApiUrl: "http://localhost:8002/v5",
identityServiceEndpoint: "http://dockerhost:3001/",
authSecret: 'secret',
authDomain: 'topcoder-dev.com',
@@ -23,11 +24,7 @@ if (process.env.NODE_ENV === 'test') {
idleTimeout: 1000,
},
elasticsearchConfig: {
- host: 'dockerhost:9200',
- // target elasticsearch 2.3 version
- apiVersion: '2.3',
- indexName: 'projects',
- docType: 'projectV4'
+ host: 'dockerhost:9200'
},
whitelistedOriginsForUserIdAuth: "[\"\"]",
};
diff --git a/config/test.json b/config/test.json
index 73b0fe82..aadd1ea9 100644
--- a/config/test.json
+++ b/config/test.json
@@ -6,11 +6,13 @@
"logentriesToken": "",
"elasticsearchConfig": {
"host": "http://localhost:9200",
- "apiVersion": "2.3",
+ "apiVersion": "6.8",
"indexName": "projects_test",
- "docType": "projectV4",
+ "docType": "doc",
"timelineIndexName": "timelines_test",
- "timelineDocType": "timelineV4"
+ "timelineDocType": "doc",
+ "metadataIndexName": "metadata_test",
+ "metadataDocType": "doc"
},
"rabbitmqUrl": "amqp://localhost:5672",
"connectProjectsUrl": "https://local.topcoder-dev.com/projects/",
diff --git a/postman.json b/docs/Project API.postman_collection.json
similarity index 59%
rename from postman.json
rename to docs/Project API.postman_collection.json
index ae743441..0c484294 100644
--- a/postman.json
+++ b/docs/Project API.postman_collection.json
@@ -1,7 +1,7 @@
{
"info": {
- "_postman_id": "51273dbf-d56a-49f4-b8a6-de33f9fb30e7",
- "name": "tc-project-service",
+ "_postman_id": "dd9afefd-0b47-4483-a4e5-33ca395791a3",
+ "name": "Project API",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
@@ -27,15 +27,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\n \"param\": {\n \"type\": \"generic\",\n \"description\": \"test project\",\n \"details\": {},\n \"billingAccountId\": 123,\n \"name\": \"test project1\"\n }\n}"
+ "raw": "{\n \"type\": \"generic\",\n \"description\": \"test project\",\n \"details\": {},\n \"billingAccountId\": 123,\n \"name\": \"test project1\"\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects",
+ "raw": "{{api-url}}/projects",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects"
]
}
@@ -58,15 +57,14 @@
],
"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}"
+ "raw": "{\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 }"
},
"url": {
- "raw": "{{api-url}}/v4/projects",
+ "raw": "{{api-url}}/projects",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects"
]
}
@@ -89,15 +87,14 @@
],
"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}"
+ "raw": "{\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 }"
},
"url": {
- "raw": "{{api-url}}/v4/projects",
+ "raw": "{{api-url}}/projects",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects"
]
}
@@ -119,14 +116,13 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/2",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "2"
+ "{{projectId}}"
]
}
},
@@ -148,17 +144,16 @@
],
"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}"
+ "raw": "{\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}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/2",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "2"
+ "{{projectId}}"
]
}
},
@@ -180,17 +175,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name2\",\n \"bookmarks\":null\n }\n}"
+ "raw": "{\n \"billingAccountId\": 9999, \n \"name\": \"new project name2\",\n \"bookmarks\":null\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/2",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "2"
+ "{{projectId}}"
]
}
},
@@ -212,17 +206,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name2\",\n \"bookmarks\":3\n }\n}"
+ "raw": "{\n \"billingAccountId\": 9999, \n \"name\": \"new project name2\",\n \"bookmarks\":3\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/2",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "2"
+ "{{projectId}}"
]
}
},
@@ -239,12 +232,11 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects",
+ "raw": "{{api-url}}/projects",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects"
]
}
@@ -252,10 +244,26 @@
"response": []
}
],
+ "protocolProfileBehavior": {},
"_postman_isSubFolder": true
},
{
"name": "Upload attachment",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "6547ada6-53f5-4e2d-bda0-f0ec5bfbe38f",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"attachmentId\", pm.response.json().id);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
@@ -270,17 +278,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"title\": \"first attachment submission\",\n\t\t\"filePath\": \"asdjshdasdas/asdsadj/asdasd.png\",\n\t\t\"s3Bucket\": \"topcoder-project-service\",\n\t\t\"contentType\": \"application/png\"\n\t}\n}"
+ "raw": "{\n\t\t\"title\": \"first attachment submission\",\n\t\t\"filePath\": \"/home/phoenix/a.png\",\n\t\t\"s3Bucket\": \"topcoder-project-service\",\n\t\t\"contentType\": \"application/png\"\n\t}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/7/attachments",
+ "raw": "{{api-url}}/projects/{{projectId}}/attachments",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "7",
+ "{{projectId}}",
"attachments"
]
},
@@ -304,19 +311,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"title\": \"first attachment submission updated\",\n\t\t\"description\": \"updated project attachment\"\n\t}\n}"
+ "raw": "{\n\t\t\"title\": \"first attachment submission updated\",\n\t\t\"description\": \"updated project attachment\"\n\t}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/7/attachments/2",
+ "raw": "{{api-url}}/projects/{{projectId}}/attachments/{{attachmentId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "7",
+ "{{projectId}}",
"attachments",
- "2"
+ "{{attachmentId}}"
]
},
"description": "Update project attachment"
@@ -342,23 +348,96 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/projects/7/attachments/2",
+ "raw": "{{api-url}}/projects/{{projectId}}/attachments/{{attachmentId}}",
+ "host": [
+ "{{api-url}}"
+ ],
+ "path": [
+ "projects",
+ "{{projectId}}",
+ "attachments",
+ "{{attachmentId}}"
+ ]
+ },
+ "description": "Delete a project attachment"
+ },
+ "response": []
+ },
+ {
+ "name": "Download attachment",
+ "protocolProfileBehavior": {
+ "disableBodyPruning": true
+ },
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "Authorization",
+ "value": "Bearer {{jwt-token}}"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": ""
+ },
+ "url": {
+ "raw": "{{api-url}}/projects/{{projectId}}/attachments/{{attachmentId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "7",
+ "{{projectId}}",
"attachments",
- "2"
+ "{{attachmentId}}"
+ ]
+ },
+ "description": "Delete a project attachment"
+ },
+ "response": []
+ },
+ {
+ "name": "List attachments",
+ "protocolProfileBehavior": {
+ "disableBodyPruning": true
+ },
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "Authorization",
+ "value": "Bearer {{jwt-token}}"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": ""
+ },
+ "url": {
+ "raw": "{{api-url}}/projects/{{projectId}}/attachments",
+ "host": [
+ "{{api-url}}"
+ ],
+ "path": [
+ "projects",
+ "{{projectId}}",
+ "attachments"
]
},
"description": "Delete a project attachment"
},
"response": []
}
- ]
+ ],
+ "protocolProfileBehavior": {}
},
{
"name": "Project With TemplateId issue",
@@ -379,15 +458,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project with templateId\",\n\t\t\"description\": \"Hello I am a test project with templateId\",\n\t\t\"type\": \"generic\",\n\t\t\"templateId\": 3000\n\t}\n}"
+ "raw": "{\n\t\t\"name\": \"test project with templateId\",\n\t\t\"description\": \"Hello I am a test project with templateId\",\n\t\t\"type\": \"generic\",\n\t\t\"templateId\": 3000\n\t}"
},
"url": {
- "raw": "{{api-url}}/v4/projects",
+ "raw": "{{api-url}}/projects",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects"
]
}
@@ -396,6 +474,21 @@
},
{
"name": "Create project with templateId",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "ed52d7f8-829b-40ec-8583-7e8dbcc6741c",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"projectId\", pm.response.json().id);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
@@ -410,22 +503,22 @@
],
"body": {
"mode": "raw",
- "raw": "{\n \"param\": {\n \"name\": \"test project with templateId\",\n \"description\": \"Hello I am a test project with templateId\",\n \"type\": \"generic\",\n \"templateId\": 3\n }\n}"
+ "raw": "{\n \"name\": \"test project with templateId\",\n \"description\": \"Hello I am a test project with templateId\",\n \"type\": \"generic\",\n \"templateId\": {{projectTemplateId}}\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects",
+ "raw": "{{api-url}}/projects",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects"
]
}
},
"response": []
}
- ]
+ ],
+ "protocolProfileBehavior": {}
},
{
"name": "Project Members",
@@ -449,18 +542,17 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/members",
+ "raw": "{{api-url}}/projects/{{projectId}}/members",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"members"
]
},
- "description": "Request payload is mandatory while creating project. If no request payload is specified this should result in 422 status code."
+ "description": "Request payload is mandatory while creating project. If no request payload is specified this should result in 400 status code."
},
"response": []
},
@@ -480,32 +572,46 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\"param\":{\n\t\"role\": \"copilot\"\n}\n}"
+ "raw": "{\n\t\"role\": \"copilot\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/members",
+ "raw": "{{api-url}}/projects/{{projectId}}/members",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"members"
]
},
- "description": "Certain fields are mandatory while creating project. If invalid fields are specified this should result in 422 status code."
+ "description": "Certain fields are mandatory while creating project. If invalid fields are specified this should result in 400 status code."
},
"response": []
},
{
"name": "Create project copilot with valid values",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "82218ddf-4bd4-485f-bcd4-1d653c674680",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"memberId\", pm.response.json().id);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
{
"key": "Authorization",
- "value": "Bearer {{jwt-token}}"
+ "value": "Bearer {{jwt-token-copilot-40051332}}"
},
{
"key": "Content-Type",
@@ -514,17 +620,16 @@
],
"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}"
+ "raw": "{\n\t\"role\": \"copilot\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/7/members",
+ "raw": "{{api-url}}/projects/{{projectId}}/members",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "7",
+ "{{projectId}}",
"members"
]
},
@@ -548,17 +653,16 @@
],
"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}"
+ "raw": "{\n\t\t\"role\": \"copilot\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/members",
+ "raw": "{{api-url}}/projects/{{projectId}}/members",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"members"
]
},
@@ -568,6 +672,21 @@
},
{
"name": "Create project manager with valid values",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "ba7b3265-aaba-4cca-8a53-819c9e96cfae",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"memberId\", pm.response.json().id);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
@@ -582,17 +701,16 @@
],
"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}"
+ "raw": "{\n\t\"role\": \"manager\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/7/members",
+ "raw": "{{api-url}}/projects/{{projectId}}/members",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "7",
+ "{{projectId}}",
"members"
]
},
@@ -602,6 +720,21 @@
},
{
"name": "Create project customer with valid values",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "87ab173c-8a4b-4d12-bfe7-ebcd272289f1",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"memberId\", pm.response.json().id);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
@@ -616,17 +749,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"role\": \"customer\",\n\t\t\"userId\": 40051332,\n\t\t\"isPrimary\": true\n\t}\n}"
+ "raw": "{\n\t\t\"role\": \"copilot\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/7/members",
+ "raw": "{{api-url}}/projects/{{projectId}}/members",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "7",
+ "{{projectId}}",
"members"
]
},
@@ -650,19 +782,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"role\": \"copilot\",\n\t\t\"isPrimary\": true\n\t}\n}"
+ "raw": "{\n\t\"role\": \"copilot\",\n\t\"isPrimary\": true\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/members/1",
+ "raw": "{{api-url}}/projects/{{projectId}}/members/{{memberId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"members",
- "1"
+ "{{memberId}}"
]
},
"description": "Update a project's member."
@@ -685,19 +816,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"role\": \"copilot\",\n\t\t\"isPrimary\": false\n\t}\n}"
+ "raw": "{\n\t\"role\": \"copilot\",\n\t\"isPrimary\": false\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/members/1",
+ "raw": "{{api-url}}/projects/{{projectId}}/members/{{memberId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"members",
- "1"
+ "{{memberId}}"
]
},
"description": "Update a project's member."
@@ -720,26 +850,118 @@
],
"body": {
"mode": "raw",
- "raw": " {\n \"param\": {\n \"role\": \"wrong\"\n }\n } "
+ "raw": " {\n\t\"role\": \"wrong\"\n } "
},
"url": {
- "raw": "{{api-url}}/v4/projects/3/members/5",
+ "raw": "{{api-url}}/projects/{{projectId}}/members/{{memberId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "3",
+ "{{projectId}}",
"members",
- "5"
+ "{{memberId}}"
]
}
},
"response": []
},
{
- "name": "Delete project member",
+ "name": "List project members",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "Authorization",
+ "value": "Bearer {{jwt-token}}"
+ }
+ ],
+ "url": {
+ "raw": "{{api-url}}/projects/{{projectId}}/members",
+ "host": [
+ "{{api-url}}"
+ ],
+ "path": [
+ "projects",
+ "{{projectId}}",
+ "members"
+ ]
+ },
+ "description": "List all the project with no filter. Default sort and limits are applied."
+ },
+ "response": []
+ },
+ {
+ "name": "List project members with roles",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "Authorization",
+ "value": "Bearer {{jwt-token}}"
+ }
+ ],
+ "url": {
+ "raw": "{{api-url}}/projects/{{projectId}}/members?role=customer",
+ "host": [
+ "{{api-url}}"
+ ],
+ "path": [
+ "projects",
+ "{{projectId}}",
+ "members"
+ ],
+ "query": [
+ {
+ "key": "role",
+ "value": "customer"
+ }
+ ]
+ },
+ "description": "List all the project with no filter. Default sort and limits are applied."
+ },
+ "response": []
+ },
+ {
+ "name": "Get project member",
+ "protocolProfileBehavior": {
+ "disableBodyPruning": true
+ },
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "Authorization",
+ "value": "Bearer {{jwt-token}}"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": ""
+ },
+ "url": {
+ "raw": "{{api-url}}/projects/{{projectId}}/members/{{memberId}}",
+ "host": [
+ "{{api-url}}"
+ ],
+ "path": [
+ "projects",
+ "{{projectId}}",
+ "members",
+ "{{memberId}}"
+ ]
+ },
+ "description": "Delete a project's member"
+ },
+ "response": []
+ },
+ {
+ "name": "Delete project member Copy",
"request": {
"method": "DELETE",
"header": [
@@ -757,16 +979,15 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/projects/3/members/5",
+ "raw": "{{api-url}}/projects/{{projectId}}/members/{{memberId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "3",
+ "{{projectId}}",
"members",
- "5"
+ "{{memberId}}"
]
},
"description": "Delete a project's member"
@@ -789,31 +1010,31 @@
],
"body": {
"mode": "raw",
- "raw": " {\n \"param\": {\n \"role\": \"manager\",\n \"isPrimary\": true\n }\n } "
+ "raw": " {\n \"role\": \"manager\",\n \"isPrimary\": true\n } "
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/members/2",
+ "raw": "{{api-url}}/projects/{{projectId}}/members/{{memberId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"members",
- "2"
+ "{{memberId}}"
]
}
},
"response": []
}
- ]
+ ],
+ "protocolProfileBehavior": {}
},
{
- "name": "Projects",
+ "name": "Project Members Invites",
"item": [
{
- "name": "Create project without payload",
+ "name": "Create project member with no payload",
"request": {
"method": "POST",
"header": [
@@ -828,24 +1049,26 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\n}"
+ "raw": "{\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects",
+ "raw": "{{api-url}}/projects/{{projectId}}/members/invite",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
- "projects"
+ "projects",
+ "{{projectId}}",
+ "members",
+ "invite"
]
},
- "description": "Request body is mandatory while creating project. If invalid request body is supplied this should return 422 status code."
+ "description": "Request payload is mandatory while creating project. If no request payload is specified this should result in 400 status code."
},
"response": []
},
{
- "name": "Create project without valid name",
+ "name": "Create project customer with valid values",
"request": {
"method": "POST",
"header": [
@@ -860,26 +1083,31 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t}\n}"
+ "raw": "{\n\t\"role\": \"customer\",\n\t\"emails\": [\"test@topcoder.com\"]\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects",
+ "raw": "{{api-url}}/projects/{{projectId}}/members/invite",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
- "projects"
+ "projects",
+ "{{projectId}}",
+ "members",
+ "invite"
]
},
- "description": "Certain fields are mandatory while creating project. If invalid request body is supplied this should return 422 status code."
+ "description": "If the request payload is valid, than project customer should be added. This should sync with the direct project is project is associated with direct project."
},
"response": []
},
{
- "name": "Create project with valid values",
+ "name": "Get project member invite",
+ "protocolProfileBehavior": {
+ "disableBodyPruning": true
+ },
"request": {
- "method": "POST",
+ "method": "GET",
"header": [
{
"key": "Authorization",
@@ -892,30 +1120,32 @@
],
"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}"
+ "raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/projects",
+ "raw": "{{api-url}}/projects/{{projectId}}/members/invite",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
- "projects"
+ "projects",
+ "{{projectId}}",
+ "members",
+ "invite"
]
},
- "description": "Valid request body. Project should be created successfully."
+ "description": "Update a project's member."
},
"response": []
},
{
- "name": "Create project by inactive user",
+ "name": "Update project member invite",
"request": {
- "method": "POST",
+ "method": "PUT",
"header": [
{
"key": "Authorization",
- "value": "Bearer userId_{{inactive-userId}}"
+ "value": "Bearer {{jwt-token}}"
},
{
"key": "Content-Type",
@@ -924,171 +1154,226 @@
],
"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}"
+ "raw": "{\n\t\"status\": \"accepted\",\n\t\"email\": \"test@topcoder.com\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects",
+ "raw": "{{api-url}}/projects/{{projectId}}/members/invite",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
- "projects"
+ "projects",
+ "{{projectId}}",
+ "members",
+ "invite"
]
},
- "description": "Valid request body. Project should be created successfully."
+ "description": "Update a project's member."
},
"response": []
},
{
- "name": "Get project by id",
+ "name": "wrong status",
"request": {
- "method": "GET",
+ "method": "PUT",
"header": [
{
"key": "Authorization",
"value": "Bearer {{jwt-token}}"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
}
],
+ "body": {
+ "mode": "raw",
+ "raw": " {\n\t\"status\": \"wrong\"\n } "
+ },
"url": {
- "raw": "{{api-url}}/v4/projects/7",
+ "raw": "{{api-url}}/projects/{{projectId}}/members/invite",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "7"
+ "{{projectId}}",
+ "members",
+ "invite"
]
- },
- "description": "Get a project by id. project members and attachments should also be returned."
+ }
},
"response": []
+ }
+ ],
+ "event": [
+ {
+ "listen": "prerequest",
+ "script": {
+ "id": "522949a9-3d94-4103-92b2-976af332f203",
+ "type": "text/javascript",
+ "exec": [
+ ""
+ ]
+ }
},
{
- "name": "Get project by id and request specific fields",
+ "listen": "test",
+ "script": {
+ "id": "df2755ee-59a5-4d8d-a6ad-6416b697c894",
+ "type": "text/javascript",
+ "exec": [
+ ""
+ ]
+ }
+ }
+ ],
+ "protocolProfileBehavior": {}
+ },
+ {
+ "name": "Projects",
+ "item": [
+ {
+ "name": "Create project without payload",
"request": {
- "method": "GET",
+ "method": "POST",
"header": [
{
"key": "Authorization",
"value": "Bearer {{jwt-token}}"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
}
],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n\t\n}"
+ },
"url": {
- "raw": "{{api-url}}/v4/projects/1?fields=id,name,description,members.id,members.projectId",
+ "raw": "{{api-url}}/projects",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
- "projects",
- "1"
- ],
- "query": [
- {
- "key": "fields",
- "value": "id,name,description,members.id,members.projectId"
- }
+ "projects"
]
},
- "description": "Get a project by id. project members and attachments should also be returned. Only those fields which are specified should be returned."
+ "description": "Request body is mandatory while creating project. If invalid request body is supplied this should return 400 status code."
},
"response": []
},
{
- "name": "List projects DB",
+ "name": "Create project without valid name",
"request": {
- "method": "GET",
+ "method": "POST",
"header": [
{
"key": "Authorization",
"value": "Bearer {{jwt-token}}"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
}
],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n}"
+ },
"url": {
- "raw": "{{api-url}}/v4/projects/db",
+ "raw": "{{api-url}}/projects",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
- "projects",
- "db"
+ "projects"
]
},
- "description": "List all the project with no filter. Default sort and limits are applied."
+ "description": "Certain fields are mandatory while creating project. If invalid request body is supplied this should return 400 status code."
},
"response": []
},
{
- "name": "List projects DB with limit and offset",
+ "name": "Create project with valid values",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "5d951b47-5aac-4af6-a24b-ef6c998b913e",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"projectId\", pm.response.json().id);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
- "method": "GET",
+ "method": "POST",
"header": [
{
"key": "Authorization",
"value": "Bearer {{jwt-token}}"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
}
],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n\t\"name\": \"test project\",\n\t\"description\": \"Hello I am a test project\",\n\t\"type\": \"{{projectTypeId}}\"\n}"
+ },
"url": {
- "raw": "{{api-url}}/v4/projects/db?limit=1&offset=1",
+ "raw": "{{api-url}}/projects",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
- "projects",
- "db"
- ],
- "query": [
- {
- "key": "limit",
- "value": "1"
- },
- {
- "key": "offset",
- "value": "1"
- }
+ "projects"
]
},
- "description": "List all the project with no filter. Limit of 1 and offset of 1 is applied"
+ "description": "Valid request body. Project should be created successfully."
},
"response": []
},
{
- "name": "List projects DB with filters applied",
+ "name": "Create project by inactive user",
"request": {
- "method": "GET",
+ "method": "POST",
"header": [
{
"key": "Authorization",
- "value": "Bearer {{jwt-token}}"
+ "value": "Bearer userId_{{inactive-userId}}"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
}
],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n\t\"name\": \"test project\",\n\t\"description\": \"Hello I am a test project\",\n\t\"type\": \"{{projectTypeId}}\"\n}"
+ },
"url": {
- "raw": "{{api-url}}/v4/projects/db?filter=type%3Dgeneric",
+ "raw": "{{api-url}}/projects",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
- "projects",
- "db"
- ],
- "query": [
- {
- "key": "filter",
- "value": "type%3Dgeneric"
- }
+ "projects"
]
},
- "description": "List all the project with filters applied. The filter string should be url encoded. Default limit and offset is applicable"
+ "description": "Valid request body. Project should be created successfully."
},
"response": []
},
{
- "name": "List projects DB with sort applied",
+ "name": "Get project by id",
"request": {
"method": "GET",
"header": [
@@ -1098,28 +1383,21 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/db?sort=type%20desc",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "db"
- ],
- "query": [
- {
- "key": "sort",
- "value": "type%20desc"
- }
+ "{{projectId}}"
]
},
- "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`"
+ "description": "Get a project by id. project members and attachments should also be returned."
},
"response": []
},
{
- "name": "List projects DB and return specific fields",
+ "name": "Get project by id and request specific fields",
"request": {
"method": "GET",
"header": [
@@ -1129,52 +1407,50 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/db?fields=id,name,description",
+ "raw": "{{api-url}}/projects/{{projectId}}?fields=id,name,description,members.id,members.projectId",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "db"
+ "{{projectId}}"
],
"query": [
{
"key": "fields",
- "value": "id,name,description"
+ "value": "id,name,description,members.id,members.projectId"
}
]
},
- "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."
+ "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 DB with copilot token",
+ "name": "List projects",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
- "value": "Bearer {{jwt-token-copilot-40051332}}"
+ "value": "Bearer {{jwt-token}}"
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/db",
+ "raw": "{{api-url}}/projects",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
- "projects",
- "db"
+ "projects"
]
- }
+ },
+ "description": "List all the project with no filter. Default sort and limits are applied."
},
"response": []
},
{
- "name": "List projects",
+ "name": "List projects with perPage and page",
"request": {
"method": "GET",
"header": [
@@ -1184,21 +1460,30 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects",
+ "raw": "{{api-url}}/projects?perPage=1&page=1",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects"
+ ],
+ "query": [
+ {
+ "key": "perPage",
+ "value": "1"
+ },
+ {
+ "key": "page",
+ "value": "1"
+ }
]
},
- "description": "List all the project with no filter. Default sort and limits are applied."
+ "description": "List all the project with no filter. Limit of 1 and offset of 1 is applied"
},
"response": []
},
{
- "name": "List projects with limit and offset",
+ "name": "List projects with type filter",
"request": {
"method": "GET",
"header": [
@@ -1208,31 +1493,26 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects?limit=1&offset=1",
+ "raw": "{{api-url}}/projects?type=generic",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects"
],
"query": [
{
- "key": "limit",
- "value": "1"
- },
- {
- "key": "offset",
- "value": "1"
+ "key": "type",
+ "value": "generic"
}
]
},
- "description": "List all the project with no filter. Limit of 1 and offset of 1 is applied"
+ "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 filters applied",
+ "name": "List projects with id filter",
"request": {
"method": "GET",
"header": [
@@ -1242,18 +1522,21 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects?filter=type%3Dgeneric",
+ "raw": "{{api-url}}/projects?id=1&id=2",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects"
],
"query": [
{
- "key": "filter",
- "value": "type%3Dgeneric"
+ "key": "id",
+ "value": "1"
+ },
+ {
+ "key": "id",
+ "value": "2"
}
]
},
@@ -1272,18 +1555,17 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects?sort=type%20desc",
+ "raw": "{{api-url}}/projects?sort=type asc",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects"
],
"query": [
{
"key": "sort",
- "value": "type%20desc"
+ "value": "type asc"
}
]
},
@@ -1302,12 +1584,11 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects?fields=id,name,description",
+ "raw": "{{api-url}}/projects?fields=id,name,description",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects"
],
"query": [
@@ -1332,12 +1613,11 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects",
+ "raw": "{{api-url}}/projects",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects"
]
}
@@ -1359,14 +1639,13 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/projects/3",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "3"
+ "{{projectId}}"
]
},
"description": "Delete a project by id"
@@ -1389,17 +1668,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n \"param\": {\n \"name\": \"project name updated\"\n }\n}"
+ "raw": "{\n \"name\": \"project name updated\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1"
+ "{{projectId}}"
]
},
"description": "Update the project name. Name should be updated successfully."
@@ -1422,17 +1700,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"project name updated\"\n\t}\n}"
+ "raw": "{\n\t\t\"name\": \"project name updated\"\n\t}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/2",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "2"
+ "{{projectId}}"
]
},
"description": "Update the project name. If user don't have permission to the project than it should return 403."
@@ -1455,17 +1732,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"project name updated\"\n\t}\n}"
+ "raw": "{\n\t\t\"name\": \"project name updated\"\n\t}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/10",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "10"
+ "{{projectId}}"
]
},
"description": "Update the project name. If project is not found than this result in 404 status code."
@@ -1488,17 +1764,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n \"param\": {\n \"status\": \"in_review\"\n }\n}"
+ "raw": "{\n \"status\": \"in_review\"\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1"
+ "{{projectId}}"
]
},
"description": "Update the project status."
@@ -1521,17 +1796,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n \"param\": {\n \"status\": \"reviewed\"\n }\n}"
+ "raw": "{\n \"status\": \"reviewed\"\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/7",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "7"
+ "{{projectId}}"
]
},
"description": "Update the project status."
@@ -1554,17 +1828,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n \"param\": {\n \"status\": \"paused\"\n }\n}"
+ "raw": "{\n \"status\": \"paused\"\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/7",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "7"
+ "{{projectId}}"
]
},
"description": "Update the project status."
@@ -1587,17 +1860,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n \"param\": {\n \"status\": \"cancelled\",\n \"cancelReason\": \"price/cost\"\n }\n}"
+ "raw": "{\n \"status\": \"cancelled\",\n \"cancelReason\": \"price/cost\"\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/7",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "7"
+ "{{projectId}}"
]
},
"description": "Update the project status. While cancelling the project `cancelReason` is mandatory."
@@ -1620,20 +1892,19 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"status\": \"cancelled\"\n\t}\n}"
+ "raw": "{\n\t\t\"status\": \"cancelled\"\n\t}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1"
+ "{{projectId}}"
]
},
- "description": "Update the project status. While cancelling the project `cancelReason` is mandatory. If no `cancelReason` is supplied this should result in 422 status code."
+ "description": "Update the project status. While cancelling the project `cancelReason` is mandatory. If no `cancelReason` is supplied this should result in 400 status code."
},
"response": []
},
@@ -1653,17 +1924,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n \"param\": {\n \"status\": \"completed\"\n }\n}"
+ "raw": "{\n \"status\": \"completed\"\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/7",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "7"
+ "{{projectId}}"
]
},
"description": "Update the project status."
@@ -1686,17 +1956,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"status\": \"active\"\n\t}\n}"
+ "raw": "{\n\t\t\"status\": \"active\"\n\t}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1"
+ "{{projectId}}"
]
},
"description": "Move a project out of cancel state. Only admin and manager is allowed to do so."
@@ -1719,17 +1988,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"status\": \"active\"\n\t}\n}"
+ "raw": "{\n\t\t\"status\": \"active\"\n\t}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1"
+ "{{projectId}}"
]
},
"description": "Move a project out of cancel state. Only admin and manager is allowed to do so."
@@ -1752,17 +2020,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n \"param\": {\n \"details\": {\n \"summary\": \"project name updated\"\n }\n }\n}"
+ "raw": "{\n \"details\": {\n \"summary\": \"project name updated\"\n }\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/8",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "8"
+ "{{projectId}}"
]
},
"description": "Update the project details. This should fire specification modified event"
@@ -1785,17 +2052,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n \"param\": {\n \"bookmarks\": [\n {\n \"title\": \"test\",\n \"address\": \"http://topcoder.com\"\n }\n \n ]\n }\n}"
+ "raw": "{\n \"bookmarks\": [\n {\n \"title\": \"test\",\n \"address\": \"http://topcoder.com\"\n }\n \n ]\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/8",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "8"
+ "{{projectId}}"
]
},
"description": "Update the project bookmarks. This should fire project link created event"
@@ -1818,17 +2084,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n \n \"param\":{\n \"name\": \"updatedProject name\",\n \"status\": \"active\"\n }\n}"
+ "raw": "{\n \"name\": \"updatedProject name\",\n \"status\": \"active\"\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1"
+ "{{projectId}}"
]
}
},
@@ -1850,17 +2115,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n \n \"param\":{\n \"name\": \"updatedProject name\",\n \"status\": \"active\"\n }\n}"
+ "raw": "{\n \"name\": \"updatedProject name\",\n \"status\": \"active\"\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1"
+ "{{projectId}}"
]
}
},
@@ -1882,24 +2146,24 @@
],
"body": {
"mode": "raw",
- "raw": "{\n \n \"param\":{\n \"name\": \"updatedProject name\",\n \"status\": \"active\"\n }\n}"
+ "raw": "{\n \"name\": \"updatedProject name\",\n \"status\": \"active\"\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1"
+ "{{projectId}}"
]
}
},
"response": []
}
],
- "description": "Requests for all things projects."
+ "description": "Requests for all things projects.",
+ "protocolProfileBehavior": {}
},
{
"name": "Workstream",
@@ -1920,55 +2184,20 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\n}"
- },
- "url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams",
- "host": [
- "{{api-url}}"
- ],
- "path": [
- "v4",
- "projects",
- "1",
- "workstreams"
- ]
- },
- "description": "Request body is mandatory while creating project. If invalid request body is supplied this should return 422 status code."
- },
- "response": []
- },
- {
- "name": "Create workstream without valid name",
- "request": {
- "method": "POST",
- "header": [
- {
- "key": "Authorization",
- "value": "Bearer {{jwt-token}}"
- },
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "body": {
- "mode": "raw",
- "raw": "{\n\t\"param\": {\n\t}\n}"
+ "raw": "{\n\t\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams"
]
},
- "description": "Certain fields are mandatory while creating project. If invalid request body is supplied this should return 422 status code."
+ "description": "Request body is mandatory while creating project. If invalid request body is supplied this should return 422 status code."
},
"response": []
},
@@ -1978,13 +2207,14 @@
{
"listen": "test",
"script": {
- "type": "text/javascript",
+ "id": "15506f7a-77d3-46cb-9b37-41015ffbfdbc",
"exec": [
"pm.test(\"Status code is 201\", function () {",
" pm.response.to.have.status(201);",
- " pm.environment.set(\"WorkStreamId\", pm.response.json().result.content.id);",
+ " pm.environment.set(\"workStreamId\", pm.response.json().id);",
"});"
- ]
+ ],
+ "type": "text/javascript"
}
}
],
@@ -2002,17 +2232,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project\",\n\t\t\"type\": \"generic\",\n\t\t\"status\": \"active\"\n\t}\n}"
+ "raw": "{\n\t\t\"name\": \"test project\",\n\t\t\"type\": \"generic\",\n\t\t\"status\": \"active\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams"
]
},
@@ -2036,17 +2265,16 @@
],
"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}"
+ "raw": "{\n\t\t\"name\": \"test project\",\n\t\t\"description\": \"Hello I am a test project\",\n\t\t\"type\": \"generic\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams"
]
},
@@ -2065,16 +2293,15 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId}}",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId}}"
+ "{{workStreamId}}"
]
},
"description": "Get a project by id. project members and attachments should also be returned."
@@ -2092,14 +2319,13 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams"
]
},
@@ -2122,16 +2348,15 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId}}",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId}}"
+ "{{workStreamId}}"
]
},
"description": "Delete a project by id"
@@ -2154,19 +2379,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project - updated\"\n\t}\n}"
+ "raw": "{\n\t\t\"name\": \"test project - updated\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId}}",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId}}"
+ "{{workStreamId}}"
]
},
"description": "Update the project name. Name should be updated successfully."
@@ -2174,7 +2398,8 @@
"response": []
}
],
- "description": "Requests for all things projects."
+ "description": "Requests for all things projects.",
+ "protocolProfileBehavior": {}
},
{
"name": "Work",
@@ -2198,16 +2423,15 @@
"raw": "{\n\t\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId}}/works",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId}}",
+ "{{workStreamId}}",
"works"
]
},
@@ -2215,55 +2439,20 @@
},
"response": []
},
- {
- "name": "Create work without valid name",
- "request": {
- "method": "POST",
- "header": [
- {
- "key": "Authorization",
- "value": "Bearer {{jwt-token}}"
- },
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "body": {
- "mode": "raw",
- "raw": "{\n\t\"param\": {\n\t}\n}"
- },
- "url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId}}/works",
- "host": [
- "{{api-url}}"
- ],
- "path": [
- "v4",
- "projects",
- "1",
- "workstreams",
- "{{WorkStreamId}}",
- "works"
- ]
- },
- "description": "Certain fields are mandatory while creating project. If invalid request body is supplied this should return 422 status code."
- },
- "response": []
- },
{
"name": "Create work with valid values",
"event": [
{
"listen": "test",
"script": {
- "type": "text/javascript",
+ "id": "34d06fac-76f6-47b8-9b70-6e9c558e2bf1",
"exec": [
"pm.test(\"Status code is 201\", function () {",
" pm.response.to.have.status(201);",
- " pm.environment.set(\"WorkId\", pm.response.json().result.content.id);",
+ " pm.environment.set(\"workId\", pm.response.json().id);",
"});"
- ]
+ ],
+ "type": "text/javascript"
}
}
],
@@ -2281,19 +2470,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project phase\",\n\t\t\"status\": \"active\",\n\t\t\"startDate\": \"2018-05-16T00:00:00\",\n\t\t\"endDate\": \"2018-05-17T00:00:00\",\n\t\t\"order\": 1,\n\t\t\"budget\": 20,\n\t\t\"details\": {\n\t\t\t\"aDetails\": \"a details\"\n\t\t}\n\t}\n}"
+ "raw": "{\n\t\t\"name\": \"test project phase\",\n\t\t\"status\": \"active\",\n\t\t\"startDate\": \"2018-05-16T00:00:00\",\n\t\t\"endDate\": \"2018-05-17T00:00:00\",\n\t\t\"order\": 1,\n\t\t\"budget\": 20,\n\t\t\"details\": {\n\t\t\t\"aDetails\": \"a details\"\n\t\t}\n\t}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId}}/works",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId}}",
+ "{{workStreamId}}",
"works"
]
},
@@ -2317,19 +2505,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project phase\",\n\t\t\"status\": \"active\",\n\t\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\t\"order\": 2,\n\t\t\"budget\": 20,\n\t\t\"details\": {\n\t\t\t\"aDetails\": \"a details\"\n\t\t}\n\t}\n}"
+ "raw": "{\n\t\t\"name\": \"test project phase\",\n\t\t\"status\": \"active\",\n\t\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\t\"order\": 2,\n\t\t\"budget\": 20,\n\t\t\"details\": {\n\t\t\t\"aDetails\": \"a details\"\n\t\t}\n\t}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId}}/works",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId}}",
+ "{{workStreamId}}",
"works"
]
},
@@ -2353,19 +2540,18 @@
],
"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}"
+ "raw": "{\n\t\t\"name\": \"test project\",\n\t\t\"description\": \"Hello I am a test project\",\n\t\t\"type\": \"generic\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId}}/works",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId}}",
+ "{{workStreamId}}",
"works"
]
},
@@ -2384,18 +2570,17 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId}}/works/{{WorkId}}",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works/{{workId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId}}",
+ "{{workStreamId}}",
"works",
- "{{WorkId}}"
+ "{{workId}}"
]
},
"description": "Get a project by id. project members and attachments should also be returned."
@@ -2413,16 +2598,15 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId}}/works",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId}}",
+ "{{workStreamId}}",
"works"
]
},
@@ -2441,16 +2625,15 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId}}/works?sort=startDate desc",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works?sort=startDate desc",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId}}",
+ "{{workStreamId}}",
"works"
],
"query": [
@@ -2475,16 +2658,15 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId}}/works?fields=status,name,budget",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works?fields=status,name,budget",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId}}",
+ "{{workStreamId}}",
"works"
],
"query": [
@@ -2513,18 +2695,17 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId}}/works/{{WorkId}}",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works/{{workId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId}}",
+ "{{workStreamId}}",
"works",
- "{{WorkId}}"
+ "{{workId}}"
]
},
"description": "Delete a project by id"
@@ -2547,21 +2728,20 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project phase 11\",\n\t\t\"status\": \"active\",\n\t\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\t\"budget\": 24,\n\t\t\"order\": 2\n\t}\n}"
+ "raw": "{\n\t\t\"name\": \"test project phase 11\",\n\t\t\"status\": \"active\",\n\t\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\t\"budget\": 24,\n\t\t\"order\": 2\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId}}/works/{{WorkId}}",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works/{{workId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId}}",
+ "{{workStreamId}}",
"works",
- "{{WorkId}}"
+ "{{workId}}"
]
},
"description": "Update the project name. Name should be updated successfully."
@@ -2584,19 +2764,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project - updated\",\n\t\t\"type\": \"generic\",\n\t\t\"status\": \"active\"\n\t}\n}"
+ "raw": "{\n\t\t\"name\": \"test project - updated\",\n\t\t\"type\": \"generic\",\n\t\t\"status\": \"active\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/1",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "1"
+ "{{workStreamId}}"
]
},
"description": "Update the project name. If user don't have permission to the project than it should return 403."
@@ -2604,7 +2783,8 @@
"response": []
}
],
- "description": "Requests for all things projects."
+ "description": "Requests for all things projects.",
+ "protocolProfileBehavior": {}
},
{
"name": "Work Item",
@@ -2628,17 +2808,16 @@
"raw": "{\n\t\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/workstreams/{{WorkStreamId}}/works/{{WorkId}}/workitems",
+ "raw": "{{api-url}}/projects/workstreams/{{workStreamId}}/works/{{workId}}/workitems",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"workstreams",
- "{{WorkStreamId}}",
+ "{{workStreamId}}",
"works",
- "{{WorkId}}",
+ "{{workId}}",
"workitems"
]
},
@@ -2646,44 +2825,6 @@
},
"response": []
},
- {
- "name": "Create work item without valid name",
- "request": {
- "method": "POST",
- "header": [
- {
- "key": "Authorization",
- "value": "Bearer {{jwt-token}}"
- },
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "body": {
- "mode": "raw",
- "raw": "{\n\t\"param\": {\n\t}\n}"
- },
- "url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId}}/works/{{WorkId}}/workitems",
- "host": [
- "{{api-url}}"
- ],
- "path": [
- "v4",
- "projects",
- "1",
- "workstreams",
- "{{WorkStreamId}}",
- "works",
- "{{WorkId}}",
- "workitems"
- ]
- },
- "description": "Certain fields are mandatory while creating project. If invalid request body is supplied this should return 422 status code."
- },
- "response": []
- },
{
"name": "Create work item with valid values",
"event": [
@@ -2694,7 +2835,7 @@
"exec": [
"pm.test(\"Status code is 201\", function () {",
" pm.response.to.have.status(201);",
- " pm.environment.set(\"ItemId\", pm.response.json().result.content.id);",
+ " pm.environment.set(\"itemId\", pm.response.json().id);",
"});"
]
}
@@ -2714,21 +2855,20 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"work item - phase product\",\n\t\t\"type\": \"type 1\",\n\t\t\"estimatedPrice\": 12\n\t}\n}"
+ "raw": "{\n\t\t\"name\": \"work item - phase product\",\n\t\t\"type\": \"type 1\",\n\t\t\"estimatedPrice\": 12\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId}}/works/{{WorkId}}/workitems",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works/{{workId}}/workitems",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId}}",
+ "{{workStreamId}}",
"works",
- "{{WorkId}}",
+ "{{workId}}",
"workitems"
]
},
@@ -2752,21 +2892,20 @@
],
"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}"
+ "raw": "{\n\t\t\"name\": \"test project\",\n\t\t\"description\": \"Hello I am a test project\",\n\t\t\"type\": \"generic\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId}}/works/{{WorkId}}/workitems",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works/{{workId}}/workitems",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId}}",
+ "{{workStreamId}}",
"works",
- "{{WorkId}}",
+ "{{workId}}",
"workitems"
]
},
@@ -2785,20 +2924,19 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId}}/works/{{WorkId}}/workitems/{{ItemId}}",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works/{{workId}}/workitems/{{itemId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId}}",
+ "{{workStreamId}}",
"works",
- "{{WorkId}}",
+ "{{workId}}",
"workitems",
- "{{ItemId}}"
+ "{{itemId}}"
]
},
"description": "Get a project by id. project members and attachments should also be returned."
@@ -2816,18 +2954,17 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId}}/works/{{WorkId}}/workitems",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works/{{workId}}/workitems",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId}}",
+ "{{workStreamId}}",
"works",
- "{{WorkId}}",
+ "{{workId}}",
"workitems"
]
},
@@ -2850,20 +2987,19 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId}}/works/{{WorkId}}/workitems/{{ItemId}}",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works/{{workId}}/workitems/{{itemId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId}}",
+ "{{workStreamId}}",
"works",
- "{{WorkId}}",
+ "{{workId}}",
"workitems",
- "{{ItemId}}"
+ "{{itemId}}"
]
},
"description": "Delete a project by id"
@@ -2886,23 +3022,22 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"work item - updated\"\n\t}\n}"
+ "raw": "{\n\t\t\"name\": \"work item - updated\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId}}/works/{{WorkId}}/workitems/{{ItemId}}",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works/{{workId}}/workitems/{{itemId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId}}",
+ "{{workStreamId}}",
"works",
- "{{WorkId}}",
+ "{{workId}}",
"workitems",
- "{{ItemId}}"
+ "{{itemId}}"
]
},
"description": "Update the project name. Name should be updated successfully."
@@ -2910,7 +3045,8 @@
"response": []
}
],
- "description": "Requests for all things projects."
+ "description": "Requests for all things projects.",
+ "protocolProfileBehavior": {}
},
{
"name": "Work Management Permission",
@@ -2934,12 +3070,11 @@
"raw": "{\n\t\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/workManagementPermission",
+ "raw": "{{api-url}}/projects/metadata/workManagementPermission",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"workManagementPermission"
@@ -2950,41 +3085,7 @@
"response": []
},
{
- "name": "Create work management permission without valid name",
- "request": {
- "method": "POST",
- "header": [
- {
- "key": "Authorization",
- "value": "Bearer {{jwt-token}}"
- },
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "body": {
- "mode": "raw",
- "raw": "{\n\t\"param\": {\n\t}\n}"
- },
- "url": {
- "raw": "{{api-url}}/v4/projects/metadata/workManagementPermission",
- "host": [
- "{{api-url}}"
- ],
- "path": [
- "v4",
- "projects",
- "metadata",
- "workManagementPermission"
- ]
- },
- "description": "Certain fields are mandatory while creating project. If invalid request body is supplied this should return 422 status code."
- },
- "response": []
- },
- {
- "name": "Create work management permission with valid values",
+ "name": "Create work management permission with valid values 1",
"event": [
{
"listen": "test",
@@ -2993,7 +3094,7 @@
"exec": [
"pm.test(\"Status code is 201\", function () {",
" pm.response.to.have.status(201);",
- " pm.environment.set(\"workManagementPermissionId\", pm.response.json().result.content.id);",
+ " pm.environment.set(\"workManagementPermissionId\", pm.response.json().id);",
"});"
],
"type": "text/javascript"
@@ -3014,15 +3115,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n \"policy\": \"work.new.create\",\n \"permission\": {\n \"allowRule\": {\n \"projectRoles\": [\"customer\", \"copilot\"],\n \"topcoderRoles\": [\"Connect Manager\", \"Connect Admin\", \"administrator\"]\n },\n \"denyRule\": { \"projectRoles\": [\"copilot\"] }\n },\n \"projectTemplateId\": 1\n\t}\n}"
+ "raw": "{\n \"policy\": \"work.new.create\",\n \"permission\": {\n \"allowRule\": {\n \"projectRoles\": [\"customer\", \"copilot\"],\n \"topcoderRoles\": [\"Connect Manager\", \"Connect Admin\", \"administrator\"]\n },\n \"denyRule\": { \"projectRoles\": [\"copilot\"] }\n },\n \"projectTemplateId\": 1\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/workManagementPermission",
+ "raw": "{{api-url}}/projects/metadata/workManagementPermission",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"workManagementPermission"
@@ -3033,7 +3133,7 @@
"response": []
},
{
- "name": "Create work management permission with valid values",
+ "name": "Create work management permission with valid values 2",
"event": [
{
"listen": "test",
@@ -3042,7 +3142,7 @@
"exec": [
"pm.test(\"Status code is 201\", function () {",
" pm.response.to.have.status(201);",
- " pm.environment.set(\"workManagementPermissionId\", pm.response.json().result.content.id);",
+ " pm.environment.set(\"workManagementPermissionId\", pm.response.json().id);",
"});"
],
"type": "text/javascript"
@@ -3063,15 +3163,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n \"policy\": \"work.new.create\",\n \"permission\": {\n \"allowRule\": {\n \"projectRoles\": [\"customer\", \"copilot\"],\n \"topcoderRoles\": [\"Connect Manager\", \"Connect Admin\", \"administrator\"]\n },\n \"denyRule\": { \"projectRoles\": [\"copilot\"] }\n },\n \"projectTemplateId\": 2\n\t}\n}"
+ "raw": "{\n \"policy\": \"work.new.create\",\n \"permission\": {\n \"allowRule\": {\n \"projectRoles\": [\"customer\", \"copilot\"],\n \"topcoderRoles\": [\"Connect Manager\", \"Connect Admin\", \"administrator\"]\n },\n \"denyRule\": { \"projectRoles\": [\"copilot\"] }\n },\n \"projectTemplateId\": 2\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/workManagementPermission",
+ "raw": "{{api-url}}/projects/metadata/workManagementPermission",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"workManagementPermission"
@@ -3097,15 +3196,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n \"policy\": \"work.create\",\n \"permission\": {\n \"allowRule\": {\n \"projectRoles\": [\"customer\", \"copilot\"],\n \"topcoderRoles\": [\"Connect Manager\", \"Connect Admin\", \"administrator\"]\n },\n \"denyRule\": { \"projectRoles\": [\"copilot\"] }\n },\n \"projectTemplateId\": 1\n\t}\n}"
+ "raw": "{\n \"policy\": \"work.create\",\n \"permission\": {\n \"allowRule\": {\n \"projectRoles\": [\"customer\", \"copilot\"],\n \"topcoderRoles\": [\"Connect Manager\", \"Connect Admin\", \"administrator\"]\n },\n \"denyRule\": { \"projectRoles\": [\"copilot\"] }\n },\n \"projectTemplateId\": 1\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/workManagementPermission",
+ "raw": "{{api-url}}/projects/metadata/workManagementPermission",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"workManagementPermission"
@@ -3126,12 +3224,11 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/workManagementPermission/{{workManagementPermissionId}}",
+ "raw": "{{api-url}}/projects/metadata/workManagementPermission/{{workManagementPermissionId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"workManagementPermission",
@@ -3153,12 +3250,11 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/workManagementPermission",
+ "raw": "{{api-url}}/projects/metadata/workManagementPermission",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"workManagementPermission"
@@ -3179,12 +3275,11 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/workManagementPermission?filter=template",
+ "raw": "{{api-url}}/projects/metadata/workManagementPermission?filter=template",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"workManagementPermission"
@@ -3211,12 +3306,11 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/workManagementPermission?filter=invalid%3D2%26projectTemplateId%3D1",
+ "raw": "{{api-url}}/projects/metadata/workManagementPermission?filter=invalid%3D2%26projectTemplateId%3D1",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"workManagementPermission"
@@ -3243,12 +3337,11 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/workManagementPermission?filter=projectTemplateId%3D1",
+ "raw": "{{api-url}}/projects/metadata/workManagementPermission?filter=projectTemplateId%3D1",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"workManagementPermission"
@@ -3280,15 +3373,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n \"policy\": \"work.new.delete\",\n \"permission\": {\n \"allowRule\": {\n \"projectRoles\": [\"customer\", \"copilot\"],\n \"topcoderRoles\": [\"Connect Manager\", \"Connect Admin\", \"administrator\"]\n },\n \"denyRule\": { \"projectRoles\": [\"copilot\"] }\n },\n \"projectTemplateId\": 1\n\t}\n}"
+ "raw": "{\n \"policy\": \"work.new.delete\",\n \"permission\": {\n \"allowRule\": {\n \"projectRoles\": [\"customer\", \"copilot\"],\n \"topcoderRoles\": [\"Connect Manager\", \"Connect Admin\", \"administrator\"]\n },\n \"denyRule\": { \"projectRoles\": [\"copilot\"] }\n },\n \"projectTemplateId\": 1\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/workManagementPermission/{{workManagementPermissionId}}",
+ "raw": "{{api-url}}/projects/metadata/workManagementPermission/{{workManagementPermissionId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"workManagementPermission",
@@ -3314,12 +3406,11 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/workManagementPermission/{{workManagementPermissionId}}",
+ "raw": "{{api-url}}/projects/metadata/workManagementPermission/{{workManagementPermissionId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"workManagementPermission",
@@ -3331,7 +3422,8 @@
"response": []
}
],
- "description": "Requests for all things projects."
+ "description": "Requests for all things projects.",
+ "protocolProfileBehavior": {}
},
{
"name": "Permissions",
@@ -3347,12 +3439,11 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/9999/permissions",
+ "raw": "{{api-url}}/projects/9999/permissions",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"9999",
"permissions"
@@ -3378,17 +3469,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"userIds\": [40051331],\n\t\t\"role\": \"customer\"\n\t}\n}"
+ "raw": "{\n\t\t\"userIds\": [40051331],\n\t\t\"role\": \"customer\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/members/invite",
+ "raw": "{{api-url}}/projects/{{projectId}}/members/invite",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"members",
"invite"
]
@@ -3413,17 +3503,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"userId\": 40051331,\n\t\t\"status\": \"accepted\"\n\t}\n}"
+ "raw": "{\n\t\t\"userId\": 40051331,\n\t\t\"status\": \"accepted\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/members/invite",
+ "raw": "{{api-url}}/projects/{{projectId}}/members/invite",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"members",
"invite"
]
@@ -3443,14 +3532,13 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/permissions",
+ "raw": "{{api-url}}/projects/{{projectId}}/permissions",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"permissions"
]
},
@@ -3469,14 +3557,13 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/permissions",
+ "raw": "{{api-url}}/projects/{{projectId}}/permissions",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"permissions"
]
},
@@ -3506,7 +3593,8 @@
]
}
}
- ]
+ ],
+ "protocolProfileBehavior": {}
},
{
"name": "WorkManagementForTemplate",
@@ -3521,7 +3609,7 @@
"exec": [
"pm.test(\"Status code is 201\", function () {",
" pm.response.to.have.status(201);",
- " pm.environment.set(\"WorkStreamId1\", pm.response.json().result.content.id);",
+ " pm.environment.set(\"workStreamId1\", pm.response.json().id);",
"});"
]
}
@@ -3541,17 +3629,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project\",\n\t\t\"type\": \"generic\",\n\t\t\"status\": \"active\"\n\t}\n}"
+ "raw": "{\n\t\t\"name\": \"test project\",\n\t\t\"type\": \"generic\",\n\t\t\"status\": \"active\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams"
]
},
@@ -3575,17 +3662,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"userIds\": [40051331],\n\t\t\"role\": \"customer\"\n\t}\n}"
+ "raw": "{\n\t\t\"userIds\": [40051331],\n\t\t\"role\": \"customer\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/members/invite",
+ "raw": "{{api-url}}/projects/{{projectId}}/members/invite",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"members",
"invite"
]
@@ -3610,17 +3696,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"userId\": 40051331,\n\t\t\"status\": \"accepted\"\n\t}\n}"
+ "raw": "{\n\t\t\"userId\": 40051331,\n\t\t\"status\": \"accepted\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/members/invite",
+ "raw": "{{api-url}}/projects/{{projectId}}/members/invite",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"members",
"invite"
]
@@ -3645,19 +3730,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"work item - updated\"\n\t}\n}"
+ "raw": "{\n\t\t\"name\": \"work item - updated\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId1}}",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId1}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId1}}"
+ "{{workStreamId1}}"
]
},
"description": "Update the project name. Name should be updated successfully."
@@ -3680,19 +3764,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"work item - updated\"\n\t}\n}"
+ "raw": "{\n\t\t\"name\": \"work item - updated\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId1}}",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId1}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId1}}"
+ "{{workStreamId1}}"
]
},
"description": "Update the project name. Name should be updated successfully."
@@ -3715,19 +3798,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"work item - updated\"\n\t}\n}"
+ "raw": "{\n\t\t\"name\": \"work item - updated\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/workstreams/{{WorkStreamId1}}",
+ "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId1}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"workstreams",
- "{{WorkStreamId1}}"
+ "{{workStreamId1}}"
]
},
"description": "Update the project name. Name should be updated successfully."
@@ -3744,7 +3826,7 @@
"exec": [
"pm.test(\"Status code is 201\", function () {",
" pm.response.to.have.status(201);",
- " pm.environment.set(\"WorkStreamId2\", pm.response.json().result.content.id);",
+ " pm.environment.set(\"workStreamId2\", pm.response.json().id);",
"});"
]
}
@@ -3764,15 +3846,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project\",\n\t\t\"type\": \"generic\",\n\t\t\"status\": \"active\"\n\t}\n}"
+ "raw": "{\n\t\t\"name\": \"test project\",\n\t\t\"type\": \"generic\",\n\t\t\"status\": \"active\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/2/workstreams",
+ "raw": "{{api-url}}/projects/2/workstreams",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"2",
"workstreams"
@@ -3798,19 +3879,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project - updated\"\n\t}\n}"
+ "raw": "{\n\t\t\"name\": \"test project - updated\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/2/workstreams/{{WorkStreamId2}}",
+ "raw": "{{api-url}}/projects/2/workstreams/{{workStreamId2}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"2",
"workstreams",
- "{{WorkStreamId2}}"
+ "{{workStreamId2}}"
]
},
"description": "Update the project name. If user don't have permission to the project than it should return 403."
@@ -3833,19 +3913,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"work item - updated\"\n\t}\n}"
+ "raw": "{\n\t\t\"name\": \"work item - updated\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/2/workstreams/{{WorkStreamId2}}",
+ "raw": "{{api-url}}/projects/2/workstreams/{{workStreamId2}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"2",
"workstreams",
- "{{WorkStreamId2}}"
+ "{{workStreamId2}}"
]
},
"description": "Update the project name. Name should be updated successfully."
@@ -3853,143 +3932,16 @@
"response": []
}
],
- "description": "Requests for all things projects."
+ "description": "Requests for all things projects.",
+ "protocolProfileBehavior": {}
},
{
- "name": "EventHandling and Integration with Direct Project API",
- "item": [
- {
- "name": "mock direct projects",
- "request": {
- "method": "GET",
- "header": [
- {
- "key": "Authorization",
- "value": "Bearer {{jwt-token}}"
- },
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "url": {
- "raw": "https://api.topcoder-dev.com/v3/direct/projects",
- "protocol": "https",
- "host": [
- "api",
- "topcoder-dev",
- "com"
- ],
- "path": [
- "v3",
- "direct",
- "projects"
- ]
- }
- },
- "response": []
- },
- {
- "name": " Create direct project when a new project is successfully created",
- "request": {
- "method": "POST",
- "header": [
- {
- "key": "Authorization",
- "value": "Bearer {{jwt-token}}"
- },
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "body": {
- "mode": "raw",
- "raw": "{\n \"param\": {\n \"type\": \"generic\",\n \"description\": \"test project\",\n \"details\": {},\n \"billingAccountId\": 123,\n \"name\": \"test project1\"\n }\n}"
- },
- "url": {
- "raw": "{{api-url}}/v4/projects",
- "host": [
- "{{api-url}}"
- ],
- "path": [
- "v4",
- "projects"
- ]
- }
- },
- "response": []
- },
- {
- "name": "Response error from direct project service",
- "request": {
- "method": "POST",
- "header": [
- {
- "key": "Authorization",
- "value": "Bearer {{jwt-token}}"
- },
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "body": {
- "mode": "raw",
- "raw": "{\n \"param\": {\n \"userId\": 2, \n \"role\": \"copilot\"\n }\n}"
- },
- "url": {
- "raw": "{{api-url}}/v4/projects/1/members",
- "host": [
- "{{api-url}}"
- ],
- "path": [
- "v4",
- "projects",
- "1",
- "members"
- ]
- }
- },
- "response": []
- },
- {
- "name": " Add co-pilot when a co-pilot is added to a project",
- "request": {
- "method": "POST",
- "header": [
- {
- "key": "Authorization",
- "value": "Bearer {{jwt-token}}"
- },
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "body": {
- "mode": "raw",
- "raw": "{\n \"param\": {\n \"userId\": 2, \n \"role\": \"copilot\"\n }\n}"
- },
- "url": {
- "raw": "{{api-url}}/v4/projects/2/members",
- "host": [
- "{{api-url}}"
- ],
- "path": [
- "v4",
- "projects",
- "2",
- "members"
- ]
- }
- },
- "response": []
- },
+ "name": "EventHandling and Integration with Direct Project API",
+ "item": [
{
- "name": "remove copilot from direct project when editing project member role",
+ "name": "mock direct projects",
"request": {
- "method": "PATCH",
+ "method": "GET",
"header": [
{
"key": "Authorization",
@@ -4000,30 +3952,27 @@
"value": "application/json"
}
],
- "body": {
- "mode": "raw",
- "raw": " {\n \"param\": {\n \"role\": \"customer\",\n \"isPrimary\": true\n }\n } "
- },
"url": {
- "raw": "{{api-url}}/v4/projects/2/members/4",
+ "raw": "https://api.topcoder-dev.com/v3/direct/projects",
+ "protocol": "https",
"host": [
- "{{api-url}}"
+ "api",
+ "topcoder-dev",
+ "com"
],
"path": [
- "v4",
- "projects",
- "2",
- "members",
- "4"
+ "v3",
+ "direct",
+ "projects"
]
}
},
"response": []
},
{
- "name": " Sync billing account id with direct",
+ "name": " Create direct project when a new project is successfully created",
"request": {
- "method": "PATCH",
+ "method": "POST",
"header": [
{
"key": "Authorization",
@@ -4036,26 +3985,24 @@
],
"body": {
"mode": "raw",
- "raw": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name\"\n }\n}"
+ "raw": "{\n \"type\": \"generic\",\n \"description\": \"test project\",\n \"details\": {},\n \"billingAccountId\": 123,\n \"name\": \"test project1\"\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/2",
+ "raw": "{{api-url}}/projects",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
- "projects",
- "2"
+ "projects"
]
}
},
"response": []
},
{
- "name": "Delete co-pilot when a co-pilot is removed from a project",
+ "name": "Response error from direct project service",
"request": {
- "method": "DELETE",
+ "method": "POST",
"header": [
{
"key": "Authorization",
@@ -4068,31 +4015,24 @@
],
"body": {
"mode": "raw",
- "raw": ""
+ "raw": "{\n\n \"role\": \"copilot\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/2/members/4",
+ "raw": "{{api-url}}/projects/{{projectId}}/members",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "2",
- "members",
- "4"
+ "{{projectId}}",
+ "members"
]
}
},
"response": []
- }
- ]
- },
- {
- "name": "Project Phase",
- "item": [
+ },
{
- "name": "Create Phase",
+ "name": " Add co-pilot when a co-pilot is added to a project",
"request": {
"method": "POST",
"header": [
@@ -4107,27 +4047,26 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project phase\",\n\t\t\"status\": \"active\",\n\t\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\t\"budget\": 20,\n\t\t\"details\": {\n\t\t\t\"aDetails\": \"a details\"\n\t\t}\n\t}\n}"
+ "raw": "{\n\t\"role\": \"copilot\"\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/phases",
+ "raw": "{{api-url}}/projects/{{projectId}}/members",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
- "phases"
+ "{{projectId}}",
+ "members"
]
}
},
"response": []
},
{
- "name": "Create Phase with order",
+ "name": "remove copilot from direct project when editing project member role",
"request": {
- "method": "POST",
+ "method": "PATCH",
"header": [
{
"key": "Authorization",
@@ -4140,27 +4079,27 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project phase\",\n\t\t\"status\": \"active\",\n\t\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\t\"budget\": 20,\n\t\t\"details\": {\n\t\t\t\"aDetails\": \"a details\"\n\t\t},\n\t\t\"order\": 1\n\t}\n}"
+ "raw": " {\n \"role\": \"customer\",\n \"isPrimary\": true\n } "
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/phases",
+ "raw": "{{api-url}}/projects/{{projectId}}/members/{{memberId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
- "phases"
+ "{{projectId}}",
+ "members",
+ "{{memberId}}"
]
}
},
"response": []
},
{
- "name": "Create Phase with productTemplateId",
+ "name": " Sync billing account id with direct",
"request": {
- "method": "POST",
+ "method": "PATCH",
"header": [
{
"key": "Authorization",
@@ -4173,27 +4112,25 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project phase\",\n\t\t\"status\": \"active\",\n\t\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\t\"budget\": 20,\n\t\t\"details\": {\n\t\t\t\"aDetails\": \"a details\"\n\t\t},\n\t\t\"order\": 1,\n\t\t\"productTemplateId\": 1\n\t}\n}"
+ "raw": "{\n \"billingAccountId\": 9999, \n \"name\": \"new project name\"\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/phases",
+ "raw": "{{api-url}}/projects/{{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
- "phases"
+ "{{projectId}}"
]
}
},
"response": []
},
{
- "name": "List Phase DB",
+ "name": "Delete co-pilot when a co-pilot is removed from a project",
"request": {
- "method": "GET",
+ "method": "DELETE",
"header": [
{
"key": "Authorization",
@@ -4204,26 +4141,72 @@
"value": "application/json"
}
],
+ "body": {
+ "mode": "raw",
+ "raw": ""
+ },
"url": {
- "raw": "{{api-url}}/v4/projects/1/phases/db",
+ "raw": "{{api-url}}/projects/{{projectId}}/members/{{memberId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
- "phases",
- "db"
+ "{{projectId}}",
+ "members",
+ "{{memberId}}"
]
}
},
"response": []
+ }
+ ],
+ "event": [
+ {
+ "listen": "prerequest",
+ "script": {
+ "id": "ef96ac6a-0fc0-4a64-a4fe-5390e17afe67",
+ "type": "text/javascript",
+ "exec": [
+ ""
+ ]
+ }
},
{
- "name": "List Phase DB with fields",
+ "listen": "test",
+ "script": {
+ "id": "12f9d794-0872-4058-aafa-77b89e72025b",
+ "type": "text/javascript",
+ "exec": [
+ ""
+ ]
+ }
+ }
+ ],
+ "protocolProfileBehavior": {}
+ },
+ {
+ "name": "Project Phase",
+ "item": [
+ {
+ "name": "Create Phase",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "7050133a-b934-4faf-8101-d2e80b5c0710",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"phaseId\", pm.response.json().id);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
- "method": "GET",
+ "method": "POST",
"header": [
{
"key": "Authorization",
@@ -4234,32 +4217,43 @@
"value": "application/json"
}
],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n\t\"name\": \"test project phase\",\n\t\"status\": \"active\",\n\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\"budget\": 20,\n\t\"details\": {\n\t\t\"aDetails\": \"a details\"\n\t}\n}"
+ },
"url": {
- "raw": "{{api-url}}/v4/projects/1/phases/db?fields=status,name,budget",
+ "raw": "{{api-url}}/projects/{{projectId}}/phases",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
- "phases",
- "db"
- ],
- "query": [
- {
- "key": "fields",
- "value": "status,name,budget"
- }
+ "{{projectId}}",
+ "phases"
]
}
},
"response": []
},
{
- "name": "List Phase DB with sort",
+ "name": "Create Phase with order",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "2f771afe-7b4e-4260-b04d-324e880eb61b",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"phaseId\", pm.response.json().id);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
- "method": "GET",
+ "method": "POST",
"header": [
{
"key": "Authorization",
@@ -4270,32 +4264,43 @@
"value": "application/json"
}
],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n\t\"name\": \"test project phase\",\n\t\"status\": \"active\",\n\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\"budget\": 20,\n\t\"details\": {\n\t\t\"aDetails\": \"a details\"\n\t},\n\t\"order\": 1\n}"
+ },
"url": {
- "raw": "{{api-url}}/v4/projects/1/phases/db?sort=status desc",
+ "raw": "{{api-url}}/projects/{{projectId}}/phases",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
- "phases",
- "db"
- ],
- "query": [
- {
- "key": "sort",
- "value": "status desc"
- }
+ "{{projectId}}",
+ "phases"
]
}
},
"response": []
},
{
- "name": "List Phase DB with sort by order",
+ "name": "Create Phase with productTemplateId",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "8415ad98-b3f6-4330-88b6-e1830da2e4f9",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"phaseId\", pm.response.json().id);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
- "method": "GET",
+ "method": "POST",
"header": [
{
"key": "Authorization",
@@ -4306,23 +4311,19 @@
"value": "application/json"
}
],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n\t\"name\": \"test project phase\",\n\t\"status\": \"active\",\n\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\"budget\": 20,\n\t\"details\": {\n\t\t\"aDetails\": \"a details\"\n\t},\n\t\"order\": 1,\n\t\"productTemplateId\": {{productTemplateId}}\n}"
+ },
"url": {
- "raw": "{{api-url}}/v4/projects/1/phases/db?sort=order desc",
+ "raw": "{{api-url}}/projects/{{projectId}}/phases",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
- "phases",
- "db"
- ],
- "query": [
- {
- "key": "sort",
- "value": "order desc"
- }
+ "{{projectId}}",
+ "phases"
]
}
},
@@ -4343,14 +4344,13 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/phases",
+ "raw": "{{api-url}}/projects/{{projectId}}/phases",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"phases"
]
}
@@ -4372,14 +4372,13 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/phases?fields=status,name,budget",
+ "raw": "{{api-url}}/projects/{{projectId}}/phases?fields=status,name,budget",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"phases"
],
"query": [
@@ -4407,14 +4406,13 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/phases?sort=status desc",
+ "raw": "{{api-url}}/projects/{{projectId}}/phases?sort=status desc",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"phases"
],
"query": [
@@ -4442,14 +4440,13 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/phases?sort=order desc",
+ "raw": "{{api-url}}/projects/{{projectId}}/phases?sort=order desc",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"phases"
],
"query": [
@@ -4477,16 +4474,15 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/phases/1",
+ "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"phases",
- "1"
+ "{{phaseId}}"
]
}
},
@@ -4508,19 +4504,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project phase xxx\",\n\t\t\"status\": \"inactive\",\n\t\t\"startDate\": \"2018-05-14T00:00:00\",\n\t\t\"endDate\": \"2018-05-15T00:00:00\",\n\t\t\"budget\": 30,\n\t\t\"progress\": 15,\n\t\t\"details\": {\n\t\t\t\"message\": \"phase details\"\n\t\t}\n\t}\n}"
+ "raw": "{\n\t\"name\": \"test project phase xxx\",\n\t\"status\": \"inactive\",\n\t\"startDate\": \"2018-05-14T00:00:00\",\n\t\"endDate\": \"2018-05-15T00:00:00\",\n\t\"budget\": 30,\n\t\"progress\": 15,\n\t\"details\": {\n\t\t\"message\": \"phase details\"\n\t}\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/phases/1",
+ "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"phases",
- "1"
+ "{{phaseId}}"
]
}
},
@@ -4542,19 +4537,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project phase xxx\",\n\t\t\"status\": \"inactive\",\n\t\t\"startDate\": \"2018-05-14T00:00:00\",\n\t\t\"endDate\": \"2018-05-15T00:00:00\",\n\t\t\"budget\": 30,\n\t\t\"progress\": 15,\n\t\t\"details\": {\n\t\t\t\"message\": \"phase details\"\n\t\t},\n\t\t\"order\": 1\n\t}\n}"
+ "raw": "{\n\t\"name\": \"test project phase xxx\",\n\t\"status\": \"inactive\",\n\t\"startDate\": \"2018-05-14T00:00:00\",\n\t\"endDate\": \"2018-05-15T00:00:00\",\n\t\"budget\": 30,\n\t\"progress\": 15,\n\t\"details\": {\n\t\t\"message\": \"phase details\"\n\t},\n\t\"order\": 1\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/phases/1",
+ "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"phases",
- "1"
+ "{{phaseId}}"
]
}
},
@@ -4579,28 +4573,43 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/phases/3",
+ "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"phases",
- "3"
+ "{{phaseId}}"
]
}
},
"response": []
}
- ]
+ ],
+ "protocolProfileBehavior": {}
},
{
"name": "Phase Products",
"item": [
{
"name": "Create Phase Product",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "77f089b3-cbe6-4fb4-b54f-2a52d138a050",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"phaseProductId\", pm.response.json().id);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
@@ -4615,53 +4624,24 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test phase product\",\n\t\t\"type\": \"type 1\",\n\t\t\"estimatedPrice\": 10\n\t}\n}"
+ "raw": "{\n\t\"name\": \"test phase product\",\n\t\"type\": \"type 1\",\n\t\"estimatedPrice\": 10\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/phases/1/products",
+ "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"phases",
- "1",
+ "{{phaseId}}",
"products"
]
}
},
"response": []
},
- {
- "name": "List Phase DB Products",
- "request": {
- "method": "GET",
- "header": [
- {
- "key": "Authorization",
- "value": "Bearer {{jwt-token}}"
- }
- ],
- "url": {
- "raw": "{{api-url}}/v4/projects/1/phases/1/products/db",
- "host": [
- "{{api-url}}"
- ],
- "path": [
- "v4",
- "projects",
- "1",
- "phases",
- "1",
- "products",
- "db"
- ]
- }
- },
- "response": []
- },
{
"name": "List Phase Products",
"request": {
@@ -4673,16 +4653,15 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/phases/1/products",
+ "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"phases",
- "1",
+ "{{phaseId}}",
"products"
]
}
@@ -4700,18 +4679,17 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/phases/1/products/1",
+ "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products/{{phaseProductId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"phases",
- "1",
+ "{{phaseId}}",
"products",
- "1"
+ "{{phaseProductId}}"
]
}
},
@@ -4733,21 +4711,20 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test phase product xxx\",\n\t\t\"type\": \"type 2\",\n\t\t\"templateId\": 10,\n\t\t\"estimatedPrice\": 1.234567,\n\t\t\"actualPrice\": 2.34567,\n\t\t\"details\": {\n\t\t\t\"message\": \"this is a JSON type. You can use any json\"\n\t\t}\n\t}\n}"
+ "raw": "{\n\t\"name\": \"test phase product xxx\",\n\t\"type\": \"type 2\",\n\t\"templateId\": 10,\n\t\"estimatedPrice\": 1.234567,\n\t\"actualPrice\": 2.34567,\n\t\"details\": {\n\t\t\"message\": \"this is a JSON type. You can use any json\"\n\t}\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/phases/1/products/1",
+ "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products/{{phaseProductId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"phases",
- "1",
+ "{{phaseId}}",
"products",
- "1"
+ "{{phaseProductId}}"
]
}
},
@@ -4768,30 +4745,45 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/phases/1/products/1",
+ "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products/{{phaseProductId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"phases",
- "1",
+ "{{phaseId}}",
"products",
- "1"
+ "{{phaseProductId}}"
]
}
},
"response": []
}
- ]
+ ],
+ "protocolProfileBehavior": {}
},
{
"name": "Project Templates",
"item": [
{
"name": "Create project template",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "2f79c07b-8076-4715-abf7-1d6903df444f",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"projectTemplateId\", pm.response.json().id);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
@@ -4806,15 +4798,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"scope\":{\r\n \"scope1\":\"scope 1\"\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\"\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"scope\":{\r\n \"scope1\":\"scope 1\"\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\"\r\n }\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/projectTemplates",
+ "raw": "{{api-url}}/projects/metadata/projectTemplates",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"projectTemplates"
@@ -4825,6 +4816,21 @@
},
{
"name": "Create project template with form, priceConfig, planConfig",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "4c442ea3-0834-4a30-8044-a4e94fd4ea2d",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"projectTemplateId\", pm.response.json().id);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
@@ -4839,15 +4845,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1\r\n }\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/projectTemplates",
+ "raw": "{{api-url}}/projects/metadata/projectTemplates",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"projectTemplates"
@@ -4858,6 +4863,21 @@
},
{
"name": "Create project template with only form key",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "7d0ae3ca-fe2d-40eb-b5c8-9b03955babec",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"projectTemplateId\", pm.response.json().id);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
@@ -4872,15 +4892,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"dev\"\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"dev\"\r\n }\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/projectTemplates",
+ "raw": "{{api-url}}/projects/metadata/projectTemplates",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"projectTemplates"
@@ -4905,15 +4924,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"wrong-key\"\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"wrong-key\"\r\n }\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/projectTemplates",
+ "raw": "{{api-url}}/projects/metadata/projectTemplates",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"projectTemplates"
@@ -4938,15 +4956,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1123\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1123\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1123\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1123\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1123\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1123\r\n }\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/projectTemplates",
+ "raw": "{{api-url}}/projects/metadata/projectTemplates",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"projectTemplates"
@@ -4970,12 +4987,11 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/projectTemplates",
+ "raw": "{{api-url}}/projects/metadata/projectTemplates",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"projectTemplates"
@@ -4999,16 +5015,15 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/projectTemplates/1",
+ "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"projectTemplates",
- "1"
+ "{{projectTemplateId}}"
]
}
},
@@ -5030,19 +5045,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 2\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"qa\",\t\r\n \t \"version\": 2\t\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 2\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"qa\",\t\r\n \t \"version\": 2\t\r\n }\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/projectTemplates/2/upgrade",
+ "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}/upgrade",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"projectTemplates",
- "2",
+ "{{projectTemplateId}}",
"upgrade"
]
}
@@ -5065,19 +5079,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1234\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1234\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1234\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1234\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1234\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1234\r\n }\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/projectTemplates/1/upgrade",
+ "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}/upgrade",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"projectTemplates",
- "1",
+ "{{projectTemplateId}}",
"upgrade"
]
}
@@ -5100,19 +5113,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{ \r\n }\r\n}"
+ "raw": "{\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/projectTemplates/3/upgrade",
+ "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}/upgrade",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"projectTemplates",
- "3",
+ "{{projectTemplateId}}",
"upgrade"
]
}
@@ -5135,19 +5147,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"scope\":{\r\n \"scope1\":\"scope 1\",\r\n \"scope2\": [\"a\"]\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\",\r\n \"phase2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"scope\":{\r\n \"scope1\":\"scope 1\",\r\n \"scope2\": [\"a\"]\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\",\r\n \"phase2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/projectTemplates/1",
+ "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"projectTemplates",
- "1"
+ "{{projectTemplateId}}"
]
}
},
@@ -5169,19 +5180,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"form\": {\r\n \"key\": \"dev\",\r\n \"version\": 1\r\n },\r\n \"priceConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1\r\n },\r\n \"planConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"form\": {\r\n \"key\": \"dev\",\r\n \"version\": 1\r\n },\r\n \"priceConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1\r\n },\r\n \"planConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1\r\n }\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/projectTemplates/1",
+ "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"projectTemplates",
- "1"
+ "{{projectTemplateId}}"
]
}
},
@@ -5203,19 +5213,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n\t\"form\": {\r\n \"key\": \"dev\",\r\n \"version\": 1123\r\n },\r\n \"priceConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1123\r\n },\r\n \"planConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1123\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n\t\"form\": {\r\n \"key\": \"dev\",\r\n \"version\": 1123\r\n },\r\n \"priceConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1123\r\n },\r\n \"planConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1123\r\n }\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/projectTemplates/1",
+ "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"projectTemplates",
- "1"
+ "{{projectTemplateId}}"
]
}
},
@@ -5237,31 +5246,46 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"new category\",\r\n \"scope\":{\r\n \"scope1\":\"scope 1\",\r\n \"scope2\": [\"a\"]\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\",\r\n \"phase2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"new category\",\r\n \"scope\":{\r\n \"scope1\":\"scope 1\",\r\n \"scope2\": [\"a\"]\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\",\r\n \"phase2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/projectTemplates/2",
+ "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"projectTemplates",
- "2"
+ "{{projectTemplateId}}"
]
}
},
"response": []
}
- ]
+ ],
+ "protocolProfileBehavior": {}
},
{
"name": "Product Templates",
"item": [
{
"name": "Create product template",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "b5aaf185-6026-4b58-b9b8-56616109cd5a",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"productTemplateId\", pm.response.json().id);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
@@ -5276,15 +5300,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\": {\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"template\": {\r\n \"template1\": {\r\n \"name\": \"template 1\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 1\"\r\n },\r\n \"others\": [\"others 11\", \"others 12\"]\r\n },\r\n \"template2\": {\r\n \"name\": \"template 2\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 2\"\r\n },\r\n \"others\": [\"others 21\", \"others 22\"]\r\n }\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"{{productCategoryId}}\",\r\n \"subCategory\": \"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"template\": {\r\n \"template1\": {\r\n \"name\": \"template 1\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 1\"\r\n },\r\n \"others\": [\"others 11\", \"others 12\"]\r\n },\r\n \"template2\": {\r\n \"name\": \"template 2\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 2\"\r\n },\r\n \"others\": [\"others 21\", \"others 22\"]\r\n }\r\n }\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/productTemplates",
+ "raw": "{{api-url}}/projects/metadata/productTemplates",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"productTemplates"
@@ -5295,6 +5318,21 @@
},
{
"name": "Create product template with form",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "d5a2af2e-97d2-415c-a533-1d52dd4003c7",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"productTemplateId\", pm.response.json().id);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
@@ -5309,15 +5347,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\": {\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"form\": {\r\n\t\t\"key\": \"dev\",\r\n\t\t\"version\": 1\r\n\t}\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"form\": {\r\n\t\t\"key\": \"dev\",\r\n\t\t\"version\": 1\r\n\t}\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/productTemplates",
+ "raw": "{{api-url}}/projects/metadata/productTemplates",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"productTemplates"
@@ -5342,15 +5379,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\": {\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"form\": {\r\n\t\t\"key\": \"wrong-key\"\r\n\t}\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"form\": {\r\n\t\t\"key\": \"wrong-key\"\r\n\t}\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/productTemplates",
+ "raw": "{{api-url}}/projects/metadata/productTemplates",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"productTemplates"
@@ -5375,15 +5411,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\": {\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"form\": {\r\n\t\t\"key\": \"dev\",\r\n\t\t\"version\": 1123\r\n\t}\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"form\": {\r\n\t\t\"key\": \"dev\",\r\n\t\t\"version\": 1123\r\n\t}\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/productTemplates",
+ "raw": "{{api-url}}/projects/metadata/productTemplates",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"productTemplates"
@@ -5407,12 +5442,11 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/productTemplates",
+ "raw": "{{api-url}}/projects/metadata/productTemplates",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"productTemplates"
@@ -5436,16 +5470,15 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/productTemplates/3",
+ "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"productTemplates",
- "3"
+ "{{productTemplateId}}"
]
}
},
@@ -5467,19 +5500,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"productKey\":\"new productKey\",\r\n \"category\":\"key1\",\r\n \"icon\":\"http://example.com/icon-new.ico\",\r\n \"brief\": \"new brief\",\r\n \"details\": \"new details\",\r\n \"aliases\":{\r\n \"alias1\":\"scope 1\",\r\n \"alias2\": [\"a\"]\r\n },\r\n \"template\":{\r\n \"template1\":\"template 1\",\r\n \"template2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"name\":\"new name\",\r\n \"productKey\":\"new productKey\",\r\n \"category\":\"key1\",\r\n \"icon\":\"http://example.com/icon-new.ico\",\r\n \"brief\": \"new brief\",\r\n \"details\": \"new details\",\r\n \"aliases\":{\r\n \"alias1\":\"scope 1\",\r\n \"alias2\": [\"a\"]\r\n },\r\n \"template\":{\r\n \"template1\":\"template 1\",\r\n \"template2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/productTemplates/1",
+ "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"productTemplates",
- "1"
+ "{{productTemplateId}}"
]
}
},
@@ -5504,16 +5536,15 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/productTemplates/1",
+ "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"productTemplates",
- "1"
+ "{{productTemplateId}}"
]
}
},
@@ -5537,19 +5568,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t\"version\": 2\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t\"version\": 2\r\n }\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/productTemplates/2/upgrade",
+ "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}/upgrade",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"productTemplates",
- "2",
+ "{{productTemplateId}}",
"upgrade"
]
}
@@ -5574,19 +5604,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t\"version\": 1234\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t\"version\": 1234\r\n }\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/productTemplates/1/upgrade",
+ "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}/upgrade",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"productTemplates",
- "1",
+ "{{productTemplateId}}",
"upgrade"
]
}
@@ -5611,32 +5640,47 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{ \r\n }\r\n}"
+ "raw": "{\r\n \r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/productTemplates/3/upgrade",
+ "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}/upgrade",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"productTemplates",
- "3",
+ "{{productTemplateId}}",
"upgrade"
]
}
},
"response": []
}
- ]
+ ],
+ "protocolProfileBehavior": {}
},
{
"name": "Project Type",
"item": [
{
"name": "Create project type",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "fbc45946-a3f2-433a-8ec5-0af82b69d2bd",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"projectTypeId\", pm.response.json().key);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
@@ -5651,15 +5695,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"key\": \"new key\",\r\n \"displayName\": \"new displayName\",\r\n \"icon\": \"http://example.com/icon4.ico\",\r\n \t\"question\": \"question 4\",\r\n \t\"info\": \"info 4\",\r\n \t\"aliases\": [\"key-41\", \"key_42\"],\r\n \t\"metadata\": {}\r\n }\r\n}"
+ "raw": "{\r\n \"key\": \"new key\",\r\n \"displayName\": \"new displayName\",\r\n \"icon\": \"http://example.com/icon4.ico\",\r\n \t\"question\": \"question 4\",\r\n \t\"info\": \"info 4\",\r\n \t\"aliases\": [\"key-41\", \"key_42\"],\r\n \t\"metadata\": {}\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/projectTypes",
+ "raw": "{{api-url}}/projects/metadata/projectTypes",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"projectTypes"
@@ -5683,12 +5726,11 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/projectTypes",
+ "raw": "{{api-url}}/projects/metadata/projectTypes",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"projectTypes"
@@ -5712,16 +5754,15 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/projectTypes/generic",
+ "raw": "{{api-url}}/projects/metadata/projectTypes/{{projectTypeId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"projectTypes",
- "generic"
+ "{{projectTypeId}}"
]
}
},
@@ -5743,19 +5784,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"displayName\": \"Chatbot-updated\"\r\n }\r\n}"
+ "raw": "{\r\n \"displayName\": \"Chatbot-updated\"\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/projectTypes/chatbot",
+ "raw": "{{api-url}}/projects/metadata/projectTypes/{{projectTypeId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"projectTypes",
- "chatbot"
+ "{{projectTypeId}}"
]
}
},
@@ -5780,28 +5820,43 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/projectTypes/chatbot",
+ "raw": "{{api-url}}/projects/metadata/projectTypes/{{projectTypeId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"projectTypes",
- "chatbot"
+ "{{projectTypeId}}"
]
}
},
"response": []
}
- ]
+ ],
+ "protocolProfileBehavior": {}
},
{
"name": "Product Category",
"item": [
{
"name": "Create product category",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "06156797-ceb2-4f8c-9448-5c453adb7b7a",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"productCategoryId\", pm.response.json().key);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
@@ -5816,15 +5871,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"key\": \"generic\",\r\n \"displayName\": \"new displayName\",\r\n \"icon\": \"icon\",\r\n \"question\": \"question\",\r\n \"info\": \"info\",\r\n \"aliases\": [\"key-1\", \"key-2\"]\r\n }\r\n}"
+ "raw": "{\r\n \"key\": \"generic\",\r\n \"displayName\": \"new displayName\",\r\n \"icon\": \"icon\",\r\n \"question\": \"question\",\r\n \"info\": \"info\",\r\n \"aliases\": [\"key-1\", \"key-2\"]\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/productCategories",
+ "raw": "{{api-url}}/projects/metadata/productCategories",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"productCategories"
@@ -5848,12 +5902,11 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/productCategories",
+ "raw": "{{api-url}}/projects/metadata/productCategories",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"productCategories"
@@ -5877,16 +5930,15 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/productCategories/generic",
+ "raw": "{{api-url}}/projects/metadata/productCategories/{{productCategoryId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"productCategories",
- "generic"
+ "{{productCategoryId}}"
]
}
},
@@ -5908,19 +5960,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"displayName\": \"Chatbot-updated\"\r\n }\r\n}"
+ "raw": "{\r\n \"displayName\": \"Chatbot-updated\"\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/productCategories/generic",
+ "raw": "{{api-url}}/projects/metadata/productCategories/{{productCategoryId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"productCategories",
- "generic"
+ "{{productCategoryId}}"
]
}
},
@@ -5942,25 +5993,57 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"new category\",\r\n \"scope\":{\r\n \"scope1\":\"scope 1\",\r\n \"scope2\": [\"a\"]\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\",\r\n \"phase2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"new category\",\r\n \"scope\":{\r\n \"scope1\":\"scope 1\",\r\n \"scope2\": [\"a\"]\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\",\r\n \"phase2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/productCategories/generic",
+ "raw": "{{api-url}}/projects/metadata/productCategories/{{productCategoryId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"productCategories",
- "generic"
+ "{{productCategoryId}}"
]
}
},
"response": []
}
- ]
+ ],
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJwc2hhaDEiLCJleHAiOjI0NjI0OTQ2MTgsInVzZXJJZCI6IjQwMTM1OTc4IiwiaWF0IjoxNDYyNDk0MDE4LCJlbWFpbCI6InBzaGFoMUB0ZXN0LmNvbSIsImp0aSI6ImY0ZTFhNTE0LTg5ODAtNDY0MC04ZWM1LWUzNmUzMWE3ZTg0OSJ9.XuNN7tpMOXvBG1QwWRQROj7NfuUbqhkjwn39Vy4tR5I",
+ "type": "string"
+ }
+ ]
+ },
+ "event": [
+ {
+ "listen": "prerequest",
+ "script": {
+ "id": "f0092ef5-e624-4c25-87b2-b6a9e4c81ec8",
+ "type": "text/javascript",
+ "exec": [
+ ""
+ ]
+ }
+ },
+ {
+ "listen": "test",
+ "script": {
+ "id": "9183c429-a5e0-4bf9-96a2-89f4d66e9b0d",
+ "type": "text/javascript",
+ "exec": [
+ ""
+ ]
+ }
+ }
+ ],
+ "protocolProfileBehavior": {}
},
{
"name": "Project upgrade",
@@ -5981,17 +6064,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3\n\t}\n}"
+ "raw": "{\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3\n\t}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/6/upgrade",
+ "raw": "{{api-url}}/projects/{{projectId}}/upgrade",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "6",
+ "{{projectId}}",
"upgrade"
]
}
@@ -6014,17 +6096,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3\n\t}\n}"
+ "raw": "{\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3\n\t}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/7/upgrade",
+ "raw": "{{api-url}}/projects/{{projectId}}/upgrade",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "7",
+ "{{projectId}}",
"upgrade"
]
}
@@ -6047,17 +6128,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3,\n\t\t\"phaseName\": \"Custom phase name\"\n\t}\n}"
+ "raw": "{\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3,\n\t\t\"phaseName\": \"Custom phase name\"\n\t}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/6/upgrade",
+ "raw": "{{api-url}}/projects/{{projectId}}/upgrade",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "6",
+ "{{projectId}}",
"upgrade"
]
}
@@ -6080,17 +6160,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"param\": {\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3,\n\t\t\"phaseName\": \"Custom phase name\"\n\t}\n}"
+ "raw": "{\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3,\n\t\t\"phaseName\": \"Custom phase name\"\n\t}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/7/upgrade",
+ "raw": "{{api-url}}/projects/{{projectId}}/upgrade",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "7",
+ "{{projectId}}",
"upgrade"
]
}
@@ -6098,13 +6177,29 @@
"response": []
}
],
- "description": "Request to migrate projects."
+ "description": "Request to migrate projects.",
+ "protocolProfileBehavior": {}
},
{
"name": "Timeline",
"item": [
{
"name": "Create timeline",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "c066e7d4-537f-406e-a768-ec4bf73a2634",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"timelineId\", pm.response.json().id);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
@@ -6119,15 +6214,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"description\":\"new description\",\r\n \"startDate\":\"2018-05-29T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-30T00:00:00.000Z\",\r\n \"reference\": \"project\",\r\n \"referenceId\": 1\r\n }\r\n}"
+ "raw": "{\r\n\t\"name\":\"new name\",\r\n\t\"description\":\"new description\",\r\n\t\"startDate\":\"2018-05-29T00:00:00.000Z\",\r\n\t\"endDate\": \"2018-05-30T00:00:00.000Z\",\r\n\t\"reference\": \"project\",\r\n\t\"referenceId\": {{projectId}}\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines",
+ "raw": "{{api-url}}/timelines",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines"
]
}
@@ -6136,6 +6230,21 @@
},
{
"name": "Create timeline with templateId",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "ee729ed9-0072-4821-9141-3615ff66f728",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"timelineId\", pm.response.json().id);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
@@ -6150,15 +6259,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"description\":\"new description\",\r\n \"startDate\":\"2018-05-29T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-30T00:00:00.000Z\",\r\n \"reference\": \"project\",\r\n \"referenceId\": 1,\r\n \"templateId\": 1\r\n }\r\n}"
+ "raw": "{\r\n \"name\":\"new name\",\r\n \"description\":\"new description\",\r\n \"startDate\":\"2018-05-29T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-30T00:00:00.000Z\",\r\n \"reference\": \"project\",\r\n \"referenceId\": 1,\r\n \"templateId\": 1\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines",
+ "raw": "{{api-url}}/timelines",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines"
]
}
@@ -6181,15 +6289,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"startDate\":\"2018-05-29T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-28T00:00:00.000Z\",\r\n \"reference\": \"invalid\",\r\n \"referenceId\": 0\r\n }\r\n}"
+ "raw": "{\r\n \"startDate\":\"2018-05-29T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-28T00:00:00.000Z\",\r\n \"reference\": \"invalid\",\r\n \"referenceId\": 0\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines",
+ "raw": "{{api-url}}/timelines",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines"
]
}
@@ -6207,22 +6314,31 @@
},
{
"key": "Authorization",
- "value": "Bearer {{jwt-token-copilot-40051332}}"
+ "value": "Bearer {{jwt-token-copilot-40051332}}",
+ "disabled": true
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{jwt-token}}",
+ "type": "text"
}
],
"url": {
- "raw": "{{api-url}}/v4/timelines?filter=reference%3Dphase%26referenceId%3D1",
+ "raw": "{{api-url}}/timelines?reference=project&referenceId={{projectId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines"
],
"query": [
{
- "key": "filter",
- "value": "reference%3Dphase%26referenceId%3D1"
+ "key": "reference",
+ "value": "project"
+ },
+ {
+ "key": "referenceId",
+ "value": "{{projectId}}"
}
]
}
@@ -6244,14 +6360,40 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/timelines/1",
+ "raw": "{{api-url}}/timelines/{{timelineId}}",
+ "host": [
+ "{{api-url}}"
+ ],
+ "path": [
+ "timelines",
+ "{{timelineId}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Get timeline from DB",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{jwt-token}}"
+ }
+ ],
+ "url": {
+ "raw": "{{api-url}}/timelines/{{timelineId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
- "1"
+ "{{timelineId}}"
]
}
},
@@ -6273,17 +6415,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\": \"timeline 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"startDate\": \"2018-05-01T00:00:00.000Z\",\r\n \"endDate\": null,\r\n \"reference\": \"project\",\r\n \"referenceId\": 1\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"timeline 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"startDate\": \"2018-05-01T00:00:00.000Z\",\r\n \"endDate\": null,\r\n \"reference\": \"project\",\r\n \"referenceId\": {{projectId}}\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/1",
+ "raw": "{{api-url}}/timelines/{{timelineId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
- "1"
+ "{{timelineId}}"
]
}
},
@@ -6305,17 +6446,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\": \"timeline 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"startDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"endDate\": null,\r\n \"reference\": \"project\",\r\n \"referenceId\": 1\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"timeline 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"startDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"reference\": \"project\",\r\n \"referenceId\": 1\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/1",
+ "raw": "{{api-url}}/timelines/{{timelineId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
- "1"
+ "{{timelineId}}"
]
}
},
@@ -6337,17 +6477,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\": \"timeline 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"startDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"reference\": \"project\",\r\n \"referenceId\": 1\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"timeline 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"startDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"reference\": \"project\",\r\n \"referenceId\": 1\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/1",
+ "raw": "{{api-url}}/timelines/{{timelineId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
- "1"
+ "{{timelineId}}"
]
}
},
@@ -6372,26 +6511,41 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/timelines/4",
+ "raw": "{{api-url}}/timelines/{{timelineId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
- "4"
+ "{{timelineId}}"
]
}
},
"response": []
}
- ]
+ ],
+ "protocolProfileBehavior": {}
},
{
"name": "Milestone",
"item": [
{
"name": "Create milestone",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "8fd1d5e9-8e6e-4cd7-9010-b855308be069",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"milestoneId\", pm.response.json().id);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
@@ -6401,22 +6555,21 @@
},
{
"key": "Authorization",
- "value": "Bearer {{jwt-token-member-40051331}}"
+ "value": "Bearer {{jwt-token-admin-40051333}}"
}
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 3\",\r\n \"description\": \"description 3\",\r\n \"duration\": 4,\r\n \"startDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-08T00:00:00.000Z\",\r\n \"status\": \"open\",\r\n \"type\": \"type3\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 2,\r\n 3,\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 3\",\r\n \"activeText\": \"activeText 3\",\r\n \"completedText\": \"completedText 3\",\r\n \"blockedText\": \"blockedText 3\"\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"milestone 3\",\r\n \"description\": \"description 3\",\r\n \"duration\": 4,\r\n \"startDate\": \"2018-05-29T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-30T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-31T00:00:00.000Z\",\r\n \"status\": \"open\",\r\n \"type\": \"type3\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 2,\r\n 3,\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 3\",\r\n \"activeText\": \"activeText 3\",\r\n \"completedText\": \"completedText 3\",\r\n \"blockedText\": \"blockedText 3\"\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/1/milestones",
+ "raw": "{{api-url}}/timelines/{{timelineId}}/milestones",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
- "1",
+ "{{timelineId}}",
"milestones"
]
}
@@ -6439,17 +6592,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"startDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-04T00:00:00.000Z\"\r\n }\r\n}"
+ "raw": "{\r\n \"startDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-04T00:00:00.000Z\"\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/1/milestones",
+ "raw": "{{api-url}}/timelines/{{timelineId}}/milestones",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
- "1",
+ "{{timelineId}}",
"milestones"
]
}
@@ -6467,18 +6619,18 @@
},
{
"key": "Authorization",
- "value": "Bearer {{jwt-token-copilot-40051332}}"
+ "value": "Bearer {{jwt-token}}",
+ "type": "text"
}
],
"url": {
- "raw": "{{api-url}}/v4/timelines/1/milestones",
+ "raw": "{{api-url}}/timelines/{{timelineId}}/milestones",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
- "1",
+ "{{timelineId}}",
"milestones"
]
}
@@ -6500,14 +6652,13 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/timelines/1/milestones?sort=order desc",
+ "raw": "{{api-url}}/timelines/{{timelineId}}/milestones?sort=order desc",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
- "1",
+ "{{timelineId}}",
"milestones"
],
"query": [
@@ -6535,16 +6686,15 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/timelines/1/milestones/1",
+ "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
- "1",
+ "{{timelineId}}",
"milestones",
- "1"
+ "{{milestoneId}}"
]
}
},
@@ -6566,19 +6716,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-09-28T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-09-28T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/1/milestones/1",
+ "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
- "1",
+ "{{timelineId}}",
"milestones",
- "1"
+ "{{milestoneId}}"
]
}
},
@@ -6600,19 +6749,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"status\": \"paused\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"statusComment\": \"milestone paused\"\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"status\": \"paused\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"statusComment\": \"milestone paused\"\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/1/milestones/1",
+ "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
- "1",
+ "{{timelineId}}",
"milestones",
- "1"
+ "{{milestoneId}}"
]
}
},
@@ -6634,19 +6782,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"status\": \"resume\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"statusComment\": \"milestone resume\"\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"status\": \"resume\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"statusComment\": \"milestone resume\"\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/1/milestones/1",
+ "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
- "1",
+ "{{timelineId}}",
"milestones",
- "1"
+ "{{milestoneId}}"
]
}
},
@@ -6668,19 +6815,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 2-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-10-28T00:00:00.000Z\",\r\n \"status\": \"active\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"milestone 2-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-10-28T00:00:00.000Z\",\r\n \"status\": \"active\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/1/milestones/2",
+ "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
- "1",
+ "{{timelineId}}",
"milestones",
- "2"
+ "{{milestoneId}}"
]
}
},
@@ -6702,19 +6848,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 2-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-10-28T00:00:00.000Z\",\r\n \"status\": \"completed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"milestone 2-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-10-28T00:00:00.000Z\",\r\n \"status\": \"completed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/1/milestones/2",
+ "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
- "1",
+ "{{timelineId}}",
"milestones",
- "2"
+ "{{milestoneId}}"
]
}
},
@@ -6736,19 +6881,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 2,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 2,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/1/milestones/1",
+ "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
- "1",
+ "{{timelineId}}",
"milestones",
- "1"
+ "{{milestoneId}}"
]
}
},
@@ -6770,19 +6914,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/1/milestones/1",
+ "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
- "1",
+ "{{timelineId}}",
"milestones",
- "1"
+ "{{milestoneId}}"
]
}
},
@@ -6804,19 +6947,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 3,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 3,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/1/milestones/1",
+ "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
- "1",
+ "{{timelineId}}",
"milestones",
- "1"
+ "{{milestoneId}}"
]
}
},
@@ -6838,19 +6980,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/1/milestones/1",
+ "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
- "1",
+ "{{timelineId}}",
"milestones",
- "1"
+ "{{milestoneId}}"
]
}
},
@@ -6875,28 +7016,43 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/timelines/1/milestones/2",
+ "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
- "1",
+ "{{timelineId}}",
"milestones",
- "2"
+ "{{milestoneId}}"
]
}
},
"response": []
}
- ]
+ ],
+ "protocolProfileBehavior": {}
},
{
"name": "Milestone Template",
"item": [
{
"name": "Create milestone template",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "3dbf8b29-2498-4b05-93de-14d809ccc285",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"milestoneTemplateId\", pm.response.json().id);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
@@ -6911,15 +7067,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\": \"milestoneTemplate 3\",\r\n \"description\": \"description 3\",\r\n \"duration\": 33,\r\n \"type\": \"type3\",\r\n \"order\": 1,\r\n \"activeText\": \"activeText 1\",\r\n \"completedText\": \"completedText 1\",\r\n \"blockedText\": \"blockedText 1\",\r\n \"plannedText\": \"planned Text 1\",\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1,\r\n\t\"metadata\": {}\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"milestoneTemplate 1\",\r\n \"description\": \"description 1\",\r\n \"duration\": 11,\r\n \"type\": \"type3\",\r\n \"order\": 1,\r\n \"activeText\": \"activeText 1\",\r\n \"completedText\": \"completedText 1\",\r\n \"blockedText\": \"blockedText 1\",\r\n \"plannedText\": \"planned Text 1\",\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": {{productTemplateId}},\r\n\t\"metadata\": {}\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates",
+ "raw": "{{api-url}}/timelines/metadata/milestoneTemplates",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
"metadata",
"milestoneTemplates"
@@ -6944,15 +7099,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\": \"milestoneTemplate 3\",\r\n \"description\": \"description 3\",\r\n \"duration\": 33,\r\n \"type\": \"type3\",\r\n \"order\": 1,\r\n \"activeText\": \"activeText 1\",\r\n \"completedText\": \"completedText 1\",\r\n \"blockedText\": \"blockedText 1\",\r\n \"plannedText\": \"planned Text 1\",\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1000,\r\n\t\"metadata\": {}\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"milestoneTemplate 3\",\r\n \"description\": \"description 3\",\r\n \"duration\": 33,\r\n \"type\": \"type3\",\r\n \"order\": 1,\r\n \"activeText\": \"activeText 1\",\r\n \"completedText\": \"completedText 1\",\r\n \"blockedText\": \"blockedText 1\",\r\n \"plannedText\": \"planned Text 1\",\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1000,\r\n\t\"metadata\": {}\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates",
+ "raw": "{{api-url}}/timelines/metadata/milestoneTemplates",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
"metadata",
"milestoneTemplates"
@@ -6977,15 +7131,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n\r\n }\r\n}"
+ "raw": "{\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates",
+ "raw": "{{api-url}}/timelines/metadata/milestoneTemplates",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
"metadata",
"milestoneTemplates"
@@ -7010,15 +7163,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"sourceReference\": \"productTemplate\",\r\n \"sourceReferenceId\": 1,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": 2\r\n }\r\n}"
+ "raw": "{\r\n \"sourceReference\": \"productTemplate\",\r\n \"sourceReferenceId\": 1,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": 2\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/clone",
+ "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/clone",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
"metadata",
"milestoneTemplates",
@@ -7044,15 +7196,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"sourceReference\": \"productTemplate\",\r\n \"sourceReferenceId\": 1,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": 2000\r\n }\r\n}"
+ "raw": "{\r\n \"sourceReference\": \"productTemplate\",\r\n \"sourceReferenceId\": 1,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": 2000\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/clone",
+ "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/clone",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
"metadata",
"milestoneTemplates",
@@ -7078,15 +7229,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"sourceReference\": \"productTemplate\",\r\n \"sourceReferenceId\": 1000,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": 2\r\n }\r\n}"
+ "raw": "{\r\n \"sourceReference\": \"productTemplate\",\r\n \"sourceReferenceId\": 1000,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": 2\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/clone",
+ "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/clone",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
"metadata",
"milestoneTemplates",
@@ -7111,12 +7261,11 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates",
+ "raw": "{{api-url}}/timelines/metadata/milestoneTemplates",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
"metadata",
"milestoneTemplates"
@@ -7140,20 +7289,23 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates?filter=reference%3DproductTemplate%26referenceId%3D1",
+ "raw": "{{api-url}}/timelines/metadata/milestoneTemplates?reference=productTemplate&referenceId={{productTemplateId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
"metadata",
"milestoneTemplates"
],
"query": [
{
- "key": "filter",
- "value": "reference%3DproductTemplate%26referenceId%3D1"
+ "key": "reference",
+ "value": "productTemplate"
+ },
+ {
+ "key": "referenceId",
+ "value": "{{productTemplateId}}"
}
]
}
@@ -7175,20 +7327,23 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates?filter=reference%3DproductTemplate%26referenceId%3D1&sort=order desc",
+ "raw": "{{api-url}}/timelines/metadata/milestoneTemplates?reference=productTemplate&referenceId={{productTemplateId}}&sort=order desc",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
"metadata",
"milestoneTemplates"
],
"query": [
{
- "key": "filter",
- "value": "reference%3DproductTemplate%26referenceId%3D1"
+ "key": "reference",
+ "value": "productTemplate"
+ },
+ {
+ "key": "referenceId",
+ "value": "{{productTemplateId}}"
},
{
"key": "sort",
@@ -7214,16 +7369,15 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/1",
+ "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
"metadata",
"milestoneTemplates",
- "1"
+ "{{milestoneTemplateId}}"
]
}
},
@@ -7245,19 +7399,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n\t\"name\": \"milestoneTemplate 1-updated\",\r\n\t\"description\": \"description 1-updated\",\r\n\t\"duration\": 34,\r\n\t\"type\": \"type1-updated\",\r\n\t\"order\": 1,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n }\r\n}"
+ "raw": "{\r\n\t\"name\": \"milestoneTemplate 1-updated\",\r\n\t\"description\": \"description 1-updated\",\r\n\t\"duration\": 34,\r\n\t\"type\": \"type1-updated\",\r\n\t\"order\": 1,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/1",
+ "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
"metadata",
"milestoneTemplates",
- "1"
+ "{{milestoneTemplateId}}"
]
}
},
@@ -7279,19 +7432,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\": \"milestoneTemplate 1-updated\",\r\n \"description\": \"description 1-updated\",\r\n \"duration\": 34,\r\n \"type\": \"type1-updated\",\r\n \"order\": 2,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"milestoneTemplate 1-updated\",\r\n \"description\": \"description 1-updated\",\r\n \"duration\": 34,\r\n \"type\": \"type1-updated\",\r\n \"order\": 2,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": {{productTemplateId}}\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/1",
+ "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
"metadata",
"milestoneTemplates",
- "1"
+ "{{milestoneTemplateId}}"
]
}
},
@@ -7313,19 +7465,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n\t\"name\": \"milestoneTemplate 1-updated\",\r\n\t\"description\": \"description 1-updated\",\r\n\t\"duration\": 34,\r\n\t\"type\": \"type1-updated\",\r\n\t\"order\": 1,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n }\r\n}"
+ "raw": "{\r\n\t\"name\": \"milestoneTemplate 1-updated\",\r\n\t\"description\": \"description 1-updated\",\r\n\t\"duration\": 34,\r\n\t\"type\": \"type1-updated\",\r\n\t\"order\": 1,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/1",
+ "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
"metadata",
"milestoneTemplates",
- "1"
+ "{{milestoneTemplateId}}"
]
}
},
@@ -7347,19 +7498,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\": \"milestoneTemplate 1-updated\",\r\n \"description\": \"description 1-updated\",\r\n \"duration\": 34,\r\n \"type\": \"type1-updated\",\r\n \"order\": 3,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"milestoneTemplate 1-updated\",\r\n \"description\": \"description 1-updated\",\r\n \"duration\": 34,\r\n \"type\": \"type1-updated\",\r\n \"order\": 3,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/1",
+ "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
"metadata",
"milestoneTemplates",
- "1"
+ "{{milestoneTemplateId}}"
]
}
},
@@ -7381,19 +7531,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"name\": \"milestoneTemplate 1-updated\",\r\n \"description\": \"description 1-updated\",\r\n \"duration\": 34,\r\n \"type\": \"type1-updated\",\r\n \"order\": 1,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n }\r\n}"
+ "raw": "{\r\n \"name\": \"milestoneTemplate 1-updated\",\r\n \"description\": \"description 1-updated\",\r\n \"duration\": 34,\r\n \"type\": \"type1-updated\",\r\n \"order\": 1,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/1",
+ "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
"metadata",
"milestoneTemplates",
- "1"
+ "{{milestoneTemplateId}}"
]
}
},
@@ -7415,19 +7564,18 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n\t\"name\": \"milestoneTemplate 5-updated\",\r\n\t\"description\": \"description 5-updated\",\r\n\t\"duration\": 34,\r\n\t\"type\": \"type5-updated\",\r\n\t\"order\": 5,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1,\r\n\t\"metadata\": {\r\n \"metadata1\": {\r\n \"name\": \"metadata 1 - update\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 1 - update\",\r\n \"newDetails\": \"new\"\r\n },\r\n \"others\": [\"others new\"]\r\n },\r\n \"metadata3\": {\r\n \"name\": \"metadata 3\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 3\"\r\n },\r\n \"others\": [\"others 31\", \"others 32\"]\r\n }\r\n }\r\n }\r\n}"
+ "raw": "{\r\n\t\"name\": \"milestoneTemplate 5-updated\",\r\n\t\"description\": \"description 5-updated\",\r\n\t\"duration\": 34,\r\n\t\"type\": \"type5-updated\",\r\n\t\"order\": 5,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1,\r\n\t\"metadata\": {\r\n \"metadata1\": {\r\n \"name\": \"metadata 1 - update\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 1 - update\",\r\n \"newDetails\": \"new\"\r\n },\r\n \"others\": [\"others new\"]\r\n },\r\n \"metadata3\": {\r\n \"name\": \"metadata 3\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 3\"\r\n },\r\n \"others\": [\"others 31\", \"others 32\"]\r\n }\r\n }\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/1",
+ "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
"metadata",
"milestoneTemplates",
- "1"
+ "{{milestoneTemplateId}}"
]
}
},
@@ -7452,22 +7600,22 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/2",
+ "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"timelines",
"metadata",
"milestoneTemplates",
- "2"
+ "{{milestoneTemplateId}}"
]
}
},
"response": []
}
- ]
+ ],
+ "protocolProfileBehavior": {}
},
{
"name": "Metadata",
@@ -7475,6 +7623,16 @@
{
"name": "Get all metadata",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "GET",
"header": [
{
@@ -7484,12 +7642,11 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata",
+ "raw": "{{api-url}}/projects/metadata",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata"
]
@@ -7500,15 +7657,24 @@
{
"name": "Get all metadata with includeAllVersion",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "GET",
"header": [],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata?includeAllReferred=true",
+ "raw": "{{api-url}}/projects/metadata?includeAllReferred=true",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata"
],
@@ -7522,7 +7688,8 @@
},
"response": []
}
- ]
+ ],
+ "protocolProfileBehavior": {}
},
{
"name": "Form Version",
@@ -7530,19 +7697,28 @@
{
"name": "List forms",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "GET",
"header": [],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/form/dev/versions",
+ "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"form",
- "dev",
+ "{{formKey}}",
"versions"
]
}
@@ -7552,21 +7728,30 @@
{
"name": "Get a particular version",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "GET",
"header": [],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/form/dev/versions/1",
+ "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"form",
- "dev",
+ "{{formKey}}",
"versions",
- "1"
+ "{{formVersion}}"
]
}
},
@@ -7575,19 +7760,28 @@
{
"name": "Get latest version",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "GET",
"header": [],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/form/dev",
+ "raw": "{{api-url}}/projects/metadata/form/{{formKey}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"form",
- "dev"
+ "{{formKey}}"
]
}
},
@@ -7595,7 +7789,33 @@
},
{
"name": "Create form",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "94f6be66-34cc-40c8-80c2-b27dd93ed527",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"formKey\", pm.response.json().key);",
+ " pm.environment.set(\"formVersion\", pm.response.json().version);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "POST",
"header": [
{
@@ -7607,15 +7827,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }\r\n}"
+ "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/form/dev/versions",
+ "raw": "{{api-url}}/projects/metadata/form/dev/versions",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"form",
@@ -7629,6 +7848,16 @@
{
"name": "Update form",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "PATCH",
"header": [
{
@@ -7640,21 +7869,20 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test111\"\r\n \t}\r\n }\r\n}"
+ "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test111\"\r\n \t}\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/form/dev/versions/1",
+ "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"form",
- "dev",
+ "{{formKey}}",
"versions",
- "1"
+ "{{formVersion}}"
]
}
},
@@ -7663,6 +7891,16 @@
{
"name": "Delete form",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "DELETE",
"header": [
{
@@ -7677,24 +7915,24 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/form/dev/versions/1",
+ "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"form",
- "dev",
+ "{{formKey}}",
"versions",
- "1"
+ "{{formVersion}}"
]
}
},
"response": []
}
- ]
+ ],
+ "protocolProfileBehavior": {}
},
{
"name": "Form Revision",
@@ -7702,21 +7940,30 @@
{
"name": "List all revision for version",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "GET",
"header": [],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/form/dev/versions/1/revisions",
+ "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}/revisions",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"form",
- "dev",
+ "{{formKey}}",
"versions",
- "1",
+ "{{formVersion}}",
"revisions"
]
}
@@ -7726,23 +7973,32 @@
{
"name": "Get a particular revision",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "GET",
"header": [],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/form/dev/versions/1/revisions/1",
+ "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}/revisions/{{formRevision}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"form",
- "dev",
+ "{{formKey}}",
"versions",
- "1",
+ "{{formVersion}}",
"revisions",
- "1"
+ "{{formRevision}}"
]
}
},
@@ -7750,7 +8006,32 @@
},
{
"name": "Create form",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "dbe5ec9f-022c-4ec5-b58c-d19c15430b61",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"formRevision\", pm.response.json().revision);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "POST",
"header": [
{
@@ -7762,21 +8043,20 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }\r\n}"
+ "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/form/dev/versions/1/revisions",
+ "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}/revisions",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"form",
- "dev",
+ "{{formKey}}",
"versions",
- "1",
+ "{{formVersion}}",
"revisions"
]
}
@@ -7786,6 +8066,16 @@
{
"name": "Create for no exist key",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "POST",
"header": [
{
@@ -7797,15 +8087,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }\r\n}"
+ "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/form/no-exist-2222key36/versions/1/revisions",
+ "raw": "{{api-url}}/projects/metadata/form/no-exist-2222key36/versions/1/revisions",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"form",
@@ -7821,6 +8110,16 @@
{
"name": "Delete revision",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "DELETE",
"header": [
{
@@ -7835,26 +8134,26 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/form/dev/versions/1/revisions/1",
+ "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}/revisions/{{formRevision}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"form",
- "dev",
+ "{{formKey}}",
"versions",
- "1",
+ "{{formVersion}}",
"revisions",
- "1"
+ "{{formRevision}}"
]
}
},
"response": []
}
- ]
+ ],
+ "protocolProfileBehavior": {}
},
{
"name": "Price Config Version",
@@ -7862,15 +8161,24 @@
{
"name": "List price configs",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "GET",
"header": [],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/priceConfig/dev/versions",
+ "raw": "{{api-url}}/projects/metadata/priceConfig/dev/versions",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"priceConfig",
@@ -7884,21 +8192,30 @@
{
"name": "Get a particular version",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "GET",
"header": [],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/priceConfig/dev/versions/1",
+ "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"priceConfig",
- "dev",
+ "{{priceKey}}",
"versions",
- "1"
+ "{{priceVersion}}"
]
}
},
@@ -7907,19 +8224,28 @@
{
"name": "Get latest version",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "GET",
"header": [],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/priceConfig/dev",
+ "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"priceConfig",
- "dev"
+ "{{priceKey}}"
]
}
},
@@ -7927,7 +8253,33 @@
},
{
"name": "Create priceConfig",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "e440c87c-49ff-4443-b9bf-b44d4e9a480f",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"priceKey\", pm.response.json().key);",
+ " pm.environment.set(\"priceVersion\", pm.response.json().version);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "POST",
"header": [
{
@@ -7939,15 +8291,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }\r\n}"
+ "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/priceConfig/dev/versions",
+ "raw": "{{api-url}}/projects/metadata/priceConfig/dev/versions",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"priceConfig",
@@ -7961,6 +8312,16 @@
{
"name": "Update priceConfig",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "PATCH",
"header": [
{
@@ -7972,21 +8333,20 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test111\"\r\n \t}\r\n }\r\n}"
+ "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test111\"\r\n \t}\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/priceConfig/dev/versions/1",
+ "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"priceConfig",
- "dev",
+ "{{priceKey}}",
"versions",
- "1"
+ "{{priceVersion}}"
]
}
},
@@ -7995,6 +8355,16 @@
{
"name": "Delete priceConfig",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "DELETE",
"header": [
{
@@ -8009,24 +8379,46 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/priceConfig/dev/versions/1",
+ "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"priceConfig",
- "dev",
+ "{{priceKey}}",
"versions",
- "1"
+ "{{priceVersion}}"
]
}
},
"response": []
}
- ]
+ ],
+ "event": [
+ {
+ "listen": "prerequest",
+ "script": {
+ "id": "59182724-4332-4d76-90ea-f7520a7b1be9",
+ "type": "text/javascript",
+ "exec": [
+ ""
+ ]
+ }
+ },
+ {
+ "listen": "test",
+ "script": {
+ "id": "abc13dca-e8a4-4995-970f-00e5889a5f2d",
+ "type": "text/javascript",
+ "exec": [
+ ""
+ ]
+ }
+ }
+ ],
+ "protocolProfileBehavior": {}
},
{
"name": "Price Config Revision",
@@ -8034,15 +8426,24 @@
{
"name": "List all revision for version",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "GET",
"header": [],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/priceConfig/dev/versions/3/revisions",
+ "raw": "{{api-url}}/projects/metadata/priceConfig/dev/versions/3/revisions",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"priceConfig",
@@ -8058,23 +8459,32 @@
{
"name": "Get a particular revision",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "GET",
"header": [],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/priceConfig/dev/versions/1/revisions/1",
+ "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}/revisions/{{priceRevision}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"priceConfig",
- "dev",
+ "{{priceKey}}",
"versions",
- "1",
+ "{{priceVersion}}",
"revisions",
- "1"
+ "{{priceRevision}}"
]
}
},
@@ -8082,7 +8492,32 @@
},
{
"name": "Create price config",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "d53ed608-b21c-4d6f-bb68-c2beda1d631d",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"priceRevision\", pm.response.json().revision);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "POST",
"header": [
{
@@ -8094,21 +8529,20 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }\r\n}"
+ "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/priceConfig/dev/versions/1/revisions",
+ "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}/revisions",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"priceConfig",
- "dev",
+ "{{priceKey}}",
"versions",
- "1",
+ "{{priceVersion}}",
"revisions"
]
}
@@ -8118,6 +8552,16 @@
{
"name": "Create for no exist key",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "POST",
"header": [
{
@@ -8129,15 +8573,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }\r\n}"
+ "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/priceConfig/no-exist-key/versions/1/revisions",
+ "raw": "{{api-url}}/projects/metadata/priceConfig/no-exist-key/versions/1/revisions",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"priceConfig",
@@ -8153,6 +8596,16 @@
{
"name": "Delete revision",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "DELETE",
"header": [
{
@@ -8167,26 +8620,26 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/priceConfig/dev/versions/1/revisions/1",
+ "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}/revisions/{{priceRevision}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"priceConfig",
- "dev",
+ "{{priceKey}}",
"versions",
- "1",
+ "{{priceVersion}}",
"revisions",
- "1"
+ "{{priceRevision}}"
]
}
},
"response": []
}
- ]
+ ],
+ "protocolProfileBehavior": {}
},
{
"name": "Plan Config Version",
@@ -8194,15 +8647,24 @@
{
"name": "List plan configs",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "GET",
"header": [],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/planConfig/dev/versions",
+ "raw": "{{api-url}}/projects/metadata/planConfig/dev/versions",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"planConfig",
@@ -8216,21 +8678,30 @@
{
"name": "Get a particular version",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "GET",
"header": [],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/planConfig/dev/versions/3",
+ "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"planConfig",
- "dev",
+ "{{planKey}}",
"versions",
- "3"
+ "{{planVersion}}"
]
}
},
@@ -8239,19 +8710,28 @@
{
"name": "Get latest version",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "GET",
"header": [],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/planConfig/dev",
+ "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"planConfig",
- "dev"
+ "{{planKey}}"
]
}
},
@@ -8259,7 +8739,33 @@
},
{
"name": "Create plan config",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "97bc350a-0c4f-46a6-a315-a62b203b3ad2",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"planKey\", pm.response.json().key);",
+ " pm.environment.set(\"planVersion\", pm.response.json().version);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "POST",
"header": [
{
@@ -8271,15 +8777,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }\r\n}"
+ "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/planConfig/dev/versions",
+ "raw": "{{api-url}}/projects/metadata/planConfig/dev/versions",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"planConfig",
@@ -8293,6 +8798,16 @@
{
"name": "Update plan config",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "PATCH",
"header": [
{
@@ -8304,21 +8819,20 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test111\"\r\n \t}\r\n }\r\n}"
+ "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test111\"\r\n \t}\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/planConfig/dev/versions/1",
+ "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"planConfig",
- "dev",
+ "{{planKey}}",
"versions",
- "1"
+ "{{planVersion}}"
]
}
},
@@ -8327,6 +8841,16 @@
{
"name": "Delete plan config",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "DELETE",
"header": [
{
@@ -8341,24 +8865,24 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/planConfig/dev/versions/1",
+ "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"planConfig",
- "dev",
+ "{{planKey}}",
"versions",
- "1"
+ "{{planVersion}}"
]
}
},
"response": []
}
- ]
+ ],
+ "protocolProfileBehavior": {}
},
{
"name": "Plan Config Revision",
@@ -8366,21 +8890,30 @@
{
"name": "List all revision for version",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "GET",
"header": [],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/planConfig/dev/versions/1/revisions",
+ "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}/revisions",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"planConfig",
- "dev",
+ "{{planKey}}",
"versions",
- "1",
+ "{{planVersion}}",
"revisions"
]
}
@@ -8390,23 +8923,32 @@
{
"name": "Get a particular revision",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "GET",
"header": [],
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/planConfig/dev/versions/1/revisions/1",
+ "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}/revisions/{{planRevision}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"planConfig",
- "dev",
+ "{{planKey}}",
"versions",
- "1",
+ "{{planVersion}}",
"revisions",
- "1"
+ "{{planRevision}}"
]
}
},
@@ -8414,7 +8956,32 @@
},
{
"name": "Create plan config",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "id": "a5373f1f-4beb-46f9-8538-10c938c204ba",
+ "exec": [
+ "pm.test(\"Status code is 201\", function () {",
+ " pm.response.to.have.status(201);",
+ " pm.environment.set(\"planRevision\", pm.response.json().revision);",
+ "});"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "POST",
"header": [
{
@@ -8426,21 +8993,20 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }\r\n}"
+ "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/planConfig/dev/versions/1/revisions",
+ "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}/revisions",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"planConfig",
- "dev",
+ "{{planKey}}",
"versions",
- "1",
+ "{{planVersion}}",
"revisions"
]
}
@@ -8450,6 +9016,16 @@
{
"name": "Create for no exist key",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "POST",
"header": [
{
@@ -8461,15 +9037,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }\r\n}"
+ "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }"
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/planConfig/no-exist-key/versions/1/revisions",
+ "raw": "{{api-url}}/projects/metadata/planConfig/no-exist-key/versions/1/revisions",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"planConfig",
@@ -8485,6 +9060,16 @@
{
"name": "Delete revision",
"request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw",
+ "type": "string"
+ }
+ ]
+ },
"method": "DELETE",
"header": [
{
@@ -8499,26 +9084,26 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/projects/metadata/planConfig/dev/versions/1/revisions/1",
+ "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}/revisions/{{planRevision}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"metadata",
"planConfig",
- "dev",
+ "{{planKey}}",
"versions",
- "1",
+ "{{planVersion}}",
"revisions",
- "1"
+ "{{planRevision}}"
]
}
},
"response": []
}
- ]
+ ],
+ "protocolProfileBehavior": {}
},
{
"name": "Project Reports",
@@ -8538,14 +9123,13 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/reports?reportName=summary",
+ "raw": "{{api-url}}/projects/{{projectId}}/reports?reportName=summary",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"reports"
],
"query": [
@@ -8570,14 +9154,13 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/reports?reportName=summary",
+ "raw": "{{api-url}}/projects/{{projectId}}/reports?reportName=summary",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"reports"
],
"query": [
@@ -8602,12 +9185,11 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/123456/reports?reportName=summary",
+ "raw": "{{api-url}}/projects/123456/reports?reportName=summary",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"123456",
"reports"
@@ -8634,14 +9216,13 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/reports?reportName=summary123",
+ "raw": "{{api-url}}/projects/{{projectId}}/reports?reportName=summary123",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"reports"
],
"query": [
@@ -8655,6 +9236,7 @@
"response": []
}
],
+ "protocolProfileBehavior": {},
"_postman_isSubFolder": true
},
{
@@ -8672,14 +9254,13 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/reports?reportName=projectBudget",
+ "raw": "{{api-url}}/projects/{{projectId}}/reports?reportName=projectBudget",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"reports"
],
"query": [
@@ -8693,9 +9274,11 @@
"response": []
}
],
+ "protocolProfileBehavior": {},
"_postman_isSubFolder": true
}
- ]
+ ],
+ "protocolProfileBehavior": {}
},
{
"name": "Project Setting",
@@ -8710,7 +9293,7 @@
"exec": [
"pm.test(\"Status code is 201\", function () {",
" pm.response.to.have.status(201);",
- " pm.environment.set(\"settingId\", pm.response.json().result.content.id);",
+ " pm.environment.set(\"settingId\", pm.response.json().id);",
"})"
],
"type": "text/javascript"
@@ -8731,17 +9314,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"key\": \"markup_topcoder_service\",\r\n \"value\": \"1000\",\r\n \"valueType\": \"double\",\r\n \"projectId\": 1,\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n }\r\n}"
+ "raw": "{\r\n \"key\": \"markup_topcoder_service\",\r\n \"value\": \"1000\",\r\n \"valueType\": \"double\",\r\n \"projectId\": 1,\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/settings",
+ "raw": "{{api-url}}/projects/{{projectId}}/settings",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"settings"
]
}
@@ -8776,17 +9358,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"key\": \"markup_fee\",\r\n \"value\": \"18.88\",\r\n \"valueType\": \"percentage\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \"topcoderRoles\": [\"Connect Copilot\"]\r\n\t },\r\n\t\"metadata\": {}\r\n }\r\n}"
+ "raw": "{\r\n \"key\": \"markup_fee\",\r\n \"value\": \"18.88\",\r\n \"valueType\": \"percentage\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \"topcoderRoles\": [\"Connect Copilot\"]\r\n\t },\r\n\t\"metadata\": {}\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/settings",
+ "raw": "{{api-url}}/projects/{{projectId}}/settings",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"settings"
]
}
@@ -8821,15 +9402,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"key\": \"markup_topcoder_service\",\r\n \"value\": \"2222\",\r\n \"valueType\": \"double\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n }\r\n}"
+ "raw": "{\r\n \"key\": \"markup_topcoder_service\",\r\n \"value\": \"2222\",\r\n \"valueType\": \"double\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/2/settings",
+ "raw": "{{api-url}}/projects/2/settings",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"2",
"settings"
@@ -8866,17 +9446,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"key\": \"markup_reference_program\",\r\n \"value\": \"17800\",\r\n \"valueType\": \"double\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\", \"copilot\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n }\r\n}"
+ "raw": "{\r\n \"key\": \"markup_reference_program\",\r\n \"value\": \"17800\",\r\n \"valueType\": \"double\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\", \"copilot\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/settings",
+ "raw": "{{api-url}}/projects/{{projectId}}/settings",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"settings"
]
}
@@ -8899,17 +9478,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"key\": \"markup_non_estimation\",\r\n \"value\": \"8765\",\r\n \"valueType\": \"string\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t \"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n }\r\n}"
+ "raw": "{\r\n \"key\": \"markup_non_estimation\",\r\n \"value\": \"8765\",\r\n \"valueType\": \"string\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t \"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/settings",
+ "raw": "{{api-url}}/projects/{{projectId}}/settings",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"settings"
]
}
@@ -8944,17 +9522,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"key\": \"markup_topcoder_service\",\r\n \"value\": \"1000\",\r\n \"valueType\": \"double\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n }\r\n}"
+ "raw": "{\r\n \"key\": \"markup_topcoder_service\",\r\n \"value\": \"1000\",\r\n \"valueType\": \"double\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/settings",
+ "raw": "{{api-url}}/projects/{{projectId}}/settings",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"settings"
]
}
@@ -8977,17 +9554,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"key\": \"markup_topcoder_service\",\r\n \"value\": \"1000\",\r\n \"valueType\": \"int1\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n }\r\n}"
+ "raw": "{\r\n \"key\": \"markup_topcoder_service\",\r\n \"value\": \"1000\",\r\n \"valueType\": \"int1\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/settings",
+ "raw": "{{api-url}}/projects/{{projectId}}/settings",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"settings"
]
}
@@ -9010,17 +9586,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"key\": \"markup_community\",\r\n \"value\": \"200\",\r\n \"valueType\": \"percentage\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n }\r\n}"
+ "raw": "{\r\n \"key\": \"markup_community\",\r\n \"value\": \"200\",\r\n \"valueType\": \"percentage\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/settings",
+ "raw": "{{api-url}}/projects/{{projectId}}/settings",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"settings"
]
}
@@ -9043,17 +9618,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"key\": \"markup_topcoder_service\",\r\n \"value\": \"1000\",\r\n \"valueType\": \"int\",\r\n \"projectId\": 1,\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"metadata\": {}\r\n }\r\n}"
+ "raw": "{\r\n \"key\": \"markup_topcoder_service\",\r\n \"value\": \"1000\",\r\n \"valueType\": \"int\",\r\n \"projectId\": 1,\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"metadata\": {}\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/settings",
+ "raw": "{{api-url}}/projects/{{projectId}}/settings",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"settings"
]
}
@@ -9076,17 +9650,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n }\r\n}"
+ "raw": "{\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/settings",
+ "raw": "{{api-url}}/projects/{{projectId}}/settings",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"settings"
]
}
@@ -9121,17 +9694,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"key\": \"markup_topcoder_service\",\r\n \"value\": \"1000\",\r\n \"valueType\": \"double\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n }\r\n}"
+ "raw": "{\r\n \"key\": \"markup_topcoder_service\",\r\n \"value\": \"1000\",\r\n \"valueType\": \"double\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/settings",
+ "raw": "{{api-url}}/projects/{{projectId}}/settings",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"settings"
]
}
@@ -9153,14 +9725,13 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/settings",
+ "raw": "{{api-url}}/projects/{{projectId}}/settings",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"settings"
]
}
@@ -9182,14 +9753,13 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/settings",
+ "raw": "{{api-url}}/projects/{{projectId}}/settings",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"settings"
]
}
@@ -9211,14 +9781,13 @@
}
],
"url": {
- "raw": "{{api-url}}/v4/projects/1/settings",
+ "raw": "{{api-url}}/projects/{{projectId}}/settings",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"settings"
]
}
@@ -9241,17 +9810,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"key\": \"markup_community\",\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"Connect Manager\"]\r\n\t }\r\n }\r\n}"
+ "raw": "{\r\n \"key\": \"markup_community\",\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"Connect Manager\"]\r\n\t }\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/settings/{{settingId}}",
+ "raw": "{{api-url}}/projects/{{projectId}}/settings/{{settingId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"settings",
"{{settingId}}"
]
@@ -9275,17 +9843,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"value\": \"35.60\",\r\n \"valueType\": \"percentage\"\r\n }\r\n}"
+ "raw": "{\r\n \"value\": \"35.60\",\r\n \"valueType\": \"percentage\"\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/settings/{{settingId}}",
+ "raw": "{{api-url}}/projects/{{projectId}}/settings/{{settingId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"settings",
"{{settingId}}"
]
@@ -9309,15 +9876,14 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"value\": \"30\",\r\n \"valueType\": \"percentage\"\r\n }\r\n}"
+ "raw": "{\r\n \"value\": \"30\",\r\n \"valueType\": \"percentage\"\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/9999/settings/{{settingId}}",
+ "raw": "{{api-url}}/projects/9999/settings/{{settingId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
"9999",
"settings",
@@ -9343,17 +9909,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n \"value\": \"30\",\r\n \"valueType\": \"percentage\"\r\n }\r\n}"
+ "raw": "{\r\n \"value\": \"30\",\r\n \"valueType\": \"percentage\"\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/settings/9999",
+ "raw": "{{api-url}}/projects/{{projectId}}/settings/9999",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"settings",
"9999"
]
@@ -9377,17 +9942,16 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"param\":{\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"Connect Manager\"]\r\n\t }\r\n }\r\n}"
+ "raw": "{\r\n\t\"readPermission\": {\r\n\t\t\"projectRoles\": [\"manager\"],\r\n\t\t\"topcoderRoles\": [\"Connect Manager\"]\r\n\t}\r\n}"
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/settings/{{settingId}}",
+ "raw": "{{api-url}}/projects/{{projectId}}/settings/{{settingId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"settings",
"{{settingId}}"
]
@@ -9414,14 +9978,13 @@
"raw": ""
},
"url": {
- "raw": "{{api-url}}/v4/projects/1/settings/{{settingId}}",
+ "raw": "{{api-url}}/projects/{{projectId}}/settings/{{settingId}}",
"host": [
"{{api-url}}"
],
"path": [
- "v4",
"projects",
- "1",
+ "{{projectId}}",
"settings",
"{{settingId}}"
]
@@ -9429,7 +9992,9 @@
},
"response": []
}
- ]
+ ],
+ "protocolProfileBehavior": {}
}
- ]
-}
+ ],
+ "protocolProfileBehavior": {}
+}
\ No newline at end of file
diff --git a/postman_environment.json b/docs/Project API.postman_environment.json
similarity index 94%
rename from postman_environment.json
rename to docs/Project API.postman_environment.json
index 3192362a..e153507d 100644
--- a/postman_environment.json
+++ b/docs/Project API.postman_environment.json
@@ -1,10 +1,10 @@
{
- "id": "be71a5b6-f6f0-413c-99ae-56f21f10dd53",
- "name": "tc-project-service",
+ "id": "9408797f-cb90-43c1-b08b-375e30edb5bb",
+ "name": "Project API",
"values": [
{
"key": "api-url",
- "value": "http://localhost:8001",
+ "value": "http://localhost:8001/v5",
"description": "",
"enabled": true
},
@@ -58,6 +58,6 @@
}
],
"_postman_variable_scope": "environment",
- "_postman_exported_at": "2018-12-04T21:50:56.610Z",
- "_postman_exported_using": "Postman/6.5.2"
+ "_postman_exported_at": "2019-06-07T11:02:18.794Z",
+ "_postman_exported_using": "Postman/6.5.3"
}
\ No newline at end of file
diff --git a/swagger.yaml b/docs/swagger.yaml
similarity index 72%
rename from swagger.yaml
rename to docs/swagger.yaml
index 77b16a60..4912bf84 100644
--- a/swagger.yaml
+++ b/docs/swagger.yaml
@@ -1,9 +1,18 @@
swagger: '2.0'
info:
- version: v4
+ version: v5
title: Projects API
+ description: |
+ Project API
+
+ ### Pagination
+ Requests that return multiple items will be paginated to 20 items by default.
+ You can specify further pages with the `page` parameter.
+ You can also set a custom page size up to 100 with the `perPage` parameter.
+
+ Pagination response data is included in http headers. By Default, the response header contains links with `next`, `last`, `first`, `prev` resource links.
host: 'localhost:3000'
-basePath: /v4
+basePath: /v5
schemes:
- http
produces:
@@ -16,52 +25,6 @@ securityDefinitions:
name: Authorization
in: header
paths:
- '/projects/db':
- get:
- tags:
- - project
- operationId: findProjectsDB
- security:
- - Bearer: []
- description: Retrieve projects that match the filter directly from database
- responses:
- '200':
- description: A list of projects
- schema:
- $ref: '#/definitions/ProjectListResponse'
- '403':
- description: No permission or wrong token
- schema:
- $ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
- schema:
- $ref: '#/definitions/ErrorModel'
- parameters:
- - $ref: '#/parameters/offsetParam'
- - $ref: '#/parameters/limitParam'
- - name: filter
- required: true
- type: string
- in: query
- description: |
- Url encoded list of Supported filters
- - id
- - status
- - type
- - memberOnly
- - keyword
- - name
- - code
- - customer
- - manager
- - name: sort
- required: false
- description: >
- sort projects by status, name, type, createdAt, updatedAt. Default
- is createdAt asc
- in: query
- type: string
'/projects':
get:
tags:
@@ -74,33 +37,55 @@ paths:
'200':
description: A list of projects
schema:
- $ref: '#/definitions/ProjectListResponse'
- '403':
- description: No permission or wrong token
+ type: array
+ items:
+ $ref: '#/definitions/Project'
+ headers:
+ X-Next-Page:
+ type: integer
+ description: The index of the next page
+ X-Page:
+ type: integer
+ description: The index of the current page (starting at 1)
+ X-Per-Page:
+ type: integer
+ description: The number of items to list per page
+ X-Prev-Page:
+ type: integer
+ description: The index of the previous page
+ X-Total:
+ type: integer
+ description: The total number of items
+ X-Total-Pages:
+ type: integer
+ description: The total number of pages
+ Link:
+ type: string
+ description: Pagination link header.
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
parameters:
- - $ref: '#/parameters/offsetParam'
- - $ref: '#/parameters/limitParam'
- - name: filter
- required: true
- type: string
- in: query
- description: |
- Url encoded list of Supported filters
- - id
- - status
- - type
- - memberOnly
- - keyword
- - name
- - code
- - customer
- - manager
+ - $ref: '#/parameters/pageParam'
+ - $ref: '#/parameters/perPageParam'
+ - $ref: '#/parameters/idQueryParam'
+ - $ref: '#/parameters/statusQueryParam'
+ - $ref: '#/parameters/typeQueryParam'
+ - $ref: '#/parameters/memberOnlyQueryParam'
+ - $ref: '#/parameters/keywordQueryParam'
+ - $ref: '#/parameters/nameQueryParam'
+ - $ref: '#/parameters/codeQueryParam'
+ - $ref: '#/parameters/customerQueryParam'
+ - $ref: '#/parameters/managerQueryParam'
- name: sort
required: false
description: >
@@ -120,18 +105,22 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/NewProjectBodyParam'
+ $ref: '#/definitions/NewProject'
responses:
- '201':
+ '200':
description: Returns the newly created project
schema:
- $ref: '#/definitions/ProjectResponse'
- '403':
- description: No permission or wrong token
+ $ref: '#/definitions/Project'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/{projectId}':
@@ -145,15 +134,23 @@ paths:
'200':
description: a project
schema:
- $ref: '#/definitions/ProjectResponse'
- '403':
- description: No permission or wrong token
+ $ref: '#/definitions/Project'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
parameters:
- $ref: '#/parameters/projectIdParam'
- name: fields
@@ -184,21 +181,25 @@ paths:
Successfully updated project. Returns original and updated project
object
schema:
- $ref: '#/definitions/UpdateProjectResponse'
+ $ref: '#/definitions/Project'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- default:
- description: error payload
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
parameters:
@@ -210,7 +211,7 @@ paths:
Only specify those properties that needs to be updated.
`cancelReason` is mandatory if status is cancelled
schema:
- $ref: '#/definitions/ProjectBodyParam'
+ $ref: '#/definitions/NewProject'
delete:
tags:
- project
@@ -222,18 +223,54 @@ paths:
responses:
'204':
description: Project successfully removed
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If project is not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
'/projects/{projectId}/attachments':
+ get:
+ tags:
+ - project attachments
+ description: Retrieve project attachments
+ security:
+ - Bearer: []
+ responses:
+ '200':
+ description: list of project attachments
+ schema:
+ type: array
+ items:
+ $ref: '#/definitions/ProjectAttachment'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ parameters:
+ - $ref: '#/parameters/projectIdParam'
+ operationId: listProjectAttachment
post:
tags:
- - project
+ - project attachments
description: add a new project attachment
security:
- Bearer: []
@@ -243,24 +280,63 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/NewProjectAttachmentBodyParam'
+ $ref: '#/definitions/NewProjectAttachment'
responses:
- '201':
+ '200':
description: Returns the newly created project attachment
schema:
- $ref: '#/definitions/NewProjectAttachmentResponse'
+ $ref: '#/definitions/ProjectAttachment'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/{projectId}/attachments/{id}':
+ get:
+ tags:
+ - project attachments
+ description: Download project attachment
+ security:
+ - Bearer: []
+ responses:
+ '200':
+ description: a project attachment
+ schema:
+ $ref: '#/definitions/ProjectAttachment'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '404':
+ description: Not found
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ parameters:
+ - $ref: '#/parameters/projectIdParam'
+ - in: path
+ name: id
+ required: true
+ description: The id of attachment to download
+ type: integer
+ operationId: downloadProjectAttachment
patch:
tags:
- - project
+ - project attachments
description: Update an existing attachment
security:
- Bearer: []
@@ -276,27 +352,35 @@ paths:
required: true
description: Specify only those properties that needs to be updated
schema:
- $ref: '#/definitions/NewProjectAttachmentBodyParam'
+ $ref: '#/definitions/NewProjectAttachment'
responses:
- '201':
+ '200':
description: Returns the newly created project
schema:
- $ref: '#/definitions/NewProjectAttachmentResponse'
+ $ref: '#/definitions/ProjectAttachment'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If project attachment is not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
delete:
tags:
- - project
+ - project attachments
description: remove an existing attachment
security:
- Bearer: []
@@ -310,18 +394,64 @@ paths:
responses:
'204':
description: Attachment successfully removed
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If attachment is not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
'/projects/{projectId}/members':
+ get:
+ tags:
+ - project members
+ description: Retrieve project members
+ security:
+ - Bearer: []
+ responses:
+ '200':
+ description: list of project members
+ schema:
+ type: array
+ items:
+ $ref: '#/definitions/ProjectMember'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ parameters:
+ - $ref: '#/parameters/projectIdParam'
+ - name: role
+ required: false
+ description: >
+ member role
+ in: query
+ type: string
+ operationId: listProjectMember
post:
tags:
- - project
+ - project members
description: add a new project member
security:
- Bearer: []
@@ -331,24 +461,62 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/NewProjectMemberBodyParam'
+ $ref: '#/definitions/NewProjectMember'
responses:
- '201':
+ '200':
description: Returns the newly created project
schema:
- $ref: '#/definitions/NewProjectMemberResponse'
+ $ref: '#/definitions/ProjectMember'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/{projectId}/members/{id}':
+ get:
+ tags:
+ - project members
+ description: Get project member
+ security:
+ - Bearer: []
+ responses:
+ '200':
+ description: a project member
+ schema:
+ $ref: '#/definitions/ProjectMember'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '404':
+ description: Not found
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ parameters:
+ - $ref: '#/parameters/projectIdParam'
+ - in: path
+ name: id
+ required: true
+ type: integer
+ operationId: getProjectMember
delete:
tags:
- - project
+ - project members
description: Delete a project member
security:
- Bearer: []
@@ -361,13 +529,29 @@ paths:
responses:
'204':
description: Member successfully removed
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '404':
+ description: If Project doesn't contain such Member
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
patch:
tags:
- - project
+ - project members
security:
- Bearer: []
description: Support editing project member roles & primary option.
@@ -377,21 +561,25 @@ paths:
Successfully updated project member. Returns entire project member
object
schema:
- $ref: '#/definitions/UpdateProjectMemberResponse'
+ $ref: '#/definitions/ProjectMember'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- default:
- description: error payload
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
parameters:
@@ -404,18 +592,18 @@ paths:
in: body
required: true
schema:
- $ref: '#/definitions/UpdateProjectMemberBodyParam'
- '/projects/{projectId}/phases/db':
+ $ref: '#/definitions/UpdateProjectMember'
+ '/projects/{projectId}/phases':
parameters:
- $ref: '#/parameters/projectIdParam'
get:
tags:
- phase
- operationId: findProjectPhasesDB
+ operationId: findProjectPhases
security:
- Bearer: []
description: >-
- Retrieve all project phases directly from database. All users who can edit project can access
+ Retrieve all project phases. All users who can edit project can access
this endpoint.
parameters:
- name: fields
@@ -435,56 +623,35 @@ paths:
'200':
description: A list of project phases
schema:
- $ref: '#/definitions/ProjectPhaseListResponse'
+ type: array
+ items:
+ $ref: '#/definitions/ProjectPhase'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
- '/projects/{projectId}/phases':
- parameters:
- - $ref: '#/parameters/projectIdParam'
- get:
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ post:
tags:
- phase
- operationId: findProjectPhases
+ operationId: addProjectPhase
security:
- Bearer: []
description: >-
- Retrieve all project phases. All users who can edit project can access
- this endpoint.
- parameters:
- - name: fields
- required: false
- type: string
- in: query
- description: |
- Comma separated list of project phase fields to return.
- - name: sort
- required: false
- description: >
- sort project phases by startDate, endDate, status, order. Default is
- startDate asc
- in: query
- type: string
- responses:
- '200':
- description: A list of project phases
- schema:
- $ref: '#/definitions/ProjectPhaseListResponse'
- '403':
- description: No permission or wrong token
- schema:
- $ref: '#/definitions/ErrorModel'
- post:
- tags:
- - phase
- operationId: addProjectPhase
- security:
- - Bearer: []
- description: >-
- Create a project phase. It also updates the `order` field of all other
- phases in the same project which have `order` greater than or equal to
- the `order` specified in the POST body.
+ Create a project phase. It also updates the `order` field of all other
+ phases in the same project which have `order` greater than or equal to
+ the `order` specified in the POST body.
parameters:
- in: body
name: body
@@ -492,28 +659,33 @@ paths:
schema:
type: object
allOf:
- - $ref: '#/definitions/ProjectPhaseBodyParam'
+ - $ref: '#/definitions/ProjectPhaseRequest'
properties:
- param:
- type: object
- properties:
- productTemplateId:
- type: number
- format: long
- description: >-
- the optional productTemplateId used to populate a new
- phase product for the created phase
- responses:
- '201':
+ productTemplateId:
+ type: number
+ format: long
+ description: >-
+ the optional productTemplateId used to populate a new
+ phase product for the created phase
+ responses:
+ '200':
description: Returns the newly created project phase
schema:
- $ref: '#/definitions/ProjectPhaseResponse'
+ $ref: '#/definitions/ProjectPhase'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/{projectId}/phases/{phaseId}':
@@ -532,15 +704,27 @@ paths:
'200':
description: a project phase
schema:
- $ref: '#/definitions/ProjectPhaseResponse'
+ $ref: '#/definitions/ProjectPhase'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
parameters:
- $ref: '#/parameters/phaseIdParam'
operationId: getProjectPhase
@@ -559,21 +743,25 @@ paths:
'200':
description: Successfully updated project phase.
schema:
- $ref: '#/definitions/ProjectPhaseResponse'
+ $ref: '#/definitions/ProjectPhase'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- default:
- description: error payload
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
parameters:
@@ -582,7 +770,7 @@ paths:
in: body
required: true
schema:
- $ref: '#/definitions/ProjectPhaseBodyParam'
+ $ref: '#/definitions/ProjectPhaseRequest'
delete:
tags:
- phase
@@ -596,14 +784,26 @@ paths:
responses:
'204':
description: Project phase successfully removed
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If project is not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
'/projects/{projectId}/workstreams':
parameters:
- $ref: '#/parameters/projectIdParam'
@@ -619,15 +819,25 @@ paths:
'200':
description: A list of project work streams
schema:
- $ref: '#/definitions/WorkStreamListResponse'
+ type: array
+ items:
+ $ref: '#/definitions/WorkStream'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If project is not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
post:
tags:
- workstream
@@ -643,22 +853,30 @@ paths:
schema:
type: object
allOf:
- - $ref: '#/definitions/WorkStreamBodyParam'
+ - $ref: '#/definitions/WorkStreamRequest'
responses:
- '201':
+ '200':
description: Returns the newly created project work stream
schema:
- $ref: '#/definitions/WorkStreamResponse'
+ $ref: '#/definitions/WorkStream'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If project is not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/{projectId}/workstreams/{workStreamId}':
@@ -676,15 +894,23 @@ paths:
'200':
description: a project work stream
schema:
- $ref: '#/definitions/WorkStreamResponse'
+ $ref: '#/definitions/WorkStream'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
parameters:
- $ref: '#/parameters/workStreamIdParam'
operationId: getWorkStream
@@ -700,21 +926,25 @@ paths:
'200':
description: Successfully updated project work stream.
schema:
- $ref: '#/definitions/WorkStreamResponse'
+ $ref: '#/definitions/WorkStream'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
- schema:
- $ref: '#/definitions/ErrorModel'
- default:
- description: error payload
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
parameters:
@@ -722,7 +952,7 @@ paths:
in: body
required: true
schema:
- $ref: '#/definitions/WorkStreamBodyParam'
+ $ref: '#/definitions/WorkStreamRequest'
delete:
tags:
- workstream
@@ -733,14 +963,22 @@ paths:
responses:
'204':
description: Project work stream successfully removed
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If project is not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
'/projects/{projectId}/workstreams/{workStreamId}/works':
parameters:
- $ref: '#/parameters/projectIdParam'
@@ -771,15 +1009,25 @@ paths:
'200':
description: A list of project works
schema:
- $ref: '#/definitions/ProjectPhaseListResponse'
+ type: array
+ items:
+ $ref: '#/definitions/ProjectPhase'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If project or workstream is not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
post:
tags:
- work
@@ -795,22 +1043,30 @@ paths:
schema:
type: object
allOf:
- - $ref: '#/definitions/ProjectPhaseBodyParam'
+ - $ref: '#/definitions/ProjectPhaseRequest'
responses:
- '201':
+ '200':
description: Returns the newly created project work
schema:
- $ref: '#/definitions/ProjectPhaseResponse'
+ $ref: '#/definitions/ProjectPhase'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If project or workstream is not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/{projectId}/workstreams/{workStreamId}/works/{phaseId}':
@@ -829,15 +1085,23 @@ paths:
'200':
description: a project work
schema:
- $ref: '#/definitions/ProjectPhaseResponse'
+ $ref: '#/definitions/ProjectPhase'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
parameters:
- $ref: '#/parameters/phaseIdParam'
operationId: getWork
@@ -853,21 +1117,25 @@ paths:
'200':
description: Successfully updated project work.
schema:
- $ref: '#/definitions/ProjectPhaseResponse'
+ $ref: '#/definitions/ProjectPhase'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
- schema:
- $ref: '#/definitions/ErrorModel'
- default:
- description: error payload
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
parameters:
@@ -876,7 +1144,7 @@ paths:
in: body
required: true
schema:
- $ref: '#/definitions/ProjectPhaseBodyParam'
+ $ref: '#/definitions/ProjectPhaseRequest'
delete:
tags:
- work
@@ -889,14 +1157,22 @@ paths:
responses:
'204':
description: Work successfully removed
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If project or workstream is not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
'/projects/{projectId}/workstreams/{workStreamId}/works/{phaseId}/workitems':
parameters:
- $ref: '#/parameters/projectIdParam'
@@ -914,15 +1190,25 @@ paths:
'200':
description: A list of work items
schema:
- $ref: '#/definitions/PhaseProductListResponse'
+ type: array
+ items:
+ $ref: '#/definitions/PhaseProduct'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If project, workstream or phase is not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
post:
tags:
- work item
@@ -935,22 +1221,30 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/PhaseProductBodyParam'
+ $ref: '#/definitions/PhaseProductRequest'
responses:
- '201':
+ '200':
description: Returns the newly created work item
schema:
- $ref: '#/definitions/PhaseProductResponse'
+ $ref: '#/definitions/PhaseProduct'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If project, workstream or phase is not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/{projectId}/workstreams/{workStreamId}/works/{phaseId}/workitems/{productId}':
@@ -970,15 +1264,23 @@ paths:
'200':
description: a work item
schema:
- $ref: '#/definitions/PhaseProductResponse'
+ $ref: '#/definitions/PhaseProduct'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
parameters:
- $ref: '#/parameters/phaseIdParam'
operationId: getWorkItem
@@ -994,21 +1296,25 @@ paths:
'200':
description: Successfully updated work item.
schema:
- $ref: '#/definitions/PhaseProductResponse'
+ $ref: '#/definitions/PhaseProduct'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
- schema:
- $ref: '#/definitions/ErrorModel'
- default:
- description: error payload
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
parameters:
@@ -1017,7 +1323,7 @@ paths:
in: body
required: true
schema:
- $ref: '#/definitions/PhaseProductBodyParam'
+ $ref: '#/definitions/PhaseProductRequest'
delete:
tags:
- work item
@@ -1030,14 +1336,22 @@ paths:
responses:
'204':
description: Work item successfully removed
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If project, workstream or phase is not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
'/projects/{projectId}/settings':
parameters:
- $ref: '#/parameters/projectIdParam'
@@ -1053,9 +1367,19 @@ paths:
'200':
description: A list of project phases
schema:
- $ref: '#/definitions/ProjectSettingListResponse'
+ type: array
+ items:
+ $ref: '#/definitions/ProjectSetting'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
post:
@@ -1073,18 +1397,26 @@ paths:
schema:
type: object
allOf:
- - $ref: '#/definitions/ProjectSettingBodyParam'
+ - $ref: '#/definitions/ProjectSettingRequest'
responses:
- '201':
+ '200':
description: Returns the newly created project phase
schema:
- $ref: '#/definitions/ProjectPhaseResponse'
+ $ref: '#/definitions/ProjectPhase'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/{projectId}/settings/{settingId}':
@@ -1103,21 +1435,25 @@ paths:
'200':
description: Successfully updated project setting.
schema:
- $ref: '#/definitions/ProjectSettingResponse'
+ $ref: '#/definitions/ProjectSetting'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
- schema:
- $ref: '#/definitions/ErrorModel'
- default:
- description: error payload
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
parameters:
@@ -1125,7 +1461,7 @@ paths:
in: body
required: true
schema:
- $ref: '#/definitions/ProjectSettingBodyParam'
+ $ref: '#/definitions/ProjectSettingRequest'
delete:
tags:
- project settings
@@ -1139,14 +1475,22 @@ paths:
responses:
'204':
description: Project setting successfully removed
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If project is not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
'/projects/{projectId}/estimations/{estimationId}/items':
get:
tags:
@@ -1161,9 +1505,15 @@ paths:
'200':
description: List of project estimation items
schema:
- $ref: '#/definitions/ProjectEstimationItemListResponse'
+ type: array
+ items:
+ $ref: '#/definitions/ProjectEstimationItem'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -1174,28 +1524,6 @@ paths:
description: Invalid server state or unknown error
schema:
$ref: '#/definitions/ErrorModel'
- '/projects/{projectId}/phases/{phaseId}/products/db':
- parameters:
- - $ref: '#/parameters/projectIdParam'
- - $ref: '#/parameters/phaseIdParam'
- get:
- tags:
- - phase product
- operationId: findPhaseProductsDB
- security:
- - Bearer: []
- description: >-
- Retrieve all phase products directly from database. All users who can edit project can access
- this endpoint.
- responses:
- '200':
- description: A list of phase products
- schema:
- $ref: '#/definitions/PhaseProductListResponse'
- '403':
- description: No permission or wrong token
- schema:
- $ref: '#/definitions/ErrorModel'
'/projects/{projectId}/phases/{phaseId}/products':
parameters:
- $ref: '#/parameters/projectIdParam'
@@ -1213,9 +1541,23 @@ paths:
'200':
description: A list of phase products
schema:
- $ref: '#/definitions/PhaseProductListResponse'
+ type: array
+ items:
+ $ref: '#/definitions/PhaseProduct'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
post:
@@ -1230,18 +1572,26 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/PhaseProductBodyParam'
+ $ref: '#/definitions/PhaseProductRequest'
responses:
- '201':
+ '200':
description: Returns the newly created phase product
schema:
- $ref: '#/definitions/PhaseProductResponse'
+ $ref: '#/definitions/PhaseProduct'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/{projectId}/phases/{phaseId}/products/{productId}':
@@ -1261,15 +1611,27 @@ paths:
'200':
description: a phase product
schema:
- $ref: '#/definitions/PhaseProductResponse'
+ $ref: '#/definitions/PhaseProduct'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
parameters:
- $ref: '#/parameters/phaseIdParam'
operationId: getPhaseProduct
@@ -1286,21 +1648,25 @@ paths:
'200':
description: Successfully updated phase product.
schema:
- $ref: '#/definitions/PhaseProductResponse'
+ $ref: '#/definitions/PhaseProduct'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- default:
- description: error payload
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
parameters:
@@ -1309,7 +1675,7 @@ paths:
in: body
required: true
schema:
- $ref: '#/definitions/PhaseProductBodyParam'
+ $ref: '#/definitions/PhaseProductRequest'
delete:
tags:
- phase product
@@ -1323,14 +1689,26 @@ paths:
responses:
'204':
description: Project phase successfully removed
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If project is not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
'/projects/{projectId}/upgrade':
post:
tags:
@@ -1348,18 +1726,22 @@ paths:
required: true
description: Project upgrade body
schema:
- $ref: '#/definitions/ProjectUpgradeBodyParam'
+ $ref: '#/definitions/ProjectUpgrade'
responses:
'200':
description: Project migrated successfully
schema:
- $ref: '#/definitions/ProjectUpgradeResponse'
+ $ref: '#/definitions/ProjectUpgrade'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'400':
- description: Invalid input
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -1367,7 +1749,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
/projects/metadata:
@@ -1386,8 +1768,12 @@ paths:
description: The metadata
schema:
$ref: '#/definitions/AllMetadataResponse'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
/projects/metadata/projectTemplates:
@@ -1402,9 +1788,15 @@ paths:
'200':
description: A list of project templates
schema:
- $ref: '#/definitions/ProjectTemplateListResponse'
- '403':
- description: No permission or wrong token
+ type: array
+ items:
+ $ref: '#/definitions/ProjectTemplate'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
post:
@@ -1419,18 +1811,26 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/ProjectTemplateBodyParam'
+ $ref: '#/definitions/ProjectTemplateRequest'
responses:
- '201':
+ '200':
description: Returns the newly created project template
schema:
- $ref: '#/definitions/ProjectTemplateResponse'
+ $ref: '#/definitions/ProjectTemplate'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/metadata/projectTemplates/{templateId}':
@@ -1446,15 +1846,23 @@ paths:
'200':
description: a project template
schema:
- $ref: '#/definitions/ProjectTemplateResponse'
- '403':
- description: No permission or wrong token
+ $ref: '#/definitions/ProjectTemplate'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
parameters:
- $ref: '#/parameters/templateIdParam'
operationId: getProjectTemplate
@@ -1473,21 +1881,25 @@ paths:
'200':
description: Successfully updated project template.
schema:
- $ref: '#/definitions/ProjectTemplateResponse'
+ $ref: '#/definitions/ProjectTemplate'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- default:
- description: error payload
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
parameters:
@@ -1496,7 +1908,7 @@ paths:
in: body
required: true
schema:
- $ref: '#/definitions/ProjectTemplateBodyParam'
+ $ref: '#/definitions/ProjectTemplateRequest'
delete:
tags:
- projectTemplate
@@ -1510,14 +1922,26 @@ paths:
responses:
'204':
description: Project template successfully removed
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If project is not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
'/projects/metadata/projectTemplates/{templateId}/upgrade':
post:
tags:
@@ -1532,24 +1956,28 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/ProjectTemplateUpgradeBodyParam'
+ $ref: '#/definitions/ProjectTemplateUpgradeBody'
responses:
'200':
description: Project template successfully upgrade
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If project template is not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Server Error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
/projects/metadata/productTemplates:
@@ -1564,9 +1992,15 @@ paths:
'200':
description: A list of product templates
schema:
- $ref: '#/definitions/ProductTemplateListResponse'
- '403':
- description: No permission or wrong token
+ type: array
+ items:
+ $ref: '#/definitions/ProductTemplate'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
post:
@@ -1581,18 +2015,26 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/ProductTemplateBodyParam'
+ $ref: '#/definitions/ProductTemplateRequest'
responses:
- '201':
+ '200':
description: Returns the newly created product template
schema:
- $ref: '#/definitions/ProductTemplateResponse'
+ $ref: '#/definitions/ProductTemplate'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/metadata/productTemplates/{templateId}':
@@ -1608,15 +2050,23 @@ paths:
'200':
description: a product template
schema:
- $ref: '#/definitions/ProductTemplateResponse'
- '403':
- description: No permission or wrong token
+ $ref: '#/definitions/ProductTemplate'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
parameters:
- $ref: '#/parameters/templateIdParam'
operationId: getProductTemplate
@@ -1635,21 +2085,25 @@ paths:
'200':
description: Successfully updated product template.
schema:
- $ref: '#/definitions/ProductTemplateResponse'
+ $ref: '#/definitions/ProductTemplate'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- default:
- description: error payload
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
parameters:
@@ -1658,7 +2112,7 @@ paths:
in: body
required: true
schema:
- $ref: '#/definitions/ProductTemplateBodyParam'
+ $ref: '#/definitions/ProductTemplateRequest'
delete:
tags:
- productTemplate
@@ -1672,14 +2126,26 @@ paths:
responses:
'204':
description: Product template successfully removed
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If product is not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
'/projects/metadata/productTemplates/{templateId}/upgrade':
post:
tags:
@@ -1694,24 +2160,28 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/ProductTemplateUpgradeBodyParam'
+ $ref: '#/definitions/ProductTemplateUpgradeBody'
responses:
'200':
description: Product template successfully upgraded
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If product template is not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Server Error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
/projects/metadata/productCategories:
@@ -1728,9 +2198,15 @@ paths:
'200':
description: A list of product categories
schema:
- $ref: '#/definitions/ProductCategoryListResponse'
- '403':
- description: No permission or wrong token
+ type: array
+ items:
+ $ref: '#/definitions/ProductCategory'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
post:
@@ -1747,18 +2223,26 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/ProductCategoryCreateBodyParam'
+ $ref: '#/definitions/ProductCategoryCreateRequest'
responses:
- '201':
+ '200':
description: Returns the newly created product category
schema:
- $ref: '#/definitions/ProductCategoryResponse'
+ $ref: '#/definitions/ProductCategory'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/metadata/productCategories/{key}':
@@ -1774,15 +2258,23 @@ paths:
'200':
description: a product category
schema:
- $ref: '#/definitions/ProductCategoryResponse'
- '403':
- description: No permission or wrong token
+ $ref: '#/definitions/ProductCategory'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
parameters:
- $ref: '#/parameters/keyParam'
operationId: getProductCategory
@@ -1799,21 +2291,25 @@ paths:
'200':
description: Successfully updated product category.
schema:
- $ref: '#/definitions/ProductCategoryResponse'
+ $ref: '#/definitions/ProductCategory'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- default:
- description: error payload
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
parameters:
@@ -1822,7 +2318,7 @@ paths:
in: body
required: true
schema:
- $ref: '#/definitions/ProductCategoryBodyParam'
+ $ref: '#/definitions/ProductCategoryRequest'
delete:
tags:
- productCategory
@@ -1836,14 +2332,26 @@ paths:
responses:
'204':
description: Product category successfully removed
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If product category is not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
/projects/metadata/projectTypes:
get:
tags:
@@ -1856,9 +2364,15 @@ paths:
'200':
description: A list of project types
schema:
- $ref: '#/definitions/ProjectTypeListResponse'
- '403':
- description: No permission or wrong token
+ type: array
+ items:
+ $ref: '#/definitions/ProjectType'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
post:
@@ -1875,18 +2389,26 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/ProjectTypeCreateBodyParam'
+ $ref: '#/definitions/ProjectTypeCreateRequest'
responses:
- '201':
+ '200':
description: Returns the newly created project type
schema:
- $ref: '#/definitions/ProjectTypeResponse'
+ $ref: '#/definitions/ProjectType'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/metadata/projectTypes/{key}':
@@ -1900,15 +2422,23 @@ paths:
'200':
description: a project type
schema:
- $ref: '#/definitions/ProjectTypeResponse'
- '403':
- description: No permission or wrong token
+ $ref: '#/definitions/ProjectType'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
parameters:
- $ref: '#/parameters/keyParam'
operationId: getProjectType
@@ -1925,21 +2455,25 @@ paths:
'200':
description: Successfully updated project type.
schema:
- $ref: '#/definitions/ProjectTypeResponse'
+ $ref: '#/definitions/ProjectType'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- default:
- description: error payload
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
parameters:
@@ -1948,7 +2482,7 @@ paths:
in: body
required: true
schema:
- $ref: '#/definitions/ProjectTypeBodyParam'
+ $ref: '#/definitions/ProjectTypeRequest'
delete:
tags:
- projectType
@@ -1962,14 +2496,26 @@ paths:
responses:
'204':
description: Project type successfully removed
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If project is not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
/projects/metadata/orgConfig:
get:
tags:
@@ -1981,21 +2527,33 @@ paths:
Retrieve all organization configs. All user roles can access this
endpoint.
parameters:
- - name: filter
+ - name: orgId
required: true
type: string
in: query
- description: |
- Url encoded list of Supported filters
- - orgId (required)
- - configName
+ description: organization id
+ - name: configName
+ required: false
+ type: string
+ in: query
+ description: configuration name
responses:
'200':
description: A list of organization configs
schema:
- $ref: '#/definitions/OrgConfigListResponse'
- '403':
- description: No permission or wrong token
+ type: array
+ items:
+ $ref: '#/definitions/OrgConfig'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
post:
@@ -2012,18 +2570,26 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/OrgConfigCreateBodyParam'
+ $ref: '#/definitions/OrgConfigCreateRequest'
responses:
- '201':
+ '200':
description: Returns the newly created organization config
schema:
- $ref: '#/definitions/OrgConfigResponse'
+ $ref: '#/definitions/OrgConfig'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/metadata/orgConfig/{id}':
@@ -2037,15 +2603,23 @@ paths:
'200':
description: a project type
schema:
- $ref: '#/definitions/OrgConfigResponse'
- '403':
- description: No permission or wrong token
+ $ref: '#/definitions/OrgConfig'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
parameters:
- $ref: '#/parameters/idParam'
operationId: getOrgConfig
@@ -2062,21 +2636,25 @@ paths:
'200':
description: Successfully updated organization config.
schema:
- $ref: '#/definitions/OrgConfigResponse'
+ $ref: '#/definitions/OrgConfig'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- default:
- description: error payload
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
parameters:
@@ -2085,7 +2663,7 @@ paths:
in: body
required: true
schema:
- $ref: '#/definitions/OrgConfigCreateBodyParam'
+ $ref: '#/definitions/OrgConfigCreateRequest'
delete:
tags:
- orgConfig
@@ -2099,15 +2677,26 @@ paths:
responses:
'204':
description: Organization config successfully removed
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If organization config is not found
schema:
$ref: '#/definitions/ErrorModel'
-
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
/projects/metadata/workManagementPermission:
get:
tags:
@@ -2130,9 +2719,19 @@ paths:
'200':
description: A list of work management permissions
schema:
- $ref: '#/definitions/WorkManagementPermissionListResponse'
+ type: array
+ items:
+ $ref: '#/definitions/WorkManagementPermission'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
post:
@@ -2149,18 +2748,26 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/WorkManagementPermissionCreateBodyParam'
+ $ref: '#/definitions/WorkManagementPermissionCreateRequest'
responses:
- '201':
+ '200':
description: Returns the newly created work management permission
schema:
- $ref: '#/definitions/WorkManagementPermissionResponse'
+ $ref: '#/definitions/WorkManagementPermission'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/metadata/workManagementPermission/{id}':
@@ -2175,15 +2782,23 @@ paths:
'200':
description: a project type
schema:
- $ref: '#/definitions/WorkManagementPermissionResponse'
+ $ref: '#/definitions/WorkManagementPermission'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
parameters:
- $ref: '#/parameters/permissionIdParam'
operationId: getWorkManagementPermission
@@ -2200,21 +2815,25 @@ paths:
'200':
description: Successfully updated work management permission.
schema:
- $ref: '#/definitions/WorkManagementPermissionResponse'
+ $ref: '#/definitions/WorkManagementPermission'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
- schema:
- $ref: '#/definitions/ErrorModel'
- default:
- description: error payload
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
parameters:
@@ -2223,7 +2842,7 @@ paths:
in: body
required: true
schema:
- $ref: '#/definitions/WorkManagementPermissionCreateBodyParam'
+ $ref: '#/definitions/WorkManagementPermissionCreateRequest'
delete:
tags:
- workManagementPermission
@@ -2237,14 +2856,22 @@ paths:
responses:
'204':
description: Work management permission successfully removed
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: If work management permission is not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
'/projects/{projectId}/permissions':
get:
@@ -2259,39 +2886,29 @@ paths:
schema:
title: Single work management permission response object
type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- type: object
- example:
- 'work.create': true
+ example:
+ 'work.create': true
+ 'workItem.edit': true
+
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
+ schema:
+ $ref: '#/definitions/ErrorModel'
parameters:
- $ref: '#/parameters/projectIdParam'
operationId: getPermissions
-
/timelines:
get:
tags:
@@ -2301,25 +2918,37 @@ paths:
- Bearer: []
description: Retrieve timelines which its projects are accessible by the user.
parameters:
- - name: filter
+ - name: reference
required: false
type: string
in: query
- description: |
- Url encoded list of supported filters
- - reference
- - referenceId
+ description: the reference filter
+ - name: referenceId
+ required: false
+ type: string
+ in: query
+ description: the reference id filter
responses:
'200':
description: A list of timelines
schema:
- $ref: '#/definitions/TimelineListResponse'
+ type: array
+ items:
+ $ref: '#/definitions/Timeline'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
post:
@@ -2336,18 +2965,26 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/TimelineBodyParam'
+ $ref: '#/definitions/TimelineRequest'
responses:
- '201':
+ '200':
description: Returns the newly created timeline
schema:
- $ref: '#/definitions/TimelineResponse'
- '403':
- description: No permission or wrong token
+ $ref: '#/definitions/Timeline'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '403':
+ description: Forbidden
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/timelines/{timelineId}':
@@ -2363,17 +3000,25 @@ paths:
'200':
description: a timeline
schema:
- $ref: '#/definitions/TimelineResponse'
+ $ref: '#/definitions/Timeline'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
parameters:
@@ -2392,21 +3037,25 @@ paths:
'200':
description: Successfully updated timeline.
schema:
- $ref: '#/definitions/TimelineResponse'
+ $ref: '#/definitions/Timeline'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- default:
- description: error payload
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
parameters:
@@ -2415,7 +3064,7 @@ paths:
in: body
required: true
schema:
- $ref: '#/definitions/TimelineBodyParam'
+ $ref: '#/definitions/TimelineRequest'
delete:
tags:
- timeline
@@ -2429,16 +3078,24 @@ paths:
responses:
'204':
description: Timeline successfully removed
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/timelines/{timelineId}/milestones':
@@ -2463,13 +3120,23 @@ paths:
'200':
description: A list of milestones
schema:
- $ref: '#/definitions/MilestoneListResponse'
+ type: array
+ items:
+ $ref: '#/definitions/Milestone'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
post:
@@ -2488,18 +3155,26 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/MilestonePostBodyParam'
+ $ref: '#/definitions/MilestonePostRequest'
responses:
- '201':
+ '200':
description: Returns the newly created milestone
schema:
- $ref: '#/definitions/MilestoneResponse'
+ $ref: '#/definitions/Milestone'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/timelines/{timelineId}/milestones/{milestoneId}':
@@ -2518,17 +3193,25 @@ paths:
'200':
description: a milestone
schema:
- $ref: '#/definitions/MilestoneResponse'
+ $ref: '#/definitions/Milestone'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
operationId: getMilestone
@@ -2547,21 +3230,25 @@ paths:
'200':
description: Successfully updated milestone.
schema:
- $ref: '#/definitions/MilestoneResponse'
+ $ref: '#/definitions/Milestone'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- default:
- description: error payload
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
parameters:
@@ -2569,7 +3256,7 @@ paths:
in: body
required: true
schema:
- $ref: '#/definitions/MilestonePatchBodyParam'
+ $ref: '#/definitions/MilestonePatchRequest'
delete:
tags:
- milestone
@@ -2581,16 +3268,24 @@ paths:
responses:
'204':
description: Milestone successfully removed
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
/timelines/metadata/milestoneTemplates:
@@ -2609,25 +3304,33 @@ paths:
description: sort by `order`. Default is `order asc`
in: query
type: string
- - name: filter
+ - name: reference
required: false
+ description: the reference filter
+ in: query
type: string
+ - name: referenceId
+ required: false
+ description: the reference id filter
in: query
- description: |
- Url encoded list of supported filters
- - reference
- - referenceId
+ type: string
responses:
'200':
description: A list of milestone templates
schema:
- $ref: '#/definitions/MilestoneTemplateListResponse'
- '403':
- description: No permission or wrong token
+ type: array
+ items:
+ $ref: '#/definitions/MilestoneTemplate'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
post:
@@ -2646,18 +3349,26 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/MilestoneTemplateBodyParam'
+ $ref: '#/definitions/MilestoneTemplate'
responses:
- '201':
+ '200':
description: Returns the newly created milestone template
schema:
- $ref: '#/definitions/MilestoneTemplateResponse'
+ $ref: '#/definitions/MilestoneTemplate'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
/timelines/metadata/milestoneTemplates/clone:
@@ -2677,20 +3388,30 @@ paths:
schema:
$ref: '#/definitions/MilestoneCloneTemplateRequest'
responses:
- '201':
+ '200':
description: Returns the list of cloned milestone templates
schema:
- $ref: '#/definitions/MilestoneTemplateListResponse'
+ type: array
+ items:
+ $ref: '#/definitions/MilestoneTemplate'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/timelines/metadata/milestoneTemplates/{milestoneTemplateId}':
@@ -2708,17 +3429,21 @@ paths:
'200':
description: a milestone template
schema:
- $ref: '#/definitions/MilestoneTemplateResponse'
- '403':
- description: No permission or wrong token
+ $ref: '#/definitions/MilestoneTemplate'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
operationId: getMilestoneTemplate
@@ -2735,21 +3460,25 @@ paths:
'200':
description: Successfully updated milestone template.
schema:
- $ref: '#/definitions/MilestoneTemplateResponse'
+ $ref: '#/definitions/MilestoneTemplate'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- default:
- description: error payload
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
parameters:
@@ -2757,7 +3486,7 @@ paths:
in: body
required: true
schema:
- $ref: '#/definitions/MilestoneTemplateBodyParam'
+ $ref: '#/definitions/MilestoneTemplate'
delete:
tags:
- milestoneTemplates
@@ -2769,16 +3498,24 @@ paths:
responses:
'204':
description: Milestone template successfully removed
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found
schema:
$ref: '#/definitions/ErrorModel'
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '500':
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/{projectId}/members/invite':
@@ -2795,13 +3532,13 @@ paths:
'200':
description: The invite for current user
schema:
- $ref: '#/definitions/ProjectMemberInviteResponse'
+ $ref: '#/definitions/ProjectMemberInviteSuccessAndFailure'
'400':
- description: Invalid input
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
- '403':
- description: No permission or wrong token
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -2809,7 +3546,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
post:
@@ -2829,20 +3566,24 @@ paths:
schema:
$ref: '#/definitions/AddProjectMemberInvitesRequest'
responses:
- '201':
+ '200':
description: Returns the newly created invite
schema:
- $ref: '#/definitions/ProjectMemberInviteResponse'
+ $ref: '#/definitions/ProjectMemberInviteSuccessAndFailure'
'400':
- description: Invalid input
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
put:
@@ -2865,17 +3606,21 @@ paths:
'200':
description: Returns the newly updated invite
schema:
- $ref: '#/definitions/ProjectMemberInviteResponse'
+ $ref: '#/definitions/ProjectMemberInviteSuccessAndFailure'
'400':
- description: Invalid input
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'403':
- description: No permission or wrong token
+ description: Forbidden
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
@@ -2892,9 +3637,13 @@ paths:
'200':
description: The model for the latest revision of latest version
schema:
- $ref: '#/definitions/FormResponse'
- '422':
- description: Invalid input
+ $ref: '#/definitions/Form'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -2902,7 +3651,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/metadata/form/{key}/versions':
@@ -2918,9 +3667,15 @@ paths:
'200':
description: The model list for the all version
schema:
- $ref: '#/definitions/FormListResponse'
- '422':
- description: Invalid input
+ type: array
+ items:
+ $ref: '#/definitions/Form'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -2928,7 +3683,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
post:
@@ -2943,14 +3698,18 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/NewFormParam'
+ $ref: '#/definitions/NewForm'
responses:
- '201':
+ '200':
description: The model created
schema:
- $ref: '#/definitions/FormResponse'
- '422':
- description: Invalid input
+ $ref: '#/definitions/Form'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -2958,7 +3717,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/metadata/form/{key}/versions/{version}':
@@ -2975,9 +3734,13 @@ paths:
'200':
description: The model for the particular version
schema:
- $ref: '#/definitions/FormResponse'
- '422':
- description: Invalid input
+ $ref: '#/definitions/Form'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -2985,7 +3748,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
patch:
@@ -3001,14 +3764,18 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/NewFormParam'
+ $ref: '#/definitions/NewForm'
responses:
- '201':
+ '200':
description: The model updated
schema:
- $ref: '#/definitions/FormResponse'
- '422':
- description: Invalid input
+ $ref: '#/definitions/Form'
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3016,7 +3783,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
delete:
@@ -3031,8 +3798,12 @@ paths:
responses:
'204':
description: Delete succuessful
- '422':
- description: Invalid input
+ '401':
+ description: Unauthorized
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '400':
+ description: Bad request
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3040,7 +3811,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/metadata/form/{key}/versions/{version}/revisions':
@@ -3057,9 +3828,15 @@ paths:
'200':
description: The model for the particular version
schema:
- $ref: '#/definitions/FormListResponse'
- '422':
- description: Invalid input
+ type: array
+ items:
+ $ref: '#/definitions/Form'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3067,7 +3844,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
post:
@@ -3083,14 +3860,18 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/NewFormParam'
+ $ref: '#/definitions/NewForm'
responses:
- '201':
+ '200':
description: The model created
schema:
- $ref: '#/definitions/FormResponse'
- '422':
- description: Invalid input
+ $ref: '#/definitions/Form'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3098,7 +3879,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/metadata/form/{key}/versions/{version}/revisions/{revision}':
@@ -3116,9 +3897,13 @@ paths:
'200':
description: The model for the particular version
schema:
- $ref: '#/definitions/FormResponse'
- '422':
- description: Invalid input
+ $ref: '#/definitions/Form'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3126,7 +3911,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
delete:
@@ -3142,8 +3927,12 @@ paths:
responses:
'204':
description: Delete succuessful
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3151,7 +3940,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
@@ -3168,9 +3957,13 @@ paths:
'200':
description: The model for the latest revision of latest version
schema:
- $ref: '#/definitions/PriceConfigResponse'
- '422':
- description: Invalid input
+ $ref: '#/definitions/PriceConfig'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3178,7 +3971,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/metadata/priceConfig/{key}/versions':
@@ -3194,9 +3987,15 @@ paths:
'200':
description: The model list for the all version
schema:
- $ref: '#/definitions/PriceConfigListResponse'
- '422':
- description: Invalid input
+ type: array
+ items:
+ $ref: '#/definitions/PriceConfig'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3204,7 +4003,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
post:
@@ -3219,18 +4018,22 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/NewPriceConfigParam'
+ $ref: '#/definitions/NewPriceConfig'
responses:
- '201':
+ '200':
description: The model created
schema:
- $ref: '#/definitions/PriceConfigResponse'
- '422':
- description: Invalid input
+ $ref: '#/definitions/PriceConfig'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/metadata/priceConfig/{key}/versions/{version}':
@@ -3247,9 +4050,13 @@ paths:
'200':
description: The model for the particular version
schema:
- $ref: '#/definitions/PriceConfigResponse'
- '422':
- description: Invalid input
+ $ref: '#/definitions/PriceConfig'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3257,7 +4064,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
patch:
@@ -3273,14 +4080,18 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/NewPriceConfigParam'
+ $ref: '#/definitions/NewPriceConfig'
responses:
- '201':
+ '200':
description: The model updated
schema:
- $ref: '#/definitions/PriceConfigResponse'
- '422':
- description: Invalid input
+ $ref: '#/definitions/PriceConfig'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3288,7 +4099,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
delete:
@@ -3303,8 +4114,12 @@ paths:
responses:
'204':
description: Delete succuessful
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3312,7 +4127,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/metadata/priceConfig/{key}/versions/{version}/revisions':
@@ -3329,9 +4144,15 @@ paths:
'200':
description: The model for the particular version
schema:
- $ref: '#/definitions/PriceConfigListResponse'
- '422':
- description: Invalid input
+ type: array
+ items:
+ $ref: '#/definitions/PriceConfig'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3339,7 +4160,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
post:
@@ -3355,14 +4176,18 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/NewPriceConfigParam'
+ $ref: '#/definitions/NewPriceConfig'
responses:
- '201':
+ '200':
description: The model created
schema:
- $ref: '#/definitions/PriceConfigResponse'
- '422':
- description: Invalid input
+ $ref: '#/definitions/PriceConfig'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3370,7 +4195,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/metadata/priceConfig/{key}/versions/{version}/revisions/{revision}':
@@ -3388,9 +4213,13 @@ paths:
'200':
description: The model for the particular version
schema:
- $ref: '#/definitions/PriceConfigResponse'
- '422':
- description: Invalid input
+ $ref: '#/definitions/PriceConfig'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3398,7 +4227,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
delete:
@@ -3414,8 +4243,12 @@ paths:
responses:
'204':
description: Delete succuessful
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3423,7 +4256,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
@@ -3440,9 +4273,13 @@ paths:
'200':
description: The model for the latest revision of latest version
schema:
- $ref: '#/definitions/PlanConfigResponse'
- '422':
- description: Invalid input
+ $ref: '#/definitions/PlanConfig'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3450,7 +4287,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/metadata/planConfig/{key}/versions':
@@ -3466,9 +4303,15 @@ paths:
'200':
description: The model list for the all version
schema:
- $ref: '#/definitions/PlanConfigListResponse'
- '422':
- description: Invalid input
+ type: array
+ items:
+ $ref: '#/definitions/PlanConfig'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3476,7 +4319,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
post:
@@ -3491,18 +4334,22 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/NewPlanConfigParam'
+ $ref: '#/definitions/NewPlanConfig'
responses:
- '201':
+ '200':
description: The model created
schema:
- $ref: '#/definitions/PlanConfigResponse'
- '422':
- description: Invalid input
+ $ref: '#/definitions/PlanConfig'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/metadata/planConfig/{key}/versions/{version}':
@@ -3519,9 +4366,13 @@ paths:
'200':
description: The model for the particular version
schema:
- $ref: '#/definitions/PlanConfigResponse'
- '422':
- description: Invalid input
+ $ref: '#/definitions/PlanConfig'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3529,7 +4380,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
patch:
@@ -3545,14 +4396,18 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/NewPlanConfigParam'
+ $ref: '#/definitions/NewPlanConfig'
responses:
- '201':
+ '200':
description: The model updated
schema:
- $ref: '#/definitions/PlanConfigResponse'
- '422':
- description: Invalid input
+ $ref: '#/definitions/PlanConfig'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3560,7 +4415,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
delete:
@@ -3575,8 +4430,12 @@ paths:
responses:
'204':
description: Delete succuessful
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3584,7 +4443,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/metadata/planConfig/{key}/versions/{version}/revisions':
@@ -3601,9 +4460,15 @@ paths:
'200':
description: The model for the particular version
schema:
- $ref: '#/definitions/PlanConfigListResponse'
- '422':
- description: Invalid input
+ type: array
+ items:
+ $ref: '#/definitions/PlanConfig'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3611,7 +4476,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
post:
@@ -3627,14 +4492,18 @@ paths:
name: body
required: true
schema:
- $ref: '#/definitions/NewPlanConfigParam'
+ $ref: '#/definitions/NewPlanConfig'
responses:
- '201':
+ '200':
description: The model created
schema:
- $ref: '#/definitions/PlanConfigResponse'
- '422':
- description: Invalid input
+ $ref: '#/definitions/PlanConfig'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3642,7 +4511,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
'/projects/metadata/planConfig/{key}/versions/{version}/revisions/{revision}':
@@ -3660,9 +4529,13 @@ paths:
'200':
description: The model for the particular version
schema:
- $ref: '#/definitions/PlanConfigResponse'
- '422':
- description: Invalid input
+ $ref: '#/definitions/PlanConfig'
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3670,7 +4543,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
delete:
@@ -3686,8 +4559,12 @@ paths:
responses:
'204':
description: Delete succuessful
- '422':
- description: Invalid input
+ '400':
+ description: Bad request
+ schema:
+ $ref: '#/definitions/ErrorModel'
+ '401':
+ description: Unauthorized
schema:
$ref: '#/definitions/ErrorModel'
'404':
@@ -3695,7 +4572,7 @@ paths:
schema:
$ref: '#/definitions/ErrorModel'
'500':
- description: Invalid server state or unknown error
+ description: Internal Server Error
schema:
$ref: '#/definitions/ErrorModel'
@@ -3799,20 +4676,21 @@ parameters:
required: true
type: integer
format: int64
- offsetParam:
- name: offset
- description: number of items to skip. Defaults to 0
+ pageParam:
+ name: page
in: query
+ description: The page number.
required: false
type: integer
- format: int32
- limitParam:
- name: limit
- description: max records to return. Defaults to 20
+ default: 1
+ perPageParam:
+ name: perPage
in: query
+ description: The number of items to list per page.
required: false
type: integer
- format: int32
+ default: 20
+ maximum: 100
modelKeyParam:
name: key
in: path
@@ -3847,6 +4725,60 @@ parameters:
required: true
type: integer
format: int64
+ idQueryParam:
+ name: id
+ in: query
+ description: id filter
+ required: false
+ type: string
+ statusQueryParam:
+ name: status
+ in: query
+ description: status filter
+ required: false
+ type: string
+ typeQueryParam:
+ name: type
+ in: query
+ description: type filter
+ required: false
+ type: string
+ memberOnlyQueryParam:
+ name: memberOnly
+ in: query
+ description: memberOnly filter
+ required: false
+ type: boolean
+ keywordQueryParam:
+ name: keyword
+ in: query
+ description: keyword filter
+ required: false
+ type: string
+ nameQueryParam:
+ name: name
+ in: query
+ description: name filter
+ required: false
+ type: string
+ codeQueryParam:
+ name: code
+ in: query
+ description: code filter
+ required: false
+ type: string
+ customerQueryParam:
+ name: customer
+ in: query
+ description: customer filter
+ required: false
+ type: string
+ managerQueryParam:
+ name: manager
+ in: query
+ description: manager filter
+ required: false
+ type: string
projectEstimationIdParam:
name: estimationId
in: path
@@ -3855,35 +4787,11 @@ parameters:
type: integer
format: int64
definitions:
- ResponseMetadata:
- title: Metadata object for a response
- type: object
- properties:
- totalCount:
- type: integer
- format: int64
- description: Total count of the objects
ErrorModel:
type: object
properties:
- id:
- type: string
- description: unique id identifying the request
- version:
+ message:
type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- description: http status code
- type: integer
- format: int32
- debug:
- type: object
- content:
- type: object
ProjectBookMark:
title: Project bookmark
type: object
@@ -3892,16 +4800,6 @@ definitions:
type: string
address:
type: string
- ProjectBodyParam:
- type: object
- properties:
- param:
- $ref: '#/definitions/Project'
- ProjectUpgradeBodyParam:
- type: object
- properties:
- param:
- $ref: '#/definitions/ProjectUpgrade'
NewProject:
type: object
required:
@@ -4000,11 +4898,6 @@ definitions:
description: the project template identifier
type: number
format: long
- NewProjectBodyParam:
- type: object
- properties:
- param:
- $ref: '#/definitions/NewProject'
ChallengeEligibility:
description: Object describing who is eligible to work on this task
type: object
@@ -4216,11 +5109,6 @@ definitions:
- customer
- manager
- copilot
- NewProjectMemberBodyParam:
- type: object
- properties:
- param:
- $ref: '#/definitions/NewProjectMember'
UpdateProjectMember:
title: Project Member object
type: object
@@ -4237,11 +5125,6 @@ definitions:
- customer
- manager
- copilot
- UpdateProjectMemberBodyParam:
- type: object
- properties:
- param:
- $ref: '#/definitions/UpdateProjectMember'
NewProjectAttachment:
title: Project attachment request
type: object
@@ -4279,31 +5162,6 @@ definitions:
type: integer
format: int64
description: Users allowed to access the attachment
- NewProjectAttachmentBodyParam:
- type: object
- properties:
- param:
- $ref: '#/definitions/NewProjectAttachment'
- NewProjectAttachmentResponse:
- title: Project attachment object response
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- content:
- $ref: '#/definitions/ProjectAttachment'
ProjectAttachment:
title: Project attachment
type: object
@@ -4391,116 +5249,6 @@ definitions:
format: int64
description: READ-ONLY. User that last updated this task
readOnly: true
- NewProjectMemberResponse:
- title: Project member object response
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- content:
- $ref: '#/definitions/ProjectMember'
- UpdateProjectMemberResponse:
- title: Project member object response
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- content:
- $ref: '#/definitions/ProjectMember'
- ProjectResponse:
- title: Single project object
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- content:
- $ref: '#/definitions/Project'
- UpdateProjectResponse:
- title: response with original and updated project object
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- content:
- type: object
- properties:
- original:
- $ref: '#/definitions/Project'
- updated:
- $ref: '#/definitions/Project'
- ProjectListResponse:
- title: List response
- type: object
- properties:
- id:
- type: string
- readOnly: true
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- type: array
- items:
- $ref: '#/definitions/Project'
ProjectTemplateRequest:
title: Project template request object
type: object
@@ -4530,28 +5278,22 @@ definitions:
$ref: '#/definitions/VersionModelParam'
planConfig:
$ref: '#/definitions/VersionModelParam'
- ProjectTemplateUpgradeBodyParam:
+ ProjectTemplateUpgradeBody:
title: Project template
type: object
properties:
- param:
- type: object
- properties:
- form:
- $ref: '#/definitions/VersionModelParam'
- priceConfig:
- $ref: '#/definitions/VersionModelParam'
- planConfig:
- $ref: '#/definitions/VersionModelParam'
- ProductTemplateUpgradeBodyParam:
+ form:
+ $ref: '#/definitions/VersionModelParam'
+ priceConfig:
+ $ref: '#/definitions/VersionModelParam'
+ planConfig:
+ $ref: '#/definitions/VersionModelParam'
+ ProductTemplateUpgradeBody:
title: Product template
type: object
properties:
- param:
- type: object
- properties:
- form:
- $ref: '#/definitions/VersionModelParam'
+ form:
+ $ref: '#/definitions/VersionModelParam'
VersionModelParam:
title: version model param
type: object
@@ -4562,14 +5304,6 @@ definitions:
version:
type: number
description: the version for model
- ProjectTemplateBodyParam:
- title: Project template body param
- type: object
- required:
- - param
- properties:
- param:
- $ref: '#/definitions/ProjectTemplateRequest'
ProjectTemplate:
title: Project template object
allOf:
@@ -4604,53 +5338,6 @@ definitions:
description: READ-ONLY. User that last updated this object
readOnly: true
- $ref: '#/definitions/ProjectTemplateRequest'
- ProjectTemplateResponse:
- title: Single project template response object
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- $ref: '#/definitions/ProjectTemplate'
- ProjectTemplateListResponse:
- title: Project template list response object
- type: object
- properties:
- id:
- type: string
- readOnly: true
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- type: array
- items:
- $ref: '#/definitions/ProjectTemplate'
ProductTemplateRequest:
title: Product template request object
type: object
@@ -4693,14 +5380,6 @@ definitions:
isAddOn:
type: boolean
description: the flag that shows if the product template is an add on
- ProductTemplateBodyParam:
- title: Product template body param
- type: object
- required:
- - param
- properties:
- param:
- $ref: '#/definitions/ProductTemplateRequest'
ProductTemplate:
title: Product template object
allOf:
@@ -4739,73 +5418,6 @@ definitions:
type: string
description: The product category of the product template
- $ref: '#/definitions/ProductTemplateRequest'
- ProjectUpgradeResponse:
- title: Project upgrade response object
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- ProductTemplateResponse:
- title: Single product template response object
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- $ref: '#/definitions/ProductTemplate'
- ProductTemplateListResponse:
- title: Product template list response object
- type: object
- properties:
- id:
- type: string
- readOnly: true
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- type: array
- items:
- $ref: '#/definitions/ProductTemplate'
ProjectPhaseRequest:
title: Project phase request object
type: object
@@ -4848,51 +5460,6 @@ definitions:
type: number
format: integer
description: the project phase order
- ProjectSettingRequest:
- title: Project setting request object
- type: object
- required:
- - key
- - value
- - valueType
- - readPermission
- - writePermission
- - metadata
- properties:
- key:
- type: string
- description: the project setting key
- value:
- type: string
- description: the project setting value
- valueType:
- type: string
- description: the project setting value type
- readPermission:
- type: object
- description: the project setting read Permission
- writePermission:
- type: object
- description: the project setting write Permission
- metadata:
- type: object
- description: the project setting metadata
- ProjectSettingBodyParam:
- title: Project setting body param
- type: object
- required:
- - param
- properties:
- param:
- $ref: '#/definitions/ProjectSettingRequest'
- ProjectPhaseBodyParam:
- title: Project phase body param
- type: object
- required:
- - param
- properties:
- param:
- $ref: '#/definitions/ProjectPhaseRequest'
ProjectPhase:
title: Project phase object
allOf:
@@ -4927,245 +5494,6 @@ definitions:
description: READ-ONLY. User that last updated this object
readOnly: true
- $ref: '#/definitions/ProjectPhaseRequest'
- WorkStream:
- title: Work stream object
- allOf:
- - type: object
- required:
- - id
- - createdAt
- - createdBy
- - updatedAt
- - updatedBy
- properties:
- id:
- type: number
- format: int64
- description: the id
- projectId:
- type: number
- format: int64
- description: the project id
- createdAt:
- type: string
- description: Datetime (GMT) when object was created
- readOnly: true
- createdBy:
- type: integer
- format: int64
- description: READ-ONLY. User who created this object
- readOnly: true
- updatedAt:
- type: string
- description: READ-ONLY. Datetime (GMT) when object was updated
- readOnly: true
- updatedBy:
- type: integer
- format: int64
- description: READ-ONLY. User that last updated this object
- readOnly: true
- - $ref: '#/definitions/WorkStreamRequest'
-
- WorkStreamRequest:
- title: Work stream request object
- type: object
- required:
- - name
- - status
- - type
- properties:
- name:
- type: string
- description: the work stream name
- status:
- type: string
- description: the work stream status
- type:
- type: string
- description: the type
- WorkStreamBodyParam:
- title: Work stream body param
- type: object
- required:
- - param
- properties:
- param:
- $ref: '#/definitions/WorkStreamRequest'
- ProjectSetting:
- title: Project setting object
- allOf:
- - type: object
- required:
- - id
- - createdAt
- - createdBy
- - updatedAt
- - updatedBy
- properties:
- id:
- type: number
- format: int64
- description: the id
- createdAt:
- type: string
- description: Datetime (GMT) when object was created
- readOnly: true
- createdBy:
- type: integer
- format: int64
- description: READ-ONLY. User who created this object
- readOnly: true
- updatedAt:
- type: string
- description: READ-ONLY. Datetime (GMT) when object was updated
- readOnly: true
- updatedBy:
- type: integer
- format: int64
- description: READ-ONLY. User that last updated this object
- readOnly: true
- - $ref: '#/definitions/ProjectSettingRequest'
- ProjectSettingResponse:
- title: Single project setting response object
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: string
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- $ref: '#/definitions/ProjectSetting'
- ProjectSettingListResponse:
- title: Project setting list response object
- type: object
- properties:
- id:
- type: string
- readOnly: true
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: string
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- type: array
- items:
- $ref: '#/definitions/ProjectSetting'
- ProjectPhaseResponse:
- title: Single project phase response object
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- $ref: '#/definitions/ProjectPhase'
- ProjectPhaseListResponse:
- title: Project phase list response object
- type: object
- properties:
- id:
- type: string
- readOnly: true
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- type: array
- items:
- $ref: '#/definitions/ProjectPhase'
-
- WorkStreamResponse:
- title: Single project work stream response object
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- $ref: '#/definitions/WorkStream'
-
- WorkStreamListResponse:
- title: Project phase list response object
- type: object
- properties:
- id:
- type: string
- readOnly: true
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- type: array
- items:
- $ref: '#/definitions/WorkStream'
PhaseProductRequest:
title: Phase product request object
type: object
@@ -5194,14 +5522,6 @@ definitions:
details:
type: object
description: the phase product details
- PhaseProductBodyParam:
- title: Phase product body param
- type: object
- required:
- - param
- properties:
- param:
- $ref: '#/definitions/PhaseProductRequest'
PhaseProduct:
title: Phase product object
allOf:
@@ -5236,53 +5556,6 @@ definitions:
description: READ-ONLY. User that last updated this object
readOnly: true
- $ref: '#/definitions/PhaseProductRequest'
- PhaseProductResponse:
- title: Single phase product response object
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- $ref: '#/definitions/PhaseProduct'
- PhaseProductListResponse:
- title: Phase product list response object
- type: object
- properties:
- id:
- type: string
- readOnly: true
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- type: array
- items:
- $ref: '#/definitions/PhaseProduct'
ProductCategoryRequest:
title: Product category request object
type: object
@@ -5292,14 +5565,6 @@ definitions:
displayName:
type: string
description: the product category display name
- ProductCategoryBodyParam:
- title: Product category body param
- type: object
- required:
- - param
- properties:
- param:
- $ref: '#/definitions/ProductCategoryRequest'
ProductCategoryCreateRequest:
title: Product category creation request object
type: object
@@ -5312,14 +5577,6 @@ definitions:
type: string
description: the product category key
- $ref: '#/definitions/ProductCategoryRequest'
- ProductCategoryCreateBodyParam:
- title: Product category creation body param
- type: object
- required:
- - param
- properties:
- param:
- $ref: '#/definitions/ProductCategoryCreateRequest'
ProductCategory:
title: Product category object
allOf:
@@ -5352,53 +5609,6 @@ definitions:
description: READ-ONLY. User that last updated this object
readOnly: true
- $ref: '#/definitions/ProductCategoryCreateRequest'
- ProductCategoryResponse:
- title: Single product category response object
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- $ref: '#/definitions/ProductCategory'
- ProductCategoryListResponse:
- title: Product category list response object
- type: object
- properties:
- id:
- type: string
- readOnly: true
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- type: array
- items:
- $ref: '#/definitions/ProductCategory'
ProjectTypeRequest:
title: Project type request object
type: object
@@ -5408,14 +5618,6 @@ definitions:
displayName:
type: string
description: the project type display name
- ProjectTypeBodyParam:
- title: Project type body param
- type: object
- required:
- - param
- properties:
- param:
- $ref: '#/definitions/ProjectTypeRequest'
ProjectTypeCreateRequest:
title: Project type creation request object
type: object
@@ -5428,14 +5630,6 @@ definitions:
type: string
description: the project type key
- $ref: '#/definitions/ProjectTypeRequest'
- ProjectTypeCreateBodyParam:
- title: Project type creation body param
- type: object
- required:
- - param
- properties:
- param:
- $ref: '#/definitions/ProjectTypeCreateRequest'
ProjectType:
title: Project type object
allOf:
@@ -5478,157 +5672,27 @@ definitions:
orgId:
type: string
description: the org id
- configName:
- type: string
- description: the organization config name
- OrgConfigCreateRequest:
- title: Organization config creation request object
- type: object
- allOf:
- - type: object
- properties:
- configValue:
- type: string
- description: the organization config id
- - $ref: '#/definitions/OrgConfigRequest'
- OrgConfigCreateBodyParam:
- title: Organization config creation body param
- type: object
- required:
- - param
- properties:
- param:
- $ref: '#/definitions/OrgConfigCreateRequest'
- OrgConfig:
- title: Organization config object
- allOf:
- - type: object
- required:
- - id
- - orgId
- - configName
- - createdAt
- - createdBy
- - updatedAt
- - updatedBy
- properties:
- id:
- type: number
- format: int64
- description: the id
- orgId:
- type: string
- description: the org id
- configName:
- type: string
- description: the config name
- configValue:
- type: string
- description: the config value
- createdAt:
- type: string
- description: Datetime (GMT) when object was created
- readOnly: true
- createdBy:
- type: integer
- format: int64
- description: READ-ONLY. User who created this object
- readOnly: true
- updatedAt:
- type: string
- description: READ-ONLY. Datetime (GMT) when object was updated
- readOnly: true
- updatedBy:
- type: integer
- format: int64
- description: READ-ONLY. User that last updated this object
- readOnly: true
- - $ref: '#/definitions/OrgConfigCreateRequest'
- OrgConfigResponse:
- title: Single organization config response object
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- $ref: '#/definitions/OrgConfig'
- OrgConfigListResponse:
- title: Organization confige list response object
- type: object
- properties:
- id:
- type: string
- readOnly: true
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- type: array
- items:
- $ref: '#/definitions/OrgConfig'
-
-
-
- WorkManagementPermissionCreateRequest:
- title: Work Management Permission request object
- type: object
- required:
- - policy
- - permission
- - projectTemplateId
- properties:
- policy:
- type: string
- description: the policy
- permission:
- type: object
- description: the permission
- projectTemplateId:
- type: number
- format: int64
- description: the template id
- WorkManagementPermissionCreateBodyParam:
- title: Work Management Permission creation body param
+ configName:
+ type: string
+ description: the organization config name
+ OrgConfigCreateRequest:
+ title: Organization config creation request object
type: object
- required:
- - param
- properties:
- param:
- $ref: '#/definitions/WorkManagementPermissionCreateRequest'
- WorkManagementPermission:
- title: Work Management Permission object
+ allOf:
+ - type: object
+ properties:
+ configValue:
+ type: string
+ description: the organization config id
+ - $ref: '#/definitions/OrgConfigRequest'
+ OrgConfig:
+ title: Organization config object
allOf:
- type: object
required:
- id
- - policy
- - permission
- - projectTemplateId
+ - orgId
+ - configName
- createdAt
- createdBy
- updatedAt
@@ -5638,16 +5702,15 @@ definitions:
type: number
format: int64
description: the id
- policy:
+ orgId:
type: string
- description: the policy
- permission:
- type: object
- description: the permission
- projectTemplateId:
- type: number
- format: int64
- description: the template id
+ description: the org id
+ configName:
+ type: string
+ description: the config name
+ configValue:
+ type: string
+ description: the config value
createdAt:
type: string
description: Datetime (GMT) when object was created
@@ -5666,102 +5729,7 @@ definitions:
format: int64
description: READ-ONLY. User that last updated this object
readOnly: true
- - $ref: '#/definitions/WorkManagementPermissionCreateRequest'
- WorkManagementPermissionResponse:
- title: Single work management permission response object
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- $ref: '#/definitions/WorkManagementPermission'
-
- WorkManagementPermissionListResponse:
- title: Work management permissions response object
- type: object
- properties:
- id:
- type: string
- readOnly: true
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- type: array
- items:
- $ref: '#/definitions/WorkManagementPermission'
- ProjectTypeResponse:
- title: Single project type response object
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- $ref: '#/definitions/ProjectType'
- ProjectTypeListResponse:
- title: Project type list response object
- type: object
- properties:
- id:
- type: string
- readOnly: true
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- type: array
- items:
- $ref: '#/definitions/ProjectType'
+ - $ref: '#/definitions/OrgConfigCreateRequest'
TimelineRequest:
title: Timeline request object
type: object
@@ -5797,14 +5765,6 @@ definitions:
description: >-
the timeline reference id (project id or phase id, corresponding to
the `reference`)
- TimelineBodyParam:
- title: Timeline body param
- type: object
- required:
- - param
- properties:
- param:
- $ref: '#/definitions/TimelineRequest'
Timeline:
title: Timeline object
allOf:
@@ -5839,53 +5799,6 @@ definitions:
description: READ-ONLY. User that last updated this object
readOnly: true
- $ref: '#/definitions/TimelineRequest'
- TimelineResponse:
- title: Single timeline response object
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- $ref: '#/definitions/Timeline'
- TimelineListResponse:
- title: Timeline list response object
- type: object
- properties:
- id:
- type: string
- readOnly: true
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- type: array
- items:
- $ref: '#/definitions/Timeline'
MilestonePostRequest:
title: Milestone request object
type: object
@@ -6004,22 +5917,6 @@ definitions:
statusComment:
type: string
description: the milestone status history comment
- MilestonePostBodyParam:
- title: Milestone body param
- type: object
- required:
- - param
- properties:
- param:
- $ref: '#/definitions/MilestonePostRequest'
- MilestonePatchBodyParam:
- title: Milestone body param
- type: object
- required:
- - param
- properties:
- param:
- $ref: '#/definitions/MilestonePatchRequest'
Milestone:
title: Milestone object
allOf:
@@ -6057,53 +5954,6 @@ definitions:
description: READ-ONLY. User that last updated this object
readOnly: true
- $ref: '#/definitions/MilestonePostRequest'
- MilestoneResponse:
- title: Single milestone response object
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- $ref: '#/definitions/Milestone'
- MilestoneListResponse:
- title: Milestone list response object
- type: object
- properties:
- id:
- type: string
- readOnly: true
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- type: array
- items:
- $ref: '#/definitions/Milestone'
MilestoneTemplateRequest:
title: Milestone template request object
type: object
@@ -6146,14 +5996,6 @@ definitions:
metadata:
type: object
description: the milestone template metadata
- MilestoneTemplateBodyParam:
- title: Milestone template body param
- type: object
- required:
- - param
- properties:
- param:
- $ref: '#/definitions/MilestoneTemplateRequest'
MilestoneCloneTemplateRequest:
title: Milestone clone template request object
type: object
@@ -6217,98 +6059,31 @@ definitions:
description: READ-ONLY. User that last updated this object
readOnly: true
- $ref: '#/definitions/MilestoneTemplateRequest'
- MilestoneTemplateResponse:
- title: Single milestone template response object
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- $ref: '#/definitions/MilestoneTemplate'
- MilestoneTemplateListResponse:
- title: Milestone template list response object
- type: object
- properties:
- id:
- type: string
- readOnly: true
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- type: array
- items:
- $ref: '#/definitions/MilestoneTemplate'
AllMetadataResponse:
title: All metadata response object
type: object
properties:
- id:
- type: string
- readOnly: true
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- type: object
- properties:
- projectTemplates:
- type: array
- items:
- $ref: '#/definitions/ProjectTemplate'
- productTemplates:
- type: array
- items:
- $ref: '#/definitions/ProductTemplate'
- milestoneTemplates:
- type: array
- items:
- $ref: '#/definitions/MilestoneTemplate'
- projectTypes:
- type: array
- items:
- $ref: '#/definitions/ProjectType'
- productCategories:
- type: array
- items:
- $ref: '#/definitions/ProductCategory'
- buildingBlocks:
+ projectTemplates:
+ type: array
+ items:
+ $ref: '#/definitions/ProjectTemplate'
+ productTemplates:
+ type: array
+ items:
+ $ref: '#/definitions/ProductTemplate'
+ milestoneTemplates:
+ type: array
+ items:
+ $ref: '#/definitions/MilestoneTemplate'
+ projectTypes:
+ type: array
+ items:
+ $ref: '#/definitions/ProjectType'
+ productCategories:
+ type: array
+ items:
+ $ref: '#/definitions/ProductCategory'
+ buildingBlocks:
type: array
items:
$ref: '#/definitions/BuildingBlock'
@@ -6376,71 +6151,43 @@ definitions:
title: Add project member invites request object
type: object
properties:
- param:
- type: object
- properties:
- userIds:
- description: 'The user Id list, could not present with emails'
- type: array
- items:
- type: integer
- format: int64
- emails:
- type: array
- items:
- type: string
- description: 'The user email list, could not present with userIds'
- role:
- description: The target role in the project
- type: string
- enum:
- - manager
- - customer
- - copilot
+ userIds:
+ description: 'The user Id list, could not present with emails'
+ type: array
+ items:
+ type: integer
+ format: int64
+ emails:
+ type: array
+ items:
+ type: string
+ description: 'The user email list, could not present with userIds'
+ role:
+ description: The target role in the project
+ type: string
+ enum:
+ - manager
+ - customer
+ - copilot
UpdateProjectMemberInviteRequest:
title: Update project member invite request object
type: object
properties:
- param:
- type: object
- properties:
- userId:
- type: integer
- format: int64
- description: 'The user Id, could not present with email'
- email:
- type: string
- description: 'The user email, could not present with userId'
- status:
- description: The invite status
- type: string
- enum:
- - pending
- - accepted
- - refused
- - canceled
- ProjectMemberInviteResponse:
- title: Project member invite response object
- type: object
- properties:
- id:
+ userId:
+ type: integer
+ format: int64
+ description: 'The user Id, could not present with email'
+ email:
type: string
- description: unique id identifying the request
- version:
+ description: 'The user email, could not present with userId'
+ status:
+ description: The invite status
type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- metadata:
- $ref: '#/definitions/ResponseMetadata'
- content:
- $ref: '#/definitions/ProjectMemberInviteSuccessAndFailure'
+ enum:
+ - pending
+ - accepted
+ - refused
+ - canceled
Form:
type: object
properties:
@@ -6480,56 +6227,11 @@ definitions:
format: int64
description: READ-ONLY. User that last updated this task
readOnly: true
- FormResponse:
- title: Form response
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- content:
- $ref: '#/definitions/Form'
- FormListResponse:
- title: From list response
+ NewForm:
type: object
properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- content:
- type: array
- items:
- $ref: '#/definitions/Form'
- NewFormParam:
- type: object
- properties:
- param:
+ config:
type: object
- properties:
- config:
- type: object
PriceConfig:
type: object
properties:
@@ -6569,57 +6271,12 @@ definitions:
format: int64
description: READ-ONLY. User that last updated this task
readOnly: true
- PriceConfigResponse:
- title: PriceConfig response
+ NewPriceConfig:
type: object
properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- content:
- $ref: '#/definitions/PriceConfig'
- PriceConfigListResponse:
- title: PriceConfig list response
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: integer
- format: int32
- description: http status code
- content:
- type: array
- items:
- $ref: '#/definitions/Form'
- NewPriceConfigParam:
- type: object
- properties:
- param:
+ config:
+ description: config json
type: object
- properties:
- config:
- description: config json
- type: object
PlanConfig:
type: object
properties:
@@ -6659,57 +6316,195 @@ definitions:
format: int64
description: READ-ONLY. User that last updated this task
readOnly: true
- PlanConfigResponse:
- title: PlanConfig response
+ NewPlanConfig:
type: object
properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
+ config:
+ description: config json
type: object
+ ProjectSetting:
+ title: Project setting object
+ allOf:
+ - type: object
+ required:
+ - id
+ - createdAt
+ - createdBy
+ - updatedAt
+ - updatedBy
properties:
- success:
- type: boolean
- status:
+ id:
+ type: number
+ format: int64
+ description: the id
+ createdAt:
+ type: string
+ description: Datetime (GMT) when object was created
+ readOnly: true
+ createdBy:
type: integer
- format: int32
- description: http status code
- content:
- $ref: '#/definitions/PlanConfig'
- PlanConfigListResponse:
- title: PlanConfig list response
+ format: int64
+ description: READ-ONLY. User who created this object
+ readOnly: true
+ updatedAt:
+ type: string
+ description: READ-ONLY. Datetime (GMT) when object was updated
+ readOnly: true
+ updatedBy:
+ type: integer
+ format: int64
+ description: READ-ONLY. User that last updated this object
+ readOnly: true
+ - $ref: '#/definitions/ProjectSettingRequest'
+ ProjectSettingRequest:
+ title: Project setting request object
type: object
+ required:
+ - key
+ - value
+ - valueType
+ - readPermission
+ - writePermission
+ - metadata
properties:
- id:
+ key:
type: string
- description: unique id identifying the request
- version:
+ description: the project setting key
+ value:
+ type: string
+ description: the project setting value
+ valueType:
type: string
- result:
+ description: the project setting value type
+ readPermission:
+ type: object
+ description: the project setting read Permission
+ writePermission:
+ type: object
+ description: the project setting write Permission
+ metadata:
type: object
+ description: the project setting metadata
+ WorkStream:
+ title: Work stream object
+ allOf:
+ - type: object
+ required:
+ - id
+ - createdAt
+ - createdBy
+ - updatedAt
+ - updatedBy
properties:
- success:
- type: boolean
- status:
+ id:
+ type: number
+ format: int64
+ description: the id
+ projectId:
+ type: number
+ format: int64
+ description: the project id
+ createdAt:
+ type: string
+ description: Datetime (GMT) when object was created
+ readOnly: true
+ createdBy:
type: integer
- format: int32
- description: http status code
- content:
- type: array
- items:
- $ref: '#/definitions/PlanConfig'
- NewPlanConfigParam:
+ format: int64
+ description: READ-ONLY. User who created this object
+ readOnly: true
+ updatedAt:
+ type: string
+ description: READ-ONLY. Datetime (GMT) when object was updated
+ readOnly: true
+ updatedBy:
+ type: integer
+ format: int64
+ description: READ-ONLY. User that last updated this object
+ readOnly: true
+ - $ref: '#/definitions/WorkStreamRequest'
+ WorkStreamRequest:
+ title: Work stream request object
+ type: object
+ required:
+ - name
+ - status
+ - type
+ properties:
+ name:
+ type: string
+ description: the work stream name
+ status:
+ type: string
+ description: the work stream status
+ type:
+ type: string
+ description: the type
+ WorkManagementPermissionCreateRequest:
+ title: Work Management Permission request object
type: object
+ required:
+ - policy
+ - permission
+ - projectTemplateId
properties:
- param:
+ policy:
+ type: string
+ description: the policy
+ permission:
type: object
+ description: the permission
+ projectTemplateId:
+ type: number
+ format: int64
+ description: the template id
+ WorkManagementPermission:
+ title: Work Management Permission object
+ allOf:
+ - type: object
+ required:
+ - id
+ - policy
+ - permission
+ - projectTemplateId
+ - createdAt
+ - createdBy
+ - updatedAt
+ - updatedBy
properties:
- config:
- description: config json
+ id:
+ type: number
+ format: int64
+ description: the id
+ policy:
+ type: string
+ description: the policy
+ permission:
type: object
+ description: the permission
+ projectTemplateId:
+ type: number
+ format: int64
+ description: the template id
+ createdAt:
+ type: string
+ description: Datetime (GMT) when object was created
+ readOnly: true
+ createdBy:
+ type: integer
+ format: int64
+ description: READ-ONLY. User who created this object
+ readOnly: true
+ updatedAt:
+ type: string
+ description: READ-ONLY. Datetime (GMT) when object was updated
+ readOnly: true
+ updatedBy:
+ type: integer
+ format: int64
+ description: READ-ONLY. User that last updated this object
+ readOnly: true
+ - $ref: '#/definitions/WorkManagementPermissionCreateRequest'
StatusHistory:
title: Status history object
type: object
@@ -6843,25 +6638,4 @@ definitions:
type: integer
format: int64
description: READ-ONLY. User that last updated this object
- readOnly: true
- ProjectEstimationItemListResponse:
- title: ProjectEstimationItem list response
- type: object
- properties:
- id:
- type: string
- description: unique id identifying the request
- version:
- type: string
- result:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: string
- description: http status code
- content:
- type: array
- items:
- $ref: '#/definitions/ProjectEstimationItem'
\ No newline at end of file
+ readOnly: true
\ No newline at end of file
diff --git a/local/full/docker-compose.base.yml b/local/full/docker-compose.base.yml
new file mode 100644
index 00000000..6020be65
--- /dev/null
+++ b/local/full/docker-compose.base.yml
@@ -0,0 +1,26 @@
+version: "2"
+services:
+ tc-notifications-base:
+ build:
+ context: ./generic-tc-service
+ args:
+ NODE_VERSION: 8.2.1
+ GIT_URL: https://github.com/topcoder-platform/tc-notifications
+ GIT_BRANCH: v5-upgrade
+ BYPASS_TOKEN_VALIDATION: 1
+ environment:
+ - VALID_ISSUERS="[\"https://topcoder-newauth.auth0.com/\",\"https://api.topcoder-dev.com\"]"
+ - TC_API_V5_BASE_URL=http://host.docker.internal:8001/v5
+ - TC_API_V4_BASE_URL=https://api.topcoder-dev.com/v4
+ - TC_API_V3_BASE_URL=https://api.topcoder-dev.com/v3
+ - KAFKA_URL=kafka:9092
+ - AUTH_SECRET=secret
+ - DATABASE_URL=postgresql://coder:mysecretpassword@notifications_db:5432/tc_notifications
+ - JWKS_URI=test
+ - LOG_LEVEL=debug
+ - ENV=development
+ - AUTH0_CLIENT_ID
+ - AUTH0_CLIENT_SECRET
+ - AUTH0_URL
+ - AUTH0_AUDIENCE
+ - AUTH0_PROXY_SERVER_URL
diff --git a/local/full/docker-compose.yml b/local/full/docker-compose.yml
new file mode 100644
index 00000000..b6f0f207
--- /dev/null
+++ b/local/full/docker-compose.yml
@@ -0,0 +1,129 @@
+version: "2"
+services:
+ jsonserver:
+ extends:
+ file: ../docker-compose.yml
+ service: jsonserver
+ db:
+ extends:
+ file: ../docker-compose.yml
+ service: db
+ notifications_db:
+ image: "postgres:9.5"
+ expose:
+ - "5432"
+ environment:
+ - POSTGRES_PASSWORD=mysecretpassword
+ - POSTGRES_USER=coder
+ - POSTGRES_DB=tc_notifications
+ db_test:
+ extends:
+ file: ../docker-compose.yml
+ service: db_test
+ esearch:
+ extends:
+ file: ../docker-compose.yml
+ service: esearch
+ queue:
+ extends:
+ file: ../docker-compose.yml
+ service: queue
+ zookeeper:
+ image: confluent/zookeeper
+ ports:
+ - "2181:2181"
+ environment:
+ zk_id: "1"
+ kafka:
+ image: confluent/kafka
+ container_name: tc-projects-kafka
+ depends_on:
+ - zookeeper
+ ports:
+ - "9092:9092"
+ kafka-client:
+ build: ./kafka-client
+ depends_on:
+ - kafka
+ - zookeeper
+ tc-bus-api:
+ build:
+ context: ./generic-tc-service
+ args:
+ NODE_VERSION: 8.2.1
+ GIT_URL: https://github.com/topcoder-platform/tc-bus-api
+ GIT_BRANCH: dev
+ BYPASS_TOKEN_VALIDATION: 1
+ command: start kafka-client
+ expose:
+ - "3000"
+ ports:
+ - "8002:3000"
+ depends_on:
+ - kafka-client
+ environment:
+ - PORT=3000
+ - KAFKA_URL=kafka:9092
+ - JWT_TOKEN_SECRET=secret
+ - VALID_ISSUERS="[\"https://topcoder-newauth.auth0.com/\",\"https://api.topcoder-dev.com\"]"
+ project-processor-es:
+ build:
+ context: ./generic-tc-service
+ args:
+ NODE_VERSION: 8.11.3
+ GIT_URL: https://github.com/topcoder-platform/project-processor-es
+ GIT_BRANCH: develop
+ command: start kafka-client
+ expose:
+ - "5000"
+ ports:
+ - "5000:5000"
+ depends_on:
+ - kafka-client
+ environment:
+ - PORT=5000
+ - KAFKA_URL=kafka:9092
+ - ES_HOST=esearch:9200
+ tc-notifications-reset-db:
+ extends:
+ file: ./docker-compose.base.yml
+ service: tc-notifications-base
+ command: reset:db
+ restart: on-failure
+ expose:
+ - "4002"
+ ports:
+ - "4002:4002"
+ depends_on:
+ - notifications_db
+ - esearch
+ environment:
+ - PORT=4002
+ tc-notifications-processor:
+ extends:
+ file: ./docker-compose.base.yml
+ service: tc-notifications-base
+ command: start kafka-client
+ depends_on:
+ - tc-notifications-reset-db
+ - kafka-client
+ expose:
+ - "4001"
+ ports:
+ - "4001:4001"
+ environment:
+ - PORT=4001
+ tc-notifications-api:
+ extends:
+ file: ./docker-compose.base.yml
+ service: tc-notifications-base
+ command: startAPI kafka-client
+ depends_on:
+ - tc-notifications-reset-db
+ - kafka-client
+ expose:
+ - "4000"
+ ports:
+ - "4000:4000"
+ environment:
+ - PORT=4000
diff --git a/local/full/generic-tc-service/Dockerfile b/local/full/generic-tc-service/Dockerfile
new file mode 100644
index 00000000..3c93b79b
--- /dev/null
+++ b/local/full/generic-tc-service/Dockerfile
@@ -0,0 +1,15 @@
+ARG NODE_VERSION=8.2.1
+
+FROM node:$NODE_VERSION
+ARG GIT_URL
+ARG GIT_BRANCH
+ARG BYPASS_TOKEN_VALIDATION
+
+RUN git clone $GIT_URL /opt/app
+WORKDIR /opt/app
+RUN git checkout -b node-branch origin/$GIT_BRANCH
+
+RUN npm install
+RUN if [ $BYPASS_TOKEN_VALIDATION -eq 1 ]; then sed -i '/decodedToken = jwt.decode/a \ callback(undefined, decodedToken.payload); return;' node_modules/tc-core-library-js/lib/auth/verifier.js; fi
+COPY docker-entrypoint.sh /opt/
+ENTRYPOINT ["/opt/docker-entrypoint.sh"]
diff --git a/local/full/generic-tc-service/docker-entrypoint.sh b/local/full/generic-tc-service/docker-entrypoint.sh
new file mode 100755
index 00000000..312b4f0a
--- /dev/null
+++ b/local/full/generic-tc-service/docker-entrypoint.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+HOST_DOMAIN="host.docker.internal"
+ping -q -c1 $HOST_DOMAIN > /dev/null 2>&1
+if [ $? -ne 0 ]; then
+ HOST_IP=$(ip route | awk 'NR==1 {print $3}')
+ echo -e "$HOST_IP\t$HOST_DOMAIN" >> /etc/hosts
+fi
+
+if [ $# -eq 2 ]; then
+ echo "Waiting for $2 to exit...."
+ while ping -c1 $2 &>/dev/null
+ do
+ sleep 1
+ done
+ echo "$2 exited!"
+fi
+
+cd /opt/app/ && npm run $1
diff --git a/local/full/kafka-client/Dockerfile b/local/full/kafka-client/Dockerfile
new file mode 100644
index 00000000..3d332b83
--- /dev/null
+++ b/local/full/kafka-client/Dockerfile
@@ -0,0 +1,5 @@
+From confluent/kafka
+WORKDIR /app/
+COPY topics.txt .
+COPY create-topics.sh .
+ENTRYPOINT ["/app/create-topics.sh"]
diff --git a/local/full/kafka-client/create-topics.sh b/local/full/kafka-client/create-topics.sh
new file mode 100755
index 00000000..c5b33e62
--- /dev/null
+++ b/local/full/kafka-client/create-topics.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+while read topic; do
+ /usr/bin/kafka-topics --create --zookeeper zookeeper:2181 --partitions 1 --replication-factor 1 --topic $topic
+done < topics.txt
diff --git a/local/full/kafka-client/topics.txt b/local/full/kafka-client/topics.txt
new file mode 100644
index 00000000..9b62bbea
--- /dev/null
+++ b/local/full/kafka-client/topics.txt
@@ -0,0 +1,55 @@
+connect.notification.email.project.member.invite.created
+connect.notification.project.active
+connect.notification.project.approved
+connect.notification.project.canceled
+connect.notification.project.completed
+connect.notification.project.created
+connect.notification.project.fileUploaded
+connect.notification.project.files.updated
+connect.notification.project.linkCreated
+connect.notification.project.member.assignedAsOwner
+connect.notification.project.member.copilotJoined
+connect.notification.project.member.invite.approved
+connect.notification.project.member.invite.created
+connect.notification.project.member.invite.rejected
+connect.notification.project.member.invite.requested
+connect.notification.project.member.invite.updated
+connect.notification.project.member.joined
+connect.notification.project.member.left
+connect.notification.project.member.managerJoined
+connect.notification.project.member.removed
+connect.notification.project.paused
+connect.notification.project.phase.transition.active
+connect.notification.project.phase.transition.completed
+connect.notification.project.phase.update.payment
+connect.notification.project.phase.update.progress
+connect.notification.project.phase.update.scope
+connect.notification.project.plan.ready
+connect.notification.project.plan.updated
+connect.notification.project.post.created
+connect.notification.project.post.edited
+connect.notification.project.product.update.spec
+connect.notification.project.submittedForReview
+connect.notification.project.team.updated
+connect.notification.project.timeline.adjusted
+connect.notification.project.timeline.milestone.added
+connect.notification.project.timeline.milestone.removed
+connect.notification.project.timeline.milestone.transition.active
+connect.notification.project.timeline.milestone.transition.completed
+connect.notification.project.timeline.milestone.transition.paused
+connect.notification.project.timeline.milestone.updated
+connect.notification.project.timeline.milestone.waiting.customer
+connect.notification.project.topic.created
+connect.notification.project.topic.updated
+connect.notification.project.updated
+connect.notification.project.updated.progress
+connect.notification.project.updated.spec
+connect.notification.project.work.transition.active
+connect.notification.project.work.transition.completed
+connect.notification.project.work.update.payment
+connect.notification.project.work.update.progress
+connect.notification.project.work.update.scope
+connect.notification.project.workitem.update.spec
+project.action.create
+project.action.delete
+project.action.update
diff --git a/local/seed/index.js b/local/seed/index.js
index a98fcc1c..acc8a068 100644
--- a/local/seed/index.js
+++ b/local/seed/index.js
@@ -1,7 +1,7 @@
const seedMetadata = require('./seedMetadata');
const seedProjects = require('./seedProjects');
-const targetUrl = 'http://localhost:8001/v4/';
+const targetUrl = 'http://localhost:8001/v5/';
const token =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw';
diff --git a/local/seed/projects.json b/local/seed/projects.json
index 9fb184af..5e76d182 100644
--- a/local/seed/projects.json
+++ b/local/seed/projects.json
@@ -1,264 +1,242 @@
[
{
- "param": {
- "name": "Develop app",
- "details": {
- "utm": {
- "code": "R&D"
+ "name": "Develop app",
+ "details": {
+ "utm": {
+ "code": "R&D"
+ },
+ "appDefinition": {
+ "primaryTarget": "phone",
+ "goal": {
+ "value": "Nothing"
},
- "appDefinition": {
- "primaryTarget": "phone",
- "goal": {
- "value": "Nothing"
- },
- "users": {
- "value": "No one"
- },
- "notes": ""
+ "users": {
+ "value": "No one"
},
- "hideDiscussions": true
+ "notes": ""
},
- "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message",
- "templateId": 1,
- "type": "app",
- "status": "draft"
- }
+ "hideDiscussions": true
+ },
+ "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message",
+ "templateId": 1,
+ "type": "app",
+ "status": "draft"
},
{
- "param": {
- "name": "Develop website",
- "details": {
- "utm": {
- "code": ""
+ "name": "Develop website",
+ "details": {
+ "utm": {
+ "code": ""
+ },
+ "appDefinition": {
+ "primaryTarget": "phone",
+ "goal": {
+ "value": "Nothing"
},
- "appDefinition": {
- "primaryTarget": "phone",
- "goal": {
- "value": "Nothing"
- },
- "users": {
- "value": "No one"
- },
- "notes": ""
+ "users": {
+ "value": "No one"
},
- "hideDiscussions": true
+ "notes": ""
},
- "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message",
- "templateId": 2,
- "type": "chatbot",
- "status": "in_review"
- }
+ "hideDiscussions": true
+ },
+ "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message",
+ "templateId": 2,
+ "type": "chatbot",
+ "status": "in_review"
},
{
- "param": {
- "name": "Develop website 2",
- "details": {
- "utm": {
- "code": ""
+ "name": "Develop website 2",
+ "details": {
+ "utm": {
+ "code": ""
+ },
+ "appDefinition": {
+ "primaryTarget": "phone",
+ "goal": {
+ "value": "Nothing"
},
- "appDefinition": {
- "primaryTarget": "phone",
- "goal": {
- "value": "Nothing"
- },
- "users": {
- "value": "No one"
- },
- "notes": ""
+ "users": {
+ "value": "No one"
},
- "hideDiscussions": true
+ "notes": ""
},
- "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message",
- "templateId": 3,
- "type": "website",
- "status": "reviewed"
- }
+ "hideDiscussions": true
+ },
+ "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message",
+ "templateId": 3,
+ "type": "website",
+ "status": "reviewed"
},
{
- "param": {
- "name": "Develop chatbot",
- "details": {
- "utm": {
- "code": ""
+ "name": "Develop chatbot",
+ "details": {
+ "utm": {
+ "code": ""
+ },
+ "appDefinition": {
+ "primaryTarget": "phone",
+ "goal": {
+ "value": "Nothing"
},
- "appDefinition": {
- "primaryTarget": "phone",
- "goal": {
- "value": "Nothing"
- },
- "users": {
- "value": "No one"
- },
- "notes": ""
+ "users": {
+ "value": "No one"
},
- "hideDiscussions": true
+ "notes": ""
},
- "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message",
- "templateId": 4,
- "type": "chatbot",
- "status": "active"
- }
+ "hideDiscussions": true
+ },
+ "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message",
+ "templateId": 4,
+ "type": "chatbot",
+ "status": "active"
},
{
- "param": {
- "name": "Develop app 2",
- "details": {
- "utm": {
- "code": ""
+ "name": "Develop app 2",
+ "details": {
+ "utm": {
+ "code": ""
+ },
+ "appDefinition": {
+ "primaryTarget": "phone",
+ "goal": {
+ "value": "Nothing"
},
- "appDefinition": {
- "primaryTarget": "phone",
- "goal": {
- "value": "Nothing"
- },
- "users": {
- "value": "No one"
- },
- "notes": ""
+ "users": {
+ "value": "No one"
},
- "hideDiscussions": true
+ "notes": ""
},
- "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message",
- "templateId": 1,
- "type": "app",
- "status": "completed"
- }
+ "hideDiscussions": true
+ },
+ "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message",
+ "templateId": 1,
+ "type": "app",
+ "status": "completed"
},
{
- "param": {
- "name": "Develop website 3",
- "details": {
- "utm": {
- "code": ""
+ "name": "Develop website 3",
+ "details": {
+ "utm": {
+ "code": ""
+ },
+ "appDefinition": {
+ "primaryTarget": "phone",
+ "goal": {
+ "value": "Nothing"
},
- "appDefinition": {
- "primaryTarget": "phone",
- "goal": {
- "value": "Nothing"
- },
- "users": {
- "value": "No one"
- },
- "notes": ""
+ "users": {
+ "value": "No one"
},
- "hideDiscussions": true
+ "notes": ""
},
- "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message",
- "templateId": 2,
- "type": "website",
- "status": "paused"
- }
+ "hideDiscussions": true
+ },
+ "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message",
+ "templateId": 2,
+ "type": "website",
+ "status": "paused"
},
{
- "param": {
- "name": "Develop app 3",
- "details": {
- "utm": {
- "code": ""
+ "name": "Develop app 3",
+ "details": {
+ "utm": {
+ "code": ""
+ },
+ "appDefinition": {
+ "primaryTarget": "phone",
+ "goal": {
+ "value": "Nothing"
},
- "appDefinition": {
- "primaryTarget": "phone",
- "goal": {
- "value": "Nothing"
- },
- "users": {
- "value": "No one"
- },
- "notes": ""
+ "users": {
+ "value": "No one"
},
- "hideDiscussions": true
+ "notes": ""
},
- "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message",
- "templateId": 1,
- "type": "app",
- "status": "cancelled",
- "cancelReason": "Test cancel"
- }
+ "hideDiscussions": true
+ },
+ "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message",
+ "templateId": 1,
+ "type": "app",
+ "status": "cancelled",
+ "cancelReason": "Test cancel"
},
{
- "param": {
- "name": "Reviewed project with copilot invited",
- "details": {
- "utm": {
- "code": ""
+ "name": "Reviewed project with copilot invited",
+ "details": {
+ "utm": {
+ "code": ""
+ },
+ "appDefinition": {
+ "primaryTarget": "phone",
+ "goal": {
+ "value": "Nothing"
},
- "appDefinition": {
- "primaryTarget": "phone",
- "goal": {
- "value": "Nothing"
- },
- "users": {
- "value": "No one"
- },
- "notes": ""
+ "users": {
+ "value": "No one"
},
- "hideDiscussions": true
+ "notes": ""
},
- "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message",
- "templateId": 3,
- "type": "website",
- "status": "reviewed",
- "invites": [{
- "param": {
- "userIds": [40152855],
- "role": "copilot"
- }}]
- }
+ "hideDiscussions": true
+ },
+ "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message",
+ "templateId": 3,
+ "type": "website",
+ "status": "reviewed",
+ "invites": [{
+ "userIds": [40152855],
+ "role": "copilot"
+ }]
},
{
- "param": {
- "name": "Reviewed project with copilot as a member with copilot role",
- "details": {
- "utm": {
- "code": ""
+ "name": "Reviewed project with copilot as a member with copilot role",
+ "details": {
+ "utm": {
+ "code": ""
+ },
+ "appDefinition": {
+ "primaryTarget": "phone",
+ "goal": {
+ "value": "Nothing"
},
- "appDefinition": {
- "primaryTarget": "phone",
- "goal": {
- "value": "Nothing"
- },
- "users": {
- "value": "No one"
- },
- "notes": ""
+ "users": {
+ "value": "No one"
},
- "hideDiscussions": true
+ "notes": ""
},
- "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message",
- "templateId": 3,
- "type": "website",
- "status": "reviewed",
- "invites": [{
- "param": {
- "userIds": [40152855],
- "role": "copilot"
- }}],
- "acceptInvitation": true
- }
+ "hideDiscussions": true
+ },
+ "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message",
+ "templateId": 3,
+ "type": "website",
+ "status": "reviewed",
+ "invites": [{
+ "userIds": [40152855],
+ "role": "copilot"
+ }],
+ "acceptInvitation": true
},
{
- "param": {
- "name": "Reviewed project when copilot is not a member and not invited",
- "details": {
- "utm": {
- "code": ""
+ "name": "Reviewed project when copilot is not a member and not invited",
+ "details": {
+ "utm": {
+ "code": ""
+ },
+ "appDefinition": {
+ "primaryTarget": "phone",
+ "goal": {
+ "value": "Nothing"
},
- "appDefinition": {
- "primaryTarget": "phone",
- "goal": {
- "value": "Nothing"
- },
- "users": {
- "value": "No one"
- },
- "notes": ""
+ "users": {
+ "value": "No one"
},
- "hideDiscussions": true
+ "notes": ""
},
- "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message",
- "templateId": 3,
- "type": "website",
- "status": "reviewed"
- }
+ "hideDiscussions": true
+ },
+ "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message",
+ "templateId": 3,
+ "type": "website",
+ "status": "reviewed"
}
]
diff --git a/local/seed/seedMetadata.js b/local/seed/seedMetadata.js
index 82e1a588..6af36f21 100644
--- a/local/seed/seedMetadata.js
+++ b/local/seed/seedMetadata.js
@@ -42,9 +42,9 @@ module.exports = (targetUrl, token) => {
promises = _(data.result.content.forms).orderBy(['key', 'asc'], ['version', 'asc']).map(pt=>{
const param = _.omit(pt, ['id', 'version', 'revision', 'key']);
return axios
- .post(destUrl + `metadata/form/${pt.key}/versions`,{param}, {headers:headers})
+ .post(destUrl + `metadata/form/${pt.key}/versions`, param, {headers:headers})
.catch((err) => {
- const errMessage = _.get(err, 'response.data.result.content.message', '');
+ const errMessage = _.get(err, 'response.data.message', '');
console.log(`Failed to create form with key=${pt.key} version=${pt.version}.`, errMessage)
})
});
@@ -54,9 +54,9 @@ module.exports = (targetUrl, token) => {
promises = _(data.result.content.planConfigs).orderBy(['key', 'asc'], ['version', 'asc']).map(pt=>{
const param = _.omit(pt, ['id', 'version', 'revision', 'key']);
return axios
- .post(destUrl + `metadata/planConfig/${pt.key}/versions`,{param}, {headers:headers})
+ .post(destUrl + `metadata/planConfig/${pt.key}/versions`, param, {headers:headers})
.catch((err) => {
- const errMessage = _.get(err, 'response.data.result.content.message', '');
+ const errMessage = _.get(err, 'response.data.message', '');
console.log(`Failed to create planConfig with key=${pt.key} version=${pt.version}.`, errMessage)
})
});
@@ -66,9 +66,9 @@ module.exports = (targetUrl, token) => {
promises = _(data.result.content.priceConfigs).orderBy(['key', 'asc'], ['version', 'asc']).map(pt=>{
const param = _.omit(pt, ['id', 'version', 'revision', 'key']);
return axios
- .post(destUrl + `metadata/priceConfig/${pt.key}/versions`,{param}, {headers:headers})
+ .post(destUrl + `metadata/priceConfig/${pt.key}/versions`, param, {headers:headers})
.catch((err) => {
- const errMessage = _.get(err, 'response.data.result.content.message', '');
+ const errMessage = _.get(err, 'response.data.message', '');
console.log(`Failed to create priceConfig with key=${pt.key} version=${pt.version}.`, errMessage)
})
});
@@ -77,9 +77,9 @@ module.exports = (targetUrl, token) => {
promises = _(data.result.content.projectTypes).map(pt=>{
return axios
- .post(destUrl+'metadata/projectTypes',{param:pt}, {headers:headers})
+ .post(destUrl+'metadata/projectTypes', pt, {headers:headers})
.catch((err) => {
- const errMessage = _.get(err, 'response.data.result.content.message', '');
+ const errMessage = _.get(err, 'response.data.message', '');
console.log(`Failed to create projectType with key=${pt.key}.`, errMessage)
})
});
@@ -88,9 +88,9 @@ module.exports = (targetUrl, token) => {
promises = _(data.result.content.productCategories).map(pt=>{
return axios
- .post(destUrl+'metadata/productCategories',{param:pt}, {headers:headers})
+ .post(destUrl+'metadata/productCategories', pt, {headers:headers})
.catch((err) => {
- const errMessage = _.get(err, 'response.data.result.content.message', '');
+ const errMessage = _.get(err, 'response.data.message', '');
console.log(`Failed to create productCategory with key=${pt.key}.`, errMessage)
})
});
@@ -99,9 +99,9 @@ module.exports = (targetUrl, token) => {
promises = _(data.result.content.projectTemplates).map(pt=>{
return axios
- .post(destUrl+'metadata/projectTemplates',{param:pt}, {headers:headers})
+ .post(destUrl+'metadata/projectTemplates', pt, {headers:headers})
.catch((err) => {
- const errMessage = _.get(err, 'response.data.result.content.message', '');
+ const errMessage = _.get(err, 'response.data.message', '');
console.log(`Failed to create projectTemplate with id=${pt.id}.`, errMessage)
})
});
@@ -110,9 +110,9 @@ module.exports = (targetUrl, token) => {
promises = _(data.result.content.productTemplates).map(pt=>{
return axios
- .post(destUrl+'metadata/productTemplates',{param:pt}, {headers:headers})
+ .post(destUrl+'metadata/productTemplates', pt, {headers:headers})
.catch((err) => {
- const errMessage = _.get(err, 'response.data.result.content.message', '');
+ const errMessage = _.get(err, 'response.data.message', '');
console.log(`Failed to create productTemplate with id=${pt.id}.`, errMessage)
})
});
@@ -121,9 +121,9 @@ module.exports = (targetUrl, token) => {
await Promise.each(data.result.content.milestoneTemplates,pt=> (
axios
- .post(destTimelines+'timelines/metadata/milestoneTemplates',{param:pt}, {headers:headers})
+ .post(destTimelines+'timelines/metadata/milestoneTemplates', pt, {headers:headers})
.catch((err) => {
- const errMessage = _.get(err, 'response.data.result.content.message', '');
+ const errMessage = _.get(err, 'response.data.message', '');
console.log(`Failed to create milestoneTemplate with id=${pt.id}.`, errMessage)
})
));
diff --git a/local/seed/seedProjects.js b/local/seed/seedProjects.js
index 4138296d..a84b82d8 100644
--- a/local/seed/seedProjects.js
+++ b/local/seed/seedProjects.js
@@ -27,16 +27,22 @@ module.exports = (targetUrl, token) => {
};
console.log('Creating projects');
- projectPromises = projects.map((project, i) => {
- const status = _.get(project, 'param.status');
- const cancelReason = _.get(project, 'param.cancelReason');
- const invites = _.cloneDeep(_.get(project, 'param.invites'));
- const acceptInvitation = _.get(project, 'param.acceptInvitation');
+ projectPromises = projects.map(async (project, i) => {
+ const status = _.get(project, 'status');
+ const cancelReason = _.get(project, 'cancelReason');
+ const invites = _.cloneDeep(_.get(project, 'invites'));
+ const acceptInvitation = _.get(project, 'acceptInvitation');
+
+ if(project.templateId) {
+ await findProjectTemplate(project.templateId, targetUrl, adminHeaders).catch((ex) => {
+ delete project.templateId;
+ });
+ }
- delete project.param.status;
- delete project.param.cancelReason;
- delete project.param.invites;
- delete project.param.acceptInvitation;
+ delete project.status;
+ delete project.cancelReason;
+ delete project.invites;
+ delete project.acceptInvitation;
return axios
.post(projectsUrl, project, { headers: adminHeaders })
@@ -44,10 +50,10 @@ module.exports = (targetUrl, token) => {
console.log(`Failed to create project ${i}: ${err.message}`);
})
.then(async (response) => {
- const projectId = _.get(response, 'data.result.content.id');
+ const projectId = _.get(response, 'data.id');
// updating status
- if (status !== _.get(response, 'data.result.content.status')) {
+ if (status !== _.get(response, 'data.status')) {
console.log(`Project #${projectId}: Wait a bit to give time ES to index before updating status...`);
await Promise.delay(ES_INDEX_DELAY);
await updateProjectStatus(projectId, { status, cancelReason }, targetUrl, adminHeaders).catch((ex) => {
@@ -84,12 +90,10 @@ module.exports = (targetUrl, token) => {
if (acceptInvitation) {
let acceptInvitationPromises = []
responses.forEach(response => {
- const userId = _.get(response, 'data.result.content.success[0].userId')
+ const userId = _.get(response, 'data.success[0].userId')
acceptInvitationPromises.push(updateProjectMemberInvite(projectId, {
- param: {
- userId,
- status: 'accepted'
- }
+ userId,
+ status: 'accepted'
}, targetUrl, connectAdminHeaders))
})
@@ -122,9 +126,7 @@ function updateProjectStatus(project, updateParams, targetUrl, headers) {
return axios.patch(
projectUpdateUrl,
- {
- param: updateParams,
- },
+ updateParams,
{
headers,
},
@@ -150,3 +152,12 @@ function updateProjectMemberInvite(projectId, params, targetUrl, headers) {
console.log(`Failed to update project member invites ${projectId}: ${err.message}`);
})
}
+
+function findProjectTemplate(templateId, targetUrl, headers) {
+ const projectTemplateUrl = `${targetUrl}projects/metadata/projectTemplates/${templateId}`;
+
+ return axios({
+ url: projectTemplateUrl,
+ headers,
+ })
+}
diff --git a/migrations/elasticsearch_sync.js b/migrations/elasticsearch_sync.js
index b4734c3c..cc20a024 100644
--- a/migrations/elasticsearch_sync.js
+++ b/migrations/elasticsearch_sync.js
@@ -13,365 +13,66 @@
import config from 'config';
import util from '../src/util';
+import esUtils from '../src/utils/es';
+import { INDEX_TO_DOC_TYPE } from '../src/utils/es-config';
const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName');
-const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType');
const ES_TIMELINE_INDEX = config.get('elasticsearchConfig.timelineIndexName');
+const ES_METADATA_INDEX = config.get('elasticsearchConfig.metadataIndexName');
-// create new elasticsearch client
-// the client modifies the config object, so always passed the cloned object
-const esClient = util.getElasticSearchClient();
+// all indexes supported by this script
+const supportedIndexes = [ES_PROJECT_INDEX, ES_TIMELINE_INDEX, ES_METADATA_INDEX];
/**
- * Get the request body for the specified index name
- * @private
+ * Sync elasticsearch indices.
*
- * @param {String} indexName the index name
- * @return {Object} the request body for the specified index name
+ * @param {String} [indexName] index name to sync, if it's not define, then all indexes are recreated
+ *
+ * @returns {Promise} resolved when sync is complete
*/
-function getRequestBody(indexName) {
- let result;
- const projectMapping = {
- _all: { enabled: false },
- properties: {
- actualPrice: {
- type: 'double',
- },
- attachments: {
- type: 'nested',
- properties: {
- category: {
- type: 'string',
- index: 'not_analyzed',
- },
- contentType: {
- type: 'string',
- index: 'not_analyzed',
- },
- createdAt: {
- type: 'date',
- format: 'strict_date_optional_time||epoch_millis',
- },
- createdBy: {
- type: 'integer',
- },
- description: {
- type: 'string',
- },
- filePath: {
- type: 'string',
- },
- id: {
- type: 'long',
- },
- projectId: {
- type: 'long',
- },
- size: {
- type: 'double',
- },
- title: {
- type: 'string',
- },
- updatedAt: {
- type: 'date',
- format: 'strict_date_optional_time||epoch_millis',
- },
- updatedBy: {
- type: 'integer',
- },
- },
- },
- billingAccountId: {
- type: 'long',
- },
- bookmarks: {
- type: 'nested',
- properties: {
- address: {
- type: 'string',
- },
- title: {
- type: 'string',
- },
- },
- },
- cancelReason: {
- type: 'string',
- },
- challengeEligibility: {
- type: 'nested',
- properties: {
- groups: {
- type: 'long',
- },
- role: {
- type: 'string',
- index: 'not_analyzed',
- },
- users: {
- type: 'long',
- },
- },
- },
- createdAt: {
- type: 'date',
- format: 'strict_date_optional_time||epoch_millis',
- },
- createdBy: {
- type: 'integer',
- },
- description: {
- type: 'string',
- },
- details: {
- type: 'nested',
- properties: {
- TBD_features: {
- type: 'nested',
- properties: {
- description: {
- type: 'string',
- },
- id: {
- type: 'integer',
- },
- isCustom: {
- type: 'boolean',
- },
- title: {
- type: 'string',
- },
- },
- },
- TBD_usageDescription: {
- type: 'string',
- },
- appDefinition: {
- properties: {
- goal: {
- properties: {
- value: {
- type: 'string',
- },
- },
- },
- primaryTarget: {
- type: 'string',
- },
- users: {
- properties: {
- value: {
- type: 'string',
- },
- },
- },
- },
- },
- hideDiscussions: {
- type: 'boolean',
- },
- products: {
- type: 'string',
- },
- summary: {
- type: 'string',
- },
- utm: {
- type: 'nested',
- properties: {
- code: {
- type: 'string',
- },
- },
- },
- },
- },
- directProjectId: {
- type: 'long',
- },
- estimatedPrice: {
- type: 'double',
- },
- external: {
- properties: {
- data: {
- type: 'string',
- },
- id: {
- type: 'string',
- index: 'not_analyzed',
- },
- type: {
- type: 'string',
- index: 'not_analyzed',
- },
- },
- },
- id: {
- type: 'long',
- },
- members: {
- type: 'nested',
- properties: {
- createdAt: {
- type: 'date',
- format: 'strict_date_optional_time||epoch_millis',
- },
- createdBy: {
- type: 'integer',
- },
- email: {
- type: 'string',
- index: 'not_analyzed',
- },
- firstName: {
- type: 'string',
- },
- handle: {
- type: 'string',
- index: 'not_analyzed',
- },
- id: {
- type: 'long',
- },
- isPrimary: {
- type: 'boolean',
- },
- lastName: {
- type: 'string',
- },
- projectId: {
- type: 'long',
- },
- role: {
- type: 'string',
- index: 'not_analyzed',
- },
- updatedAt: {
- type: 'date',
- format: 'strict_date_optional_time||epoch_millis',
- },
- updatedBy: {
- type: 'integer',
- },
- userId: {
- type: 'long',
- },
- },
- },
- invites: {
- type: 'nested',
- properties: {
- createdAt: {
- type: 'date',
- format: 'strict_date_optional_time||epoch_millis',
- },
- createdBy: {
- type: 'integer',
- },
- email: {
- type: 'string',
- index: 'not_analyzed',
- },
- id: {
- type: 'long',
- },
- role: {
- type: 'string',
- index: 'not_analyzed',
- },
- updatedAt: {
- type: 'date',
- format: 'strict_date_optional_time||epoch_millis',
- },
- updatedBy: {
- type: 'integer',
- },
- userId: {
- type: 'long',
- },
- },
- },
- name: {
- type: 'string',
- },
- status: {
- type: 'string',
- index: 'not_analyzed',
- },
- terms: {
- type: 'integer',
- },
- type: {
- type: 'string',
- index: 'not_analyzed',
- },
- updatedAt: {
- type: 'date',
- format: 'strict_date_optional_time||epoch_millis',
- },
- updatedBy: {
- type: 'integer',
- },
- lastActivityAt: {
- type: 'date',
- format: 'strict_date_optional_time||epoch_millis',
- },
- lastActivityUserId: {
- type: 'string',
- },
- utm: {
- properties: {
- campaign: {
- type: 'string',
- },
- medium: {
- type: 'string',
- },
- source: {
- type: 'string',
- },
- },
- },
- phases: {
- type: 'nested',
- dynamic: true,
- },
- },
- };
- switch (indexName) {
- case ES_PROJECT_INDEX:
- result = {
- index: indexName,
- updateAllTypes: true,
- body: {
- mappings: { },
- },
- };
- result.body.mappings[ES_PROJECT_TYPE] = projectMapping;
- break;
- default:
- throw new Error(`Invalid index name '${indexName}'`);
+async function sync(indexName) {
+ if (indexName && supportedIndexes.indexOf(indexName) === -1) {
+ throw new Error(`Index "${indexName}" is not supported.`);
+ }
+ const indexesToSync = indexName ? [indexName] : supportedIndexes;
+
+ // create new elasticsearch client
+ // the client modifies the config object, so always passed the cloned object
+ const esClient = util.getElasticSearchClient();
+
+ for (let i = 0; i < indexesToSync.length; i += 1) {
+ const indexToSync = indexesToSync[i];
+
+ console.log(`Deleting "${indexToSync}" index...`);
+ await esClient.indices.delete({ index: indexToSync, ignore: [404] }); // eslint-disable-line no-await-in-loop
+ console.log(`Creating "${indexToSync}" index...`);
+ await esClient.indices.create(esUtils.buildCreateIndexRequest(indexToSync, INDEX_TO_DOC_TYPE[indexToSync])); // eslint-disable-line no-await-in-loop
}
- return result;
}
- // first delete the index if already present
-esClient.indices.delete({
- index: ES_PROJECT_INDEX,
- // we would want to ignore no such index error
- ignore: [404],
-})
-.then(() => esClient.indices.create(getRequestBody(ES_PROJECT_INDEX)))
-// Re-create timeline index
-.then(() => esClient.indices.delete({ index: ES_TIMELINE_INDEX, ignore: [404] }))
-.then(() => esClient.indices.create({ index: ES_TIMELINE_INDEX }))
-.then(() => {
- console.log('elasticsearch indices synced successfully');
- process.exit();
-})
-.catch((err) => {
- console.error('elasticsearch indices sync failed', err);
- process.exit();
-});
+if (!module.parent) {
+ // if we pass index name in command line arguments, then sync only that index
+ const indexName = process.argv[2] === '--index-name' && process.argv[3] ? process.argv[3] : undefined;
+
+ // to avoid accidental resetting of all indexes in PROD, enforce explicit defining of index name if not in
+ // development or test environment
+ if (['development', 'test'].indexOf(process.env.NODE_ENV) === -1 && !indexName) {
+ console.error('Error. "--index-name" should be provided when run this command in non-development environment.');
+ console.error('Example usage: "$ npm run sync:es -- --index-name metadata"');
+ process.exit(1);
+ }
+
+ sync(indexName)
+ .then(() => {
+ console.log('ElasticSearch indices synced successfully.');
+ process.exit();
+ })
+ .catch((err) => {
+ console.error('ElasticSearch indices sync failed: ', err);
+ process.exit(1);
+ });
+}
+
+module.exports = {
+ sync,
+};
diff --git a/migrations/fixMetadataForES.js b/migrations/fixMetadataForES.js
new file mode 100644
index 00000000..3f9dc725
--- /dev/null
+++ b/migrations/fixMetadataForES.js
@@ -0,0 +1,19 @@
+/* eslint-disable no-console */
+/**
+ * Update all records in the ProjectTemplate table.
+ * - inside “scope” field update “buildingBlocks..price” (for any ) to be a string if it’s not a string.
+ * - inside “scope” field replace all the ‘“wizard”: true’ with ‘“wizard”: {“enabled”: true}’,
+ * and ‘“wizard”: false’ replace with ‘“wizard”: {“enabled”: false}’.
+ * Update all records in the ProductTemplate table.
+ * - inside "template" field update all "required" properties which is not of "boolean" type to boolean.
+ */
+import fixMetadataForES from '../src/utils/fixMetadataForES';
+
+fixMetadataForES(console)
+ .then(() => {
+ console.log('done!');
+ process.exit();
+ }).catch((err) => {
+ console.error('Error syncing database', err);
+ process.exit(1);
+ });
diff --git a/migrations/helpers/indexMetadataByProcessor.js b/migrations/helpers/indexMetadataByProcessor.js
new file mode 100644
index 00000000..b042eed1
--- /dev/null
+++ b/migrations/helpers/indexMetadataByProcessor.js
@@ -0,0 +1,98 @@
+/* eslint-disable no-console */
+/**
+ * Sync metadata models from DB to ES using "project-processor-es".
+ *
+ * The main purpose of this script is to use during development to validate that all metadata can be
+ * correctly indexed using "project-processor-es".
+ *
+ * Advantage: It sync DB data to ES using "project-processor-es" as if were adding them using API.
+ * So the result of this process is closer to what we would get if we added these objects
+ * using API.
+ *
+ * Disadvantage: We don't know when the syncing process is done and if there are any errors or no.
+ * To get this information we have to watch the log of "project-processor-es".
+ */
+import _ from 'lodash';
+import models from '../../src/models';
+import { RESOURCES, BUS_API_EVENT } from '../../src/constants';
+import { createEvent } from '../../src/services/busApi';
+
+const modelConfigs = {
+ ProjectTemplate: {
+ indexProperty: 'projectTemplates',
+ resource: RESOURCES.PROJECT_TEMPLATE,
+ },
+ ProductTemplate: {
+ indexProperty: 'productTemplates',
+ resource: RESOURCES.PRODUCT_TEMPLATE,
+ },
+ ProjectType: {
+ indexProperty: 'projectTypes',
+ resource: RESOURCES.PROJECT_TYPE,
+ },
+ ProductCategory: {
+ indexProperty: 'productCategories',
+ resource: RESOURCES.PRODUCT_CATEGORY,
+ },
+ MilestoneTemplate: {
+ indexProperty: 'milestoneTemplates',
+ resource: RESOURCES.MILESTONE_TEMPLATE,
+ },
+ OrgConfig: {
+ indexProperty: 'orgConfigs',
+ resource: RESOURCES.ORG_CONFIG,
+ },
+ Form: {
+ indexProperty: 'forms',
+ resource: RESOURCES.FORM_REVISION,
+ },
+ PlanConfig: {
+ indexProperty: 'planConfigs',
+ resource: RESOURCES.PLAN_CONFIG_REVISION,
+ },
+ PriceConfig: {
+ indexProperty: 'priceConfigs',
+ resource: RESOURCES.PRICE_CONFIG_REVISION,
+ },
+ // This model is not yet supported by "project-processor-es"
+ // BuildingBlock: {
+ // indexProperty: 'buildingBlocks',
+ // resource: RESOURCES.
+ // },
+};
+
+/**
+ * Sync metadata index
+ *
+ * @returns {Promise} promise when all is done
+ */
+async function syncMetadataIndex() {
+ const modelNames = _.keys(modelConfigs);
+
+ for (let i = 0; i < modelNames.length; i += 1) {
+ const modelName = modelNames[i];
+ const modelConfig = modelConfigs[modelName];
+ const records = await models[modelName].findAll({ raw: true }); // eslint-disable-line no-await-in-loop
+
+ console.log(`Syncing ${records.length} records for model ${modelName}...`);
+
+ await Promise.all( // eslint-disable-line no-await-in-loop
+ records.map(record =>
+ createEvent(
+ BUS_API_EVENT.PROJECT_METADATA_CREATE,
+ _.assign({ resource: modelConfig.resource }, record),
+ console,
+ ),
+ ),
+ );
+ }
+}
+
+syncMetadataIndex()
+ .then(() => {
+ console.log('Done. The event to sync data in ES have been published to Bus API!');
+ process.exit();
+ }).catch((err) => {
+ console.error('Error', err);
+ process.exit(1);
+ });
diff --git a/migrations/helpers/indexMetadataDirectly.js b/migrations/helpers/indexMetadataDirectly.js
new file mode 100644
index 00000000..6f50a7dc
--- /dev/null
+++ b/migrations/helpers/indexMetadataDirectly.js
@@ -0,0 +1,21 @@
+/* eslint-disable no-console */
+/**
+ * Sync metadata models from DB to ES using direct call of es client.
+ *
+ * Advantage: It syncs data fast and we know if the process was successful or no.
+ *
+ * Disadvantage: As in real life data is indexing using "project-processor-es", it may happen
+ * that our custom implementation is somehow different. Though it shouldn't.
+ */
+import { indexMetadata } from '../../src/utils/es';
+
+console.log('Indexing metadata from DB...');
+
+indexMetadata()
+ .then(() => {
+ console.log('Done!');
+ process.exit();
+ }).catch((err) => {
+ console.error('Error', err);
+ process.exit(1);
+ });
diff --git a/package-lock.json b/package-lock.json
index 8b18977b..586f817e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,9 +19,9 @@
"integrity": "sha1-JjNHCk6r6aR82aRf2yDtX5NAe8o="
},
"@types/body-parser": {
- "version": "1.17.0",
- "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz",
- "integrity": "sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w==",
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz",
+ "integrity": "sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w==",
"requires": {
"@types/connect": "*",
"@types/node": "*"
@@ -35,15 +35,10 @@
"@types/node": "*"
}
},
- "@types/events": {
- "version": "1.2.0",
- "resolved": "http://registry.npmjs.org/@types/events/-/events-1.2.0.tgz",
- "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA=="
- },
"@types/express": {
- "version": "4.16.0",
- "resolved": "https://registry.npmjs.org/@types/express/-/express-4.16.0.tgz",
- "integrity": "sha512-TtPEYumsmSTtTetAPXlJVf3kEqb6wZK0bZojpJQrnD/djV4q1oB6QQ8aKvKqwNPACoe02GNiy5zDzcYivR5Z2w==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2.tgz",
+ "integrity": "sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==",
"requires": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "*",
@@ -51,51 +46,45 @@
}
},
"@types/express-jwt": {
- "version": "0.0.34",
- "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.34.tgz",
- "integrity": "sha1-/b7kxq9cCiRu8qkz9VGZc8dxfwI=",
+ "version": "0.0.42",
+ "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz",
+ "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==",
"requires": {
"@types/express": "*",
"@types/express-unless": "*"
}
},
"@types/express-serve-static-core": {
- "version": "4.16.0",
- "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.0.tgz",
- "integrity": "sha512-lTeoCu5NxJU4OD9moCgm0ESZzweAx0YqsAcab6OB0EB3+As1OaHtKnaGJvcngQxYsi9UNv0abn4/DRavrRxt4w==",
+ "version": "4.16.11",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.11.tgz",
+ "integrity": "sha512-K8d2M5t3tBQimkyaYTXxtHYyoJPUEhy2/omVRnTAKw5FEdT+Ft6lTaTOpoJdHeG+mIwQXXtqiTcYZ6IR8LTzjQ==",
"requires": {
- "@types/events": "*",
"@types/node": "*",
"@types/range-parser": "*"
}
},
"@types/express-unless": {
- "version": "0.0.32",
- "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.0.32.tgz",
- "integrity": "sha512-6YpJyFNlDDnPnRjMOvJCoDYlSDDmG/OEEUsPk7yhNkL4G9hUYtgab6vi1CcWsGSSSM0CsvNlWTG+ywAGnvF03g==",
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.1.tgz",
+ "integrity": "sha512-5fuvg7C69lemNgl0+v+CUxDYWVPSfXHhJPst4yTLcqi4zKJpORCxnDrnnilk3k0DTq/WrAUdvXFs01+vUqUZHw==",
"requires": {
"@types/express": "*"
}
},
- "@types/geojson": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.6.tgz",
- "integrity": "sha512-Xqg/lIZMrUd0VRmSRbCAewtwGZiAk3mEUDvV4op1tGl+LvyPcb/MIOSxTl9z+9+J+R4/vpjiCAT4xeKzH9ji1w=="
- },
"@types/lodash": {
- "version": "4.14.116",
- "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.116.tgz",
- "integrity": "sha512-lRnAtKnxMXcYYXqOiotTmJd74uawNWuPnsnPrrO7HiFuE3npE2iQhfABatbYDyxTNqZNuXzcKGhw37R7RjBFLg=="
+ "version": "4.14.146",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.146.tgz",
+ "integrity": "sha512-JzJcmQ/ikHSv7pbvrVNKJU5j9jL9VLf3/gqs048CEnBVVVEv4kve3vLxoPHGvclutS+Il4SBIuQQ087m1eHffw=="
},
"@types/mime": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.0.tgz",
- "integrity": "sha512-A2TAGbTFdBw9azHbpVd+/FkdW2T6msN1uct1O9bH3vTerEHKZhTXJUQXy+hNq1B0RagfU8U+KBdqiZpxjhOUQA=="
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz",
+ "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw=="
},
"@types/node": {
- "version": "10.12.15",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.15.tgz",
- "integrity": "sha512-9kROxduaN98QghwwHmxXO2Xz3MaWf+I1sLVAA6KJDF5xix+IyXVhds0MAfdNwtcpSrzhaTsNB0/jnL86fgUhqA=="
+ "version": "12.12.7",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.7.tgz",
+ "integrity": "sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w=="
},
"@types/range-parser": {
"version": "1.2.3",
@@ -103,9 +92,9 @@
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA=="
},
"@types/serve-static": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz",
- "integrity": "sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q==",
+ "version": "1.13.3",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz",
+ "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==",
"requires": {
"@types/express-serve-static-core": "*",
"@types/mime": "*"
@@ -118,18 +107,18 @@
"dev": true
},
"accepts": {
- "version": "1.3.4",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz",
- "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=",
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+ "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
"requires": {
- "mime-types": "~2.1.16",
- "negotiator": "0.6.1"
+ "mime-types": "~2.1.24",
+ "negotiator": "0.6.2"
}
},
"acorn": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.2.1.tgz",
- "integrity": "sha512-jG0u7c4Ly+3QkkW18V+NRDN+4bWHdln30NL1ZL2AvFZZmQe/BfopYCtghCKKVBUSetZ4QKcyA0pY6/4Gw8Pv8w==",
+ "version": "5.7.3",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
+ "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==",
"dev": true
},
"acorn-jsx": {
@@ -149,14 +138,23 @@
}
}
},
+ "agentkeepalive": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz",
+ "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==",
+ "requires": {
+ "humanize-ms": "^1.2.1"
+ }
+ },
"ajv": {
- "version": "4.11.8",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
- "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=",
- "dev": true,
+ "version": "6.10.2",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
+ "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
"requires": {
- "co": "^4.6.0",
- "json-stable-stringify": "^1.0.1"
+ "fast-deep-equal": "^2.0.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
}
},
"ajv-keywords": {
@@ -165,32 +163,17 @@
"integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=",
"dev": true
},
- "align-text": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
- "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
- "dev": true,
- "requires": {
- "kind-of": "^3.0.2",
- "longest": "^1.0.1",
- "repeat-string": "^1.5.2"
- }
- },
- "amdefine": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
- "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
- "dev": true
- },
"amqplib": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.1.tgz",
- "integrity": "sha1-fMz+ur5WwumE6noiQ/fO/m+/xs8=",
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.5.tgz",
+ "integrity": "sha512-sWx1hbfHbyKMw6bXOK2k6+lHL8TESWxjAx5hG8fBtT7wcxoXNIsFxZMnFyBjxt3yL14vn7WqBDe5U6BGOadtLg==",
"requires": {
- "bitsyntax": "~0.0.4",
- "bluebird": "^3.4.6",
- "buffer-more-ints": "0.0.2",
- "readable-stream": "1.x >=1.1.9"
+ "bitsyntax": "~0.1.0",
+ "bluebird": "^3.5.2",
+ "buffer-more-ints": "~1.0.0",
+ "readable-stream": "1.x >=1.1.9",
+ "safe-buffer": "~5.1.2",
+ "url-parse": "~1.4.3"
}
},
"analytics-node": {
@@ -251,6 +234,12 @@
}
}
},
+ "ansi-colors": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz",
+ "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==",
+ "dev": true
+ },
"ansi-escapes": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz",
@@ -267,11 +256,17 @@
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
},
+ "any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
+ },
"anymatch": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz",
"integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==",
"dev": true,
+ "optional": true,
"requires": {
"micromatch": "^2.1.5",
"normalize-path": "^2.0.0"
@@ -291,17 +286,10 @@
"default-require-extensions": "^1.0.0"
}
},
- "archy": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
- "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=",
- "dev": true
- },
"argparse": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz",
- "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=",
- "dev": true,
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "~1.0.2"
}
@@ -311,6 +299,7 @@
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
"integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=",
"dev": true,
+ "optional": true,
"requires": {
"arr-flatten": "^1.0.1"
}
@@ -321,16 +310,10 @@
"integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
"dev": true
},
- "array-differ": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz",
- "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=",
- "dev": true
- },
- "array-each": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz",
- "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=",
+ "arr-union": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
"dev": true
},
"array-flatten": {
@@ -338,43 +321,22 @@
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
- "array-slice": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.0.0.tgz",
- "integrity": "sha1-5zA08A3MH0CHYAj9IP6ud71LfC8=",
- "dev": true
- },
- "array-union": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
- "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
+ "array-includes": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz",
+ "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=",
"dev": true,
"requires": {
- "array-uniq": "^1.0.1"
+ "define-properties": "^1.1.2",
+ "es-abstract": "^1.7.0"
}
},
- "array-uniq": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
- "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
- "dev": true
- },
"array-unique": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz",
"integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=",
- "dev": true
- },
- "arrify": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
- "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
- "dev": true
- },
- "asap": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
- "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
+ "dev": true,
+ "optional": true
},
"asn1": {
"version": "0.2.4",
@@ -390,9 +352,15 @@
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
},
"assertion-error": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz",
- "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+ "dev": true
+ },
+ "assign-symbols": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
"dev": true
},
"async": {
@@ -402,15 +370,15 @@
"dev": true
},
"async-each": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz",
- "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz",
+ "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==",
"dev": true
},
"async-listener": {
- "version": "0.6.8",
- "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.8.tgz",
- "integrity": "sha512-1Sy1jDhjlgxcSd9/ICHqiAHT8VSJ9R1lzEyWwP/4Hm9p8nVTNtU0SxG/Z15XHD/aZvQraSw9BpDU3EBcFnOVrw==",
+ "version": "0.6.10",
+ "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.10.tgz",
+ "integrity": "sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==",
"requires": {
"semver": "^5.3.0",
"shimmer": "^1.1.0"
@@ -421,111 +389,40 @@
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
+ "atob": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
+ "dev": true
+ },
"auth0-js": {
- "version": "9.8.2",
- "resolved": "https://registry.npmjs.org/auth0-js/-/auth0-js-9.8.2.tgz",
- "integrity": "sha512-fwUkIABBA0e1B6hfkePtjOFlhXzvOUc/ZFx3NE1X9Ij3VZeqtJK7QU/Pc6tar+NkOpgZbRUXkxEG5qPGiwixWQ==",
+ "version": "9.11.3",
+ "resolved": "https://registry.npmjs.org/auth0-js/-/auth0-js-9.11.3.tgz",
+ "integrity": "sha512-86EGbaXPHBuyYPPPpvkckH7rCnEgS14DHsK64v2tb4ph4NsZ+peW6pjwBHkOdz4Ytd/ibhGTYCEbA9xdHWSqiA==",
"requires": {
- "base64-js": "^1.2.0",
- "idtoken-verifier": "^1.2.0",
+ "base64-js": "^1.3.0",
+ "idtoken-verifier": "^1.4.1",
"js-cookie": "^2.2.0",
- "qs": "^6.4.0",
- "superagent": "^3.8.2",
- "url-join": "^4.0.0",
- "winchan": "^0.2.1"
- },
- "dependencies": {
- "debug": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
- "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
- "requires": {
- "ms": "^2.1.1"
- }
- },
- "formidable": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz",
- "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg=="
- },
- "isarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
- },
- "ms": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
- "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
- },
- "process-nextick-args": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
- "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
- },
- "readable-stream": {
- "version": "2.3.6",
- "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
- "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
- "requires": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
- }
- },
- "string_decoder": {
- "version": "1.1.1",
- "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "requires": {
- "safe-buffer": "~5.1.0"
- }
- },
- "superagent": {
- "version": "3.8.3",
- "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz",
- "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==",
- "requires": {
- "component-emitter": "^1.2.0",
- "cookiejar": "^2.1.0",
- "debug": "^3.1.0",
- "extend": "^3.0.0",
- "form-data": "^2.3.1",
- "formidable": "^1.2.0",
- "methods": "^1.1.1",
- "mime": "^1.4.1",
- "qs": "^6.5.1",
- "readable-stream": "^2.3.5"
- }
- }
+ "qs": "^6.7.0",
+ "superagent": "^3.8.3",
+ "url-join": "^4.0.1",
+ "winchan": "^0.2.2"
}
},
"aws-sdk": {
- "version": "2.143.0",
- "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.143.0.tgz",
- "integrity": "sha1-dRBovUzxPTJl9tobAlSQO+S6yO4=",
+ "version": "2.568.0",
+ "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.568.0.tgz",
+ "integrity": "sha512-jPvhiJV2iLyWbJJDM01gvUCzeChWUeRMkIr6dsHu+leH2QnzvGNunTwMGculKE1jouXatajZEoA9bdqfosranw==",
"requires": {
- "buffer": "4.9.1",
- "crypto-browserify": "1.0.9",
+ "buffer": "^4.9.1",
"events": "^1.1.1",
- "jmespath": "0.15.0",
- "querystring": "0.2.0",
- "sax": "1.2.1",
- "url": "0.10.3",
- "uuid": "3.1.0",
- "xml2js": "0.4.17",
- "xmlbuilder": "4.2.1"
- },
- "dependencies": {
- "uuid": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
- "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g=="
- }
+ "ieee754": "^1.1.13",
+ "jmespath": "^0.15.0",
+ "querystring": "^0.2.0",
+ "sax": "^1.2.1",
+ "url": "^0.10.3",
+ "uuid": "^3.3.2",
+ "xml2js": "^0.4.19"
}
},
"aws-sign2": {
@@ -539,12 +436,12 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
},
"axios": {
- "version": "0.17.1",
- "resolved": "https://registry.npmjs.org/axios/-/axios-0.17.1.tgz",
- "integrity": "sha1-LY4+XQvb1zJ/kbyBT1xXZg+Bgk0=",
+ "version": "0.19.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz",
+ "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==",
"requires": {
- "follow-redirects": "^1.2.5",
- "is-buffer": "^1.1.5"
+ "follow-redirects": "1.5.10",
+ "is-buffer": "^2.0.2"
}
},
"babel-cli": {
@@ -581,9 +478,9 @@
}
},
"glob": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
- "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
@@ -608,9 +505,9 @@
}
},
"babel-core": {
- "version": "6.26.0",
- "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz",
- "integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=",
+ "version": "6.26.3",
+ "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz",
+ "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==",
"dev": true,
"requires": {
"babel-code-frame": "^6.26.0",
@@ -623,15 +520,15 @@
"babel-traverse": "^6.26.0",
"babel-types": "^6.26.0",
"babylon": "^6.18.0",
- "convert-source-map": "^1.5.0",
- "debug": "^2.6.8",
+ "convert-source-map": "^1.5.1",
+ "debug": "^2.6.9",
"json5": "^0.5.1",
"lodash": "^4.17.4",
"minimatch": "^3.0.4",
"path-is-absolute": "^1.0.1",
- "private": "^0.1.7",
+ "private": "^0.1.8",
"slash": "^1.0.0",
- "source-map": "^0.5.6"
+ "source-map": "^0.5.7"
},
"dependencies": {
"babel-runtime": {
@@ -665,9 +562,9 @@
}
},
"babel-generator": {
- "version": "6.26.0",
- "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.0.tgz",
- "integrity": "sha1-rBriAHC3n248odMmlhMFN3TyDcU=",
+ "version": "6.26.1",
+ "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz",
+ "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==",
"dev": true,
"requires": {
"babel-messages": "^6.23.0",
@@ -676,7 +573,7 @@
"detect-indent": "^4.0.0",
"jsesc": "^1.3.0",
"lodash": "^4.17.4",
- "source-map": "^0.5.6",
+ "source-map": "^0.5.7",
"trim-right": "^1.0.1"
},
"dependencies": {
@@ -1200,9 +1097,9 @@
}
},
"babel-plugin-transform-es2015-modules-commonjs": {
- "version": "6.26.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz",
- "integrity": "sha1-DYOUApt9xqvhqX7xgeAHWN0uXYo=",
+ "version": "6.26.2",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz",
+ "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==",
"dev": true,
"requires": {
"babel-plugin-transform-strict-mode": "^6.24.1",
@@ -1522,9 +1419,9 @@
},
"dependencies": {
"regenerator-runtime": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz",
- "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==",
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
+ "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
"dev": true
}
}
@@ -1598,7 +1495,7 @@
},
"babel-runtime": {
"version": "6.6.1",
- "resolved": "http://registry.npmjs.org/babel-runtime/-/babel-runtime-6.6.1.tgz",
+ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.6.1.tgz",
"integrity": "sha1-eIuUtvY04luRvWxd9y1GdFevsAA=",
"requires": {
"core-js": "^2.1.0"
@@ -1701,10 +1598,77 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
+ "base": {
+ "version": "0.11.2",
+ "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
+ "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
+ "dev": true,
+ "requires": {
+ "cache-base": "^1.0.1",
+ "class-utils": "^0.3.5",
+ "component-emitter": "^1.2.1",
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.1",
+ "mixin-deep": "^1.2.0",
+ "pascalcase": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
+ "dev": true
+ }
+ }
+ },
"base64-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz",
- "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw=="
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
+ "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"bcrypt-pbkdf": {
"version": "1.0.2",
@@ -1714,67 +1678,73 @@
"tweetnacl": "^0.14.3"
}
},
- "beeper": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz",
- "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=",
- "dev": true
- },
"bin-protocol": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/bin-protocol/-/bin-protocol-3.0.4.tgz",
- "integrity": "sha1-RlqdNQb+sOEmtStbIWDZNuFbJ/Q=",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/bin-protocol/-/bin-protocol-3.1.1.tgz",
+ "integrity": "sha512-9vCGfaHC2GBHZwGQdG+DpyXfmLvx9uKtf570wMLwIc9wmTIDgsdCBXQxTZu5X2GyogkfBks2Ode4N0sUVxJ2qQ==",
"requires": {
- "lodash": "^4.1.0",
- "long": "^3.0.3",
+ "lodash": "^4.17.11",
+ "long": "^4.0.0",
"protocol-buffers-schema": "^3.0.0"
}
},
"binary-extensions": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.10.0.tgz",
- "integrity": "sha1-muuabF6IY4qtFx4Wf1kAq+JINdA=",
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
+ "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
"dev": true
},
"bindings": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz",
- "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE="
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+ "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+ "requires": {
+ "file-uri-to-path": "1.0.0"
+ }
},
"bitsyntax": {
- "version": "0.0.4",
- "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.0.4.tgz",
- "integrity": "sha1-6xDMb4K4xJDj6FaY8H6D1G4MuoI=",
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.1.0.tgz",
+ "integrity": "sha512-ikAdCnrloKmFOugAfxWws89/fPc+nw0OOG1IzIE72uSOg/A3cYptKCjSUhDTuj7fhsJtzkzlv7l3b8PzRHLN0Q==",
"requires": {
- "buffer-more-ints": "0.0.2"
+ "buffer-more-ints": "~1.0.0",
+ "debug": "~2.6.9",
+ "safe-buffer": "~5.1.2"
}
},
"bluebird": {
- "version": "3.5.1",
- "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz",
- "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA=="
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz",
+ "integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg=="
},
"body-parser": {
- "version": "1.18.2",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz",
- "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=",
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
+ "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
"requires": {
- "bytes": "3.0.0",
+ "bytes": "3.1.0",
"content-type": "~1.0.4",
"debug": "2.6.9",
- "depd": "~1.1.1",
- "http-errors": "~1.6.2",
- "iconv-lite": "0.4.19",
+ "depd": "~1.1.2",
+ "http-errors": "1.7.2",
+ "iconv-lite": "0.4.24",
"on-finished": "~2.3.0",
- "qs": "6.5.1",
- "raw-body": "2.3.2",
- "type-is": "~1.6.15"
+ "qs": "6.7.0",
+ "raw-body": "2.4.0",
+ "type-is": "~1.6.17"
+ },
+ "dependencies": {
+ "qs": {
+ "version": "6.7.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+ "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
+ }
}
},
"boxen": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.2.2.tgz",
- "integrity": "sha1-Px1AMsMP/qnUsCwyLq8up0HcvOU=",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz",
+ "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==",
"dev": true,
"requires": {
"ansi-align": "^2.0.0",
@@ -1783,7 +1753,7 @@
"cli-boxes": "^1.0.0",
"string-width": "^2.0.0",
"term-size": "^1.2.0",
- "widest-line": "^1.0.0"
+ "widest-line": "^2.0.0"
},
"dependencies": {
"ansi-regex": {
@@ -1793,9 +1763,9 @@
"dev": true
},
"ansi-styles": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
- "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
@@ -1808,20 +1778,20 @@
"dev": true
},
"chalk": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
- "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==",
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
- "ansi-styles": "^3.1.0",
+ "ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
- "supports-color": "^4.0.0"
+ "supports-color": "^5.3.0"
}
},
"has-flag": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
- "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"is-fullwidth-code-point": {
@@ -1850,20 +1820,20 @@
}
},
"supports-color": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
- "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
- "has-flag": "^2.0.0"
+ "has-flag": "^3.0.0"
}
}
}
},
"brace-expansion": {
- "version": "1.1.8",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
- "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -1874,16 +1844,23 @@
"resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz",
"integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=",
"dev": true,
+ "optional": true,
"requires": {
"expand-range": "^1.8.1",
"preserve": "^0.2.0",
"repeat-element": "^1.1.2"
}
},
+ "browser-stdout": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+ "dev": true
+ },
"buffer": {
- "version": "4.9.1",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
- "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
+ "version": "4.9.2",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
+ "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
"requires": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4",
@@ -1907,21 +1884,21 @@
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
},
+ "buffer-from": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+ "dev": true
+ },
"buffer-more-ints": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-0.0.2.tgz",
- "integrity": "sha1-JrOIXRD6E9t/wBquOquHAZngEkw="
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz",
+ "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg=="
},
"buffer-writer": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-1.0.1.tgz",
- "integrity": "sha1-Iqk2kB4wKa/NdUfrRIfOtpejvwg="
- },
- "builtin-modules": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
- "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
- "dev": true
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
+ "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw=="
},
"bunyan": {
"version": "1.8.12",
@@ -1935,9 +1912,34 @@
}
},
"bytes": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
- "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
+ "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
+ },
+ "cache-base": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
+ "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
+ "dev": true,
+ "requires": {
+ "collection-visit": "^1.0.0",
+ "component-emitter": "^1.2.1",
+ "get-value": "^2.0.6",
+ "has-value": "^1.0.0",
+ "isobject": "^3.0.1",
+ "set-value": "^2.0.0",
+ "to-object-path": "^0.3.0",
+ "union-value": "^1.0.0",
+ "unset-value": "^1.0.0"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ }
+ }
},
"caller-path": {
"version": "0.1.0",
@@ -1955,16 +1957,15 @@
"dev": true
},
"camelcase": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
- "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=",
- "dev": true,
- "optional": true
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true
},
"capture-stack-trace": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz",
- "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz",
+ "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==",
"dev": true
},
"caseless": {
@@ -1972,17 +1973,6 @@
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
},
- "center-align": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
- "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=",
- "dev": true,
- "optional": true,
- "requires": {
- "align-text": "^0.1.3",
- "lazy-cache": "^1.0.3"
- }
- },
"chai": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz",
@@ -2032,6 +2022,7 @@
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
"integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=",
"dev": true,
+ "optional": true,
"requires": {
"anymatch": "^1.3.0",
"async-each": "^1.0.0",
@@ -2044,12 +2035,47 @@
"readdirp": "^2.0.0"
}
},
+ "ci-info": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz",
+ "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==",
+ "dev": true
+ },
"circular-json": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz",
"integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==",
"dev": true
},
+ "class-utils": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
+ "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
+ "dev": true,
+ "requires": {
+ "arr-union": "^3.1.0",
+ "define-property": "^0.2.5",
+ "isobject": "^3.0.0",
+ "static-extend": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ }
+ }
+ },
"cli-boxes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz",
@@ -2057,17 +2083,17 @@
"dev": true
},
"cli-color": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.2.0.tgz",
- "integrity": "sha1-OlrnT9drYmevZm5p4q+70B3vNNE=",
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz",
+ "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==",
"dev": true,
"requires": {
"ansi-regex": "^2.1.1",
"d": "1",
- "es5-ext": "^0.10.12",
- "es6-iterator": "2",
- "memoizee": "^0.4.3",
- "timers-ext": "0.1"
+ "es5-ext": "^0.10.46",
+ "es6-iterator": "^2.0.3",
+ "memoizee": "^0.4.14",
+ "timers-ext": "^0.1.5"
}
},
"cli-cursor": {
@@ -2086,36 +2112,63 @@
"dev": true
},
"cliui": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
- "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
+ "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
"dev": true,
- "optional": true,
"requires": {
- "center-align": "^0.1.1",
- "right-align": "^0.1.1",
- "wordwrap": "0.0.2"
+ "string-width": "^3.1.0",
+ "strip-ansi": "^5.2.0",
+ "wrap-ansi": "^5.1.0"
},
"dependencies": {
- "wordwrap": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz",
- "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=",
- "dev": true,
- "optional": true
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
+ },
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
}
}
},
"clone": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz",
- "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs="
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+ "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18="
},
- "clone-stats": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz",
- "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=",
- "dev": true
+ "cls-bluebird": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz",
+ "integrity": "sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4=",
+ "requires": {
+ "is-bluebird": "^1.0.2",
+ "shimmer": "^1.1.0"
+ }
},
"co": {
"version": "4.6.0",
@@ -2138,18 +2191,28 @@
"dependencies": {
"semver": {
"version": "5.0.1",
- "resolved": "http://registry.npmjs.org/semver/-/semver-5.0.1.tgz",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.1.tgz",
"integrity": "sha1-n7P0AE+QDYPEeWj+QvdYPgWDLMk="
}
}
},
+ "collection-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
+ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
+ "dev": true,
+ "requires": {
+ "map-visit": "^1.0.0",
+ "object-visit": "^1.0.0"
+ }
+ },
"color-convert": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz",
- "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=",
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
- "color-name": "^1.1.1"
+ "color-name": "1.1.3"
}
},
"color-name": {
@@ -2159,27 +2222,27 @@
"dev": true
},
"colors": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
- "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM="
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
+ "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
},
"combined-stream": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
- "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=",
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"requires": {
"delayed-stream": "~1.0.0"
}
},
"commander": {
- "version": "2.11.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
- "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ=="
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
"component-emitter": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
- "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
+ "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
},
"component-type": {
"version": "1.2.1",
@@ -2187,60 +2250,31 @@
"integrity": "sha1-ikeQFwAjjk/DIml3EjAibyS0Fak="
},
"compressible": {
- "version": "2.0.15",
- "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.15.tgz",
- "integrity": "sha512-4aE67DL33dSW9gw4CI2H/yTxqHLNcxp0yS6jB+4h+wr3e43+1z7vm0HU9qXOH8j+qjKuL8+UtkOxYQSMq60Ylw==",
+ "version": "2.0.17",
+ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz",
+ "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==",
"requires": {
- "mime-db": ">= 1.36.0 < 2"
- },
- "dependencies": {
- "mime-db": {
- "version": "1.37.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz",
- "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg=="
- }
+ "mime-db": ">= 1.40.0 < 2"
}
},
"compression": {
- "version": "1.7.3",
- "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz",
- "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==",
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
+ "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
"requires": {
"accepts": "~1.3.5",
"bytes": "3.0.0",
- "compressible": "~2.0.14",
+ "compressible": "~2.0.16",
"debug": "2.6.9",
- "on-headers": "~1.0.1",
+ "on-headers": "~1.0.2",
"safe-buffer": "5.1.2",
"vary": "~1.1.2"
},
"dependencies": {
- "accepts": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
- "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
- "requires": {
- "mime-types": "~2.1.18",
- "negotiator": "0.6.1"
- }
- },
- "mime-db": {
- "version": "1.37.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz",
- "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg=="
- },
- "mime-types": {
- "version": "2.1.21",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz",
- "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==",
- "requires": {
- "mime-db": "~1.37.0"
- }
- },
- "safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ "bytes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+ "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
}
}
},
@@ -2250,11 +2284,12 @@
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"concat-stream": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz",
- "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=",
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
"dev": true,
"requires": {
+ "buffer-from": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^2.2.2",
"typedarray": "^0.0.6"
@@ -2267,24 +2302,24 @@
"dev": true
},
"readable-stream": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
- "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
- "process-nextick-args": "~1.0.6",
+ "process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
- "string_decoder": "~1.0.3",
+ "string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"string_decoder": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
- "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
"safe-buffer": "~5.1.0"
@@ -2293,18 +2328,17 @@
}
},
"config": {
- "version": "1.27.0",
- "resolved": "https://registry.npmjs.org/config/-/config-1.27.0.tgz",
- "integrity": "sha1-OrMNAID/dvQHwvR6wTJq39kIr18=",
+ "version": "1.31.0",
+ "resolved": "https://registry.npmjs.org/config/-/config-1.31.0.tgz",
+ "integrity": "sha512-Ep/l9Rd1J9IPueztJfpbOqVzuKHQh4ZODMNt9xqTYdBBNRXbV4oTu34kCkkfdRVcDq0ohtpaeXGgb+c0LQxFRA==",
"requires": {
- "json5": "0.4.0",
- "os-homedir": "1.0.2"
+ "json5": "^1.0.1"
}
},
"config-chain": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz",
- "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=",
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz",
+ "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==",
"dev": true,
"requires": {
"ini": "^1.3.4",
@@ -2312,9 +2346,9 @@
}
},
"configstore": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.1.tgz",
- "integrity": "sha512-5oNkD/L++l0O6xGXxb1EWS7SivtjfGQlRyxJsYgE0Z495/L81e2h4/d3r969hoPXuFItzNOKMtsXgYG4c7dYvw==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz",
+ "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==",
"dev": true,
"requires": {
"dot-prop": "^4.1.0",
@@ -2337,9 +2371,12 @@
"dev": true
},
"content-disposition": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
- "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
+ "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
+ "requires": {
+ "safe-buffer": "5.1.2"
+ }
},
"content-type": {
"version": "1.0.4",
@@ -2347,24 +2384,27 @@
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
},
"continuation-local-storage": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.0.tgz",
- "integrity": "sha1-4Z/Da1lwkKXU5KOy6j68XilpSiQ=",
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz",
+ "integrity": "sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==",
"requires": {
"async-listener": "^0.6.0",
- "emitter-listener": "^1.0.1"
+ "emitter-listener": "^1.1.1"
}
},
"convert-source-map": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz",
- "integrity": "sha1-ms1whRxtXf3ZPZKC5e35SgP/RrU=",
- "dev": true
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
+ "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.1"
+ }
},
"cookie": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
- "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
+ "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
},
"cookie-signature": {
"version": "1.0.6",
@@ -2372,14 +2412,20 @@
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"cookiejar": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz",
- "integrity": "sha1-Qa1XsbVVlR7BcUEqgZQrHoIA00o="
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz",
+ "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA=="
+ },
+ "copy-descriptor": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
+ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
+ "dev": true
},
"core-js": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz",
- "integrity": "sha1-rmh03GaTd4m4B1T/VCjfZoGcpQs="
+ "version": "2.6.10",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.10.tgz",
+ "integrity": "sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA=="
},
"core-util-is": {
"version": "1.0.2",
@@ -2387,9 +2433,9 @@
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"cors": {
- "version": "2.8.4",
- "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz",
- "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=",
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"requires": {
"object-assign": "^4",
"vary": "^1"
@@ -2413,25 +2459,8 @@
"lru-cache": "^4.0.1",
"shebang-command": "^1.2.0",
"which": "^1.2.9"
- },
- "dependencies": {
- "lru-cache": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
- "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
- "dev": true,
- "requires": {
- "pseudomap": "^1.0.2",
- "yallist": "^2.1.2"
- }
- }
}
},
- "crypto-browserify": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-1.0.9.tgz",
- "integrity": "sha1-zFRJaF37hesRyYKKzHy4erW7/MA="
- },
"crypto-js": {
"version": "3.1.9-1",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz",
@@ -2449,12 +2478,13 @@
"integrity": "sha1-J8ZIL687Y8L12hFXf4MENG/nl6U="
},
"d": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz",
- "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
+ "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
"dev": true,
"requires": {
- "es5-ext": "^0.10.9"
+ "es5-ext": "^0.10.50",
+ "type": "^1.0.1"
}
},
"dashdash": {
@@ -2465,12 +2495,6 @@
"assert-plus": "^1.0.0"
}
},
- "dateformat": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz",
- "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=",
- "dev": true
- },
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -2485,6 +2509,12 @@
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"dev": true
},
+ "decode-uri-component": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
+ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
+ "dev": true
+ },
"deep-eql": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz",
@@ -2503,9 +2533,9 @@
}
},
"deep-extend": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz",
- "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=",
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"dev": true
},
"deep-is": {
@@ -2534,68 +2564,82 @@
}
}
},
- "defaults": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
- "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
+ "define-properties": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+ "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+ "requires": {
+ "object-keys": "^1.0.12"
+ }
+ },
+ "define-property": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+ "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
"dev": true,
"requires": {
- "clone": "^1.0.2"
+ "is-descriptor": "^1.0.2",
+ "isobject": "^3.0.1"
},
"dependencies": {
- "clone": {
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
"version": "1.0.2",
- "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz",
- "integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
"dev": true
}
}
},
- "del": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz",
- "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=",
- "dev": true,
- "requires": {
- "globby": "^5.0.0",
- "is-path-cwd": "^1.0.0",
- "is-path-in-cwd": "^1.0.0",
- "object-assign": "^4.0.1",
- "pify": "^2.0.0",
- "pinkie-promise": "^2.0.0",
- "rimraf": "^2.2.8"
- }
- },
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"depd": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
- "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k="
- },
- "deprecated": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/deprecated/-/deprecated-0.0.1.tgz",
- "integrity": "sha1-+cmvVGSvoeepcUWKi97yqpTVuxk=",
- "dev": true
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
},
"destroy": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
},
- "detect-file": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-0.1.0.tgz",
- "integrity": "sha1-STXe39lIhkjgBrASlWbpOGcR6mM=",
- "dev": true,
- "requires": {
- "fs-exists-sync": "^0.1.0"
- }
- },
"detect-indent": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
@@ -2606,27 +2650,18 @@
}
},
"diff": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz",
- "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=",
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
"dev": true
},
"doctrine": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz",
- "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
"dev": true,
"requires": {
- "esutils": "^2.0.2",
- "isarray": "^1.0.0"
- },
- "dependencies": {
- "isarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
- "dev": true
- }
+ "esutils": "^2.0.2"
}
},
"dot-prop": {
@@ -2639,32 +2674,17 @@
}
},
"dottie": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/dottie/-/dottie-1.1.1.tgz",
- "integrity": "sha1-RcKj9IvWUo7u0memmoSOqspvqmo="
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.1.tgz",
+ "integrity": "sha512-ch5OQgvGDK2u8pSZeSYAQaV/lczImd7pMJ7BcEPXmnFVjy4yJIzP6CsODJUTH8mg1tyH1Z2abOiuJO3DjZ/GBw=="
},
"dtrace-provider": {
- "version": "0.8.5",
- "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.5.tgz",
- "integrity": "sha1-mOu6Ihr6xG4cOf02hY2Pk2dSS5I=",
+ "version": "0.8.8",
+ "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz",
+ "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==",
"optional": true,
"requires": {
- "nan": "^2.3.3"
- }
- },
- "duplexer": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
- "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=",
- "dev": true
- },
- "duplexer2": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz",
- "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=",
- "dev": true,
- "requires": {
- "readable-stream": "~1.1.9"
+ "nan": "^2.14.0"
}
},
"duplexer3": {
@@ -2683,33 +2703,33 @@
}
},
"ecdsa-sig-formatter": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz",
- "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=",
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"requires": {
"safe-buffer": "^5.0.1"
}
},
"editorconfig": {
- "version": "0.13.3",
- "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.13.3.tgz",
- "integrity": "sha512-WkjsUNVCu+ITKDj73QDvi0trvpdDWdkDyHybDGSXPfekLCqwmpD7CP7iPbvBgosNuLcI96XTDwNa75JyFl7tEQ==",
+ "version": "0.15.3",
+ "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz",
+ "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==",
"dev": true,
"requires": {
- "bluebird": "^3.0.5",
- "commander": "^2.9.0",
- "lru-cache": "^3.2.0",
- "semver": "^5.1.0",
+ "commander": "^2.19.0",
+ "lru-cache": "^4.1.5",
+ "semver": "^5.6.0",
"sigmund": "^1.0.1"
},
"dependencies": {
"lru-cache": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz",
- "integrity": "sha1-cXibO39Tmb7IVl3aOKow0qCX7+4=",
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
+ "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
"dev": true,
"requires": {
- "pseudomap": "^1.0.1"
+ "pseudomap": "^1.0.2",
+ "yallist": "^2.1.2"
}
}
}
@@ -2720,81 +2740,79 @@
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"elasticsearch": {
- "version": "11.0.1",
- "resolved": "https://registry.npmjs.org/elasticsearch/-/elasticsearch-11.0.1.tgz",
- "integrity": "sha1-0YBoTGvefs+g+iTmL6HIcu6uCOc=",
+ "version": "16.5.0",
+ "resolved": "https://registry.npmjs.org/elasticsearch/-/elasticsearch-16.5.0.tgz",
+ "integrity": "sha512-9YbmU2AtM/kQdmp96EI5nu2bjxowdarV6IsKmcS+jQowJ3mhG98J1DCVOtEKuFvsnNaLyKD3aPbCAmb72+WX3w==",
"requires": {
+ "agentkeepalive": "^3.4.1",
"chalk": "^1.0.0",
- "forever-agent": "^0.6.0",
- "lodash": "^3.10.0",
- "lodash-compat": "^3.0.0",
- "promise": "^7.1.1"
- },
- "dependencies": {
- "lodash": {
- "version": "3.10.1",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
- "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y="
- }
+ "lodash": "^4.17.10"
}
},
"emitter-listener": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.0.1.tgz",
- "integrity": "sha1-skmepuWCMKUsJo1d8mHuzZ8Q/pc=",
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz",
+ "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==",
"requires": {
- "shimmer": "1.0.0"
- },
- "dependencies": {
- "shimmer": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.0.0.tgz",
- "integrity": "sha1-ScLXHGeDYLgCvhiyeDgtHLuAXDk="
- }
+ "shimmer": "^1.2.0"
}
},
+ "emoji-regex": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+ "dev": true
+ },
"encodeurl": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz",
- "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA="
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
- "end-of-stream": {
- "version": "0.1.5",
- "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-0.1.5.tgz",
- "integrity": "sha1-jhdyBsPICDfYVjLouTWd/osvbq8=",
+ "error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"dev": true,
"requires": {
- "once": "~1.3.0"
- },
- "dependencies": {
- "once": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
- "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=",
- "dev": true,
- "requires": {
- "wrappy": "1"
- }
- }
+ "is-arrayish": "^0.2.1"
}
},
- "error-ex": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
- "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=",
- "dev": true,
+ "es-abstract": {
+ "version": "1.16.0",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.0.tgz",
+ "integrity": "sha512-xdQnfykZ9JMEiasTAJZJdMWCQ1Vm00NBw79/AWi7ELfZuuPCSOMDZbT9mkOfSctVtfhb+sAAzrm+j//GjjLHLg==",
"requires": {
- "is-arrayish": "^0.2.1"
+ "es-to-primitive": "^1.2.0",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.0",
+ "is-callable": "^1.1.4",
+ "is-regex": "^1.0.4",
+ "object-inspect": "^1.6.0",
+ "object-keys": "^1.1.1",
+ "string.prototype.trimleft": "^2.1.0",
+ "string.prototype.trimright": "^2.1.0"
+ }
+ },
+ "es-to-primitive": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "requires": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
}
},
"es5-ext": {
- "version": "0.10.35",
- "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.35.tgz",
- "integrity": "sha1-GO6FjOajxFx9eekcFfzKnsVoSU8=",
+ "version": "0.10.52",
+ "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.52.tgz",
+ "integrity": "sha512-bWCbE9fbpYQY4CU6hJbJ1vSz70EClMlDgJ7BmwI+zEJhxrwjesZRPglGJlsZhu0334U3hI+gaspwksH9IGD6ag==",
"dev": true,
"requires": {
- "es6-iterator": "~2.0.1",
- "es6-symbol": "~3.1.1"
+ "es6-iterator": "~2.0.3",
+ "es6-symbol": "~3.1.2",
+ "next-tick": "~1.0.0"
}
},
"es6-iterator": {
@@ -2822,12 +2840,6 @@
"event-emitter": "~0.3.5"
}
},
- "es6-promise": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
- "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=",
- "dev": true
- },
"es6-set": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz",
@@ -2839,27 +2851,39 @@
"es6-iterator": "~2.0.1",
"es6-symbol": "3.1.1",
"event-emitter": "~0.3.5"
+ },
+ "dependencies": {
+ "es6-symbol": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz",
+ "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
+ "dev": true,
+ "requires": {
+ "d": "1",
+ "es5-ext": "~0.10.14"
+ }
+ }
}
},
"es6-symbol": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz",
- "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
+ "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
"dev": true,
"requires": {
- "d": "1",
- "es5-ext": "~0.10.14"
+ "d": "^1.0.1",
+ "ext": "^1.1.2"
}
},
"es6-weak-map": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz",
- "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz",
+ "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==",
"dev": true,
"requires": {
"d": "1",
- "es5-ext": "^0.10.14",
- "es6-iterator": "^2.0.1",
+ "es5-ext": "^0.10.46",
+ "es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.1"
}
},
@@ -2929,9 +2953,9 @@
},
"dependencies": {
"glob": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
- "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
@@ -2963,41 +2987,42 @@
}
},
"eslint-import-resolver-node": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz",
- "integrity": "sha512-yUtXS15gIcij68NmXmP9Ni77AQuCN0itXbCc/jWd8C6/yKZaSNXicpC8cgvjnxVdmfsosIXrjpzFq7GcDryb6A==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz",
+ "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==",
"dev": true,
"requires": {
- "debug": "^2.6.8",
- "resolve": "^1.2.0"
+ "debug": "^2.6.9",
+ "resolve": "^1.5.0"
}
},
"eslint-module-utils": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz",
- "integrity": "sha512-jDI/X5l/6D1rRD/3T43q8Qgbls2nq5km5KSqiwlyUbGo5+04fXhMKdCPhjwbqAa6HXWaMxj8Q4hQDIh7IadJQw==",
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz",
+ "integrity": "sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw==",
"dev": true,
"requires": {
"debug": "^2.6.8",
- "pkg-dir": "^1.0.0"
+ "pkg-dir": "^2.0.0"
}
},
"eslint-plugin-import": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.8.0.tgz",
- "integrity": "sha512-Rf7dfKJxZ16QuTgVv1OYNxkZcsu/hULFnC+e+w0Gzi6jMC3guQoWQgxYxc54IDRinlb6/0v5z/PxxIKmVctN+g==",
+ "version": "2.18.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz",
+ "integrity": "sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==",
"dev": true,
"requires": {
- "builtin-modules": "^1.1.1",
+ "array-includes": "^3.0.3",
"contains-path": "^0.1.0",
- "debug": "^2.6.8",
+ "debug": "^2.6.9",
"doctrine": "1.5.0",
- "eslint-import-resolver-node": "^0.3.1",
- "eslint-module-utils": "^2.1.1",
- "has": "^1.0.1",
- "lodash.cond": "^4.3.0",
- "minimatch": "^3.0.3",
- "read-pkg-up": "^2.0.0"
+ "eslint-import-resolver-node": "^0.3.2",
+ "eslint-module-utils": "^2.4.0",
+ "has": "^1.0.3",
+ "minimatch": "^3.0.4",
+ "object.values": "^1.1.0",
+ "read-pkg-up": "^2.0.0",
+ "resolve": "^1.11.0"
},
"dependencies": {
"doctrine": {
@@ -3025,50 +3050,49 @@
"dev": true
},
"espree": {
- "version": "3.5.1",
- "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.1.tgz",
- "integrity": "sha1-DJiLirRttTEAoZVK5LqZXd0n2H4=",
+ "version": "3.5.4",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz",
+ "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==",
"dev": true,
"requires": {
- "acorn": "^5.1.1",
+ "acorn": "^5.5.0",
"acorn-jsx": "^3.0.0"
}
},
"esprima": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
- "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true
},
"esquery": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz",
- "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz",
+ "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==",
"dev": true,
"requires": {
"estraverse": "^4.0.0"
}
},
"esrecurse": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz",
- "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
+ "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
"dev": true,
"requires": {
- "estraverse": "^4.1.0",
- "object-assign": "^4.0.1"
+ "estraverse": "^4.1.0"
}
},
"estraverse": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
- "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
"dev": true
},
"esutils": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
- "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true
},
"etag": {
@@ -3086,21 +3110,6 @@
"es5-ext": "~0.10.14"
}
},
- "event-stream": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz",
- "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=",
- "dev": true,
- "requires": {
- "duplexer": "~0.1.1",
- "from": "~0",
- "map-stream": "~0.1.0",
- "pause-stream": "0.0.11",
- "split": "0.3",
- "stream-combiner": "~0.0.4",
- "through": "~2.3.1"
- }
- },
"events": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
@@ -3132,6 +3141,7 @@
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz",
"integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=",
"dev": true,
+ "optional": true,
"requires": {
"is-posix-bracket": "^0.1.0"
}
@@ -3141,65 +3151,52 @@
"resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz",
"integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=",
"dev": true,
+ "optional": true,
"requires": {
"fill-range": "^2.1.0"
}
},
- "expand-tilde": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz",
- "integrity": "sha1-C4HrqJflo9MdHD0QL48BRB5VlEk=",
- "dev": true,
- "requires": {
- "os-homedir": "^1.0.1"
- }
- },
"express": {
- "version": "4.16.2",
- "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz",
- "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=",
+ "version": "4.17.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
+ "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
"requires": {
- "accepts": "~1.3.4",
+ "accepts": "~1.3.7",
"array-flatten": "1.1.1",
- "body-parser": "1.18.2",
- "content-disposition": "0.5.2",
+ "body-parser": "1.19.0",
+ "content-disposition": "0.5.3",
"content-type": "~1.0.4",
- "cookie": "0.3.1",
+ "cookie": "0.4.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
- "depd": "~1.1.1",
- "encodeurl": "~1.0.1",
+ "depd": "~1.1.2",
+ "encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
- "finalhandler": "1.1.0",
+ "finalhandler": "~1.1.2",
"fresh": "0.5.2",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "~2.3.0",
- "parseurl": "~1.3.2",
+ "parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
- "proxy-addr": "~2.0.2",
- "qs": "6.5.1",
- "range-parser": "~1.2.0",
- "safe-buffer": "5.1.1",
- "send": "0.16.1",
- "serve-static": "1.13.1",
- "setprototypeof": "1.1.0",
- "statuses": "~1.3.1",
- "type-is": "~1.6.15",
+ "proxy-addr": "~2.0.5",
+ "qs": "6.7.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.1.2",
+ "send": "0.17.1",
+ "serve-static": "1.14.1",
+ "setprototypeof": "1.1.1",
+ "statuses": "~1.5.0",
+ "type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"dependencies": {
- "setprototypeof": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
- "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
- },
- "statuses": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz",
- "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4="
+ "qs": {
+ "version": "6.7.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+ "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
}
}
},
@@ -3220,19 +3217,20 @@
}
},
"express-request-id": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/express-request-id/-/express-request-id-1.4.0.tgz",
- "integrity": "sha1-J3ssCUmAPmgQTJ1Fw+aJNPlr9aI=",
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/express-request-id/-/express-request-id-1.4.1.tgz",
+ "integrity": "sha512-qpxK6XhDYtdx9FvxwCHkUeZVWtkGbWR87hBAzGECfwYF/QQCPXEwwB2/9NGkOR1tT7/aLs9mma3CT0vjSzuZVw==",
"requires": {
- "uuid": "^3.0.1"
+ "uuid": "^3.3.2"
}
},
"express-sanitizer": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/express-sanitizer/-/express-sanitizer-1.0.2.tgz",
- "integrity": "sha1-In78pxSpy+M7siFY6ePocU06UnU=",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/express-sanitizer/-/express-sanitizer-1.0.5.tgz",
+ "integrity": "sha512-48/Tf1DZ7JklRVTcXQLHAxhq4GNJTuHq2jjIYhyTmu0Bw+X06YPDD/e/tdn1QLYk706xw4N8JFxtjslRrDGb8g==",
"requires": {
- "sanitizer": "0.1.3"
+ "sanitizer": "0.1.3",
+ "underscore": "1.8.3"
}
},
"express-validation": {
@@ -3243,16 +3241,55 @@
"lodash": "^4.9.0"
}
},
+ "ext": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/ext/-/ext-1.2.0.tgz",
+ "integrity": "sha512-0ccUQK/9e3NreLFg6K6np8aPyRgwycx+oFGtfx1dSp7Wj00Ozw9r05FgBRlzjf2XBM7LAzwgLyDscRrtSU91hA==",
+ "dev": true,
+ "requires": {
+ "type": "^2.0.0"
+ },
+ "dependencies": {
+ "type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz",
+ "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==",
+ "dev": true
+ }
+ }
+ },
"extend": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
- "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ="
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+ },
+ "extend-shallow": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+ "dev": true,
+ "requires": {
+ "assign-symbols": "^1.0.0",
+ "is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "dev": true,
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
},
"extglob": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz",
"integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=",
"dev": true,
+ "optional": true,
"requires": {
"is-extglob": "^1.0.0"
}
@@ -3262,16 +3299,6 @@
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
},
- "fancy-log": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.0.tgz",
- "integrity": "sha1-Rb4X0Cu5kX1gzP/UmVyZnmyMmUg=",
- "dev": true,
- "requires": {
- "chalk": "^1.1.1",
- "time-stamp": "^1.0.0"
- }
- },
"fast-deep-equal": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
@@ -3317,7 +3344,8 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
"integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=",
- "dev": true
+ "dev": true,
+ "optional": true
},
"fileset": {
"version": "2.0.3",
@@ -3330,9 +3358,9 @@
},
"dependencies": {
"glob": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
- "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
@@ -3346,121 +3374,94 @@
}
},
"fill-range": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz",
- "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=",
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz",
+ "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==",
"dev": true,
+ "optional": true,
"requires": {
"is-number": "^2.1.0",
"isobject": "^2.0.0",
- "randomatic": "^1.1.3",
+ "randomatic": "^3.0.0",
"repeat-element": "^1.1.2",
"repeat-string": "^1.5.2"
}
},
"finalhandler": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz",
- "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=",
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+ "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
"requires": {
"debug": "2.6.9",
- "encodeurl": "~1.0.1",
+ "encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "~2.3.0",
- "parseurl": "~1.3.2",
- "statuses": "~1.3.1",
+ "parseurl": "~1.3.3",
+ "statuses": "~1.5.0",
"unpipe": "~1.0.0"
- },
- "dependencies": {
- "statuses": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz",
- "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4="
- }
}
},
- "find-index": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz",
- "integrity": "sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ=",
- "dev": true
- },
"find-up": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
- "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
"dev": true,
"requires": {
- "path-exists": "^2.0.0",
- "pinkie-promise": "^2.0.0"
+ "locate-path": "^2.0.0"
}
},
- "findup-sync": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-1.0.0.tgz",
- "integrity": "sha1-b35LV7buOkA3tEFOrt6j9Y9x4Ow=",
+ "flat": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz",
+ "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==",
"dev": true,
"requires": {
- "detect-file": "^0.1.0",
- "is-glob": "^2.0.1",
- "micromatch": "^2.3.7",
- "resolve-dir": "^0.1.0"
+ "is-buffer": "~2.0.3"
}
},
- "fined": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/fined/-/fined-1.1.0.tgz",
- "integrity": "sha1-s33IRLdqL15wgeiE98CuNE8VNHY=",
+ "flat-cache": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz",
+ "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==",
"dev": true,
"requires": {
- "expand-tilde": "^2.0.2",
- "is-plain-object": "^2.0.3",
- "object.defaults": "^1.1.0",
- "object.pick": "^1.2.0",
- "parse-filepath": "^1.0.1"
+ "circular-json": "^0.3.1",
+ "graceful-fs": "^4.1.2",
+ "rimraf": "~2.6.2",
+ "write": "^0.2.1"
},
"dependencies": {
- "expand-tilde": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
- "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=",
+ "glob": {
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "rimraf": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+ "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"dev": true,
"requires": {
- "homedir-polyfill": "^1.0.1"
+ "glob": "^7.1.3"
}
}
}
},
- "first-chunk-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz",
- "integrity": "sha1-Wb+1DNkF9g18OUzT2ayqtOatk04=",
- "dev": true
- },
- "flagged-respawn": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-0.3.2.tgz",
- "integrity": "sha1-/xke3c1wiKZ1smEP/8l2vpuAdLU=",
- "dev": true
- },
- "flat-cache": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz",
- "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=",
- "dev": true,
- "requires": {
- "circular-json": "^0.3.1",
- "del": "^2.0.2",
- "graceful-fs": "^4.1.2",
- "write": "^0.2.1"
- }
- },
"follow-redirects": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.2.6.tgz",
- "integrity": "sha512-FrMqZ/FONtHnbqO651UPpfRUVukIEwJhXMfdr/JWAmrDbeYBu773b1J6gdWDyRIj4hvvzQEHoEOTrdR8o6KLYA==",
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
+ "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
"requires": {
- "debug": "^3.1.0"
+ "debug": "=3.1.0"
},
"dependencies": {
"debug": {
@@ -3484,6 +3485,7 @@
"resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz",
"integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=",
"dev": true,
+ "optional": true,
"requires": {
"for-in": "^1.0.1"
}
@@ -3494,12 +3496,12 @@
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
},
"form-data": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz",
- "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=",
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz",
+ "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==",
"requires": {
"asynckit": "^0.4.0",
- "combined-stream": "^1.0.5",
+ "combined-stream": "^1.0.6",
"mime-types": "^2.1.12"
}
},
@@ -3513,36 +3515,33 @@
}
},
"formidable": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.1.1.tgz",
- "integrity": "sha1-lriIb3w8NQi5Mta9cMTTqI818ak="
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz",
+ "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg=="
},
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
},
+ "fragment-cache": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
+ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
+ "dev": true,
+ "requires": {
+ "map-cache": "^0.2.2"
+ }
+ },
"fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
},
- "from": {
- "version": "0.1.7",
- "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
- "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=",
- "dev": true
- },
- "fs-exists-sync": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz",
- "integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=",
- "dev": true
- },
"fs-extra": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.2.tgz",
- "integrity": "sha1-+RcExT0bRh+JNFKwwwfZmXZHq2s=",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.2",
@@ -3551,16 +3550,15 @@
}
},
"fs-readdir-recursive": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz",
- "integrity": "sha1-jNF0XItPiinIyuw5JHaSG6GV9WA=",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
+ "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==",
"dev": true
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
- "dev": true
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"fsevents": {
"version": "1.2.9",
@@ -3582,7 +3580,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
@@ -3603,12 +3602,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -3623,17 +3624,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"core-util-is": {
"version": "1.0.2",
@@ -3750,7 +3754,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"ini": {
"version": "1.3.5",
@@ -3762,6 +3767,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -3776,6 +3782,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -3783,12 +3790,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -3807,6 +3816,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -3817,13 +3827,6 @@
"dev": true,
"optional": true
},
- "nan": {
- "version": "2.14.0",
- "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
- "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==",
- "dev": true,
- "optional": true
- },
"needle": {
"version": "2.3.0",
"bundled": true,
@@ -3894,7 +3897,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -3906,6 +3910,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"wrappy": "1"
}
@@ -3991,7 +3996,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -4027,6 +4033,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -4046,6 +4053,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -4089,36 +4097,31 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
}
}
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
- "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
- "dev": true
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
- "gaze": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz",
- "integrity": "sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8=",
+ "generate-function": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
+ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
"dev": true,
"requires": {
- "globule": "~0.1.0"
+ "is-property": "^1.0.2"
}
},
- "generate-function": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
- "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=",
- "dev": true
- },
"generate-object-property": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
@@ -4128,15 +4131,10 @@
"is-property": "^1.0.0"
}
},
- "generic-pool": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.2.tgz",
- "integrity": "sha1-iGvFvwvrfblugby7oHiBjeWmJoM="
- },
"get-caller-file": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz",
- "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=",
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
},
"get-stream": {
@@ -4145,6 +4143,12 @@
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
"dev": true
},
+ "get-value": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
+ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
+ "dev": true
+ },
"getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
@@ -4157,6 +4161,7 @@
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
"integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
+ "optional": true,
"requires": {
"inflight": "^1.0.4",
"inherits": "2",
@@ -4170,6 +4175,7 @@
"resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz",
"integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=",
"dev": true,
+ "optional": true,
"requires": {
"glob-parent": "^2.0.0",
"is-glob": "^2.0.0"
@@ -4180,219 +4186,30 @@
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
"integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
"dev": true,
+ "optional": true,
"requires": {
"is-glob": "^2.0.0"
}
},
- "glob-stream": {
- "version": "3.1.18",
- "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-3.1.18.tgz",
- "integrity": "sha1-kXCl8St5Awb9/lmPMT+PeVT9FDs=",
- "dev": true,
- "requires": {
- "glob": "^4.3.1",
- "glob2base": "^0.0.12",
- "minimatch": "^2.0.1",
- "ordered-read-streams": "^0.1.0",
- "through2": "^0.6.1",
- "unique-stream": "^1.0.0"
- },
- "dependencies": {
- "glob": {
- "version": "4.5.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz",
- "integrity": "sha1-xstz0yJsHv7wTePFbQEvAzd+4V8=",
- "dev": true,
- "requires": {
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^2.0.1",
- "once": "^1.3.0"
- }
- },
- "minimatch": {
- "version": "2.0.10",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz",
- "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=",
- "dev": true,
- "requires": {
- "brace-expansion": "^1.0.0"
- }
- },
- "readable-stream": {
- "version": "1.0.34",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
- "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
- "dev": true,
- "requires": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.1",
- "isarray": "0.0.1",
- "string_decoder": "~0.10.x"
- }
- },
- "through2": {
- "version": "0.6.5",
- "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
- "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=",
- "dev": true,
- "requires": {
- "readable-stream": ">=1.0.33-1 <1.1.0-0",
- "xtend": ">=4.0.0 <4.1.0-0"
- }
- }
- }
- },
- "glob-watcher": {
- "version": "0.0.6",
- "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-0.0.6.tgz",
- "integrity": "sha1-uVtKjfdLOcgymLDAXJeLTZo7cQs=",
- "dev": true,
- "requires": {
- "gaze": "^0.5.1"
- }
- },
- "glob2base": {
- "version": "0.0.12",
- "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz",
- "integrity": "sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY=",
- "dev": true,
- "requires": {
- "find-index": "^0.1.1"
- }
- },
"global-dirs": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.0.tgz",
- "integrity": "sha1-ENNAOeDfBCcuJizyQiT3IJQ0308=",
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz",
+ "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=",
"dev": true,
"requires": {
"ini": "^1.3.4"
}
},
- "global-modules": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz",
- "integrity": "sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0=",
- "dev": true,
- "requires": {
- "global-prefix": "^0.1.4",
- "is-windows": "^0.2.0"
- }
- },
- "global-prefix": {
- "version": "0.1.5",
- "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz",
- "integrity": "sha1-jTvGuNo8qBEqFg2NSW/wRiv+948=",
- "dev": true,
- "requires": {
- "homedir-polyfill": "^1.0.0",
- "ini": "^1.3.4",
- "is-windows": "^0.2.0",
- "which": "^1.2.12"
- }
- },
"globals": {
"version": "9.18.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
"integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==",
"dev": true
},
- "globby": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz",
- "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=",
- "dev": true,
- "requires": {
- "array-union": "^1.0.1",
- "arrify": "^1.0.0",
- "glob": "^7.0.3",
- "object-assign": "^4.0.1",
- "pify": "^2.0.0",
- "pinkie-promise": "^2.0.0"
- },
- "dependencies": {
- "glob": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
- "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
- "dev": true,
- "requires": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- }
- }
- }
- },
- "globule": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz",
- "integrity": "sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU=",
- "dev": true,
- "requires": {
- "glob": "~3.1.21",
- "lodash": "~1.0.1",
- "minimatch": "~0.2.11"
- },
- "dependencies": {
- "glob": {
- "version": "3.1.21",
- "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz",
- "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=",
- "dev": true,
- "requires": {
- "graceful-fs": "~1.2.0",
- "inherits": "1",
- "minimatch": "~0.2.11"
- }
- },
- "graceful-fs": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz",
- "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=",
- "dev": true
- },
- "inherits": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz",
- "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=",
- "dev": true
- },
- "lodash": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz",
- "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=",
- "dev": true
- },
- "minimatch": {
- "version": "0.2.14",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz",
- "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=",
- "dev": true,
- "requires": {
- "lru-cache": "2",
- "sigmund": "~1.0.0"
- }
- }
- }
- },
- "glogg": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz",
- "integrity": "sha1-f+DxmfV6yQbPUS/urY+Q7kooT8U=",
- "dev": true,
- "requires": {
- "sparkles": "^1.0.0"
- }
- },
- "got": {
- "version": "6.7.1",
- "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz",
- "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=",
+ "got": {
+ "version": "6.7.1",
+ "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz",
+ "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=",
"dev": true,
"requires": {
"create-error-class": "^3.0.0",
@@ -4409,139 +4226,34 @@
}
},
"graceful-fs": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
- "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
+ "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==",
"dev": true
},
"growl": {
- "version": "1.9.2",
- "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz",
- "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=",
+ "version": "1.10.5",
+ "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
+ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
"dev": true
},
- "gulp": {
- "version": "3.9.1",
- "resolved": "https://registry.npmjs.org/gulp/-/gulp-3.9.1.tgz",
- "integrity": "sha1-VxzkWSjdQK9lFPxAEYZgFsE4RbQ=",
- "dev": true,
- "requires": {
- "archy": "^1.0.0",
- "chalk": "^1.0.0",
- "deprecated": "^0.0.1",
- "gulp-util": "^3.0.0",
- "interpret": "^1.0.0",
- "liftoff": "^2.1.0",
- "minimist": "^1.1.0",
- "orchestrator": "^0.3.0",
- "pretty-hrtime": "^1.0.0",
- "semver": "^4.1.0",
- "tildify": "^1.0.0",
- "v8flags": "^2.0.2",
- "vinyl-fs": "^0.3.0"
- },
- "dependencies": {
- "minimist": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
- "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
- "dev": true
- },
- "semver": {
- "version": "4.3.6",
- "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz",
- "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=",
- "dev": true
- }
- }
- },
- "gulp-help": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/gulp-help/-/gulp-help-1.6.1.tgz",
- "integrity": "sha1-Jh2xhuGDl/7z9qLCLpwxW/qIrgw=",
- "dev": true,
- "requires": {
- "chalk": "^1.0.0",
- "object-assign": "^3.0.0"
- },
- "dependencies": {
- "object-assign": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz",
- "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=",
- "dev": true
- }
- }
- },
- "gulp-util": {
- "version": "3.0.8",
- "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz",
- "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=",
- "dev": true,
- "requires": {
- "array-differ": "^1.0.0",
- "array-uniq": "^1.0.2",
- "beeper": "^1.0.0",
- "chalk": "^1.0.0",
- "dateformat": "^2.0.0",
- "fancy-log": "^1.1.0",
- "gulplog": "^1.0.0",
- "has-gulplog": "^0.1.0",
- "lodash._reescape": "^3.0.0",
- "lodash._reevaluate": "^3.0.0",
- "lodash._reinterpolate": "^3.0.0",
- "lodash.template": "^3.0.0",
- "minimist": "^1.1.0",
- "multipipe": "^0.1.2",
- "object-assign": "^3.0.0",
- "replace-ext": "0.0.1",
- "through2": "^2.0.0",
- "vinyl": "^0.5.0"
- },
- "dependencies": {
- "minimist": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
- "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
- "dev": true
- },
- "object-assign": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz",
- "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=",
- "dev": true
- }
- }
- },
- "gulplog": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz",
- "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=",
- "dev": true,
- "requires": {
- "glogg": "^1.0.0"
- }
- },
"handlebars": {
- "version": "4.0.11",
- "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz",
- "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=",
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz",
+ "integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==",
"dev": true,
"requires": {
- "async": "^1.4.0",
+ "neo-async": "^2.6.0",
"optimist": "^0.6.1",
- "source-map": "^0.4.4",
- "uglify-js": "^2.6"
+ "source-map": "^0.6.1",
+ "uglify-js": "^3.1.4"
},
"dependencies": {
"source-map": {
- "version": "0.4.4",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
- "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
- "dev": true,
- "requires": {
- "amdefine": ">=0.0.4"
- }
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
}
}
},
@@ -4557,28 +4269,14 @@
"requires": {
"ajv": "^6.5.5",
"har-schema": "^2.0.0"
- },
- "dependencies": {
- "ajv": {
- "version": "6.6.1",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz",
- "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==",
- "requires": {
- "fast-deep-equal": "^2.0.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- }
- }
}
},
"has": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz",
- "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=",
- "dev": true,
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"requires": {
- "function-bind": "^1.0.2"
+ "function-bind": "^1.1.1"
}
},
"has-ansi": {
@@ -4595,13 +4293,75 @@
"integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=",
"dev": true
},
- "has-gulplog": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz",
- "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=",
+ "has-symbols": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
+ "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q="
+ },
+ "has-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
+ "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
+ "dev": true,
+ "requires": {
+ "get-value": "^2.0.6",
+ "has-values": "^1.0.0",
+ "isobject": "^3.0.0"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ }
+ }
+ },
+ "has-values": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
+ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
"dev": true,
"requires": {
- "sparkles": "^1.0.0"
+ "is-number": "^3.0.0",
+ "kind-of": "^4.0.0"
+ },
+ "dependencies": {
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
+ "dev": true
+ },
+ "is-number": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "kind-of": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
}
},
"hashring": {
@@ -4613,10 +4373,16 @@
"simple-lru-cache": "0.0.x"
}
},
+ "he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true
+ },
"hoek": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz",
- "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ=="
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz",
+ "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA=="
},
"home-or-tmp": {
"version": "2.0.0",
@@ -4628,19 +4394,10 @@
"os-tmpdir": "^1.0.1"
}
},
- "homedir-polyfill": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz",
- "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=",
- "dev": true,
- "requires": {
- "parse-passwd": "^1.0.0"
- }
- },
"hosted-git-info": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz",
- "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==",
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz",
+ "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==",
"dev": true
},
"http-aws-es": {
@@ -4649,14 +4406,22 @@
"integrity": "sha512-5OJVj9/JSNOVFgIOnBK+9fwDePd35PF1odskYjp/aqstuurZy1XdmHoDP+wPE5LH9Pe/TasIJyARyH7aJnLh/A=="
},
"http-errors": {
- "version": "1.6.2",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz",
- "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=",
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
+ "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
"requires": {
- "depd": "1.1.1",
+ "depd": "~1.1.2",
"inherits": "2.0.3",
- "setprototypeof": "1.0.3",
- "statuses": ">= 1.3.1 < 2"
+ "setprototypeof": "1.1.1",
+ "statuses": ">= 1.5.0 < 2",
+ "toidentifier": "1.0.0"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ }
}
},
"http-signature": {
@@ -4669,90 +4434,35 @@
"sshpk": "^1.7.0"
}
},
+ "humanize-ms": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
+ "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=",
+ "requires": {
+ "ms": "^2.0.0"
+ }
+ },
"iconv-lite": {
- "version": "0.4.19",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
- "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ=="
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
},
"idtoken-verifier": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/idtoken-verifier/-/idtoken-verifier-1.2.0.tgz",
- "integrity": "sha512-8jmmFHwdPz8L73zGNAXHHOV9yXNC+Z0TUBN5rafpoaFaLFltlIFr1JkQa3FYAETP23eSsulVw0sBiwrE8jqbUg==",
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/idtoken-verifier/-/idtoken-verifier-1.4.1.tgz",
+ "integrity": "sha512-BoJc00Gj37hrNlx7NYmd8uJFvvC9/FiWDKugDluP4JmgOGT/AfNlPfnRmi9fHEEqSatnIIr3WTyf0dlhHfSHnA==",
"requires": {
"base64-js": "^1.2.0",
"crypto-js": "^3.1.9-1",
"jsbn": "^0.1.0",
- "superagent": "^3.8.2",
+ "promise-polyfill": "^8.1.3",
+ "unfetch": "^4.1.0",
"url-join": "^1.1.0"
},
"dependencies": {
- "debug": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
- "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
- "requires": {
- "ms": "^2.1.1"
- }
- },
- "formidable": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz",
- "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg=="
- },
- "isarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
- },
- "ms": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
- "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
- },
- "process-nextick-args": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
- "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
- },
- "readable-stream": {
- "version": "2.3.6",
- "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
- "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
- "requires": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
- }
- },
- "string_decoder": {
- "version": "1.1.1",
- "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "requires": {
- "safe-buffer": "~5.1.0"
- }
- },
- "superagent": {
- "version": "3.8.3",
- "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz",
- "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==",
- "requires": {
- "component-emitter": "^1.2.0",
- "cookiejar": "^2.1.0",
- "debug": "^3.1.0",
- "extend": "^3.0.0",
- "form-data": "^2.3.1",
- "formidable": "^1.2.0",
- "methods": "^1.1.1",
- "mime": "^1.4.1",
- "qs": "^6.5.1",
- "readable-stream": "^2.3.5"
- }
- },
"url-join": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/url-join/-/url-join-1.1.0.tgz",
@@ -4761,14 +4471,14 @@
}
},
"ieee754": {
- "version": "1.1.8",
- "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz",
- "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q="
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
+ "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
},
"ignore": {
- "version": "3.3.7",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz",
- "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==",
+ "version": "3.3.10",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz",
+ "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==",
"dev": true
},
"ignore-by-default": {
@@ -4804,14 +4514,14 @@
}
},
"inherits": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
- "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"ini": {
- "version": "1.3.4",
- "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz",
- "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=",
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
+ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"dev": true
},
"inquirer": {
@@ -4836,41 +4546,40 @@
}
},
"interpret": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.4.tgz",
- "integrity": "sha1-ggzdWIuGj/sZGoCVBtbJyPISsbA=",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz",
+ "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==",
"dev": true
},
"invariant": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz",
- "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=",
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
"dev": true,
"requires": {
"loose-envify": "^1.0.0"
}
},
- "invert-kv": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
- "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=",
- "dev": true
- },
"ipaddr.js": {
- "version": "1.5.2",
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz",
- "integrity": "sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A="
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
+ "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA=="
},
- "is-absolute": {
- "version": "0.2.6",
- "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.2.6.tgz",
- "integrity": "sha1-IN5p89uULvLYe5wto28XIjWxtes=",
+ "is-accessor-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
"dev": true,
"requires": {
- "is-relative": "^0.2.1",
- "is-windows": "^0.2.0"
+ "kind-of": "^3.0.2"
}
},
+ "is-arguments": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz",
+ "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==",
+ "dev": true
+ },
"is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@@ -4886,31 +4595,76 @@
"binary-extensions": "^1.0.0"
}
},
+ "is-bluebird": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz",
+ "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI="
+ },
"is-buffer": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
- "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
+ "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A=="
},
- "is-builtin-module": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
- "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
+ "is-callable": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
+ "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA=="
+ },
+ "is-ci": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz",
+ "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==",
"dev": true,
"requires": {
- "builtin-modules": "^1.0.0"
+ "ci-info": "^1.5.0"
}
},
- "is-dotfile": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz",
- "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=",
- "dev": true
- },
- "is-equal-shallow": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz",
- "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=",
+ "is-data-descriptor": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
"dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ }
+ },
+ "is-date-object": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
+ "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY="
+ },
+ "is-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+ "dev": true
+ }
+ }
+ },
+ "is-dotfile": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz",
+ "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=",
+ "dev": true,
+ "optional": true
+ },
+ "is-equal-shallow": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz",
+ "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=",
+ "dev": true,
+ "optional": true,
"requires": {
"is-primitive": "^2.0.0"
}
@@ -4925,7 +4679,8 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
"integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
- "dev": true
+ "dev": true,
+ "optional": true
},
"is-finite": {
"version": "1.0.2",
@@ -4945,11 +4700,18 @@
"number-is-nan": "^1.0.0"
}
},
+ "is-generator-function": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz",
+ "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==",
+ "dev": true
+ },
"is-glob": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
"integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
"dev": true,
+ "optional": true,
"requires": {
"is-extglob": "^1.0.0"
}
@@ -4964,14 +4726,21 @@
"is-path-inside": "^1.0.0"
}
},
+ "is-my-ip-valid": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz",
+ "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==",
+ "dev": true
+ },
"is-my-json-valid": {
- "version": "2.16.1",
- "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz",
- "integrity": "sha512-ochPsqWS1WXj8ZnMIV0vnNXooaMhp7cyL4FMSIPKTtnV0Ha/T19G2b9kkhcNsabV9bxYkze7/aLZJb/bYuFduQ==",
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.0.tgz",
+ "integrity": "sha512-XTHBZSIIxNsIsZXg7XB5l8z/OBFosl1Wao4tXLpeC7eKU4Vm/kdop2azkPqULwnfGQjmeDIyey9g7afMMtdWAA==",
"dev": true,
"requires": {
"generate-function": "^2.0.0",
"generate-object-property": "^1.1.0",
+ "is-my-ip-valid": "^1.0.0",
"jsonpointer": "^4.0.0",
"xtend": "^4.0.0"
}
@@ -4987,6 +4756,7 @@
"resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz",
"integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=",
"dev": true,
+ "optional": true,
"requires": {
"kind-of": "^3.0.2"
}
@@ -4997,25 +4767,10 @@
"integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
"dev": true
},
- "is-path-cwd": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
- "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=",
- "dev": true
- },
- "is-path-in-cwd": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz",
- "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=",
- "dev": true,
- "requires": {
- "is-path-inside": "^1.0.0"
- }
- },
"is-path-inside": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz",
- "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
+ "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
"dev": true,
"requires": {
"path-is-inside": "^1.0.1"
@@ -5042,13 +4797,15 @@
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz",
"integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=",
- "dev": true
+ "dev": true,
+ "optional": true
},
"is-primitive": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz",
"integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=",
- "dev": true
+ "dev": true,
+ "optional": true
},
"is-promise": {
"version": "2.1.0",
@@ -5068,28 +4825,24 @@
"integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=",
"dev": true
},
- "is-relative": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.2.1.tgz",
- "integrity": "sha1-0n9MfVFtF1+2ENuEu+7yPDvJeqU=",
- "dev": true,
+ "is-regex": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
+ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
"requires": {
- "is-unc-path": "^0.1.1"
+ "has": "^1.0.1"
}
},
"is-resolvable": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz",
- "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=",
- "dev": true,
- "requires": {
- "tryit": "^1.0.1"
- }
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz",
+ "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==",
+ "dev": true
},
"is-retry-allowed": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz",
- "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz",
+ "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==",
"dev": true
},
"is-stream": {
@@ -5098,20 +4851,19 @@
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
"dev": true
},
+ "is-symbol": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
+ "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
+ "requires": {
+ "has-symbols": "^1.0.0"
+ }
+ },
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
},
- "is-unc-path": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-0.1.2.tgz",
- "integrity": "sha1-arBTpyVzwQJQ/0FqOBTDUXivObk=",
- "dev": true,
- "requires": {
- "unc-path-regex": "^0.1.0"
- }
- },
"is-utf8": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
@@ -5119,9 +4871,9 @@
"dev": true
},
"is-windows": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz",
- "integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
"dev": true
},
"isarray": {
@@ -5145,6 +4897,7 @@
"resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
"integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
"dev": true,
+ "optional": true,
"requires": {
"isarray": "1.0.0"
},
@@ -5153,7 +4906,8 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
- "dev": true
+ "dev": true,
+ "optional": true
}
}
},
@@ -5163,14 +4917,14 @@
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
},
"istanbul": {
- "version": "1.0.0-alpha.2",
- "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-1.0.0-alpha.2.tgz",
- "integrity": "sha1-BglrwI6Yuq10Sq5Gli2N+frGPQg=",
+ "version": "1.1.0-alpha.1",
+ "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-1.1.0-alpha.1.tgz",
+ "integrity": "sha1-eBeVZWAYohdMX2DzZ+5dNhy1e3c=",
"dev": true,
"requires": {
"abbrev": "1.0.x",
"async": "1.x",
- "istanbul-api": "^1.0.0-alpha",
+ "istanbul-api": "^1.1.0-alpha",
"js-yaml": "3.x",
"mkdirp": "0.5.x",
"nopt": "3.x",
@@ -5179,54 +4933,54 @@
}
},
"istanbul-api": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.2.1.tgz",
- "integrity": "sha512-oFCwXvd65amgaPCzqrR+a2XjanS1MvpXN6l/MlMUTv6uiA1NOgGX+I0uyq8Lg3GDxsxPsaP1049krz3hIJ5+KA==",
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.7.tgz",
+ "integrity": "sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA==",
"dev": true,
"requires": {
"async": "^2.1.4",
"fileset": "^2.0.2",
- "istanbul-lib-coverage": "^1.1.1",
- "istanbul-lib-hook": "^1.1.0",
- "istanbul-lib-instrument": "^1.9.1",
- "istanbul-lib-report": "^1.1.2",
- "istanbul-lib-source-maps": "^1.2.2",
- "istanbul-reports": "^1.1.3",
+ "istanbul-lib-coverage": "^1.2.1",
+ "istanbul-lib-hook": "^1.2.2",
+ "istanbul-lib-instrument": "^1.10.2",
+ "istanbul-lib-report": "^1.1.5",
+ "istanbul-lib-source-maps": "^1.2.6",
+ "istanbul-reports": "^1.5.1",
"js-yaml": "^3.7.0",
"mkdirp": "^0.5.1",
"once": "^1.4.0"
},
"dependencies": {
"async": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz",
- "integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==",
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
+ "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"dev": true,
"requires": {
- "lodash": "^4.14.0"
+ "lodash": "^4.17.14"
}
}
}
},
"istanbul-lib-coverage": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz",
- "integrity": "sha512-0+1vDkmzxqJIn5rcoEqapSB4DmPxE31EtI2dF2aCkV5esN9EWHxZ0dwgDClivMXJqE7zaYQxq30hj5L0nlTN5Q==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz",
+ "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==",
"dev": true
},
"istanbul-lib-hook": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.1.0.tgz",
- "integrity": "sha512-U3qEgwVDUerZ0bt8cfl3dSP3S6opBoOtk3ROO5f2EfBr/SRiD9FQqzwaZBqFORu8W7O0EXpai+k7kxHK13beRg==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz",
+ "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==",
"dev": true,
"requires": {
"append-transform": "^0.4.0"
}
},
"istanbul-lib-instrument": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.1.tgz",
- "integrity": "sha512-RQmXeQ7sphar7k7O1wTNzVczF9igKpaeGQAG9qR2L+BS4DCJNTI9nytRmIVYevwO0bbq+2CXvJmYDuz0gMrywA==",
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz",
+ "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==",
"dev": true,
"requires": {
"babel-generator": "^6.18.0",
@@ -5234,17 +4988,17 @@
"babel-traverse": "^6.18.0",
"babel-types": "^6.18.0",
"babylon": "^6.18.0",
- "istanbul-lib-coverage": "^1.1.1",
+ "istanbul-lib-coverage": "^1.2.1",
"semver": "^5.3.0"
}
},
"istanbul-lib-report": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.2.tgz",
- "integrity": "sha512-UTv4VGx+HZivJQwAo1wnRwe1KTvFpfi/NYwN7DcsrdzMXwpRT/Yb6r4SBPoHWj4VuQPakR32g4PUUeyKkdDkBA==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz",
+ "integrity": "sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==",
"dev": true,
"requires": {
- "istanbul-lib-coverage": "^1.1.1",
+ "istanbul-lib-coverage": "^1.2.1",
"mkdirp": "^0.5.1",
"path-parse": "^1.0.5",
"supports-color": "^3.1.2"
@@ -5262,31 +5016,31 @@
}
},
"istanbul-lib-source-maps": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.2.tgz",
- "integrity": "sha512-8BfdqSfEdtip7/wo1RnrvLpHVEd8zMZEDmOFEnpC6dg0vXflHt9nvoAyQUzig2uMSXfF2OBEYBV3CVjIL9JvaQ==",
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz",
+ "integrity": "sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==",
"dev": true,
"requires": {
"debug": "^3.1.0",
- "istanbul-lib-coverage": "^1.1.1",
+ "istanbul-lib-coverage": "^1.2.1",
"mkdirp": "^0.5.1",
"rimraf": "^2.6.1",
"source-map": "^0.5.3"
},
"dependencies": {
"debug": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
- "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"dev": true,
"requires": {
- "ms": "2.0.0"
+ "ms": "^2.1.1"
}
},
"glob": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
- "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
@@ -5297,50 +5051,32 @@
"path-is-absolute": "^1.0.0"
}
},
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
"rimraf": {
- "version": "2.6.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
- "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"requires": {
- "glob": "^7.0.5"
+ "glob": "^7.1.3"
}
}
}
},
"istanbul-reports": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.1.3.tgz",
- "integrity": "sha512-ZEelkHh8hrZNI5xDaKwPMFwDsUf5wIEI2bXAFGp1e6deR2mnEKBPhLJEgr4ZBt8Gi6Mj38E/C8kcy9XLggVO2Q==",
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz",
+ "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==",
"dev": true,
"requires": {
"handlebars": "^4.0.3"
}
},
- "jade": {
- "version": "0.26.3",
- "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz",
- "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=",
- "dev": true,
- "requires": {
- "commander": "0.6.1",
- "mkdirp": "0.3.0"
- },
- "dependencies": {
- "commander": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz",
- "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=",
- "dev": true
- },
- "mkdirp": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz",
- "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=",
- "dev": true
- }
- }
- },
"jmespath": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz",
@@ -5363,26 +5099,48 @@
"integrity": "sha1-uEF7dQZho5K+4sJTfGiyqdSXfNU="
},
"js-beautify": {
- "version": "1.7.4",
- "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.7.4.tgz",
- "integrity": "sha512-6YX1g+lIl0/JDxjFFbgj7fz6i0bWFa2Hdc7PfGqFhynaEiYe1NJ3R1nda0VGaRiGU82OllR+EGDoWFpGr3k5Kg==",
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.10.2.tgz",
+ "integrity": "sha512-ZtBYyNUYJIsBWERnQP0rPN9KjkrDfJcMjuVGcvXOUJrD1zmOGwhRwQ4msG+HJ+Ni/FA7+sRQEMYVzdTQDvnzvQ==",
"dev": true,
"requires": {
- "config-chain": "~1.1.5",
- "editorconfig": "^0.13.2",
- "mkdirp": "~0.5.0",
- "nopt": "~3.0.1"
+ "config-chain": "^1.1.12",
+ "editorconfig": "^0.15.3",
+ "glob": "^7.1.3",
+ "mkdirp": "~0.5.1",
+ "nopt": "~4.0.1"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "nopt": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
+ "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
+ "dev": true,
+ "requires": {
+ "abbrev": "1",
+ "osenv": "^0.1.4"
+ }
+ }
}
},
"js-cookie": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.0.tgz",
- "integrity": "sha1-Gywnmm7s44ChIWi5JIUmWzWx7/s="
- },
- "js-string-escape": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz",
- "integrity": "sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8="
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz",
+ "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ=="
},
"js-tokens": {
"version": "3.0.2",
@@ -5391,9 +5149,9 @@
"dev": true
},
"js-yaml": {
- "version": "3.10.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz",
- "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==",
+ "version": "3.13.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+ "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
@@ -5436,9 +5194,12 @@
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
},
"json5": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz",
- "integrity": "sha1-BUNS5MTIDIbAkjh31EneF2pzLI0="
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+ "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "requires": {
+ "minimist": "^1.2.0"
+ }
},
"jsonfile": {
"version": "4.0.0",
@@ -5462,11 +5223,11 @@
"dev": true
},
"jsonwebtoken": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz",
- "integrity": "sha512-oge/hvlmeJCH+iIz1DwcO7vKPkNGJHhgkspk8OH3VKlw+mbi42WtD4ig1+VXRln765vxptAv+xT26Fd3cteqag==",
+ "version": "8.5.1",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
+ "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
"requires": {
- "jws": "^3.1.5",
+ "jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
@@ -5474,13 +5235,14 @@
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
- "ms": "^2.1.1"
+ "ms": "^2.1.1",
+ "semver": "^5.6.0"
},
"dependencies": {
"ms": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
- "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
@@ -5496,34 +5258,42 @@
}
},
"jwa": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz",
- "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==",
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+ "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
"requires": {
"buffer-equal-constant-time": "1.0.1",
- "ecdsa-sig-formatter": "1.0.10",
+ "ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"jwks-rsa": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-1.3.0.tgz",
- "integrity": "sha512-9q+d5VffK/FvFAjuXoddrq7zQybFSINV4mcwJJExGKXGyjWWpTt3vsn/aX33aB0heY02LK0qSyicdtRK0gVTig==",
- "requires": {
- "@types/express-jwt": "0.0.34",
- "debug": "^2.2.0",
- "limiter": "^1.1.0",
- "lru-memoizer": "^1.6.0",
- "ms": "^2.0.0",
- "request": "^2.73.0"
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-1.6.0.tgz",
+ "integrity": "sha512-gLhpd7Ka7Jy8ofm9OVj0PFPtSdx3+W2dncF3UCA1wDTAbvfiB1GhHbbyQlz8bqLF5+rge7pgD/DALRfgZi8Fgg==",
+ "requires": {
+ "@types/express-jwt": "0.0.42",
+ "debug": "^2.6.9",
+ "jsonwebtoken": "^8.5.1",
+ "limiter": "^1.1.4",
+ "lru-memoizer": "^1.12.0",
+ "ms": "^2.1.1",
+ "request": "^2.88.0"
+ },
+ "dependencies": {
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ }
}
},
"jws": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz",
- "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==",
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"requires": {
- "jwa": "^1.1.5",
+ "jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
@@ -5534,6 +5304,14 @@
"dev": true,
"requires": {
"is-buffer": "^1.1.5"
+ },
+ "dependencies": {
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
+ "dev": true
+ }
}
},
"latest-version": {
@@ -5551,22 +5329,6 @@
"integrity": "sha1-fQ0U7vPslwLG8wxg6oHxqNP5APs=",
"dev": true
},
- "lazy-cache": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
- "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=",
- "dev": true,
- "optional": true
- },
- "lcid": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
- "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
- "dev": true,
- "requires": {
- "invert-kv": "^1.0.0"
- }
- },
"le_node": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/le_node/-/le_node-1.8.0.tgz",
@@ -5587,7 +5349,7 @@
},
"semver": {
"version": "5.1.0",
- "resolved": "http://registry.npmjs.org/semver/-/semver-5.1.0.tgz",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.1.0.tgz",
"integrity": "sha1-hfLPhVBGXE3wAM99hvawVBBqueU="
}
}
@@ -5609,58 +5371,12 @@
"requires": {
"bindings": "1.5.0",
"nan": "^2.14.0"
- },
- "dependencies": {
- "bindings": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
- "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
- "requires": {
- "file-uri-to-path": "1.0.0"
- }
- },
- "nan": {
- "version": "2.14.0",
- "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
- "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg=="
- }
- }
- },
- "liftoff": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.3.0.tgz",
- "integrity": "sha1-qY8v9nGD2Lp8+soQVIvX/wVQs4U=",
- "dev": true,
- "requires": {
- "extend": "^3.0.0",
- "findup-sync": "^0.4.2",
- "fined": "^1.0.1",
- "flagged-respawn": "^0.3.2",
- "lodash.isplainobject": "^4.0.4",
- "lodash.isstring": "^4.0.1",
- "lodash.mapvalues": "^4.4.0",
- "rechoir": "^0.6.2",
- "resolve": "^1.1.7"
- },
- "dependencies": {
- "findup-sync": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.4.3.tgz",
- "integrity": "sha1-QAQ5Kee8YK3wt/SCfExudaDeyhI=",
- "dev": true,
- "requires": {
- "detect-file": "^0.1.0",
- "is-glob": "^2.0.1",
- "micromatch": "^2.3.7",
- "resolve-dir": "^0.1.0"
- }
- }
}
},
"limiter": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.3.tgz",
- "integrity": "sha512-zrycnIMsLw/3ZxTbW7HCez56rcFGecWTx5OZNplzcXUUmJLmoYArC6qdJzmAN5BWiNXGcpjhF9RQ1HSv5zebEw=="
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.4.tgz",
+ "integrity": "sha512-XCpr5bElgDI65vVgstP8TWjv6/QKWm9GU5UG0Pr5sLQ3QLo8NVKsioe+Jed5/3vFOe3IQuqE7DKwTvKQkjTHvg=="
},
"load-json-file": {
"version": "2.0.0",
@@ -5682,14 +5398,6 @@
"requires": {
"p-locate": "^2.0.0",
"path-exists": "^3.0.0"
- },
- "dependencies": {
- "path-exists": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
- "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
- "dev": true
- }
}
},
"lock": {
@@ -5698,227 +5406,89 @@
"integrity": "sha1-/sfervF+fDoKVeHaBCgD4l2RdF0="
},
"lodash": {
- "version": "4.17.4",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
- "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
+ "version": "4.17.15",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
},
- "lodash-compat": {
- "version": "3.10.2",
- "resolved": "https://registry.npmjs.org/lodash-compat/-/lodash-compat-3.10.2.tgz",
- "integrity": "sha1-xpQBKKnTD46QLNLPmf0Muk7PwYM="
+ "lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
},
- "lodash._baseassign": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz",
- "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=",
- "dev": true,
- "requires": {
- "lodash._basecopy": "^3.0.0",
- "lodash.keys": "^3.0.0"
- }
+ "lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
},
- "lodash._basecopy": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz",
- "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=",
- "dev": true
+ "lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M="
},
- "lodash._basetostring": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz",
- "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=",
- "dev": true
+ "lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
},
- "lodash._basevalues": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz",
- "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=",
- "dev": true
+ "lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
},
- "lodash._bindcallback": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz",
- "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=",
- "dev": true
+ "lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
},
- "lodash._createassigner": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz",
- "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=",
+ "lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
+ },
+ "log-symbols": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
+ "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==",
"dev": true,
"requires": {
- "lodash._bindcallback": "^3.0.0",
- "lodash._isiterateecall": "^3.0.0",
- "lodash.restparam": "^3.0.0"
- }
- },
- "lodash._getnative": {
- "version": "3.9.1",
- "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz",
- "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=",
- "dev": true
- },
- "lodash._isiterateecall": {
- "version": "3.0.9",
- "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz",
- "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=",
- "dev": true
- },
- "lodash._reescape": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz",
- "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=",
- "dev": true
- },
- "lodash._reevaluate": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz",
- "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=",
- "dev": true
- },
- "lodash._reinterpolate": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
- "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=",
- "dev": true
- },
- "lodash._root": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz",
- "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=",
- "dev": true
- },
- "lodash.assign": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz",
- "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=",
- "dev": true,
- "requires": {
- "lodash._baseassign": "^3.0.0",
- "lodash._createassigner": "^3.0.0",
- "lodash.keys": "^3.0.0"
- }
- },
- "lodash.cond": {
- "version": "4.5.2",
- "resolved": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz",
- "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=",
- "dev": true
- },
- "lodash.defaults": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-3.1.2.tgz",
- "integrity": "sha1-xzCLGNv4vJNy1wGnNJPGEZK9Liw=",
- "dev": true,
- "requires": {
- "lodash.assign": "^3.0.0",
- "lodash.restparam": "^3.0.0"
- }
- },
- "lodash.escape": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz",
- "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=",
- "dev": true,
- "requires": {
- "lodash._root": "^3.0.0"
- }
- },
- "lodash.includes": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
- "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
- },
- "lodash.isarguments": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
- "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=",
- "dev": true
- },
- "lodash.isarray": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz",
- "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=",
- "dev": true
- },
- "lodash.isboolean": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
- "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
- },
- "lodash.isinteger": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
- "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M="
- },
- "lodash.isnumber": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
- "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
- },
- "lodash.isplainobject": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
- "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
- },
- "lodash.isstring": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
- "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
- },
- "lodash.keys": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
- "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=",
- "dev": true,
- "requires": {
- "lodash._getnative": "^3.0.0",
- "lodash.isarguments": "^3.0.0",
- "lodash.isarray": "^3.0.0"
- }
- },
- "lodash.mapvalues": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz",
- "integrity": "sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw=",
- "dev": true
- },
- "lodash.once": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
- "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
- },
- "lodash.restparam": {
- "version": "3.6.1",
- "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz",
- "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=",
- "dev": true
- },
- "lodash.template": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz",
- "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=",
- "dev": true,
- "requires": {
- "lodash._basecopy": "^3.0.0",
- "lodash._basetostring": "^3.0.0",
- "lodash._basevalues": "^3.0.0",
- "lodash._isiterateecall": "^3.0.0",
- "lodash._reinterpolate": "^3.0.0",
- "lodash.escape": "^3.0.0",
- "lodash.keys": "^3.0.0",
- "lodash.restparam": "^3.0.0",
- "lodash.templatesettings": "^3.0.0"
- }
- },
- "lodash.templatesettings": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz",
- "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=",
- "dev": true,
- "requires": {
- "lodash._reinterpolate": "^3.0.0",
- "lodash.escape": "^3.0.0"
+ "chalk": "^2.0.1"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
}
},
"lolex": {
@@ -5928,57 +5498,43 @@
"dev": true
},
"long": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz",
- "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s="
- },
- "longest": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
- "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=",
- "dev": true
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
+ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
},
"loose-envify": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
- "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"requires": {
- "js-tokens": "^3.0.0"
+ "js-tokens": "^3.0.0 || ^4.0.0"
}
},
"lowercase-keys": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz",
- "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
+ "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==",
"dev": true
},
"lru-cache": {
- "version": "2.7.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz",
- "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=",
- "dev": true
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz",
+ "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=",
+ "requires": {
+ "pseudomap": "^1.0.1",
+ "yallist": "^2.0.0"
+ }
},
"lru-memoizer": {
- "version": "1.12.0",
- "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-1.12.0.tgz",
- "integrity": "sha1-7+ZXBsyKnMZT+A8NWm6jitlQ41I=",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-1.13.0.tgz",
+ "integrity": "sha512-q0wMolfI7yimhZ36kBAfMLOIuDBpRkieN9do0YPjSzCaiy6r73s8wOEq7Ue/B95VSRbXzfnOr1O1QdJc5UIqaw==",
"requires": {
"lock": "~0.1.2",
"lodash": "^4.17.4",
"lru-cache": "~4.0.0",
"very-fast-args": "^1.1.0"
- },
- "dependencies": {
- "lru-cache": {
- "version": "4.0.2",
- "resolved": "http://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz",
- "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=",
- "requires": {
- "pseudomap": "^1.0.1",
- "yallist": "^2.0.0"
- }
- }
}
},
"lru-queue": {
@@ -5991,9 +5547,9 @@
}
},
"make-dir": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.1.0.tgz",
- "integrity": "sha512-0Pkui4wLJ7rxvmfUvs87skoEaxmu0hCUApF8nonzpl7q//FWp9zu8W61Scz4sd/kUiqDxvUhtoam2efDyiBzcA==",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
+ "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
"dev": true,
"requires": {
"pify": "^3.0.0"
@@ -6013,40 +5569,41 @@
"integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
"dev": true
},
- "map-stream": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz",
- "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=",
- "dev": true
+ "map-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
+ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
+ "dev": true,
+ "requires": {
+ "object-visit": "^1.0.0"
+ }
+ },
+ "math-random": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz",
+ "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==",
+ "dev": true,
+ "optional": true
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
- "mem": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz",
- "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=",
- "dev": true,
- "requires": {
- "mimic-fn": "^1.0.0"
- }
- },
"memoizee": {
- "version": "0.4.11",
- "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.11.tgz",
- "integrity": "sha1-vemBdmPJ5A/bKk6hw2cpYIeujI8=",
+ "version": "0.4.14",
+ "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz",
+ "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==",
"dev": true,
"requires": {
"d": "1",
- "es5-ext": "^0.10.30",
+ "es5-ext": "^0.10.45",
"es6-weak-map": "^2.0.2",
"event-emitter": "^0.3.5",
"is-promise": "^2.1",
"lru-queue": "0.1",
"next-tick": "1",
- "timers-ext": "^0.1.2"
+ "timers-ext": "^0.1.5"
}
},
"memwatch-next": {
@@ -6084,6 +5641,7 @@
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz",
"integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=",
"dev": true,
+ "optional": true,
"requires": {
"arr-diff": "^2.0.0",
"array-unique": "^0.2.1",
@@ -6106,29 +5664,23 @@
"integrity": "sha1-bMWtOGJByrjniv+WT4cCjuyS2sU="
},
"mime": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
- "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
},
"mime-db": {
- "version": "1.30.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz",
- "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE="
+ "version": "1.40.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
+ "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
},
"mime-types": {
- "version": "2.1.17",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz",
- "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=",
+ "version": "2.1.24",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
+ "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
"requires": {
- "mime-db": "~1.30.0"
+ "mime-db": "1.40.0"
}
},
- "mimic-fn": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz",
- "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=",
- "dev": true
- },
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
@@ -6138,9 +5690,30 @@
}
},
"minimist": {
- "version": "0.0.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
- "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+ },
+ "mixin-deep": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
+ "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
+ "dev": true,
+ "requires": {
+ "for-in": "^1.0.2",
+ "is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "dev": true,
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
},
"mkdirp": {
"version": "0.5.1",
@@ -6148,90 +5721,144 @@
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"requires": {
"minimist": "0.0.8"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+ }
}
},
"mocha": {
- "version": "2.5.3",
- "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.5.3.tgz",
- "integrity": "sha1-FhvlvetJZ3HrmzV0UFC2IrWu/Fg=",
- "dev": true,
- "requires": {
- "commander": "2.3.0",
- "debug": "2.2.0",
- "diff": "1.4.0",
- "escape-string-regexp": "1.0.2",
- "glob": "3.2.11",
- "growl": "1.9.2",
- "jade": "0.26.3",
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.2.tgz",
+ "integrity": "sha512-FgDS9Re79yU1xz5d+C4rv1G7QagNGHZ+iXF81hO8zY35YZZcLEsJVfFolfsqKFWunATEvNzMK0r/CwWd/szO9A==",
+ "dev": true,
+ "requires": {
+ "ansi-colors": "3.2.3",
+ "browser-stdout": "1.3.1",
+ "debug": "3.2.6",
+ "diff": "3.5.0",
+ "escape-string-regexp": "1.0.5",
+ "find-up": "3.0.0",
+ "glob": "7.1.3",
+ "growl": "1.10.5",
+ "he": "1.2.0",
+ "js-yaml": "3.13.1",
+ "log-symbols": "2.2.0",
+ "minimatch": "3.0.4",
"mkdirp": "0.5.1",
- "supports-color": "1.2.0",
- "to-iso-string": "0.0.2"
+ "ms": "2.1.1",
+ "node-environment-flags": "1.0.5",
+ "object.assign": "4.1.0",
+ "strip-json-comments": "2.0.1",
+ "supports-color": "6.0.0",
+ "which": "1.3.1",
+ "wide-align": "1.1.3",
+ "yargs": "13.3.0",
+ "yargs-parser": "13.1.1",
+ "yargs-unparser": "1.6.0"
},
"dependencies": {
- "commander": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz",
- "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=",
- "dev": true
- },
"debug": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
- "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"dev": true,
"requires": {
- "ms": "0.7.1"
+ "ms": "^2.1.1"
}
},
- "escape-string-regexp": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz",
- "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=",
- "dev": true
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^3.0.0"
+ }
},
"glob": {
- "version": "3.2.11",
- "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz",
- "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=",
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
+ "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
"dev": true,
"requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
"inherits": "2",
- "minimatch": "0.3"
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
}
},
- "minimatch": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz",
- "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=",
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"dev": true,
"requires": {
- "lru-cache": "2",
- "sigmund": "~1.0.0"
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
}
},
"ms": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
- "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
"dev": true
},
- "supports-color": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz",
- "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=",
+ "p-limit": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz",
+ "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.0.0"
+ }
+ },
+ "p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
+ },
+ "supports-color": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz",
+ "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
}
}
},
"moment": {
- "version": "2.22.2",
- "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
- "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y="
+ "version": "2.24.0",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
+ "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
},
"moment-timezone": {
- "version": "0.5.14",
- "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.14.tgz",
- "integrity": "sha1-TrOP+VOLgBCLpGekWPPtQmjM/LE=",
+ "version": "0.5.27",
+ "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.27.tgz",
+ "integrity": "sha512-EIKQs7h5sAsjhPCqN6ggx6cEbs94GK050254TIJySD1bzoM5JTYDwAU1IoVOeTOL6Gm27kYJ51/uuvq1kIlrbw==",
"requires": {
"moment": ">= 2.9.0"
}
@@ -6241,15 +5868,6 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
- "multipipe": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz",
- "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=",
- "dev": true,
- "requires": {
- "duplexer2": "0.0.2"
- }
- },
"murmur-hash-js": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/murmur-hash-js/-/murmur-hash-js-1.0.0.tgz",
@@ -6273,15 +5891,48 @@
}
},
"nan": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz",
- "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY="
- },
- "natives": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.0.tgz",
- "integrity": "sha1-6f+EFBimsux6SV6TmYT3jxY+bjE=",
- "dev": true
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
+ "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg=="
+ },
+ "nanomatch": {
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
+ "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
+ "dev": true,
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "fragment-cache": "^0.2.1",
+ "is-windows": "^1.0.2",
+ "kind-of": "^6.0.2",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "arr-diff": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+ "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
+ "dev": true
+ },
+ "array-unique": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
+ "dev": true
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
+ "dev": true
+ }
+ }
},
"natural-compare": {
"version": "1.4.0",
@@ -6296,9 +5947,15 @@
"optional": true
},
"negotiator": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
- "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+ "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
+ },
+ "neo-async": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
+ "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==",
+ "dev": true
},
"next-tick": {
"version": "1.0.0",
@@ -6315,45 +5972,461 @@
}
},
"no-kafka": {
- "version": "3.2.10",
- "resolved": "https://registry.npmjs.org/no-kafka/-/no-kafka-3.2.10.tgz",
- "integrity": "sha1-0sq8QwZbSS24wVyiOK6V8WgIGvU=",
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/no-kafka/-/no-kafka-3.4.3.tgz",
+ "integrity": "sha512-hYnkg1OWVdaxORdzVvdQ4ueWYpf7IICObPzd24BBiDyVG5219VkUnRxSH9wZmisFb6NpgABzlSIL1pIZaCKmXg==",
"requires": {
"@types/bluebird": "3.5.0",
"@types/lodash": "^4.14.55",
- "bin-protocol": "^3.0.4",
+ "bin-protocol": "^3.1.1",
"bluebird": "^3.3.3",
"buffer-crc32": "^0.2.5",
"hashring": "^3.2.0",
- "lodash": "=4.17.5",
+ "lodash": "=4.17.11",
"murmur-hash-js": "^1.0.0",
"nice-simple-logger": "^1.0.1",
"wrr-pool": "^1.0.3"
},
"dependencies": {
"lodash": {
- "version": "4.17.5",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz",
- "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw=="
+ "version": "4.17.11",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
+ "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
}
}
},
+ "node-environment-flags": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz",
+ "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==",
+ "dev": true,
+ "requires": {
+ "object.getownpropertydescriptors": "^2.0.3",
+ "semver": "^5.7.0"
+ }
+ },
"nodemon": {
- "version": "1.12.1",
- "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.12.1.tgz",
- "integrity": "sha1-mWpW3EnZ8Wu/G3ik3gjxNjSzh40=",
+ "version": "1.19.4",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.4.tgz",
+ "integrity": "sha512-VGPaqQBNk193lrJFotBU8nvWZPqEZY2eIzymy2jjY0fJ9qIsxA0sxQ8ATPl0gZC645gijYEc1jtZvpS8QWzJGQ==",
"dev": true,
"requires": {
- "chokidar": "^1.7.0",
- "debug": "^2.6.8",
- "es6-promise": "^3.3.1",
+ "chokidar": "^2.1.8",
+ "debug": "^3.2.6",
"ignore-by-default": "^1.0.1",
- "lodash.defaults": "^3.1.2",
"minimatch": "^3.0.4",
- "ps-tree": "^1.1.0",
+ "pstree.remy": "^1.1.7",
+ "semver": "^5.7.1",
+ "supports-color": "^5.5.0",
"touch": "^3.1.0",
- "undefsafe": "0.0.3",
- "update-notifier": "^2.2.0"
+ "undefsafe": "^2.0.2",
+ "update-notifier": "^2.5.0"
+ },
+ "dependencies": {
+ "anymatch": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
+ "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
+ "dev": true,
+ "requires": {
+ "micromatch": "^3.1.4",
+ "normalize-path": "^2.1.1"
+ },
+ "dependencies": {
+ "normalize-path": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
+ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+ "dev": true,
+ "requires": {
+ "remove-trailing-separator": "^1.0.1"
+ }
+ }
+ }
+ },
+ "arr-diff": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+ "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
+ "dev": true
+ },
+ "array-unique": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
+ "dev": true
+ },
+ "braces": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+ "dev": true,
+ "requires": {
+ "arr-flatten": "^1.1.0",
+ "array-unique": "^0.3.2",
+ "extend-shallow": "^2.0.1",
+ "fill-range": "^4.0.0",
+ "isobject": "^3.0.1",
+ "repeat-element": "^1.1.2",
+ "snapdragon": "^0.8.1",
+ "snapdragon-node": "^2.0.1",
+ "split-string": "^3.0.2",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "chokidar": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
+ "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
+ "dev": true,
+ "requires": {
+ "anymatch": "^2.0.0",
+ "async-each": "^1.0.1",
+ "braces": "^2.3.2",
+ "fsevents": "^1.2.7",
+ "glob-parent": "^3.1.0",
+ "inherits": "^2.0.3",
+ "is-binary-path": "^1.0.0",
+ "is-glob": "^4.0.0",
+ "normalize-path": "^3.0.0",
+ "path-is-absolute": "^1.0.0",
+ "readdirp": "^2.2.1",
+ "upath": "^1.1.1"
+ }
+ },
+ "debug": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ },
+ "dependencies": {
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ }
+ }
+ },
+ "expand-brackets": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+ "dev": true,
+ "requires": {
+ "debug": "^2.3.3",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "posix-character-classes": "^0.1.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-data-descriptor": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ }
+ },
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+ "dev": true
+ }
+ }
+ },
+ "extglob": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+ "dev": true,
+ "requires": {
+ "array-unique": "^0.3.2",
+ "define-property": "^1.0.0",
+ "expand-brackets": "^2.1.4",
+ "extend-shallow": "^2.0.1",
+ "fragment-cache": "^0.2.1",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "fill-range": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1",
+ "to-regex-range": "^2.1.0"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "glob-parent": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+ "dev": true,
+ "requires": {
+ "is-glob": "^3.1.0",
+ "path-dirname": "^1.0.0"
+ },
+ "dependencies": {
+ "is-glob": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.0"
+ }
+ }
+ }
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
+ "dev": true
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-number": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
+ "dev": true
+ },
+ "micromatch": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+ "dev": true,
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "braces": "^2.3.1",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "extglob": "^2.0.4",
+ "fragment-cache": "^0.2.1",
+ "kind-of": "^6.0.2",
+ "nanomatch": "^1.2.9",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.2"
+ }
+ },
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
}
},
"nopt": {
@@ -6366,13 +6439,13 @@
}
},
"normalize-package-data": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
- "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
"dev": true,
"requires": {
"hosted-git-info": "^2.1.4",
- "is-builtin-module": "^1.0.0",
+ "resolve": "^1.10.0",
"semver": "2 || 3 || 4 || 5",
"validate-npm-package-license": "^3.0.1"
}
@@ -6382,6 +6455,7 @@
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
"integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
"dev": true,
+ "optional": true,
"requires": {
"remove-trailing-separator": "^1.0.1"
}
@@ -6411,27 +6485,47 @@
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
- "object.defaults": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz",
- "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=",
+ "object-copy": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
+ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
"dev": true,
"requires": {
- "array-each": "^1.0.1",
- "array-slice": "^1.0.0",
- "for-own": "^1.0.0",
- "isobject": "^3.0.0"
+ "copy-descriptor": "^0.1.0",
+ "define-property": "^0.2.5",
+ "kind-of": "^3.0.3"
},
"dependencies": {
- "for-own": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz",
- "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=",
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
"dev": true,
"requires": {
- "for-in": "^1.0.1"
+ "is-descriptor": "^0.1.0"
}
- },
+ }
+ }
+ },
+ "object-inspect": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz",
+ "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ=="
+ },
+ "object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
+ },
+ "object-visit": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
+ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.0"
+ },
+ "dependencies": {
"isobject": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
@@ -6440,11 +6534,45 @@
}
}
},
+ "object.assign": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
+ "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.2",
+ "function-bind": "^1.1.1",
+ "has-symbols": "^1.0.0",
+ "object-keys": "^1.0.11"
+ }
+ },
+ "object.entries": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz",
+ "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.12.0",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3"
+ }
+ },
+ "object.getownpropertydescriptors": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz",
+ "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=",
+ "requires": {
+ "define-properties": "^1.1.2",
+ "es-abstract": "^1.5.1"
+ }
+ },
"object.omit": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz",
"integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=",
"dev": true,
+ "optional": true,
"requires": {
"for-own": "^0.1.4",
"is-extendable": "^0.1.1"
@@ -6467,6 +6595,18 @@
}
}
},
+ "object.values": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz",
+ "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.12.0",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3"
+ }
+ },
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@@ -6476,9 +6616,9 @@
}
},
"on-headers": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz",
- "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c="
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+ "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
},
"once": {
"version": "1.4.0",
@@ -6504,6 +6644,12 @@
"wordwrap": "~0.0.2"
},
"dependencies": {
+ "minimist": {
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
+ "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=",
+ "dev": true
+ },
"wordwrap": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
@@ -6513,51 +6659,24 @@
}
},
"optionator": {
- "version": "0.8.2",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
- "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
+ "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
"dev": true,
"requires": {
"deep-is": "~0.1.3",
- "fast-levenshtein": "~2.0.4",
+ "fast-levenshtein": "~2.0.6",
"levn": "~0.3.0",
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2",
- "wordwrap": "~1.0.0"
- }
- },
- "orchestrator": {
- "version": "0.3.8",
- "resolved": "https://registry.npmjs.org/orchestrator/-/orchestrator-0.3.8.tgz",
- "integrity": "sha1-FOfp4nZPcxX7rBhOUGx6pt+UrX4=",
- "dev": true,
- "requires": {
- "end-of-stream": "~0.1.5",
- "sequencify": "~0.0.7",
- "stream-consume": "~0.1.0"
+ "word-wrap": "~1.2.3"
}
},
- "ordered-read-streams": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz",
- "integrity": "sha1-/VZamvjrRHO6abbtijQ1LLVS8SY=",
- "dev": true
- },
"os-homedir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
- "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
- },
- "os-locale": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz",
- "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==",
- "dev": true,
- "requires": {
- "execa": "^0.7.0",
- "lcid": "^1.0.0",
- "mem": "^1.1.0"
- }
+ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
+ "dev": true
},
"os-tmpdir": {
"version": "1.0.2",
@@ -6565,6 +6684,16 @@
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"dev": true
},
+ "osenv": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
+ "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
+ "dev": true,
+ "requires": {
+ "os-homedir": "^1.0.0",
+ "os-tmpdir": "^1.0.0"
+ }
+ },
"output-file-sync": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz",
@@ -6583,10 +6712,13 @@
"dev": true
},
"p-limit": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz",
- "integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw=",
- "dev": true
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+ "dev": true,
+ "requires": {
+ "p-try": "^1.0.0"
+ }
},
"p-locate": {
"version": "2.0.0",
@@ -6597,6 +6729,12 @@
"p-limit": "^1.1.0"
}
},
+ "p-try": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
+ "dev": true
+ },
"package-json": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz",
@@ -6610,26 +6748,16 @@
}
},
"packet-reader": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-0.2.0.tgz",
- "integrity": "sha1-gZ300BC4LV6lZx+KGjrPA5vNdwA="
- },
- "parse-filepath": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.1.tgz",
- "integrity": "sha1-FZ1hVdQ5BNFsEO9piRHaHpGWm3M=",
- "dev": true,
- "requires": {
- "is-absolute": "^0.2.3",
- "map-cache": "^0.2.0",
- "path-root": "^0.1.1"
- }
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
+ "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
},
"parse-glob": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz",
"integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=",
"dev": true,
+ "optional": true,
"requires": {
"glob-base": "^0.3.0",
"is-dotfile": "^1.0.0",
@@ -6646,25 +6774,28 @@
"error-ex": "^1.2.0"
}
},
- "parse-passwd": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
- "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=",
+ "parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
+ },
+ "pascalcase": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
+ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
"dev": true
},
- "parseurl": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
- "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M="
+ "path-dirname": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
+ "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
+ "dev": true
},
"path-exists": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
- "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
- "dev": true,
- "requires": {
- "pinkie-promise": "^2.0.0"
- }
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+ "dev": true
},
"path-is-absolute": {
"version": "1.0.1",
@@ -6684,24 +6815,9 @@
"dev": true
},
"path-parse": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
- "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=",
- "dev": true
- },
- "path-root": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz",
- "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=",
- "dev": true,
- "requires": {
- "path-root-regex": "^0.1.0"
- }
- },
- "path-root-regex": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz",
- "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=",
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
"path-to-regexp": {
@@ -6718,39 +6834,29 @@
"pify": "^2.0.0"
}
},
- "pause-stream": {
- "version": "0.0.11",
- "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
- "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=",
- "dev": true,
- "requires": {
- "through": "~2.3"
- }
- },
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
},
"pg": {
- "version": "4.5.7",
- "resolved": "https://registry.npmjs.org/pg/-/pg-4.5.7.tgz",
- "integrity": "sha1-Ra4WsjcGpjRaAyed7MaveVwW0ps=",
- "requires": {
- "buffer-writer": "1.0.1",
- "generic-pool": "2.4.2",
- "js-string-escape": "1.0.1",
- "packet-reader": "0.2.0",
+ "version": "7.12.1",
+ "resolved": "https://registry.npmjs.org/pg/-/pg-7.12.1.tgz",
+ "integrity": "sha512-l1UuyfEvoswYfcUe6k+JaxiN+5vkOgYcVSbSuw3FvdLqDbaoa2RJo1zfJKfPsSYPFVERd4GHvX3s2PjG1asSDA==",
+ "requires": {
+ "buffer-writer": "2.0.0",
+ "packet-reader": "1.0.0",
"pg-connection-string": "0.1.3",
- "pg-types": "1.*",
- "pgpass": "0.0.3",
- "semver": "^4.1.0"
+ "pg-pool": "^2.0.4",
+ "pg-types": "^2.1.0",
+ "pgpass": "1.x",
+ "semver": "4.3.2"
},
"dependencies": {
"semver": {
- "version": "4.3.6",
- "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz",
- "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto="
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz",
+ "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c="
}
}
},
@@ -6759,20 +6865,37 @@
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz",
"integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc="
},
+ "pg-int8": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="
+ },
"pg-native": {
- "version": "1.10.1",
- "resolved": "https://registry.npmjs.org/pg-native/-/pg-native-1.10.1.tgz",
- "integrity": "sha1-lOYcy7hafzQ2suUmMVx1gRB/5Aw=",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pg-native/-/pg-native-3.0.0.tgz",
+ "integrity": "sha512-qZZyywXJ8O4lbiIN7mn6vXIow1fd3QZFqzRe+uET/SZIXvCa3HBooXQA4ZU8EQX8Ae6SmaYtDGLp5DwU+8vrfg==",
"requires": {
"libpq": "^1.7.0",
- "pg-types": "1.6.0",
+ "pg-types": "^1.12.1",
"readable-stream": "1.0.31"
},
"dependencies": {
"pg-types": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.6.0.tgz",
- "integrity": "sha1-OHKg8ZkUMCVJf07ipl/a8A1+qLM="
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.13.0.tgz",
+ "integrity": "sha512-lfKli0Gkl/+za/+b6lzENajczwZHc7D5kiUCZfgm914jipD2kIOIvEkAhZ8GrW3/TUoP9w8FHjwpPObBye5KQQ==",
+ "requires": {
+ "pg-int8": "1.0.1",
+ "postgres-array": "~1.0.0",
+ "postgres-bytea": "~1.0.0",
+ "postgres-date": "~1.0.0",
+ "postgres-interval": "^1.1.0"
+ }
+ },
+ "postgres-array": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.3.tgz",
+ "integrity": "sha512-5wClXrAP0+78mcsNX3/ithQ5exKvCyK5lr5NEEEeGwwM6NJdQgzIJBVxLvRW+huFpX92F2QnZ5CcokH0VhK2qQ=="
},
"readable-stream": {
"version": "1.0.31",
@@ -6787,23 +6910,29 @@
}
}
},
+ "pg-pool": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.7.tgz",
+ "integrity": "sha512-UiJyO5B9zZpu32GSlP0tXy8J2NsJ9EFGFfz5v6PSbdz/1hBLX1rNiiy5+mAm5iJJYwfCv4A0EBcQLGWwjbpzZw=="
+ },
"pg-types": {
- "version": "1.12.1",
- "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.12.1.tgz",
- "integrity": "sha1-1kCH45A7WP+q0nnnWVxSIIoUw9I=",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+ "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
"requires": {
- "postgres-array": "~1.0.0",
+ "pg-int8": "1.0.1",
+ "postgres-array": "~2.0.0",
"postgres-bytea": "~1.0.0",
- "postgres-date": "~1.0.0",
+ "postgres-date": "~1.0.4",
"postgres-interval": "^1.1.0"
}
},
"pgpass": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-0.0.3.tgz",
- "integrity": "sha1-EuZ+NDsxicLzEgbrycwL7//PkUA=",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz",
+ "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=",
"requires": {
- "split": "~0.3"
+ "split": "^1.0.0"
}
},
"pify": {
@@ -6812,28 +6941,13 @@
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
},
- "pinkie": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
- "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
- "dev": true
- },
- "pinkie-promise": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
- "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
- "dev": true,
- "requires": {
- "pinkie": "^2.0.0"
- }
- },
"pkg-dir": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz",
- "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
+ "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=",
"dev": true,
"requires": {
- "find-up": "^1.0.0"
+ "find-up": "^2.1.0"
}
},
"pluralize": {
@@ -6842,10 +6956,16 @@
"integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=",
"dev": true
},
+ "posix-character-classes": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
+ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
+ "dev": true
+ },
"postgres-array": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.2.tgz",
- "integrity": "sha1-jgsy6wO/d6XAp4UeBEHBaaJWojg="
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+ "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="
},
"postgres-bytea": {
"version": "1.0.0",
@@ -6853,14 +6973,14 @@
"integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU="
},
"postgres-date": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.3.tgz",
- "integrity": "sha1-4tiXAu/bJY/52c7g/pG9BpdSV6g="
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.4.tgz",
+ "integrity": "sha512-bESRvKVuTrjoBluEcpv2346+6kgB7UlnqWZsnbnCccTNq/pqfj1j6oBaN5+b/NrDXepYUT/HKadqv3iS9lJuVA=="
},
"postgres-interval": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.1.1.tgz",
- "integrity": "sha512-OkuCi9t/3CZmeQreutGgx/OVNv9MKHGIT5jH8KldQ4NLYXkvmT9nDVxEuCENlNwhlGPE374oA/xMqn05G49pHA==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+ "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"requires": {
"xtend": "^4.0.0"
}
@@ -6886,13 +7006,8 @@
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz",
"integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=",
- "dev": true
- },
- "pretty-hrtime": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
- "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=",
- "dev": true
+ "dev": true,
+ "optional": true
},
"private": {
"version": "0.1.8",
@@ -6901,9 +7016,9 @@
"dev": true
},
"process-nextick-args": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
- "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"progress": {
"version": "1.1.8",
@@ -6911,13 +7026,10 @@
"integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=",
"dev": true
},
- "promise": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
- "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
- "requires": {
- "asap": "~2.0.3"
- }
+ "promise-polyfill": {
+ "version": "8.1.3",
+ "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz",
+ "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g=="
},
"proto-list": {
"version": "1.2.4",
@@ -6931,21 +7043,12 @@
"integrity": "sha512-Xdayp8sB/mU+sUV4G7ws8xtYMGdQnxbeIfLjyO9TZZRJdztBGhlmbI5x1qcY4TG5hBkIKGnc28i7nXxaugu88w=="
},
"proxy-addr": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz",
- "integrity": "sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=",
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
+ "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==",
"requires": {
"forwarded": "~0.1.2",
- "ipaddr.js": "1.5.2"
- }
- },
- "ps-tree": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz",
- "integrity": "sha1-tCGyQUDWID8e08dplrRCewjowBQ=",
- "dev": true,
- "requires": {
- "event-stream": "~3.3.0"
+ "ipaddr.js": "1.9.0"
}
},
"pseudomap": {
@@ -6954,9 +7057,15 @@
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
},
"psl": {
- "version": "1.1.31",
- "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz",
- "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw=="
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz",
+ "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw=="
+ },
+ "pstree.remy": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz",
+ "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==",
+ "dev": true
},
"punycode": {
"version": "1.3.2",
@@ -6964,90 +7073,74 @@
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
},
"qs": {
- "version": "6.5.1",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
- "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A=="
+ "version": "6.9.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.1.tgz",
+ "integrity": "sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA=="
},
"querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
},
+ "querystringify": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz",
+ "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA=="
+ },
"randomatic": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
- "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz",
+ "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==",
"dev": true,
+ "optional": true,
"requires": {
- "is-number": "^3.0.0",
- "kind-of": "^4.0.0"
+ "is-number": "^4.0.0",
+ "kind-of": "^6.0.0",
+ "math-random": "^1.0.1"
},
"dependencies": {
"is-number": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
- "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz",
+ "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==",
"dev": true,
- "requires": {
- "kind-of": "^3.0.2"
- },
- "dependencies": {
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
+ "optional": true
},
"kind-of": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
- "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
"dev": true,
- "requires": {
- "is-buffer": "^1.1.5"
- }
+ "optional": true
}
}
},
"range-parser": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
- "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4="
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
},
"raw-body": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz",
- "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=",
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
+ "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
"requires": {
- "bytes": "3.0.0",
- "http-errors": "1.6.2",
- "iconv-lite": "0.4.19",
+ "bytes": "3.1.0",
+ "http-errors": "1.7.2",
+ "iconv-lite": "0.4.24",
"unpipe": "1.0.0"
}
},
"rc": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.2.tgz",
- "integrity": "sha1-2M6ctX6NZNnHut2YdsfDTL48cHc=",
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"dev": true,
"requires": {
- "deep-extend": "~0.4.0",
+ "deep-extend": "^0.6.0",
"ini": "~1.3.0",
"minimist": "^1.2.0",
"strip-json-comments": "~2.0.1"
- },
- "dependencies": {
- "minimist": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
- "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
- "dev": true
- }
}
},
"read-pkg": {
@@ -7069,17 +7162,6 @@
"requires": {
"find-up": "^2.0.0",
"read-pkg": "^2.0.0"
- },
- "dependencies": {
- "find-up": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
- "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
- "dev": true,
- "requires": {
- "locate-path": "^2.0.0"
- }
- }
}
},
"readable-stream": {
@@ -7094,42 +7176,321 @@
}
},
"readdirp": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz",
- "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=",
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
+ "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
"dev": true,
"requires": {
- "graceful-fs": "^4.1.2",
- "minimatch": "^3.0.2",
- "readable-stream": "^2.0.2",
- "set-immediate-shim": "^1.0.1"
+ "graceful-fs": "^4.1.11",
+ "micromatch": "^3.1.10",
+ "readable-stream": "^2.0.2"
},
"dependencies": {
+ "arr-diff": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+ "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
+ "dev": true
+ },
+ "array-unique": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
+ "dev": true
+ },
+ "braces": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+ "dev": true,
+ "requires": {
+ "arr-flatten": "^1.1.0",
+ "array-unique": "^0.3.2",
+ "extend-shallow": "^2.0.1",
+ "fill-range": "^4.0.0",
+ "isobject": "^3.0.1",
+ "repeat-element": "^1.1.2",
+ "snapdragon": "^0.8.1",
+ "snapdragon-node": "^2.0.1",
+ "split-string": "^3.0.2",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "expand-brackets": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+ "dev": true,
+ "requires": {
+ "debug": "^2.3.3",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "posix-character-classes": "^0.1.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-data-descriptor": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ }
+ },
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+ "dev": true
+ }
+ }
+ },
+ "extglob": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+ "dev": true,
+ "requires": {
+ "array-unique": "^0.3.2",
+ "define-property": "^1.0.0",
+ "expand-brackets": "^2.1.4",
+ "extend-shallow": "^2.0.1",
+ "fragment-cache": "^0.2.1",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "fill-range": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1",
+ "to-regex-range": "^2.1.0"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
+ "dev": true
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ },
+ "is-number": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true
},
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
+ "dev": true
+ },
+ "micromatch": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+ "dev": true,
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "braces": "^2.3.1",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "extglob": "^2.0.4",
+ "fragment-cache": "^0.2.1",
+ "kind-of": "^6.0.2",
+ "nanomatch": "^1.2.9",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.2"
+ }
+ },
"readable-stream": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
- "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
- "process-nextick-args": "~1.0.6",
+ "process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
- "string_decoder": "~1.0.3",
+ "string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"string_decoder": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
- "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
"safe-buffer": "~5.1.0"
@@ -7175,22 +7536,16 @@
"backoff": "~2.5.0"
}
},
- "redefine": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/redefine/-/redefine-0.2.1.tgz",
- "integrity": "sha1-6J7npvJNGf/2JZBWkzLcYDgKiaM=",
- "dev": true
- },
"regenerate": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz",
- "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==",
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
+ "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==",
"dev": true
},
"regenerator-runtime": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz",
- "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==",
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
+ "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
"dev": true
},
"regenerator-transform": {
@@ -7221,10 +7576,21 @@
"resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz",
"integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==",
"dev": true,
+ "optional": true,
"requires": {
"is-equal-shallow": "^0.1.3"
}
},
+ "regex-not": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
+ "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^3.0.2",
+ "safe-regex": "^1.1.0"
+ }
+ },
"regexpu-core": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz",
@@ -7237,9 +7603,9 @@
}
},
"registry-auth-token": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.1.tgz",
- "integrity": "sha1-+w0yie4Nmtosu1KvXf5mywcNMAY=",
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz",
+ "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==",
"dev": true,
"requires": {
"rc": "^1.1.6",
@@ -7290,9 +7656,9 @@
"integrity": "sha1-FJjl3wmEwn5Jt26/Boh8otARUNI="
},
"repeat-element": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz",
- "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
+ "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==",
"dev": true
},
"repeat-string": {
@@ -7310,12 +7676,6 @@
"is-finite": "^1.0.0"
}
},
- "replace-ext": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz",
- "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=",
- "dev": true
- },
"request": {
"version": "2.88.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
@@ -7343,19 +7703,6 @@
"uuid": "^3.3.2"
},
"dependencies": {
- "combined-stream": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
- "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==",
- "requires": {
- "delayed-stream": "~1.0.0"
- }
- },
- "extend": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
- "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
- },
"form-data": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
@@ -7366,28 +7713,10 @@
"mime-types": "^2.1.12"
}
},
- "mime-db": {
- "version": "1.37.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz",
- "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg=="
- },
- "mime-types": {
- "version": "2.1.21",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz",
- "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==",
- "requires": {
- "mime-db": "~1.37.0"
- }
- },
"qs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
- },
- "safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
},
@@ -7398,9 +7727,9 @@
"dev": true
},
"require-main-filename": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
- "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"dev": true
},
"require-uncached": {
@@ -7413,23 +7742,18 @@
"resolve-from": "^1.0.0"
}
},
- "resolve": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz",
- "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==",
- "dev": true,
- "requires": {
- "path-parse": "^1.0.5"
- }
+ "requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
- "resolve-dir": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz",
- "integrity": "sha1-shklmlYC+sXFxJatiUpujMQwJh4=",
+ "resolve": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
+ "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==",
"dev": true,
"requires": {
- "expand-tilde": "^1.2.2",
- "global-modules": "^0.2.3"
+ "path-parse": "^1.0.6"
}
},
"resolve-from": {
@@ -7438,6 +7762,12 @@
"integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=",
"dev": true
},
+ "resolve-url": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
+ "dev": true
+ },
"restore-cursor": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
@@ -7448,29 +7778,25 @@
"onetime": "^1.0.0"
}
},
- "retry-as-promised": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-2.3.2.tgz",
- "integrity": "sha1-zZdO5P2bX+A8vzGHHuSCIcB3N7c=",
- "requires": {
- "bluebird": "^3.4.6",
- "debug": "^2.6.9"
- }
+ "ret": {
+ "version": "0.1.15",
+ "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
+ "dev": true
},
- "right-align": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
- "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=",
- "dev": true,
- "optional": true,
+ "retry-as-promised": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz",
+ "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==",
"requires": {
- "align-text": "^0.1.1"
+ "any-promise": "^1.3.0"
}
},
"rimraf": {
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
"integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=",
+ "optional": true,
"requires": {
"glob": "^6.0.1"
}
@@ -7491,16 +7817,25 @@
"dev": true
},
"safe-buffer": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
- "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"safe-json-stringify": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz",
- "integrity": "sha1-gaCY9Efku8P/MxKiQ1IbwGDvWRE=",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz",
+ "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==",
"optional": true
},
+ "safe-regex": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
+ "dev": true,
+ "requires": {
+ "ret": "~0.1.10"
+ }
+ },
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -7518,14 +7853,14 @@
"integrity": "sha1-1PCvdHXZp7ryqeWmEXGLqheKOeE="
},
"sax": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
- "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"semver": {
- "version": "5.4.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
- "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg=="
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
},
"semver-diff": {
"version": "2.1.0",
@@ -7537,183 +7872,104 @@
}
},
"send": {
- "version": "0.16.1",
- "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz",
- "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==",
+ "version": "0.17.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
+ "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
"requires": {
"debug": "2.6.9",
- "depd": "~1.1.1",
+ "depd": "~1.1.2",
"destroy": "~1.0.4",
- "encodeurl": "~1.0.1",
+ "encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
- "http-errors": "~1.6.2",
- "mime": "1.4.1",
- "ms": "2.0.0",
+ "http-errors": "~1.7.2",
+ "mime": "1.6.0",
+ "ms": "2.1.1",
"on-finished": "~2.3.0",
- "range-parser": "~1.2.0",
- "statuses": "~1.3.1"
+ "range-parser": "~1.2.1",
+ "statuses": "~1.5.0"
},
"dependencies": {
- "statuses": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz",
- "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4="
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
}
}
},
"sequelize": {
- "version": "3.30.4",
- "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-3.30.4.tgz",
- "integrity": "sha1-vaLfHjGFSwmeQUmhEen8Clyh0aQ=",
- "requires": {
- "bluebird": "^3.3.4",
- "depd": "^1.1.0",
- "dottie": "^1.0.0",
- "generic-pool": "2.4.2",
- "inflection": "^1.6.0",
- "lodash": "4.12.0",
- "moment": "^2.13.0",
- "moment-timezone": "^0.5.4",
- "retry-as-promised": "^2.0.0",
- "semver": "^5.0.1",
- "shimmer": "1.1.0",
- "terraformer-wkt-parser": "^1.1.0",
- "toposort-class": "^1.0.1",
- "uuid": "^3.0.0",
- "validator": "^5.2.0",
- "wkx": "0.2.0"
- },
- "dependencies": {
- "lodash": {
- "version": "4.12.0",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.12.0.tgz",
- "integrity": "sha1-K9bcRqBA9Z5obJcu0h2T3FkFMlg="
- }
- }
- },
- "sequelize-cli": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-2.8.0.tgz",
- "integrity": "sha1-QwTM5g5JkWlgP4ON7bq0IcmEnnQ=",
- "dev": true,
+ "version": "5.21.2",
+ "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.21.2.tgz",
+ "integrity": "sha512-MEqJ9NwQi4oy/ylLb2WkfPmhki/BOXC/gJfc8uWUUTETcpLwD1y/5bI1kqVh+qWcECHNsE9G4lmhj5hFbsxqvA==",
"requires": {
"bluebird": "^3.5.0",
- "cli-color": "~1.2.0",
- "findup-sync": "^1.0.0",
- "fs-extra": "^4.0.1",
- "gulp": "^3.9.1",
- "gulp-help": "~1.6.1",
- "js-beautify": "^1.6.11",
- "lodash": "^4.17.4",
- "moment": "^2.17.1",
- "resolve": "^1.3.3",
- "umzug": "^1.12.0",
- "yargs": "^8.0.1"
+ "cls-bluebird": "^2.1.0",
+ "debug": "^4.1.1",
+ "dottie": "^2.0.0",
+ "inflection": "1.12.0",
+ "lodash": "^4.17.15",
+ "moment": "^2.24.0",
+ "moment-timezone": "^0.5.21",
+ "retry-as-promised": "^3.2.0",
+ "semver": "^6.3.0",
+ "sequelize-pool": "^2.3.0",
+ "toposort-class": "^1.0.1",
+ "uuid": "^3.3.3",
+ "validator": "^10.11.0",
+ "wkx": "^0.4.8"
},
"dependencies": {
- "ansi-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
- "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
- "dev": true
- },
- "camelcase": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
- "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
- "dev": true
- },
- "cliui": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
- "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
- "dev": true,
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
- "string-width": "^1.0.1",
- "strip-ansi": "^3.0.1",
- "wrap-ansi": "^2.0.0"
- },
- "dependencies": {
- "string-width": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
- "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
- "dev": true,
- "requires": {
- "code-point-at": "^1.0.0",
- "is-fullwidth-code-point": "^1.0.0",
- "strip-ansi": "^3.0.0"
- }
- }
+ "ms": "^2.1.1"
}
},
- "string-width": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
- "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
- "dev": true,
- "requires": {
- "is-fullwidth-code-point": "^2.0.0",
- "strip-ansi": "^4.0.0"
- },
- "dependencies": {
- "is-fullwidth-code-point": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
- "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
- "dev": true
- },
- "strip-ansi": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
- "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
- "dev": true,
- "requires": {
- "ansi-regex": "^3.0.0"
- }
- }
- }
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
- "yargs": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz",
- "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=",
- "dev": true,
- "requires": {
- "camelcase": "^4.1.0",
- "cliui": "^3.2.0",
- "decamelize": "^1.1.1",
- "get-caller-file": "^1.0.1",
- "os-locale": "^2.0.0",
- "read-pkg-up": "^2.0.0",
- "require-directory": "^2.1.1",
- "require-main-filename": "^1.0.1",
- "set-blocking": "^2.0.0",
- "string-width": "^2.0.0",
- "which-module": "^2.0.0",
- "y18n": "^3.2.1",
- "yargs-parser": "^7.0.0"
- }
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
}
}
},
- "sequencify": {
- "version": "0.0.7",
- "resolved": "https://registry.npmjs.org/sequencify/-/sequencify-0.0.7.tgz",
- "integrity": "sha1-kM/xnQLgcCf9dn9erT57ldHnOAw=",
- "dev": true
+ "sequelize-cli": {
+ "version": "5.5.1",
+ "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-5.5.1.tgz",
+ "integrity": "sha512-ZM4kUZvY3y14y+Rq3cYxGH7YDJz11jWHcN2p2x7rhAIemouu4CEXr5ebw30lzTBtyXV4j2kTO+nUjZOqzG7k+Q==",
+ "dev": true,
+ "requires": {
+ "bluebird": "^3.5.3",
+ "cli-color": "^1.4.0",
+ "fs-extra": "^7.0.1",
+ "js-beautify": "^1.8.8",
+ "lodash": "^4.17.5",
+ "resolve": "^1.5.0",
+ "umzug": "^2.1.0",
+ "yargs": "^13.1.0"
+ }
+ },
+ "sequelize-pool": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-2.3.0.tgz",
+ "integrity": "sha512-Ibz08vnXvkZ8LJTiUOxRcj1Ckdn7qafNZ2t59jYHMX1VIebTAOYefWdRYFt6z6+hy52WGthAHAoLc9hvk3onqA=="
},
"serve-static": {
- "version": "1.13.1",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz",
- "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==",
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
+ "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
"requires": {
- "encodeurl": "~1.0.1",
+ "encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
- "parseurl": "~1.3.2",
- "send": "0.16.1"
+ "parseurl": "~1.3.3",
+ "send": "0.17.1"
}
},
"set-blocking": {
@@ -7722,16 +7978,33 @@
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"dev": true
},
- "set-immediate-shim": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
- "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=",
- "dev": true
+ "set-value": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
+ "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-extendable": "^0.1.1",
+ "is-plain-object": "^2.0.3",
+ "split-string": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
},
"setprototypeof": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz",
- "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ="
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
+ "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
},
"shebang-command": {
"version": "1.2.0",
@@ -7760,9 +8033,9 @@
},
"dependencies": {
"glob": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
- "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
@@ -7776,9 +8049,9 @@
}
},
"shimmer": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.1.0.tgz",
- "integrity": "sha1-l9c3cTf/u6tCVSLkKf4KqJpIizU="
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz",
+ "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw=="
},
"sigmund": {
"version": "1.0.1",
@@ -7822,12 +8095,12 @@
"dev": true
},
"sleep": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/sleep/-/sleep-5.1.1.tgz",
- "integrity": "sha1-h4+h1E0I7rDyb7IBjvhinrGjq5Q=",
+ "version": "5.2.4",
+ "resolved": "https://registry.npmjs.org/sleep/-/sleep-5.2.4.tgz",
+ "integrity": "sha512-SoltvxayTifWOgOGD6CTh+djcp5TaOa/zdbaA38wEH1ahF2azmiLOh8CPt6ExHf0pAJAsA9OCHTS7zK24Ym4yA==",
"dev": true,
"requires": {
- "nan": ">=2.5.1"
+ "nan": ">=2.12.1"
}
},
"slice-ansi": {
@@ -7836,12 +8109,133 @@
"integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=",
"dev": true
},
+ "snapdragon": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
+ "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
+ "dev": true,
+ "requires": {
+ "base": "^0.11.1",
+ "debug": "^2.2.0",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "map-cache": "^0.2.2",
+ "source-map": "^0.5.6",
+ "source-map-resolve": "^0.5.0",
+ "use": "^3.1.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "snapdragon-node": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
+ "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
+ "dev": true,
+ "requires": {
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.0",
+ "snapdragon-util": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
+ "dev": true
+ }
+ }
+ },
+ "snapdragon-util": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
+ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.2.0"
+ }
+ },
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"dev": true
},
+ "source-map-resolve": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz",
+ "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==",
+ "dev": true,
+ "requires": {
+ "atob": "^2.1.1",
+ "decode-uri-component": "^0.2.0",
+ "resolve-url": "^0.2.1",
+ "source-map-url": "^0.4.0",
+ "urix": "^0.1.0"
+ }
+ },
"source-map-support": {
"version": "0.4.18",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz",
@@ -7851,51 +8245,70 @@
"source-map": "^0.5.6"
}
},
- "sparkles": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz",
- "integrity": "sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM=",
+ "source-map-url": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
+ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
"dev": true
},
"spdx-correct": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz",
- "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
+ "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
"dev": true,
"requires": {
- "spdx-license-ids": "^1.0.2"
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
}
},
- "spdx-expression-parse": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz",
- "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=",
+ "spdx-exceptions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
+ "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
"dev": true
},
+ "spdx-expression-parse": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
+ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
+ "dev": true,
+ "requires": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
"spdx-license-ids": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz",
- "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=",
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
+ "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
"dev": true
},
"split": {
- "version": "0.3.3",
- "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz",
- "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
+ "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
"requires": {
"through": "2"
}
},
+ "split-string": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
+ "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^3.0.0"
+ }
+ },
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
- "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
- "dev": true
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"sshpk": {
- "version": "1.15.2",
- "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz",
- "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==",
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
+ "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
"requires": {
"asn1": "~0.2.3",
"assert-plus": "^1.0.0",
@@ -7908,24 +8321,31 @@
"tweetnacl": "~0.14.0"
}
},
- "statuses": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
- "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
- },
- "stream-combiner": {
- "version": "0.0.4",
- "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz",
- "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=",
+ "static-extend": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
+ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
"dev": true,
"requires": {
- "duplexer": "~0.1.1"
+ "define-property": "^0.2.5",
+ "object-copy": "^0.1.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
}
},
- "stream-consume": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.0.tgz",
- "integrity": "sha1-pB6tGm1ggc63n2WwYZAbbY89HQ8="
+ "statuses": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
},
"string-width": {
"version": "1.0.2",
@@ -7938,6 +8358,24 @@
"strip-ansi": "^3.0.0"
}
},
+ "string.prototype.trimleft": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz",
+ "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==",
+ "requires": {
+ "define-properties": "^1.1.3",
+ "function-bind": "^1.1.1"
+ }
+ },
+ "string.prototype.trimright": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz",
+ "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==",
+ "requires": {
+ "define-properties": "^1.1.3",
+ "function-bind": "^1.1.1"
+ }
+ },
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
@@ -7970,28 +8408,28 @@
"dev": true
},
"superagent": {
- "version": "3.8.0",
- "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.0.tgz",
- "integrity": "sha512-71XGWgtn70TNwgmgYa69dPOYg55aU9FCahjUNY03rOrKvaTCaU3b9MeZmqonmf9Od96SCxr3vGfEAnhM7dtxCw==",
+ "version": "3.8.3",
+ "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz",
+ "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==",
"requires": {
"component-emitter": "^1.2.0",
"cookiejar": "^2.1.0",
"debug": "^3.1.0",
"extend": "^3.0.0",
"form-data": "^2.3.1",
- "formidable": "^1.1.1",
+ "formidable": "^1.2.0",
"methods": "^1.1.1",
"mime": "^1.4.1",
"qs": "^6.5.1",
- "readable-stream": "^2.0.5"
+ "readable-stream": "^2.3.5"
},
"dependencies": {
"debug": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
- "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"requires": {
- "ms": "2.0.0"
+ "ms": "^2.1.1"
}
},
"isarray": {
@@ -7999,24 +8437,29 @@
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
"readable-stream": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
- "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
- "process-nextick-args": "~1.0.6",
+ "process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
- "string_decoder": "~1.0.3",
+ "string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"string_decoder": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
- "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
@@ -8029,74 +8472,13 @@
"integrity": "sha1-5Js1ypbA47HQ4/SWBRNt8OCgKLc="
},
"supertest": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/supertest/-/supertest-2.0.1.tgz",
- "integrity": "sha1-oFgIHXiPFRXUcA11Aogea3WeRM0=",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/supertest/-/supertest-4.0.2.tgz",
+ "integrity": "sha512-1BAbvrOZsGA3YTCWqbmh14L0YEq0EGICX/nBnfkfVJn7SrxQV1I3pMYjSzG9y/7ZU2V9dWqyqk2POwxlb09duQ==",
"dev": true,
"requires": {
- "methods": "1.x",
- "superagent": "^2.0.0"
- },
- "dependencies": {
- "form-data": {
- "version": "1.0.0-rc4",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz",
- "integrity": "sha1-BaxrwiIntD5EYfSIFhVUaZ1Pi14=",
- "dev": true,
- "requires": {
- "async": "^1.5.2",
- "combined-stream": "^1.0.5",
- "mime-types": "^2.1.10"
- }
- },
- "isarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
- "dev": true
- },
- "readable-stream": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
- "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
- "dev": true,
- "requires": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~1.0.6",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.0.3",
- "util-deprecate": "~1.0.1"
- }
- },
- "string_decoder": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
- "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
- "dev": true,
- "requires": {
- "safe-buffer": "~5.1.0"
- }
- },
- "superagent": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/superagent/-/superagent-2.3.0.tgz",
- "integrity": "sha1-cDUpoHFOV+EjlZ3e+84ZOy5Q0RU=",
- "dev": true,
- "requires": {
- "component-emitter": "^1.2.0",
- "cookiejar": "^2.0.6",
- "debug": "^2.2.0",
- "extend": "^3.0.0",
- "form-data": "1.0.0-rc4",
- "formidable": "^1.0.17",
- "methods": "^1.1.1",
- "mime": "^1.3.4",
- "qs": "^6.1.0",
- "readable-stream": "^2.0.5"
- }
- }
+ "methods": "^1.1.2",
+ "superagent": "^3.8.3"
}
},
"supports-color": {
@@ -8104,6 +8486,19 @@
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
},
+ "swagger-ui-dist": {
+ "version": "3.24.2",
+ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.24.2.tgz",
+ "integrity": "sha512-Nhx9hODibHEa53ErTrguM/N0XaEBcQeKkTlfgJvRwMo/CrJI6ncy8xKOh3meSqQj+oVqz2nhWjMBBudJsRYz5g=="
+ },
+ "swagger-ui-express": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.1.2.tgz",
+ "integrity": "sha512-bVT16qj6WdNlEKFkSLOoTeGuqEm2lfOFRq6mVHAx+viA/ikORE+n4CS3WpVcYmQzM4HE6+DUFgAWcMRBJNpjcw==",
+ "requires": {
+ "swagger-ui-dist": "^3.18.1"
+ }
+ },
"table": {
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz",
@@ -8118,6 +8513,16 @@
"string-width": "^2.0.0"
},
"dependencies": {
+ "ajv": {
+ "version": "4.11.8",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
+ "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=",
+ "dev": true,
+ "requires": {
+ "co": "^4.6.0",
+ "json-stable-stringify": "^1.0.1"
+ }
+ },
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
@@ -8152,11 +8557,11 @@
}
},
"tc-core-library-js": {
- "version": "github:appirio-tech/tc-core-library-js#d16413db30b1eed21c0cf426e185bedb2329ddab",
- "from": "github:appirio-tech/tc-core-library-js#v2.6",
+ "version": "github:appirio-tech/tc-core-library-js#f45352974dafe5a10c86fc50bdd59ef399b50c65",
+ "from": "github:appirio-tech/tc-core-library-js#v2.6.3",
"requires": {
"auth0-js": "^9.4.2",
- "axios": "^0.12.0",
+ "axios": "^0.19.0",
"bunyan": "^1.8.12",
"jsonwebtoken": "^8.3.0",
"jwks-rsa": "^1.3.0",
@@ -8164,30 +8569,6 @@
"lodash": "^4.17.10",
"millisecond": "^0.1.2",
"request": "^2.88.0"
- },
- "dependencies": {
- "axios": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/axios/-/axios-0.12.0.tgz",
- "integrity": "sha1-uQewIhzDTsHJ+sGOx/B935V4W6Q=",
- "requires": {
- "follow-redirects": "0.0.7"
- }
- },
- "follow-redirects": {
- "version": "0.0.7",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-0.0.7.tgz",
- "integrity": "sha1-NLkLqyqRGqNHVx2pDyK9NuzYqRk=",
- "requires": {
- "debug": "^2.2.0",
- "stream-consume": "^0.1.0"
- }
- },
- "lodash": {
- "version": "4.17.11",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
- "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
- }
}
},
"term-size": {
@@ -8199,22 +8580,6 @@
"execa": "^0.7.0"
}
},
- "terraformer": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/terraformer/-/terraformer-1.0.8.tgz",
- "integrity": "sha1-UeCtiXRvzyFh3G9lqnDkI3fItZM=",
- "requires": {
- "@types/geojson": "^1.0.0"
- }
- },
- "terraformer-wkt-parser": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/terraformer-wkt-parser/-/terraformer-wkt-parser-1.1.2.tgz",
- "integrity": "sha1-M2oMj8gglKWv+DKI9prt7NNpvww=",
- "requires": {
- "terraformer": "~1.0.5"
- }
- },
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -8226,63 +8591,6 @@
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
},
- "through2": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
- "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
- "dev": true,
- "requires": {
- "readable-stream": "^2.1.5",
- "xtend": "~4.0.1"
- },
- "dependencies": {
- "isarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
- "dev": true
- },
- "readable-stream": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
- "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
- "dev": true,
- "requires": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~1.0.6",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.0.3",
- "util-deprecate": "~1.0.1"
- }
- },
- "string_decoder": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
- "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
- "dev": true,
- "requires": {
- "safe-buffer": "~5.1.0"
- }
- }
- }
- },
- "tildify": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/tildify/-/tildify-1.2.0.tgz",
- "integrity": "sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo=",
- "dev": true,
- "requires": {
- "os-homedir": "^1.0.0"
- }
- },
- "time-stamp": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz",
- "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=",
- "dev": true
- },
"timed-out": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
@@ -8290,12 +8598,12 @@
"dev": true
},
"timers-ext": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.2.tgz",
- "integrity": "sha1-YcxHp2wavTGV8UUn+XjViulMUgQ=",
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz",
+ "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==",
"dev": true,
"requires": {
- "es5-ext": "~0.10.14",
+ "es5-ext": "~0.10.46",
"next-tick": "1"
}
},
@@ -8305,11 +8613,52 @@
"integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=",
"dev": true
},
- "to-iso-string": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz",
- "integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=",
- "dev": true
+ "to-object-path": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
+ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ }
+ },
+ "to-regex": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
+ "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
+ "dev": true,
+ "requires": {
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "regex-not": "^1.0.2",
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "to-regex-range": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
+ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
+ "dev": true,
+ "requires": {
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1"
+ },
+ "dependencies": {
+ "is-number": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ }
+ }
+ }
+ },
+ "toidentifier": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
+ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
},
"topo": {
"version": "2.0.2",
@@ -8371,12 +8720,6 @@
"integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
"dev": true
},
- "tryit": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz",
- "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=",
- "dev": true
- },
"tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@@ -8390,6 +8733,12 @@
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
},
+ "type": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
+ "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
+ "dev": true
+ },
"type-check": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
@@ -8406,12 +8755,12 @@
"dev": true
},
"type-is": {
- "version": "1.6.15",
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz",
- "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=",
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"requires": {
"media-typer": "0.3.0",
- "mime-types": "~2.1.15"
+ "mime-types": "~2.1.24"
}
},
"typedarray": {
@@ -8421,54 +8770,77 @@
"dev": true
},
"uglify-js": {
- "version": "2.8.29",
- "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz",
- "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=",
+ "version": "3.6.8",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.8.tgz",
+ "integrity": "sha512-XhHJ3S3ZyMwP8kY1Gkugqx3CJh2C3O0y8NPiSxtm1tyD/pktLAkFZsFGpuNfTZddKDQ/bbDBLAd2YyA1pbi8HQ==",
"dev": true,
"optional": true,
"requires": {
- "source-map": "~0.5.1",
- "uglify-to-browserify": "~1.0.0",
- "yargs": "~3.10.0"
+ "commander": "~2.20.3",
+ "source-map": "~0.6.1"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "optional": true
+ }
}
},
- "uglify-to-browserify": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz",
- "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=",
+ "umzug": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.2.0.tgz",
+ "integrity": "sha512-xZLW76ax70pND9bx3wqwb8zqkFGzZIK8dIHD9WdNy/CrNfjWcwQgQkGCuUqcuwEBvUm+g07z+qWvY+pxDmMEEw==",
"dev": true,
- "optional": true
+ "requires": {
+ "babel-runtime": "^6.23.0",
+ "bluebird": "^3.5.3"
+ },
+ "dependencies": {
+ "babel-runtime": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
+ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
+ "dev": true,
+ "requires": {
+ "core-js": "^2.4.0",
+ "regenerator-runtime": "^0.11.0"
+ }
+ }
+ }
},
- "umzug": {
- "version": "1.12.0",
- "resolved": "https://registry.npmjs.org/umzug/-/umzug-1.12.0.tgz",
- "integrity": "sha1-p5yR8oYu7jEwxsNH8rkK1opm6Lg=",
+ "undefsafe": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz",
+ "integrity": "sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY=",
"dev": true,
"requires": {
- "bluebird": "^3.4.1",
- "lodash": "^4.17.0",
- "moment": "^2.16.0",
- "redefine": "^0.2.0",
- "resolve": "^1.0.0"
+ "debug": "^2.2.0"
}
},
- "unc-path-regex": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",
- "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=",
- "dev": true
+ "underscore": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
+ "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI="
},
- "undefsafe": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-0.0.3.tgz",
- "integrity": "sha1-7Mo6A+VrmvFzhbqsgSrIO5lKli8=",
- "dev": true
+ "unfetch": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.1.0.tgz",
+ "integrity": "sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg=="
},
- "unique-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-1.0.0.tgz",
- "integrity": "sha1-1ZpKdUJ0R9mqbJHnAmP40mpLEEs=",
- "dev": true
+ "union-value": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
+ "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
+ "dev": true,
+ "requires": {
+ "arr-union": "^3.1.0",
+ "get-value": "^2.0.6",
+ "is-extendable": "^0.1.1",
+ "set-value": "^2.0.1"
+ }
},
"unique-string": {
"version": "1.0.0",
@@ -8479,33 +8851,92 @@
"crypto-random-string": "^1.0.0"
}
},
- "universalify": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz",
- "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=",
- "dev": true
- },
- "unpipe": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
- "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
- },
+ "universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true
+ },
+ "unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+ },
+ "unset-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
+ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
+ "dev": true,
+ "requires": {
+ "has-value": "^0.3.1",
+ "isobject": "^3.0.0"
+ },
+ "dependencies": {
+ "has-value": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
+ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
+ "dev": true,
+ "requires": {
+ "get-value": "^2.0.3",
+ "has-values": "^0.1.4",
+ "isobject": "^2.0.0"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+ "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+ "dev": true,
+ "requires": {
+ "isarray": "1.0.0"
+ }
+ }
+ }
+ },
+ "has-values": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
+ "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
+ "dev": true
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ }
+ }
+ },
"unzip-response": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz",
"integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=",
"dev": true
},
+ "upath": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
+ "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==",
+ "dev": true
+ },
"update-notifier": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.3.0.tgz",
- "integrity": "sha1-TognpruRUUCrCTVZ1wFOPruDdFE=",
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz",
+ "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==",
"dev": true,
"requires": {
"boxen": "^1.2.1",
"chalk": "^2.0.1",
"configstore": "^3.0.0",
"import-lazy": "^2.1.0",
+ "is-ci": "^1.0.10",
"is-installed-globally": "^0.1.0",
"is-npm": "^1.0.0",
"latest-version": "^3.0.0",
@@ -8514,38 +8945,38 @@
},
"dependencies": {
"ansi-styles": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
- "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"chalk": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
- "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==",
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
- "ansi-styles": "^3.1.0",
+ "ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
- "supports-color": "^4.0.0"
+ "supports-color": "^5.3.0"
}
},
"has-flag": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
- "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"supports-color": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
- "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
- "has-flag": "^2.0.0"
+ "has-flag": "^3.0.0"
}
}
}
@@ -8565,6 +8996,12 @@
}
}
},
+ "urix": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
+ "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
+ "dev": true
+ },
"url": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz",
@@ -8575,9 +9012,18 @@
}
},
"url-join": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.0.tgz",
- "integrity": "sha1-TTNA6AfTdzvamZH4MFrNzCpmXSo="
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
+ "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="
+ },
+ "url-parse": {
+ "version": "1.4.7",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
+ "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
+ "requires": {
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
+ }
},
"url-parse-lax": {
"version": "1.0.0",
@@ -8596,6 +9042,12 @@
"iconv-lite": "~0.4.11"
}
},
+ "use": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
+ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
+ "dev": true
+ },
"user-home": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz",
@@ -8603,20 +9055,16 @@
"dev": true
},
"util": {
- "version": "0.10.3",
- "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
- "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
+ "version": "0.12.1",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.12.1.tgz",
+ "integrity": "sha512-MREAtYOp+GTt9/+kwf00IYoHZyjM8VU4aVrkzUlejyqaIjd2GztVl5V9hGXKlvBKE3gENn/FMfHE5v6hElXGcQ==",
"dev": true,
"requires": {
- "inherits": "2.0.1"
- },
- "dependencies": {
- "inherits": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
- "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=",
- "dev": true
- }
+ "inherits": "^2.0.3",
+ "is-arguments": "^1.0.4",
+ "is-generator-function": "^1.0.7",
+ "object.entries": "^1.1.0",
+ "safe-buffer": "^5.1.2"
}
},
"util-deprecate": {
@@ -8624,15 +9072,24 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
+ "util.promisify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz",
+ "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==",
+ "requires": {
+ "define-properties": "^1.1.2",
+ "object.getownpropertydescriptors": "^2.0.3"
+ }
+ },
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
"uuid": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
- "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
+ "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
},
"v8flags": {
"version": "2.1.1",
@@ -8644,19 +9101,19 @@
}
},
"validate-npm-package-license": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",
- "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=",
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
"dev": true,
"requires": {
- "spdx-correct": "~1.0.0",
- "spdx-expression-parse": "~1.0.0"
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
}
},
"validator": {
- "version": "5.7.0",
- "resolved": "https://registry.npmjs.org/validator/-/validator-5.7.0.tgz",
- "integrity": "sha1-eoelgUa2laxIYHEUHAxJ1n2gXlw="
+ "version": "10.11.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz",
+ "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw=="
},
"vary": {
"version": "1.1.2",
@@ -8678,140 +9135,90 @@
"resolved": "https://registry.npmjs.org/very-fast-args/-/very-fast-args-1.1.0.tgz",
"integrity": "sha1-4W0dH6+KbllqJGQh/ZCneWPQs5Y="
},
- "vinyl": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz",
- "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=",
+ "which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"dev": true,
"requires": {
- "clone": "^1.0.0",
- "clone-stats": "^0.0.1",
- "replace-ext": "0.0.1"
- },
- "dependencies": {
- "clone": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz",
- "integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=",
- "dev": true
- }
+ "isexe": "^2.0.0"
}
},
- "vinyl-fs": {
- "version": "0.3.14",
- "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-0.3.14.tgz",
- "integrity": "sha1-mmhRzhysHBzqX+hsCTHWIMLPqeY=",
+ "which-module": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+ "dev": true
+ },
+ "wide-align": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+ "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
"dev": true,
"requires": {
- "defaults": "^1.0.0",
- "glob-stream": "^3.1.5",
- "glob-watcher": "^0.0.6",
- "graceful-fs": "^3.0.0",
- "mkdirp": "^0.5.0",
- "strip-bom": "^1.0.0",
- "through2": "^0.6.1",
- "vinyl": "^0.4.0"
+ "string-width": "^1.0.2 || 2"
+ }
+ },
+ "widest-line": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz",
+ "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==",
+ "dev": true,
+ "requires": {
+ "string-width": "^2.1.1"
},
"dependencies": {
- "clone": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz",
- "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=",
+ "ansi-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true
},
- "graceful-fs": {
- "version": "3.0.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz",
- "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=",
- "dev": true,
- "requires": {
- "natives": "^1.1.0"
- }
- },
- "readable-stream": {
- "version": "1.0.34",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
- "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
- "dev": true,
- "requires": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.1",
- "isarray": "0.0.1",
- "string_decoder": "~0.10.x"
- }
- },
- "strip-bom": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-1.0.0.tgz",
- "integrity": "sha1-hbiGLzhEtabV7IRnqTWYFzo295Q=",
- "dev": true,
- "requires": {
- "first-chunk-stream": "^1.0.0",
- "is-utf8": "^0.2.0"
- }
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
},
- "through2": {
- "version": "0.6.5",
- "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
- "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=",
+ "string-width": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"dev": true,
"requires": {
- "readable-stream": ">=1.0.33-1 <1.1.0-0",
- "xtend": ">=4.0.0 <4.1.0-0"
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^4.0.0"
}
},
- "vinyl": {
- "version": "0.4.6",
- "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz",
- "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=",
+ "strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true,
"requires": {
- "clone": "^0.2.0",
- "clone-stats": "^0.0.1"
+ "ansi-regex": "^3.0.0"
}
}
}
},
- "which": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz",
- "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==",
- "dev": true,
- "requires": {
- "isexe": "^2.0.0"
- }
- },
- "which-module": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
- "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
- "dev": true
+ "winchan": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/winchan/-/winchan-0.2.2.tgz",
+ "integrity": "sha512-pvN+IFAbRP74n/6mc6phNyCH8oVkzXsto4KCHPJ2AScniAnA1AmeLI03I2BzjePpaClGSI4GUMowzsD3qz5PRQ=="
},
- "widest-line": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-1.0.0.tgz",
- "integrity": "sha1-DAnIXCqUaD0Nfq+O4JfVZL8OEFw=",
- "dev": true,
+ "wkx": {
+ "version": "0.4.8",
+ "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.8.tgz",
+ "integrity": "sha512-ikPXMM9IR/gy/LwiOSqWlSL3X/J5uk9EO2hHNRXS41eTLXaUFEVw9fn/593jW/tE5tedNg8YjT5HkCa4FqQZyQ==",
"requires": {
- "string-width": "^1.0.1"
+ "@types/node": "*"
}
},
- "winchan": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/winchan/-/winchan-0.2.1.tgz",
- "integrity": "sha512-QrG9q+ObfmZBxScv0HSCqFm/owcgyR5Sgpiy1NlCZPpFXhbsmNHhTiLWoogItdBUi0fnU7Io/5ABEqRta5/6Dw=="
- },
- "window-size": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz",
- "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=",
- "dev": true,
- "optional": true
- },
- "wkx": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.2.0.tgz",
- "integrity": "sha1-dsJPFqzQzY+TzTSqMx4PeWElboQ="
+ "word-wrap": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "dev": true
},
"wordwrap": {
"version": "1.0.0",
@@ -8820,13 +9227,57 @@
"dev": true
},
"wrap-ansi": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
- "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+ "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
"dev": true,
"requires": {
- "string-width": "^1.0.1",
- "strip-ansi": "^3.0.1"
+ "ansi-styles": "^3.2.0",
+ "string-width": "^3.0.0",
+ "strip-ansi": "^5.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
+ },
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ }
}
},
"wrappy": {
@@ -8844,9 +9295,9 @@
}
},
"write-file-atomic": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz",
- "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==",
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz",
+ "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.11",
@@ -8855,11 +9306,11 @@
}
},
"wrr-pool": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/wrr-pool/-/wrr-pool-1.1.3.tgz",
- "integrity": "sha1-/a0i8uofMDY//l14HPeUl6d/8H4=",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/wrr-pool/-/wrr-pool-1.1.4.tgz",
+ "integrity": "sha512-+lEdj42HlYqmzhvkZrx6xEymj0wzPBxqr7U1Xh9IWikMzOge03JSQT9YzTGq54SkOh/noViq32UejADZVzrgAg==",
"requires": {
- "lodash": "^4.0.1"
+ "lodash": "^4.17.11"
}
},
"xdg-basedir": {
@@ -8869,31 +9320,29 @@
"dev": true
},
"xml2js": {
- "version": "0.4.17",
- "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz",
- "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=",
+ "version": "0.4.22",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.22.tgz",
+ "integrity": "sha512-MWTbxAQqclRSTnehWWe5nMKzI3VmJ8ltiJEco8akcC6j3miOhjjfzKum5sId+CWhfxdOs/1xauYr8/ZDBtQiRw==",
"requires": {
"sax": ">=0.6.0",
- "xmlbuilder": "^4.1.0"
+ "util.promisify": "~1.0.0",
+ "xmlbuilder": "~11.0.0"
}
},
"xmlbuilder": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz",
- "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=",
- "requires": {
- "lodash": "^4.0.0"
- }
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
},
"xtend": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
- "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
},
"y18n": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
- "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+ "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
"dev": true
},
"yallist": {
@@ -8901,35 +9350,145 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
},
- "yargs": {
- "version": "3.10.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",
- "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=",
- "dev": true,
- "optional": true,
+ "yamljs": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz",
+ "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==",
"requires": {
- "camelcase": "^1.0.2",
- "cliui": "^2.1.0",
- "decamelize": "^1.0.0",
- "window-size": "0.1.0"
+ "argparse": "^1.0.7",
+ "glob": "^7.0.5"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ }
}
},
- "yargs-parser": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz",
- "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=",
- "dev": true,
- "requires": {
- "camelcase": "^4.1.0"
+ "yargs": {
+ "version": "13.3.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz",
+ "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==",
+ "dev": true,
+ "requires": {
+ "cliui": "^5.0.0",
+ "find-up": "^3.0.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^3.0.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^13.1.1"
},
"dependencies": {
- "camelcase": {
+ "ansi-regex": {
"version": "4.1.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
- "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^3.0.0"
+ }
+ },
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
+ },
+ "locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "p-limit": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz",
+ "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.0.0"
+ }
+ },
+ "p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
+ },
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
}
}
+ },
+ "yargs-parser": {
+ "version": "13.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz",
+ "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ },
+ "yargs-unparser": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz",
+ "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==",
+ "dev": true,
+ "requires": {
+ "flat": "^4.1.0",
+ "lodash": "^4.17.15",
+ "yargs": "^13.3.0"
+ }
}
}
}
diff --git a/package.json b/package.json
index c42182d3..a7e72a9c 100644
--- a/package.json
+++ b/package.json
@@ -12,13 +12,15 @@
"build": "babel src -d dist --presets es2015 --copy-files",
"sync:db": "./node_modules/.bin/babel-node migrations/sync.js",
"sync:es": "./node_modules/.bin/babel-node migrations/elasticsearch_sync.js",
+ "sync:es:metadata": "./node_modules/.bin/babel-node migrations/elasticsearch_sync.js --index-name metadata",
"migrate:es": "./node_modules/.bin/babel-node migrations/seedElasticsearchIndex.js",
+ "migrate:es:metadata": "./node_modules/.bin/babel-node migrations/helpers/indexMetadataDirectly.js",
"prestart": "npm run -s build",
"start": "node dist",
"startKafkaConsumers": "npm run -s build && node dist/index-kafka.js",
"start:dev": "NODE_ENV=development PORT=8001 nodemon -w src --exec \"babel-node src --presets es2015\" | ./node_modules/.bin/bunyan",
- "test": "NODE_ENV=test npm run lint && NODE_ENV=test npm run sync:es && NODE_ENV=test npm run sync:db && NODE_ENV=test ./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha -- --timeout 10000 --compilers js:babel-core/register $(find src -path '*spec.js*')",
- "test:watch": "NODE_ENV=test ./node_modules/.bin/mocha -w --compilers js:babel-core/register $(find src -path '*spec.js*')",
+ "test": "NODE_ENV=test npm run lint && NODE_ENV=test npm run sync:es && NODE_ENV=test npm run sync:db && NODE_ENV=test ./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha -- --timeout 10000 --require babel-core/register $(find src -path '*spec.js*') --exit",
+ "test:watch": "NODE_ENV=test ./node_modules/.bin/mocha -w --require babel-core/register $(find src -path '*spec.js*')",
"seed": "babel-node src/tests/seed.js --presets es2015",
"demo-data": "babel-node local/seed"
},
@@ -36,8 +38,8 @@
"amqplib": "^0.5.1",
"analytics-node": "^2.1.1",
"app-module-path": "^1.0.7",
- "aws-sdk": "^2.33.0",
- "axios": "^0.17.1",
+ "aws-sdk": "^2.468.0",
+ "axios": "^0.19.0",
"bluebird": "^3.4.1",
"body-parser": "^1.15.0",
"co": "^4.6.0",
@@ -45,7 +47,7 @@
"config": "^1.20.1",
"continuation-local-storage": "^3.1.7",
"cors": "^2.8.4",
- "elasticsearch": "^11.0.1",
+ "elasticsearch": "^16.1.1",
"express": "^4.13.4",
"express-list-routes": "^0.1.4",
"express-request-id": "^1.1.0",
@@ -54,22 +56,24 @@
"http-aws-es": "^4.0.0",
"joi": "^8.0.5",
"jsonwebtoken": "^8.3.0",
- "lodash": "^4.16.4",
+ "lodash": "^4.17.11",
"memwatch-next": "^0.3.0",
"method-override": "^2.3.9",
"moment": "^2.22.2",
- "no-kafka": "^3.2.10",
- "pg": "^4.5.5",
- "pg-native": "^1.10.1",
- "sequelize": "^3.23.0",
+ "no-kafka": "^3.4.3",
+ "pg": "^7.11.0",
+ "pg-native": "^3.0.0",
+ "sequelize": "^5.8.7",
"jsonpath": "^1.0.2",
- "tc-core-library-js": "appirio-tech/tc-core-library-js.git#v2.6",
+ "swagger-ui-express": "^4.0.6",
+ "tc-core-library-js": "appirio-tech/tc-core-library-js.git#v2.6.3",
"traverse": "^0.6.6",
- "urlencode": "^1.1.0"
+ "urlencode": "^1.1.0",
+ "yamljs": "^0.3.0"
},
"devDependencies": {
- "babel-cli": "^6.9.0",
- "babel-core": "^6.11.4",
+ "babel-cli": "^6.26.0",
+ "babel-core": "^6.26.3",
"babel-eslint": "^7.1.1",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-transform-runtime": "^6.23.0",
@@ -80,14 +84,14 @@
"eslint": "^3.16.1",
"eslint-config-airbnb-base": "^11.1.0",
"eslint-plugin-import": "^2.2.0",
- "istanbul": "^1.0.0-alpha.2",
- "mocha": "^2.5.3",
- "nodemon": "^1.9.1",
+ "istanbul": "^1.1.0-alpha.1",
+ "mocha": "^6.1.4",
+ "nodemon": "^1.19.1",
"really-need": "^1.9.2",
- "sequelize-cli": "^2.4.0",
+ "sequelize-cli": "^5.5.0",
"sinon": "^1.17.4",
"sinon-chai": "^2.8.0",
"sleep": "^5.1.0",
- "supertest": "^2.0.0"
+ "supertest": "^4.0.2"
}
}
diff --git a/src/app.js b/src/app.js
index ff6d807c..d5fa7bd7 100644
--- a/src/app.js
+++ b/src/app.js
@@ -8,6 +8,8 @@ import cors from 'cors';
import coreLib from 'tc-core-library-js';
import expressRequestId from 'express-request-id';
import memWatch from 'memwatch-next';
+import swaggerUi from 'swagger-ui-express';
+import YAML from 'yamljs';
import performanceRequestLogger from './middlewares/performanceRequestLogger';
import router from './routes';
import permissions from './permissions';
@@ -15,6 +17,8 @@ import models from './models';
import analytics from './events/analytics';
import busApi from './events/busApi';
+const swaggerDocument = YAML.load('./docs/swagger.yaml');
+
const app = express();
// allows overriding HTTP Method
@@ -130,6 +134,8 @@ busApi(app, logger);
// require('app/permissions')()
permissions();
+app.use(`/${config.apiVersion}/projects/docs`, swaggerUi.serve, swaggerUi.setup(swaggerDocument));
+
// ========================
// Routes
// ========================
@@ -137,6 +143,7 @@ permissions();
app.use(router);
app.routerRef = router;
+
// =======================
// Initialize services
// =======================
diff --git a/src/constants.js b/src/constants.js
index afbcaf99..a31c965b 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -16,7 +16,6 @@ export const WORKSTREAM_STATUS = {
COMPLETED: 'completed',
PAUSED: 'paused',
};
-
export const PROJECT_PHASE_STATUS = PROJECT_STATUS;
export const MILESTONE_STATUS = PROJECT_STATUS;
@@ -104,12 +103,76 @@ export const EVENT = {
MILESTONE_UPDATED: 'milestone.updated',
MILESTONE_REMOVED: 'milestone.removed',
+ MILESTONE_TEMPLATE_ADDED: 'milestone.template.added',
+ MILESTONE_TEMPLATE_UPDATED: 'milestone.template.updated',
+ MILESTONE_TEMPLATE_REMOVED: 'milestone.template.removed',
+
PROJECT_MEMBER_INVITE_CREATED: 'project.member.invite.created',
PROJECT_MEMBER_INVITE_UPDATED: 'project.member.invite.updated',
+ PROJECT_MEMBER_INVITE_REMOVED: 'project.member.invite.deleted',
+
+ // project metadata
+ PROJECT_METADATA_CREATE: 'project.metadata.create',
+ PROJECT_METADATA_UPDATE: 'project.metadata.update',
+ PROJECT_METADATA_DELETE: 'project.metadata.delete',
+
+ // project template
+ PROJECT_TEMPLATE_CREATED: 'project.template.created',
+ PROJECT_TEMPLATE_UPDATED: 'project.template.updated',
+ PROJECT_TEMPLATE_DELETED: 'project.template.deleted',
},
};
export const BUS_API_EVENT = {
+ PROJECT_CREATED: 'project.action.create',
+ PROJECT_UPDATED: 'project.action.update',
+ PROJECT_DELETED: 'project.action.delete',
+
+ PROJECT_MEMBER_ADDED: 'project.action.create',
+ PROJECT_MEMBER_REMOVED: 'project.action.delete',
+ PROJECT_MEMBER_UPDATED: 'project.action.update',
+
+ PROJECT_ATTACHMENT_ADDED: 'project.action.create',
+ PROJECT_ATTACHMENT_REMOVED: 'project.action.delete',
+ PROJECT_ATTACHMENT_UPDATED: 'project.action.update',
+
+ // When phase is added/updated/deleted from the project,
+ // When product is added/deleted from a phase
+ // When product is updated on any field other than specification
+ PROJECT_PHASE_CREATED: 'project.action.create',
+ PROJECT_PHASE_UPDATED: 'project.action.update',
+ PROJECT_PHASE_DELETED: 'project.action.delete',
+
+ // phase product
+ PROJECT_PHASE_PRODUCT_ADDED: 'project.action.create',
+ PROJECT_PHASE_PRODUCT_UPDATED: 'project.action.update',
+ PROJECT_PHASE_PRODUCT_REMOVED: 'project.action.delete',
+
+ // timeline
+ TIMELINE_CREATED: 'project.action.create',
+ TIMELINE_UPDATED: 'project.action.update',
+ TIMELINE_DELETED: 'project.action.delete',
+
+ MILESTONE_ADDED: 'project.action.create',
+ MILESTONE_REMOVED: 'project.action.delete',
+ MILESTONE_UPDATED: 'project.action.update',
+
+ MILESTONE_TEMPLATE_ADDED: 'project.action.create',
+ MILESTONE_TEMPLATE_REMOVED: 'project.action.delete',
+ MILESTONE_TEMPLATE_UPDATED: 'project.action.update',
+
+ // Project Member Invites
+ PROJECT_MEMBER_INVITE_CREATED: 'project.action.create',
+ PROJECT_MEMBER_INVITE_UPDATED: 'project.action.update',
+ PROJECT_MEMBER_INVITE_REMOVED: 'project.action.delete',
+
+ // metadata
+ PROJECT_METADATA_CREATE: 'project.action.create',
+ PROJECT_METADATA_UPDATE: 'project.action.update',
+ PROJECT_METADATA_DELETE: 'project.action.delete',
+};
+
+export const CONNECT_NOTIFICATION_EVENT = {
PROJECT_CREATED: 'connect.notification.project.created',
PROJECT_UPDATED: 'connect.notification.project.updated',
PROJECT_SUBMITTED_FOR_REVIEW: 'connect.notification.project.submittedForReview',
@@ -174,12 +237,6 @@ export const BUS_API_EVENT = {
// When milestone is waiting for customers's input
MILESTONE_WAITING_CUSTOMER: 'connect.notification.project.timeline.milestone.waiting.customer',
- // TC Message Service events
- TOPIC_CREATED: 'connect.notification.project.topic.created',
- TOPIC_UPDATED: 'connect.notification.project.topic.updated',
- POST_CREATED: 'connect.notification.project.post.created',
- POST_UPDATED: 'connect.notification.project.post.edited',
-
// Project Member Invites
PROJECT_MEMBER_INVITE_CREATED: 'connect.notification.project.member.invite.created',
PROJECT_MEMBER_INVITE_REQUESTED: 'connect.notification.project.member.invite.requested',
@@ -187,6 +244,12 @@ export const BUS_API_EVENT = {
PROJECT_MEMBER_INVITE_APPROVED: 'connect.notification.project.member.invite.approved',
PROJECT_MEMBER_INVITE_REJECTED: 'connect.notification.project.member.invite.rejected',
PROJECT_MEMBER_EMAIL_INVITE_CREATED: 'connect.notification.email.project.member.invite.created',
+
+ // TC Message Service events
+ TOPIC_CREATED: 'connect.notification.project.topic.created',
+ TOPIC_UPDATED: 'connect.notification.project.topic.updated',
+ POST_CREATED: 'connect.notification.project.post.created',
+ POST_UPDATED: 'connect.notification.project.post.edited',
};
export const REGEX = {
@@ -258,3 +321,27 @@ export const VALUE_TYPE = {
STRING: 'string',
PERCENTAGE: 'percentage',
};
+
+
+export const RESOURCES = {
+ PROJECT: 'project',
+ PROJECT_TEMPLATE: 'project.template',
+ PROJECT_TYPE: 'project.type',
+ PROJECT_MEMBER: 'project.member',
+ PROJECT_MEMBER_INVITE: 'project.member.invite',
+ ORG_CONFIG: 'project.orgConfig',
+ FORM_VERSION: 'project.form.version',
+ FORM_REVISION: 'project.form.revision',
+ PRICE_CONFIG_VERSION: 'project.priceConfig.version',
+ PRICE_CONFIG_REVISION: 'project.priceConfig.revision',
+ PLAN_CONFIG_VERSION: 'project.planConfig.version',
+ PLAN_CONFIG_REVISION: 'project.planConfig.revision',
+ PRODUCT_TEMPLATE: 'product.template',
+ PRODUCT_CATEGORY: 'product.category',
+ PHASE: 'project.phase',
+ PHASE_PRODUCT: 'project.phase.product',
+ TIMELINE: 'timeline',
+ MILESTONE: 'milestone',
+ MILESTONE_TEMPLATE: 'milestone.template',
+ ATTACHMENT: 'attachment',
+};
diff --git a/src/events/analytics.js b/src/events/analytics.js
index 8574f5d7..0b10c975 100644
--- a/src/events/analytics.js
+++ b/src/events/analytics.js
@@ -60,8 +60,8 @@ module.exports = (analyticsKey, app) => {
});
});
- app.on(EVENT.ROUTING_KEY.PROJECT_DELETED, ({ req, id }) => {
- track(req, PROJECT_CREATED, { id });
+ app.on(EVENT.ROUTING_KEY.PROJECT_DELETED, ({ req, project }) => {
+ track(req, PROJECT_CREATED, { id: project.id });
});
app.on(EVENT.ROUTING_KEY.PROJECT_UPDATED, ({ req, original, updated }) => {
@@ -84,11 +84,15 @@ module.exports = (analyticsKey, app) => {
}
});
- app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED, ({ req, member }) => {
+ app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED, ({ req, resource }) => {
+ const member = _.omit(resource, 'resource');
+
track(req, PROJECT_MEMBER_ADDED, { role: member.role });
});
- app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, ({ req, member }) => {
+ app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, ({ req, resource }) => {
+ const member = _.omit(resource, 'resource');
+
track(req, PROJECT_MEMBER_REMOVED, { role: member.role });
});
};
diff --git a/src/events/busApi.js b/src/events/busApi.js
index 926285e8..3bc92cae 100644
--- a/src/events/busApi.js
+++ b/src/events/busApi.js
@@ -1,8 +1,17 @@
import _ from 'lodash';
+import moment from 'moment';
import config from 'config';
-import { EVENT, BUS_API_EVENT, PROJECT_STATUS, PROJECT_PHASE_STATUS, PROJECT_MEMBER_ROLE, MILESTONE_STATUS,
- INVITE_STATUS, ROUTES }
- from '../constants';
+import {
+ EVENT,
+ BUS_API_EVENT,
+ CONNECT_NOTIFICATION_EVENT,
+ PROJECT_STATUS,
+ PROJECT_PHASE_STATUS,
+ PROJECT_MEMBER_ROLE,
+ ROUTES,
+ MILESTONE_STATUS,
+ INVITE_STATUS,
+} from '../constants';
import { createEvent } from '../services/busApi';
import models from '../models';
import util from '../util';
@@ -11,13 +20,13 @@ import util from '../util';
* Map of project status and event name sent to bus api
*/
const mapEventTypes = {
- [PROJECT_STATUS.DRAFT]: BUS_API_EVENT.PROJECT_CREATED,
- [PROJECT_STATUS.IN_REVIEW]: BUS_API_EVENT.PROJECT_SUBMITTED_FOR_REVIEW,
- [PROJECT_STATUS.REVIEWED]: BUS_API_EVENT.PROJECT_APPROVED,
- [PROJECT_STATUS.COMPLETED]: BUS_API_EVENT.PROJECT_COMPLETED,
- [PROJECT_STATUS.CANCELLED]: BUS_API_EVENT.PROJECT_CANCELED,
- [PROJECT_STATUS.PAUSED]: BUS_API_EVENT.PROJECT_PAUSED,
- [PROJECT_STATUS.ACTIVE]: BUS_API_EVENT.PROJECT_ACTIVE,
+ [PROJECT_STATUS.DRAFT]: CONNECT_NOTIFICATION_EVENT.PROJECT_CREATED,
+ [PROJECT_STATUS.IN_REVIEW]: CONNECT_NOTIFICATION_EVENT.PROJECT_SUBMITTED_FOR_REVIEW,
+ [PROJECT_STATUS.REVIEWED]: CONNECT_NOTIFICATION_EVENT.PROJECT_APPROVED,
+ [PROJECT_STATUS.COMPLETED]: CONNECT_NOTIFICATION_EVENT.PROJECT_COMPLETED,
+ [PROJECT_STATUS.CANCELLED]: CONNECT_NOTIFICATION_EVENT.PROJECT_CANCELED,
+ [PROJECT_STATUS.PAUSED]: CONNECT_NOTIFICATION_EVENT.PROJECT_PAUSED,
+ [PROJECT_STATUS.ACTIVE]: CONNECT_NOTIFICATION_EVENT.PROJECT_ACTIVE,
};
/**
@@ -49,7 +58,15 @@ module.exports = (app, logger) => {
logger.debug('receive PROJECT_DRAFT_CREATED event');
// send event to bus api
- createEvent(BUS_API_EVENT.PROJECT_CREATED, {
+ createEvent(BUS_API_EVENT.PROJECT_CREATED, _.assign(project, {
+ refCode: _.get(project, 'details.utm.code'),
+ projectUrl: connectProjectUrl(project.id),
+ }), logger);
+
+ /*
+ Send event for Notification Service
+ */
+ createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_CREATED, {
projectId: project.id,
projectName: project.name,
refCode: _.get(project, 'details.utm.code'),
@@ -65,6 +82,14 @@ module.exports = (app, logger) => {
app.on(EVENT.ROUTING_KEY.PROJECT_UPDATED, ({ req, original, updated }) => {
logger.debug('receive PROJECT_UPDATED event');
+ createEvent(BUS_API_EVENT.PROJECT_UPDATED, _.assign(updated, {
+ refCode: _.get(updated, 'details.utm.code'),
+ projectUrl: connectProjectUrl(updated.id),
+ }), logger);
+
+ /*
+ Send event for Notification Service
+ */
if (original.status !== updated.status) {
logger.debug(`project status is updated from ${original.status} to ${updated.status}`);
createEvent(mapEventTypes[updated.status], {
@@ -80,7 +105,7 @@ module.exports = (app, logger) => {
!_.isEqual(original.name, updated.name) ||
!_.isEqual(original.description, updated.description)) {
logger.debug('project spec is updated');
- createEvent(BUS_API_EVENT.PROJECT_SPECIFICATION_MODIFIED, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_SPECIFICATION_MODIFIED, {
projectId: updated.id,
projectName: updated.name,
refCode: _.get(updated, 'details.utm.code'),
@@ -90,7 +115,7 @@ module.exports = (app, logger) => {
}, logger);
} else if (!_.isEqual(original.bookmarks, updated.bookmarks)) {
logger.debug('project bookmarks is updated');
- createEvent(BUS_API_EVENT.PROJECT_LINK_CREATED, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_LINK_CREATED, {
projectId: updated.id,
projectName: updated.name,
refCode: _.get(updated, 'details.utm.code'),
@@ -104,7 +129,7 @@ module.exports = (app, logger) => {
const watchProperties = ['status', 'details', 'name', 'description', 'bookmarks'];
if (!_.isEqual(_.pick(original, watchProperties),
_.pick(updated, watchProperties))) {
- createEvent(BUS_API_EVENT.PROJECT_UPDATED, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_UPDATED, {
projectId: updated.id,
projectName: updated.name,
refCode: _.get(updated, 'details.utm.code'),
@@ -115,24 +140,68 @@ module.exports = (app, logger) => {
}
});
+ /**
+ * PROJECT_DELETED
+ */
+ app.on(EVENT.ROUTING_KEY.PROJECT_DELETED, ({ req, project }) => { // eslint-disable-line no-unused-vars
+ logger.debug('receive PROJECT_DELETED event');
+
+ createEvent(BUS_API_EVENT.PROJECT_DELETED, project, logger);
+ });
+
+ /**
+ * PROJECT_METADATA_CREATE
+ */
+ app.on(EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE, ({ req, resource }) => { // eslint-disable-line no-unused-vars
+ logger.debug('receive PROJECT_METADATA_CREATE event');
+
+ // send event to bus api
+ createEvent(BUS_API_EVENT.PROJECT_METADATA_CREATE, resource, logger);
+ });
+
+ /**
+ * PROJECT_METADATA_UPDATE
+ */
+ app.on(EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE, ({ req, resource }) => { // eslint-disable-line no-unused-vars
+ logger.debug('receive PROJECT_METADATA_UPDATE event');
+
+ createEvent(BUS_API_EVENT.PROJECT_METADATA_UPDATE, resource, logger);
+ });
+
+ /**
+ * PROJECT_METADATA_DELETE
+ */
+ app.on(EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE, ({ req, resource }) => { // eslint-disable-line no-unused-vars
+ logger.debug('receive PROJECT_METADATA_DELETE event');
+
+ createEvent(BUS_API_EVENT.PROJECT_METADATA_DELETE, resource, logger);
+ });
+
/**
* PROJECT_MEMBER_ADDED
*/
- app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED, ({ req, member }) => {
+ app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED, ({ req, resource }) => {
logger.debug('receive PROJECT_MEMBER_ADDED event');
+ createEvent(BUS_API_EVENT.PROJECT_MEMBER_ADDED, resource, logger);
+
+ /*
+ Send event for Notification Service
+ */
let eventType;
+ const member = _.omit(resource, 'resource');
+
if ([
PROJECT_MEMBER_ROLE.MANAGER,
PROJECT_MEMBER_ROLE.PROJECT_MANAGER,
PROJECT_MEMBER_ROLE.PROGRAM_MANAGER,
PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT,
].includes(member.role)) {
- eventType = BUS_API_EVENT.MEMBER_JOINED_MANAGER;
+ eventType = CONNECT_NOTIFICATION_EVENT.MEMBER_JOINED_MANAGER;
} else if (member.role === PROJECT_MEMBER_ROLE.COPILOT) {
- eventType = BUS_API_EVENT.MEMBER_JOINED_COPILOT;
+ eventType = CONNECT_NOTIFICATION_EVENT.MEMBER_JOINED_COPILOT;
} else {
- eventType = BUS_API_EVENT.MEMBER_JOINED;
+ eventType = CONNECT_NOTIFICATION_EVENT.MEMBER_JOINED;
}
const projectId = _.parseInt(req.params.projectId);
@@ -149,7 +218,7 @@ module.exports = (app, logger) => {
initiatorUserId: req.authUser.userId,
}, logger);
- createEvent(BUS_API_EVENT.PROJECT_TEAM_UPDATED, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED, {
projectId: project.id,
projectName: project.name,
refCode: _.get(project, 'details.utm.code'),
@@ -163,14 +232,20 @@ module.exports = (app, logger) => {
/**
* PROJECT_MEMBER_REMOVED
*/
- app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, ({ req, member }) => {
+ app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, ({ req, resource }) => {
logger.debug('receive PROJECT_MEMBER_REMOVED event');
+ createEvent(BUS_API_EVENT.PROJECT_MEMBER_REMOVED, resource, logger);
+
+ /*
+ Send event for Notification Service
+ */
let eventType;
+ const member = _.omit(resource, 'resource');
if (member.userId === req.authUser.userId) {
- eventType = BUS_API_EVENT.MEMBER_LEFT;
+ eventType = CONNECT_NOTIFICATION_EVENT.MEMBER_LEFT;
} else {
- eventType = BUS_API_EVENT.MEMBER_REMOVED;
+ eventType = CONNECT_NOTIFICATION_EVENT.MEMBER_REMOVED;
}
const projectId = _.parseInt(req.params.projectId);
@@ -188,7 +263,7 @@ module.exports = (app, logger) => {
initiatorUserId: req.authUser.userId,
}, logger);
- createEvent(BUS_API_EVENT.PROJECT_TEAM_UPDATED, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED, {
projectId: project.id,
projectName: project.name,
refCode: _.get(project, 'details.utm.code'),
@@ -203,10 +278,17 @@ module.exports = (app, logger) => {
/**
* PROJECT_MEMBER_UPDATED
*/
- app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_UPDATED, ({ req, original, updated }) => { // eslint-disable-line no-unused-vars
+ app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_UPDATED, ({ req, resource, originalResource }) => {
logger.debug('receive PROJECT_MEMBER_UPDATED event');
+ createEvent(BUS_API_EVENT.PROJECT_MEMBER_UPDATED, resource, logger);
+
+ /*
+ Send event for Notification Service
+ */
const projectId = _.parseInt(req.params.projectId);
+ const updated = _.omit(resource, 'resource');
+ const original = _.omit(originalResource, 'resource');
models.Project.findOne({
where: { id: projectId },
@@ -214,7 +296,7 @@ module.exports = (app, logger) => {
.then((project) => {
if (project) {
if (updated.isPrimary && !original.isPrimary) {
- createEvent(BUS_API_EVENT.MEMBER_ASSIGNED_AS_OWNER, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.MEMBER_ASSIGNED_AS_OWNER, {
projectId,
projectName: project.name,
refCode: _.get(project, 'details.utm.code'),
@@ -224,7 +306,7 @@ module.exports = (app, logger) => {
}, logger);
}
- createEvent(BUS_API_EVENT.PROJECT_TEAM_UPDATED, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED, {
projectId: project.id,
projectName: project.name,
refCode: _.get(project, 'details.utm.code'),
@@ -239,16 +321,22 @@ module.exports = (app, logger) => {
/**
* PROJECT_ATTACHMENT_ADDED
*/
- app.on(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED, ({ req, attachment }) => {
+ app.on(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED, ({ req, resource }) => {
logger.debug('receive PROJECT_ATTACHMENT_ADDED event');
+ createEvent(BUS_API_EVENT.PROJECT_ATTACHMENT_ADDED, resource, logger);
+
+ /*
+ Send event for Notification Service
+ */
const projectId = _.parseInt(req.params.projectId);
+ const attachment = _.omit(resource, 'resource');
models.Project.findOne({
where: { id: projectId },
})
.then((project) => {
- createEvent(BUS_API_EVENT.PROJECT_FILE_UPLOADED, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_FILE_UPLOADED, {
projectId,
projectName: project.name,
refCode: _.get(project, 'details.utm.code'),
@@ -260,7 +348,7 @@ module.exports = (app, logger) => {
initiatorUserId: req.authUser.userId,
}, logger);
- createEvent(BUS_API_EVENT.PROJECT_FILES_UPDATED, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_FILES_UPDATED, {
projectId: project.id,
projectName: project.name,
refCode: _.get(project, 'details.utm.code'),
@@ -271,20 +359,24 @@ module.exports = (app, logger) => {
}).catch(err => null); // eslint-disable-line no-unused-vars
});
-
/**
* PROJECT_ATTACHMENT_UPDATED
*/
- app.on(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_UPDATED, ({ req }) => {
+ app.on(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_UPDATED, ({ req, resource }) => {
logger.debug('receive PROJECT_ATTACHMENT_UPDATED event');
+ createEvent(BUS_API_EVENT.PROJECT_ATTACHMENT_UPDATED, resource, logger);
+
+ /*
+ Send event for Notification Service
+ */
const projectId = _.parseInt(req.params.projectId);
models.Project.findOne({
where: { id: projectId },
})
.then((project) => {
- createEvent(BUS_API_EVENT.PROJECT_FILES_UPDATED, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_FILES_UPDATED, {
projectId: project.id,
projectName: project.name,
refCode: _.get(project, 'details.utm.code'),
@@ -298,16 +390,21 @@ module.exports = (app, logger) => {
/**
* PROJECT_ATTACHMENT_REMOVED
*/
- app.on(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_REMOVED, ({ req }) => {
+ app.on(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_REMOVED, ({ req, resource }) => {
logger.debug('receive PROJECT_ATTACHMENT_REMOVED event');
+ createEvent(BUS_API_EVENT.PROJECT_ATTACHMENT_REMOVED, resource, logger);
+
+ /*
+ Send event for Notification Service
+ */
const projectId = _.parseInt(req.params.projectId);
models.Project.findOne({
where: { id: projectId },
})
.then((project) => {
- createEvent(BUS_API_EVENT.PROJECT_FILES_UPDATED, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_FILES_UPDATED, {
projectId: project.id,
projectName: project.name,
refCode: _.get(project, 'details.utm.code'),
@@ -335,7 +432,7 @@ module.exports = (app, logger) => {
}).then(((count) => {
// only send the plan ready event when this is the only reviewed phase in the project
if (count === 1) {
- createEvent(BUS_API_EVENT.PROJECT_PLAN_READY, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_READY, {
projectId: project.id,
phaseId: phase.id,
projectName: project.name,
@@ -351,16 +448,22 @@ module.exports = (app, logger) => {
/**
* PROJECT_PHASE_ADDED
*/
- app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED, ({ req, created }) => { // eslint-disable-line no-unused-vars
+ app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED, ({ req, resource }) => {
logger.debug('receive PROJECT_PHASE_ADDED event');
+ createEvent(BUS_API_EVENT.PROJECT_PHASE_CREATED, resource, logger);
+
+ /*
+ Send event for Notification Service
+ */
const projectId = _.parseInt(req.params.projectId);
+ const created = _.omit(resource, 'resource');
models.Project.findOne({
where: { id: projectId },
})
.then((project) => {
- createEvent(BUS_API_EVENT.PROJECT_PLAN_UPDATED, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, {
projectId,
projectName: project.name,
refCode: _.get(project, 'details.utm.code'),
@@ -377,16 +480,22 @@ module.exports = (app, logger) => {
/**
* PROJECT_PHASE_REMOVED
*/
- app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED, ({ req, deleted }) => { // eslint-disable-line no-unused-vars
+ app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED, ({ req, resource }) => {
logger.debug('receive PROJECT_PHASE_REMOVED event');
+ createEvent(BUS_API_EVENT.PROJECT_PHASE_DELETED, resource, logger);
+
+ /*
+ Send event for Notification Service
+ */
const projectId = _.parseInt(req.params.projectId);
+ const deleted = _.omit(resource, 'resource');
models.Project.findOne({
where: { id: projectId },
})
.then((project) => {
- createEvent(BUS_API_EVENT.PROJECT_PLAN_UPDATED, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, {
projectId,
projectName: project.name,
refCode: _.get(project, 'details.utm.code'),
@@ -402,124 +511,86 @@ module.exports = (app, logger) => {
/**
* PROJECT_PHASE_UPDATED
*/
- app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, ({ req, original, updated, route }) => { // eslint-disable-line no-unused-vars
+ app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, ({ req, resource, originalResource, route, skipNotification }) => { // eslint-disable-line no-unused-vars
logger.debug('receive PROJECT_PHASE_UPDATED event');
- const projectId = _.parseInt(req.params.projectId);
- const phaseId = _.parseInt(req.params.phaseId);
-
- models.Project.findOne({
- where: { id: projectId },
- })
- .then((project) => {
- logger.debug(`Fetched project ${projectId} for the phase ${phaseId}`);
- const eventsMap = {};
- [
- ['duration', BUS_API_EVENT.PROJECT_PLAN_UPDATED],
- ['startDate', BUS_API_EVENT.PROJECT_PLAN_UPDATED],
- ['spentBudget', route === ROUTES.PHASES.UPDATE
- ? BUS_API_EVENT.PROJECT_PHASE_UPDATE_PAYMENT
- : BUS_API_EVENT.PROJECT_WORK_UPDATE_PAYMENT,
- ],
- ['progress', [route === ROUTES.PHASES.UPDATE
- ? BUS_API_EVENT.PROJECT_PHASE_UPDATE_PROGRESS
- : BUS_API_EVENT.PROJECT_WORK_UPDATE_PROGRESS,
- BUS_API_EVENT.PROJECT_PROGRESS_MODIFIED,
- ]],
- ['details', route === ROUTES.PHASES.UPDATE
- ? BUS_API_EVENT.PROJECT_PHASE_UPDATE_SCOPE
- : BUS_API_EVENT.PROJECT_WORK_UPDATE_SCOPE,
- ],
- ['status', route === ROUTES.PHASES.UPDATE
- ? BUS_API_EVENT.PROJECT_PHASE_TRANSITION_ACTIVE
- : BUS_API_EVENT.PROJECT_WORK_TRANSITION_ACTIVE,
- PROJECT_PHASE_STATUS.ACTIVE,
- ],
- ['status', route === ROUTES.PHASES.UPDATE
- ? BUS_API_EVENT.PROJECT_PHASE_TRANSITION_COMPLETED
- : BUS_API_EVENT.PROJECT_WORK_TRANSITION_COMPLETED,
- PROJECT_PHASE_STATUS.COMPLETED,
- ],
- // ideally we should validate the old value being 'DRAFT' but there is no other status from which
- // we can move phase to REVIEWED status
- ['status', BUS_API_EVENT.PROJECT_PLAN_UPDATED, PROJECT_PHASE_STATUS.REVIEWED],
- // ideally we should validate the old value being 'REVIEWED' but there is no other status from which
- // we can move phase to DRAFT status
- ['status', BUS_API_EVENT.PROJECT_PLAN_UPDATED, PROJECT_PHASE_STATUS.DRAFT],
- ].forEach(([key, events, sendIfNewEqual]) => {
- // eslint-disable-next-line no-param-reassign
- events = Array.isArray(events) ? events : [events];
- // eslint-disable-next-line no-param-reassign
- events = _.filter(events, e => !eventsMap[e]);
-
- // send event(s) only if the target field's value was updated, or when an update matches a "sendIfNewEqual" value
- if ((!sendIfNewEqual && !_.isEqual(original[key], updated[key])) ||
- (original[key] !== sendIfNewEqual && updated[key] === sendIfNewEqual)) {
- events.forEach(event => createEvent(event, {
- projectId,
- phaseId,
- projectUrl: connectProjectUrl(projectId),
- originalPhase: original,
- updatedPhase: updated,
- projectName: project.name,
- userId: req.authUser.userId,
- initiatorUserId: req.authUser.userId,
- allowedUsers: updated.status === PROJECT_PHASE_STATUS.DRAFT ?
- util.getTopcoderProjectMembers(project.members) : null,
- }, logger));
- events.forEach((event) => { eventsMap[event] = true; });
- }
- });
-
- return sendPlanReadyEventIfNeeded(req, project, updated);
- }).catch(err => null); // eslint-disable-line no-unused-vars
- });
-
- /**
- * PROJECT_PHASE_PRODUCT_UPDATED
- */
- app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED, ({ req, original, updated, route }) => { // eslint-disable-line no-unused-vars
- logger.debug('receive PROJECT_PHASE_PRODUCT_UPDATED event');
+ createEvent(BUS_API_EVENT.PROJECT_PHASE_UPDATED, resource, logger);
- const projectId = _.parseInt(req.params.projectId);
-
- models.Project.findOne({
- where: { id: projectId },
- })
- .then((project) => {
- // Spec changes
- if (!_.isEqual(original.details, updated.details)) {
- logger.debug(`Spec changed for product id ${updated.id}`);
-
- const busApiEvent = route === 'updatePhaseProducts'
- ? BUS_API_EVENT.PROJECT_PRODUCT_SPECIFICATION_MODIFIED
- : BUS_API_EVENT.PROJECT_WORKITEM_SPECIFICATION_MODIFIED;
-
- createEvent(busApiEvent, {
- projectId,
- projectName: project.name,
- refCode: _.get(project, 'details.utm.code'),
- projectUrl: connectProjectUrl(projectId),
- userId: req.authUser.userId,
- initiatorUserId: req.authUser.userId,
- }, logger);
- }
+ /*
+ Send event for Notification Service
+ */
+ if (!skipNotification) {
+ const projectId = _.parseInt(req.params.projectId);
+ const phaseId = _.parseInt(req.params.phaseId);
+ const updated = _.omit(resource, 'resource');
+ const original = _.omit(originalResource, 'resource');
- const watchProperties = ['name', 'estimatedPrice', 'actualPrice', 'details'];
- if (!_.isEqual(_.pick(original, watchProperties),
- _.pick(updated, watchProperties))) {
- createEvent(BUS_API_EVENT.PROJECT_PLAN_UPDATED, {
- projectId,
- projectName: project.name,
- refCode: _.get(project, 'details.utm.code'),
- projectUrl: connectProjectUrl(projectId),
- userId: req.authUser.userId,
- initiatorUserId: req.authUser.userId,
- allowedUsers: updated.status === PROJECT_PHASE_STATUS.DRAFT ?
- util.getTopcoderProjectMembers(project.members) : null,
- }, logger);
- }
- }).catch(err => null); // eslint-disable-line no-unused-vars
+ models.Project.findOne({
+ where: { id: projectId },
+ })
+ .then((project) => {
+ logger.debug(`Fetched project ${projectId} for the phase ${phaseId}`);
+ const eventsMap = {};
+ [
+ ['duration', CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED],
+ ['startDate', CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED],
+ ['spentBudget', route === ROUTES.PHASES.UPDATE
+ ? CONNECT_NOTIFICATION_EVENT.PROJECT_PHASE_UPDATE_PAYMENT
+ : CONNECT_NOTIFICATION_EVENT.PROJECT_WORK_UPDATE_PAYMENT,
+ ],
+ ['progress', [route === ROUTES.PHASES.UPDATE
+ ? CONNECT_NOTIFICATION_EVENT.PROJECT_PHASE_UPDATE_PROGRESS
+ : CONNECT_NOTIFICATION_EVENT.PROJECT_WORK_UPDATE_PROGRESS,
+ CONNECT_NOTIFICATION_EVENT.PROJECT_PROGRESS_MODIFIED,
+ ]],
+ ['details', route === ROUTES.PHASES.UPDATE
+ ? CONNECT_NOTIFICATION_EVENT.PROJECT_PHASE_UPDATE_SCOPE
+ : CONNECT_NOTIFICATION_EVENT.PROJECT_WORK_UPDATE_SCOPE,
+ ],
+ ['status', route === ROUTES.PHASES.UPDATE
+ ? CONNECT_NOTIFICATION_EVENT.PROJECT_PHASE_TRANSITION_ACTIVE
+ : CONNECT_NOTIFICATION_EVENT.PROJECT_WORK_TRANSITION_ACTIVE,
+ PROJECT_PHASE_STATUS.ACTIVE,
+ ],
+ ['status', route === ROUTES.PHASES.UPDATE
+ ? CONNECT_NOTIFICATION_EVENT.PROJECT_PHASE_TRANSITION_COMPLETED
+ : CONNECT_NOTIFICATION_EVENT.PROJECT_WORK_TRANSITION_COMPLETED,
+ PROJECT_PHASE_STATUS.COMPLETED,
+ ],
+ // ideally we should validate the old value being 'DRAFT' but there is no other status from which
+ // we can move phase to REVIEWED status
+ ['status', CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, PROJECT_PHASE_STATUS.REVIEWED],
+ // ideally we should validate the old value being 'REVIEWED' but there is no other status from which
+ // we can move phase to DRAFT status
+ ['status', CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, PROJECT_PHASE_STATUS.DRAFT],
+ ].forEach(([key, events, sendIfNewEqual]) => {
+ // eslint-disable-next-line no-param-reassign
+ events = Array.isArray(events) ? events : [events];
+ // eslint-disable-next-line no-param-reassign
+ events = _.filter(events, e => !eventsMap[e]);
+
+ // send event(s) only if the target field's value was updated, or when an update matches a "sendIfNewEqual" value
+ if ((!sendIfNewEqual && !_.isEqual(original[key], updated[key])) ||
+ (original[key] !== sendIfNewEqual && updated[key] === sendIfNewEqual)) {
+ events.forEach(event => createEvent(event, {
+ projectId,
+ phaseId,
+ projectUrl: connectProjectUrl(projectId),
+ originalPhase: original,
+ updatedPhase: updated,
+ projectName: project.name,
+ userId: req.authUser.userId,
+ initiatorUserId: req.authUser.userId,
+ allowedUsers: updated.status === PROJECT_PHASE_STATUS.DRAFT ?
+ util.getTopcoderProjectMembers(project.members) : null,
+ }, logger));
+ events.forEach((event) => { eventsMap[event] = true; });
+ }
+ });
+
+ return sendPlanReadyEventIfNeeded(req, project, updated);
+ }).catch(err => null); // eslint-disable-line no-unused-vars
+ }
});
/**
@@ -534,7 +605,7 @@ module.exports = (app, logger) => {
function sendMilestoneNotification(req, original, updated, project, timeline) {
logger.debug('sendMilestoneNotification', original, updated);
// throw generic milestone updated bus api event
- createEvent(BUS_API_EVENT.MILESTONE_UPDATED, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.MILESTONE_UPDATED, {
projectId: project.id,
projectName: project.name,
refCode: _.get(project, 'details.utm.code'),
@@ -549,11 +620,11 @@ module.exports = (app, logger) => {
if (original.status !== updated.status) {
let event;
if (updated.status === MILESTONE_STATUS.COMPLETED) {
- event = BUS_API_EVENT.MILESTONE_TRANSITION_COMPLETED;
+ event = CONNECT_NOTIFICATION_EVENT.MILESTONE_TRANSITION_COMPLETED;
} else if (updated.status === MILESTONE_STATUS.ACTIVE) {
- event = BUS_API_EVENT.MILESTONE_TRANSITION_ACTIVE;
+ event = CONNECT_NOTIFICATION_EVENT.MILESTONE_TRANSITION_ACTIVE;
} else if (updated.status === MILESTONE_STATUS.PAUSED) {
- event = BUS_API_EVENT.MILESTONE_TRANSITION_PAUSED;
+ event = CONNECT_NOTIFICATION_EVENT.MILESTONE_TRANSITION_PAUSED;
}
if (event) {
@@ -575,7 +646,7 @@ module.exports = (app, logger) => {
const originalWaiting = _.get(original, 'details.metadata.waitingForCustomer', false);
const updatedWaiting = _.get(updated, 'details.metadata.waitingForCustomer', false);
if (!originalWaiting && updatedWaiting) {
- createEvent(BUS_API_EVENT.MILESTONE_WAITING_CUSTOMER, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.MILESTONE_WAITING_CUSTOMER, {
projectId: project.id,
projectName: project.name,
refCode: _.get(project, 'details.utm.code'),
@@ -592,17 +663,23 @@ module.exports = (app, logger) => {
/**
* MILESTONE_ADDED.
*/
- app.on(EVENT.ROUTING_KEY.MILESTONE_ADDED, ({ req, created }) => {
+ app.on(EVENT.ROUTING_KEY.MILESTONE_ADDED, ({ req, resource }) => { // eslint-disable-line no-unused-vars
logger.debug('receive MILESTONE_ADDED event');
+ createEvent(BUS_API_EVENT.MILESTONE_ADDED, resource, logger);
+
+ /*
+ Send event for Notification Service
+ */
const projectId = _.parseInt(req.params.projectId);
+ const created = _.omit(resource, 'resource');
models.Project.findOne({
where: { id: projectId },
})
.then((project) => {
if (project) {
- createEvent(BUS_API_EVENT.MILESTONE_ADDED, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.MILESTONE_ADDED, {
projectId,
projectName: project.name,
refCode: _.get(project, 'details.utm.code'),
@@ -617,84 +694,90 @@ module.exports = (app, logger) => {
.catch(err => null); // eslint-disable-line no-unused-vars
});
- /**
+ /**
* MILESTONE_UPDATED.
*/
+ app.on(EVENT.ROUTING_KEY.MILESTONE_UPDATED, ({
+ req,
+ resource,
+ originalResource,
+ cascadedUpdates,
+ skipNotification,
+ }) => { // eslint-disable-line no-unused-vars
+ logger.debug(`receive MILESTONE_UPDATED event for milestone ${resource.id}`);
+
+ createEvent(BUS_API_EVENT.MILESTONE_UPDATED, resource, logger);
+
+ /*
+ Send event for Notification Service
+ */
+ if (!skipNotification) {
+ const projectId = _.parseInt(req.params.projectId);
+ const timeline = _.omit(req.timeline.toJSON(), 'deletedAt', 'deletedBy');
+ const updated = _.omit(resource, 'resource');
+ const original = _.omit(originalResource, 'resource');
- /**
- * Handlers for updated milestones which sends events to Kafka
- *
- * @param {String} eventName event name which causes calling this method
- * @param {Object} params params
- * @param {Object} params.req request object
- * @param {Object} params.original original milestone object
- * @param {Object} params.updated updated milestone object
- * @param {Object} params.cascadedUpdates milestones updated cascaded
- *
- * @return {undefined}
- */
- function handleMilestoneUpdated(eventName, { req, original, updated, cascadedUpdates }) {
- logger.debug(`receive ${eventName} event for milestone ${original.id}`);
-
- const projectId = _.parseInt(req.params.projectId);
- const timeline = _.omit(req.timeline.toJSON(), 'deletedAt', 'deletedBy');
-
- models.Project.findOne({
- where: { id: projectId },
- })
- .then((project) => {
- logger.debug(`Found project with id ${projectId}`);
- return models.Milestone.getTimelineDuration(timeline.id)
- .then(({ duration, progress }) => {
- timeline.duration = duration;
- timeline.progress = progress;
- sendMilestoneNotification(req, original, updated, project, timeline);
-
- logger.debug('cascadedUpdates', cascadedUpdates);
- if (cascadedUpdates && cascadedUpdates.milestones && cascadedUpdates.milestones.length > 0) {
- _.each(cascadedUpdates.milestones, cascadedUpdate =>
- sendMilestoneNotification(req, cascadedUpdate.original, cascadedUpdate.updated, project, timeline),
- );
- }
-
- // if timeline is modified
- if (cascadedUpdates && cascadedUpdates.timeline) {
- const cTimeline = cascadedUpdates.timeline;
- // if endDate of the timeline is modified, raise TIMELINE_ADJUSTED event
- if (cTimeline.original.endDate !== cTimeline.updated.endDate) {
- // Raise Timeline changed event
- createEvent(BUS_API_EVENT.TIMELINE_ADJUSTED, {
- projectId: project.id,
- projectName: project.name,
- refCode: _.get(project, 'details.utm.code'),
- projectUrl: connectProjectUrl(project.id),
- originalTimeline: cTimeline.original,
- updatedTimeline: cTimeline.updated,
- userId: req.authUser.userId,
- initiatorUserId: req.authUser.userId,
- }, logger);
+ models.Project.findOne({
+ where: { id: projectId },
+ })
+ .then((project) => {
+ logger.debug(`Found project with id ${projectId}`);
+ return models.Milestone.getTimelineDuration(timeline.id)
+ .then(({ duration, progress }) => {
+ timeline.duration = duration;
+ timeline.progress = progress;
+ sendMilestoneNotification(req, original, updated, project, timeline);
+
+ logger.debug('cascadedUpdates', cascadedUpdates);
+ if (cascadedUpdates && cascadedUpdates.milestones && cascadedUpdates.milestones.length > 0) {
+ _.each(cascadedUpdates.milestones, cascadedUpdate =>
+ sendMilestoneNotification(req, cascadedUpdate.original, cascadedUpdate.updated, project, timeline),
+ );
}
- }
- });
- }).catch(err => null); // eslint-disable-line no-unused-vars
- }
- app.on(EVENT.ROUTING_KEY.MILESTONE_UPDATED, handleMilestoneUpdated.bind(null, EVENT.ROUTING_KEY.MILESTONE_UPDATED));
+ // if timeline is modified
+ if (cascadedUpdates && cascadedUpdates.timeline) {
+ const cTimeline = cascadedUpdates.timeline;
+ // if endDate of the timeline is modified, raise TIMELINE_ADJUSTED event
+ if (!moment(cTimeline.original.endDate).isSame(cTimeline.updated.endDate)) {
+ // Raise Timeline changed event
+ createEvent(CONNECT_NOTIFICATION_EVENT.TIMELINE_ADJUSTED, {
+ projectId: project.id,
+ projectName: project.name,
+ refCode: _.get(project, 'details.utm.code'),
+ projectUrl: connectProjectUrl(project.id),
+ originalTimeline: cTimeline.original,
+ updatedTimeline: cTimeline.updated,
+ userId: req.authUser.userId,
+ initiatorUserId: req.authUser.userId,
+ }, logger);
+ }
+ }
+ });
+ }).catch(err => null); // eslint-disable-line no-unused-vars
+ }
+ });
/**
* MILESTONE_REMOVED.
*/
- app.on(EVENT.ROUTING_KEY.MILESTONE_REMOVED, ({ req, deleted }) => {
+ app.on(EVENT.ROUTING_KEY.MILESTONE_REMOVED, ({ req, resource }) => { // eslint-disable-line no-unused-vars
logger.debug('receive MILESTONE_REMOVED event');
- // req.params.projectId is set by validateTimelineIdParam middleware
+
+ createEvent(BUS_API_EVENT.MILESTONE_REMOVED, resource, logger);
+
+ /*
+ Send event for Notification Service
+ */
const projectId = _.parseInt(req.params.projectId);
+ const deleted = _.omit(resource, 'resource');
models.Project.findOne({
where: { id: projectId },
})
.then((project) => {
if (project) {
- createEvent(BUS_API_EVENT.MILESTONE_REMOVED, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.MILESTONE_REMOVED, {
projectId,
projectName: project.name,
refCode: _.get(project, 'details.utm.code'),
@@ -707,8 +790,64 @@ module.exports = (app, logger) => {
}).catch(err => null); // eslint-disable-line no-unused-vars
});
- app.on(EVENT.ROUTING_KEY.TIMELINE_UPDATED, ({ req, original, updated }) => {
+ /**
+ * MILESTONE_TEMPLATE_ADDED.
+ */
+ app.on(EVENT.ROUTING_KEY.MILESTONE_TEMPLATE_ADDED, ({ req, resource }) => { // eslint-disable-line no-unused-vars
+ logger.debug('receive MILESTONE_ADDED event');
+
+ createEvent(BUS_API_EVENT.MILESTONE_TEMPLATE_ADDED, resource, logger);
+ });
+
+ /**
+ * MILESTONE_TEMPLATE_UPDATED.
+ */
+ app.on(EVENT.ROUTING_KEY.MILESTONE_TEMPLATE_UPDATED, ({ req, resource }) => { // eslint-disable-line no-unused-vars
+ logger.debug(`receive MILESTONE_TEMPLATE_UPDATED event for milestone ${resource.id}`);
+
+ createEvent(BUS_API_EVENT.MILESTONE_TEMPLATE_UPDATED, resource, logger);
+ });
+
+ /**
+ * MILESTONE_TEMPLATE_REMOVED.
+ */
+ app.on(EVENT.ROUTING_KEY.MILESTONE_TEMPLATE_REMOVED, ({ req, resource }) => { // eslint-disable-line no-unused-vars
+ logger.debug('receive MILESTONE_TEMPLATE_REMOVED event');
+
+ createEvent(BUS_API_EVENT.MILESTONE_TEMPLATE_REMOVED, resource, logger);
+ });
+
+ /**
+ * TIMELINE_ADDED
+ */
+ app.on(EVENT.ROUTING_KEY.TIMELINE_ADDED, ({ req, resource }) => { // eslint-disable-line no-unused-vars
+ logger.debug('receive TIMELINE_ADDED event');
+
+ createEvent(BUS_API_EVENT.TIMELINE_CREATED, resource, logger);
+ });
+
+ /**
+ * TIMELINE_REMOVED
+ */
+ app.on(EVENT.ROUTING_KEY.TIMELINE_REMOVED, ({ req, resource }) => { // eslint-disable-line no-unused-vars
+ logger.debug('receive TIMELINE_REMOVED event');
+
+ createEvent(BUS_API_EVENT.TIMELINE_DELETED, resource, logger);
+ });
+
+ /**
+ * TIMELINE_UPDATED
+ */
+ app.on(EVENT.ROUTING_KEY.TIMELINE_UPDATED, ({ req, resource, originalResource }) => { // eslint-disable-line no-unused-vars
logger.debug('receive TIMELINE_UPDATED event');
+
+ createEvent(BUS_API_EVENT.TIMELINE_UPDATED, resource, logger);
+
+ /*
+ Send event for Notification Service
+ */
+ const updated = _.omit(resource, 'resource');
+ const original = _.omit(originalResource, 'resource');
// send PROJECT_UPDATED Kafka message when one of the specified below properties changed
const watchProperties = ['startDate', 'endDate'];
if (!_.isEqual(_.pick(original, watchProperties),
@@ -721,7 +860,7 @@ module.exports = (app, logger) => {
})
.then((project) => {
if (project) {
- createEvent(BUS_API_EVENT.TIMELINE_ADJUSTED, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.TIMELINE_ADJUSTED, {
projectId,
projectName: project.name,
refCode: _.get(project, 'details.utm.code'),
@@ -736,16 +875,102 @@ module.exports = (app, logger) => {
}
});
- app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, ({ req, userId, email, status, role }) => {
+ /**
+ * PROJECT_PHASE_PRODUCT_ADDED
+ */
+ app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_ADDED, ({ req, resource }) => { // eslint-disable-line no-unused-vars
+ logger.debug('receive PROJECT_PHASE_PRODUCT_ADDED event');
+
+ createEvent(BUS_API_EVENT.TIMELINE_CREATED, resource, logger);
+ });
+
+ /**
+ * PROJECT_PHASE_PRODUCT_REMOVED
+ */
+ app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_REMOVED, ({ req, resource }) => { // eslint-disable-line no-unused-vars
+ logger.debug('receive PROJECT_PHASE_PRODUCT_REMOVED event');
+
+ createEvent(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_REMOVED, resource, logger);
+ });
+
+ /**
+ * PROJECT_PHASE_PRODUCT_UPDATED
+ */
+ app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED, ({ req, resource, originalResource, route }) => { // eslint-disable-line no-unused-vars
+ logger.debug('receive PROJECT_PHASE_PRODUCT_UPDATED event');
+
+ createEvent(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, resource, logger);
+
+ /*
+ Send event for Notification Service
+ */
+ const projectId = _.parseInt(req.params.projectId);
+ const updated = _.omit(resource, 'resource');
+ const original = _.omit(originalResource, 'resource');
+
+ models.Project.findOne({
+ where: { id: projectId },
+ })
+ .then((project) => {
+ // Spec changes
+ if (!_.isEqual(original.details, updated.details)) {
+ logger.debug(`Spec changed for product id ${updated.id}`);
+
+ const busApiEvent = route === ROUTES.PHASE_PRODUCTS.UPDATE
+ ? CONNECT_NOTIFICATION_EVENT.PROJECT_PRODUCT_SPECIFICATION_MODIFIED
+ : CONNECT_NOTIFICATION_EVENT.PROJECT_WORKITEM_SPECIFICATION_MODIFIED;
+
+ createEvent(busApiEvent, {
+ projectId,
+ projectName: project.name,
+ refCode: _.get(project, 'details.utm.code'),
+ projectUrl: connectProjectUrl(projectId),
+ userId: req.authUser.userId,
+ initiatorUserId: req.authUser.userId,
+ }, logger);
+ }
+
+ const watchProperties = ['name', 'estimatedPrice', 'actualPrice', 'details'];
+ if (!_.isEqual(_.pick(original, watchProperties),
+ _.pick(updated, watchProperties))) {
+ createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, {
+ projectId,
+ projectName: project.name,
+ refCode: _.get(project, 'details.utm.code'),
+ projectUrl: connectProjectUrl(projectId),
+ userId: req.authUser.userId,
+ initiatorUserId: req.authUser.userId,
+ allowedUsers: updated.status === PROJECT_PHASE_STATUS.DRAFT ?
+ util.getTopcoderProjectMembers(project.members) : null,
+ }, logger);
+ }
+ }).catch(err => null); // eslint-disable-line no-unused-vars
+ });
+
+ /**
+ * PROJECT_MEMBER_INVITE_CREATED
+ */
+ app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, ({ req, resource }) => { // eslint-disable-line no-unused-vars
logger.debug('receive PROJECT_MEMBER_INVITE_CREATED event');
+
+ createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, resource, logger);
+
+ /*
+ Send event for Notification Service
+ */
const projectId = _.parseInt(req.params.projectId);
+ const userId = resource.userId;
+ const email = resource.email;
+ const status = resource.status;
+ const role = resource.role;
+
models.Project.findOne({
where: { id: projectId },
})
.then((project) => {
logger.debug(util.isSSO);
if (status === INVITE_STATUS.REQUESTED) {
- createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_REQUESTED, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_REQUESTED, {
projectId,
userId,
email,
@@ -755,15 +980,15 @@ module.exports = (app, logger) => {
}, logger);
} else {
// send event to bus api
- logger.debug(JSON.stringify({
+ logger.debug({
projectId,
userId,
email,
role,
initiatorUserId: req.authUser.userId,
isSSO: util.isSSO(project),
- }));
- createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, {
+ });
+ createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_CREATED, {
projectId,
userId,
email,
@@ -775,9 +1000,23 @@ module.exports = (app, logger) => {
}).catch(err => logger.error(err)); // eslint-disable-line no-unused-vars
});
- app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED, ({ req, userId, email, status, role, createdBy }) => {
+ /**
+ * PROJECT_MEMBER_INVITE_UPDATED
+ */
+ app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED, ({ req, resource }) => { // eslint-disable-line no-unused-vars
logger.debug('receive PROJECT_MEMBER_INVITE_UPDATED event');
+
+ createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, resource, logger);
+
+ /*
+ Send event for Notification Service
+ */
const projectId = _.parseInt(req.params.projectId);
+ const userId = resource.userId;
+ const email = resource.email;
+ const status = resource.status;
+ const role = resource.role;
+ const createdBy = resource.createdBy;
models.Project.findOne({
where: { id: projectId },
@@ -786,7 +1025,7 @@ module.exports = (app, logger) => {
logger.debug(util.isSSO);
if (status === INVITE_STATUS.REQUEST_APPROVED) {
// send event to bus api
- createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_APPROVED, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_APPROVED, {
projectId,
userId,
originator: createdBy,
@@ -798,7 +1037,7 @@ module.exports = (app, logger) => {
}, logger);
} else if (status === INVITE_STATUS.REQUEST_REJECTED) {
// send event to bus api
- createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_REJECTED, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_REJECTED, {
projectId,
userId,
originator: createdBy,
@@ -810,7 +1049,7 @@ module.exports = (app, logger) => {
}, logger);
} else {
// send event to bus api
- createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, {
+ createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_UPDATED, {
projectId,
userId,
email,
@@ -822,4 +1061,13 @@ module.exports = (app, logger) => {
}
}).catch(err => null); // eslint-disable-line no-unused-vars
});
+
+ /**
+ * PROJECT_MEMBER_INVITE_REMOVED
+ */
+ app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_REMOVED, ({ req, resource }) => { // eslint-disable-line no-unused-vars
+ logger.debug('receive PROJECT_MEMBER_INVITE_REMOVED event');
+
+ createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_REMOVED, resource, logger);
+ });
};
diff --git a/src/events/index.js b/src/events/index.js
index 1327a01e..c4c121b8 100644
--- a/src/events/index.js
+++ b/src/events/index.js
@@ -1,73 +1,89 @@
-import { EVENT, BUS_API_EVENT } from '../constants';
-import { projectCreatedHandler, projectUpdatedHandler, projectDeletedHandler,
+import { EVENT, CONNECT_NOTIFICATION_EVENT } from '../constants';
+import { projectCreatedHandler,
projectUpdatedKafkaHandler } from './projects';
-import { projectMemberAddedHandler, projectMemberRemovedHandler,
- projectMemberUpdatedHandler } from './projectMembers';
-import { projectMemberInviteCreatedHandler,
- projectMemberInviteUpdatedHandler } from './projectMemberInvites';
-import { projectAttachmentRemovedHandler,
- projectAttachmentUpdatedHandler, projectAttachmentAddedHandler } from './projectAttachments';
import { projectPhaseAddedHandler, projectPhaseRemovedHandler,
projectPhaseUpdatedHandler } from './projectPhases';
-import { phaseProductAddedHandler, phaseProductRemovedHandler,
- phaseProductUpdatedHandler } from './phaseProducts';
import {
timelineAddedHandler,
- timelineUpdatedHandler,
- timelineRemovedHandler,
timelineAdjustedKafkaHandler,
} from './timelines';
import {
milestoneAddedHandler,
milestoneUpdatedHandler,
- milestoneRemovedHandler,
milestoneUpdatedKafkaHandler,
} from './milestones';
+/**
+ * Void RabbitMQ event handler.
+ * It "ack"s messages which are still published but we don't want to consume.
+ *
+ * It's used to "disable" events which we don't want to handle anymore. But for a time being
+ * we don't want to remove the code of them until we validate that we are good without them.
+ *
+ * @param {Object} logger logger
+ * @param {Object} msg RabbitMQ message
+ * @param {Object} channel RabbitMQ channel
+ * @returns {Promise} nothing
+ */
+const voidRabbitHandler = (logger, msg, channel) => {
+ logger.debug('Calling void RabbitMQ handler.');
+ channel.ack(msg);
+ return Promise.resolve();
+};
+
+// NOTE: We use "project-processor-es" for ES indexing now.
+// So I disable indexing using RabbitMQ for a transition period for most of the objects
+// which don't have any special logic.
+// As soon as we are sure, that "project-processor-es" works well for ES indexing,
+// we should completely remove the handlers for this events.
export const rabbitHandlers = {
- 'project.initial': projectCreatedHandler,
- [EVENT.ROUTING_KEY.PROJECT_DRAFT_CREATED]: projectCreatedHandler,
- [EVENT.ROUTING_KEY.PROJECT_UPDATED]: projectUpdatedHandler,
- [EVENT.ROUTING_KEY.PROJECT_DELETED]: projectDeletedHandler,
- [EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED]: projectMemberAddedHandler,
- [EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED]: projectMemberRemovedHandler,
- [EVENT.ROUTING_KEY.PROJECT_MEMBER_UPDATED]: projectMemberUpdatedHandler,
- [EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED]: projectMemberInviteCreatedHandler,
- [EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED]: projectMemberInviteUpdatedHandler,
- [EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED]: projectAttachmentAddedHandler,
- [EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_REMOVED]: projectAttachmentRemovedHandler,
- [EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_UPDATED]: projectAttachmentUpdatedHandler,
- [EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED]: projectPhaseAddedHandler,
- [EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED]: projectPhaseRemovedHandler,
- [EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED]: projectPhaseUpdatedHandler,
- [EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_ADDED]: phaseProductAddedHandler,
- [EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_REMOVED]: phaseProductRemovedHandler,
- [EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED]: phaseProductUpdatedHandler,
+ 'project.initial': projectCreatedHandler, // is only used `seedElasticsearchIndex.js` and can be removed
+ [EVENT.ROUTING_KEY.PROJECT_DRAFT_CREATED]: voidRabbitHandler, // DISABLED
+ [EVENT.ROUTING_KEY.PROJECT_UPDATED]: voidRabbitHandler, // DISABLED
+ [EVENT.ROUTING_KEY.PROJECT_DELETED]: voidRabbitHandler, // DISABLED
+ [EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED]: voidRabbitHandler, // DISABLED
+ [EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED]: voidRabbitHandler, // DISABLED
+ [EVENT.ROUTING_KEY.PROJECT_MEMBER_UPDATED]: voidRabbitHandler, // DISABLED
+ [EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED]: voidRabbitHandler, // DISABLED
+ [EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED]: voidRabbitHandler, // DISABLED
+ [EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED]: voidRabbitHandler, // DISABLED
+ [EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_REMOVED]: voidRabbitHandler, // DISABLED
+ [EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_UPDATED]: voidRabbitHandler, // DISABLED
+
+ // project phase handles additionally implement logic for creating associated topics in Message Service
+ [EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED]: projectPhaseAddedHandler, // index in ES because of cascade updates
+ [EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED]: projectPhaseRemovedHandler, // doesn't index in ES
+ [EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED]: projectPhaseUpdatedHandler, // index in ES because of cascade updates
+
+ [EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_ADDED]: voidRabbitHandler, // DISABLED
+ [EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_REMOVED]: voidRabbitHandler, // DISABLED
+ [EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED]: voidRabbitHandler, // DISABLED
+
// Timeline and milestone
- 'timeline.initial': timelineAddedHandler,
- [EVENT.ROUTING_KEY.TIMELINE_ADDED]: timelineAddedHandler,
- [EVENT.ROUTING_KEY.TIMELINE_REMOVED]: timelineRemovedHandler,
- [EVENT.ROUTING_KEY.TIMELINE_UPDATED]: timelineUpdatedHandler,
- [EVENT.ROUTING_KEY.MILESTONE_ADDED]: milestoneAddedHandler,
- [EVENT.ROUTING_KEY.MILESTONE_REMOVED]: milestoneRemovedHandler,
- [EVENT.ROUTING_KEY.MILESTONE_UPDATED]: milestoneUpdatedHandler,
+ 'timeline.initial': timelineAddedHandler, // is only used `seedElasticsearchIndex.js` and can be removed
+ [EVENT.ROUTING_KEY.TIMELINE_ADDED]: voidRabbitHandler, // DISABLED
+ [EVENT.ROUTING_KEY.TIMELINE_REMOVED]: voidRabbitHandler, // DISABLED
+ [EVENT.ROUTING_KEY.TIMELINE_UPDATED]: voidRabbitHandler, // DISABLED
+ [EVENT.ROUTING_KEY.MILESTONE_ADDED]: milestoneAddedHandler, // index in ES because of cascade updates
+ [EVENT.ROUTING_KEY.MILESTONE_REMOVED]: voidRabbitHandler, // DISABLED
+ [EVENT.ROUTING_KEY.MILESTONE_UPDATED]: milestoneUpdatedHandler, // index in ES because of cascade updates
};
export const kafkaHandlers = {
- // Events defined by project-service
- [BUS_API_EVENT.PROJECT_UPDATED]: projectUpdatedKafkaHandler,
- [BUS_API_EVENT.PROJECT_FILES_UPDATED]: projectUpdatedKafkaHandler,
- [BUS_API_EVENT.PROJECT_TEAM_UPDATED]: projectUpdatedKafkaHandler,
- [BUS_API_EVENT.PROJECT_PLAN_UPDATED]: projectUpdatedKafkaHandler,
+ // Events defined by project-api
+ [CONNECT_NOTIFICATION_EVENT.PROJECT_UPDATED]: projectUpdatedKafkaHandler,
+ [CONNECT_NOTIFICATION_EVENT.PROJECT_FILES_UPDATED]: projectUpdatedKafkaHandler,
+ [CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED]: projectUpdatedKafkaHandler,
+ [CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED]: projectUpdatedKafkaHandler,
// Events from message-service
- [BUS_API_EVENT.TOPIC_CREATED]: projectUpdatedKafkaHandler,
- [BUS_API_EVENT.TOPIC_UPDATED]: projectUpdatedKafkaHandler,
- [BUS_API_EVENT.POST_CREATED]: projectUpdatedKafkaHandler,
- [BUS_API_EVENT.POST_UPDATED]: projectUpdatedKafkaHandler,
+ [CONNECT_NOTIFICATION_EVENT.TOPIC_CREATED]: projectUpdatedKafkaHandler,
+ [CONNECT_NOTIFICATION_EVENT.TOPIC_UPDATED]: projectUpdatedKafkaHandler,
+ [CONNECT_NOTIFICATION_EVENT.POST_CREATED]: projectUpdatedKafkaHandler,
+ [CONNECT_NOTIFICATION_EVENT.POST_UPDATED]: projectUpdatedKafkaHandler,
// Events coming from timeline/milestones (considering it as a separate module/service in future)
- [BUS_API_EVENT.MILESTONE_TRANSITION_COMPLETED]: milestoneUpdatedKafkaHandler,
- [BUS_API_EVENT.TIMELINE_ADJUSTED]: timelineAdjustedKafkaHandler,
+ [CONNECT_NOTIFICATION_EVENT.MILESTONE_TRANSITION_COMPLETED]: milestoneUpdatedKafkaHandler,
+ [CONNECT_NOTIFICATION_EVENT.TIMELINE_ADJUSTED]: timelineAdjustedKafkaHandler,
};
diff --git a/src/events/milestones/index.js b/src/events/milestones/index.js
index 83698255..8b717662 100644
--- a/src/events/milestones/index.js
+++ b/src/events/milestones/index.js
@@ -7,7 +7,7 @@ import Joi from 'joi';
import Promise from 'bluebird';
import util from '../../util';
// import { createEvent } from '../../services/busApi';
-import { EVENT, TIMELINE_REFERENCES, MILESTONE_STATUS, REGEX } from '../../constants';
+import { EVENT, TIMELINE_REFERENCES, MILESTONE_STATUS, REGEX, RESOURCES, ROUTES } from '../../constants';
import models from '../../models';
const ES_TIMELINE_INDEX = config.get('elasticsearchConfig.timelineIndexName');
@@ -219,14 +219,18 @@ async function milestoneUpdatedKafkaHandler(app, topic, payload) {
}, ['progress', 'duration']);
app.logger.debug(`Updated phase progress ${timeline.progress} and duration ${timeline.duration}`);
app.logger.debug('Raising node event for PROJECT_PHASE_UPDATED');
- app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, {
- req: {
+ util.sendResourceToKafkaBus(
+ {
params: { projectId: project.id, phaseId: phase.id },
authUser: { userId: payload.userId },
},
- original: phase,
- updated: _.omit(updatedPhase.toJSON(), 'deletedAt', 'deletedBy'),
- });
+ EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED,
+ RESOURCES.PHASE,
+ _.omit(updatedPhase.toJSON(), 'deletedAt', 'deletedBy'),
+ phase,
+ _.get(project, 'details.settings.workstreams') ? ROUTES.WORKS.UPDATE : ROUTES.PHASES.UPDATE,
+ true, // don't send event to Notification Service as the main event here is updating milestones, not phase
+ );
}
}
}
diff --git a/src/events/projectMembers/index.js b/src/events/projectMembers/index.js
index f5750362..2b6868a6 100644
--- a/src/events/projectMembers/index.js
+++ b/src/events/projectMembers/index.js
@@ -4,16 +4,12 @@
import _ from 'lodash';
import Promise from 'bluebird';
import config from 'config';
-import { PROJECT_MEMBER_ROLE } from '../../constants';
import util from '../../util';
-import models from '../../models';
-import directProject from '../../services/directProject';
const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName');
const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType');
const eClient = util.getElasticSearchClient();
-
const updateESPromise = Promise.coroutine(function* a(logger, requestId, projectId, updateDocHandler) {
try {
const doc = yield eClient.get({ index: ES_PROJECT_INDEX, type: ES_PROJECT_TYPE, id: projectId });
@@ -43,47 +39,6 @@ const projectMemberAddedHandler = Promise.coroutine(function* a(logger, msg, cha
const origRequestId = msg.properties.correlationId;
const newMember = JSON.parse(msg.content.toString());
const projectId = newMember.projectId;
- const directUpdatePromise = Promise.coroutine(function* () { // eslint-disable-line func-names
- if (_.indexOf([
- PROJECT_MEMBER_ROLE.COPILOT,
- PROJECT_MEMBER_ROLE.MANAGER,
- PROJECT_MEMBER_ROLE.PROJECT_MANAGER,
- PROJECT_MEMBER_ROLE.PROGRAM_MANAGER,
- PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT,
- ], newMember.role) > -1) {
- // add copilot/update manager permissions operation promise
- const directProjectId = yield models.Project.getDirectProjectId(projectId);
- if (directProjectId) {
- const token = yield util.getM2MToken();
- const req = {
- id: origRequestId,
- log: logger,
- headers: {
- authorization: `Bearer ${token}`,
- },
- };
- if (newMember.role === PROJECT_MEMBER_ROLE.COPILOT) {
- // add copilot to direct project
- try {
- yield directProject.addCopilot(req, directProjectId, newMember.userId);
- logger.info('added copilot to direct');
- } catch (err) {
- logger.error('Failed to add copilot, continue..', _.pick(err, ['config', 'data']));
- }
- } else {
- // update direct project permissions
- try {
- yield directProject.addManager(req, directProjectId, newMember.userId);
- logger.info('added manager to direct');
- } catch (err) {
- logger.error('Failed to add manager, continue..', _.pick(err, ['config', 'data']));
- }
- }
- } else {
- logger.debug(`Project# ${projectId} not associated with a Direct project, skipping`);
- }
- }
- });
// handle ES Update
// fetch the member information
const updateDocPromise = Promise.coroutine(function* (doc) { // eslint-disable-line func-names
@@ -98,7 +53,7 @@ const projectMemberAddedHandler = Promise.coroutine(function* a(logger, msg, cha
_.remove(invites, invite => invite.email === payload.email || invite.userId === payload.userId);
return _.merge(doc._source, { members, invites }); // eslint-disable-line no-underscore-dangle
});
- yield Promise.all([directUpdatePromise(), updateESPromise(logger, origRequestId, projectId, updateDocPromise)]);
+ yield Promise.all([updateESPromise(logger, origRequestId, projectId, updateDocPromise)]);
logger.debug('elasticsearch index updated successfully and co-pilot/manager updated in direct project');
channel.ack(msg);
} catch (error) {
@@ -120,54 +75,11 @@ const projectMemberRemovedHandler = Promise.coroutine(function* (logger, msg, ch
const origRequestId = msg.properties.correlationId;
const member = JSON.parse(msg.content.toString());
const projectId = member.projectId;
- // remove copilot/manager operation promise
- const updateDirectProjectPromise = Promise.coroutine(function* () { // eslint-disable-line func-names
- if (_.indexOf([
- PROJECT_MEMBER_ROLE.COPILOT,
- PROJECT_MEMBER_ROLE.MANAGER,
- PROJECT_MEMBER_ROLE.PROJECT_MANAGER,
- PROJECT_MEMBER_ROLE.PROGRAM_MANAGER,
- PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT,
- ], member.role) > -1) {
- const directProjectId = yield models.Project.getDirectProjectId(projectId);
- if (directProjectId) {
- const token = yield util.getM2MToken();
- const req = {
- id: origRequestId,
- log: logger,
- headers: {
- authorization: `Bearer ${token}`,
- },
- };
- if (member.role === PROJECT_MEMBER_ROLE.COPILOT) {
- // remove copilot to direct project
- try {
- yield directProject.deleteCopilot(req, directProjectId, member.userId);
- logger.info('removed copilot to direct');
- } catch (err) {
- logger.error('Failed to remove copilot, continue..', _.pick(err, ['config', 'data']));
- }
- } else {
- // remove manager from direct
- try {
- yield directProject.removeManager(req, directProjectId, member.userId);
- logger.info('removed manager to direct');
- } catch (err) {
- logger.error('Failed to remove manager, continue..', _.pick(err, ['config', 'data']));
- }
- }
- } else {
- logger.info(`Project# ${projectId} not associated with a Direct project, skipping`);
- }
- }
- });
-
const updateDocPromise = (doc) => {
const members = _.filter(doc._source.members, single => single.id !== member.id); // eslint-disable-line no-underscore-dangle
return Promise.resolve(_.set(doc._source, 'members', members)); // eslint-disable-line no-underscore-dangle
};
yield Promise.all([
- updateDirectProjectPromise(),
updateESPromise(logger, origRequestId, projectId, updateDocPromise),
]);
logger.info('elasticsearch index updated successfully and co-pilot/manager removed in direct project');
diff --git a/src/events/projectPhases/index.js b/src/events/projectPhases/index.js
index e8e33379..ec5c21b0 100644
--- a/src/events/projectPhases/index.js
+++ b/src/events/projectPhases/index.js
@@ -243,7 +243,7 @@ const projectPhaseUpdatedHandler = Promise.coroutine(function* (logger, msg, cha
* @param {Object} msg event payload
* @returns {undefined}
*/
-const removePhaseFromIndex = Promise.coroutine(function* (logger, msg) { // eslint-disable-line func-names
+const removePhaseFromIndex = Promise.coroutine(function* (logger, msg) { // eslint-disable-line func-names, no-unused-vars
try {
const data = JSON.parse(msg.content.toString());
const phase = _.get(data, 'deleted', {});
@@ -316,7 +316,8 @@ const removeTopics = Promise.coroutine(function* (logger, phase, route) { // esl
*/
const projectPhaseRemovedHandler = Promise.coroutine(function* (logger, msg, channel) { // eslint-disable-line func-names
try {
- yield removePhaseFromIndex(logger, msg, channel);
+ // NOTE We use "project-processor-es" for ES indexing now.
+ // yield removePhaseFromIndex(logger, msg, channel);
const data = JSON.parse(msg.content.toString());
const phase = _.get(data, 'deleted', {});
const route = _.get(data, 'route');
diff --git a/src/events/projects/index.js b/src/events/projects/index.js
index 976bbbdf..ab53c704 100644
--- a/src/events/projects/index.js
+++ b/src/events/projects/index.js
@@ -41,10 +41,6 @@ const indexProject = Promise.coroutine(function* (logger, msg) { // eslint-disab
// removes non required fields from phase objects
data.phases = data.phases.map(phase => _.omit(phase, ['deletedAt', 'deletedBy']));
}
- // TEMPORARY FIX: should fix ES mapping instead and reindex all the projects instead
- if (typeof _.get(data, 'details.taasDefinition.team.skills') !== 'string') {
- _.set(data, 'details.taasDefinition.team.skills', '');
- }
// add the record to the index
const result = yield eClient.index({
index: ES_PROJECT_INDEX,
@@ -95,10 +91,6 @@ const projectUpdatedHandler = Promise.coroutine(function* (logger, msg, channel)
// first get the existing document and than merge the updated changes and save the new document
const doc = yield eClient.get({ index: ES_PROJECT_INDEX, type: ES_PROJECT_TYPE, id: data.original.id });
const merged = _.merge(doc._source, data.updated); // eslint-disable-line no-underscore-dangle
- // TEMPORARY FIX: should fix ES mapping instead and reindex all the projects instead
- if (typeof _.get(merged, 'details.taasDefinition.team.skills') !== 'string') {
- _.set(merged, 'details.taasDefinition.team.skills', '');
- }
// update the merged document
yield eClient.update({
index: ES_PROJECT_INDEX,
@@ -167,7 +159,7 @@ async function projectUpdatedKafkaHandler(app, topic, payload) {
// Find project by id and update activity. Single update is used as there is no need to wrap it into transaction
const projectId = payload.projectId;
- const project = await models.Project.findById(projectId);
+ const project = await models.Project.findByPk(projectId);
if (!project) {
throw new Error(`Project with id ${projectId} not found`);
}
@@ -181,10 +173,6 @@ async function projectUpdatedKafkaHandler(app, topic, payload) {
try {
const doc = await eClient.get({ index: ES_PROJECT_INDEX, type: ES_PROJECT_TYPE, id: previousValue.id });
const merged = _.merge(doc._source, project.get({ plain: true })); // eslint-disable-line no-underscore-dangle
- // TEMPORARY FIX: should fix ES mapping instead and reindex all the projects instead
- if (typeof _.get(merged, 'details.taasDefinition.team.skills') !== 'string') {
- _.set(merged, 'details.taasDefinition.team.skills', '');
- }
// update the merged document
await eClient.update({
index: ES_PROJECT_INDEX,
diff --git a/src/events/projects/index.spec.js b/src/events/projects/index.spec.js
index 0f524c17..e66754c4 100644
--- a/src/events/projects/index.spec.js
+++ b/src/events/projects/index.spec.js
@@ -94,6 +94,7 @@ describe('projectUpdatedKafkaHandler', () => {
beforeEach(async () => {
await testUtil.clearDb();
+ await testUtil.clearES();
project = await models.Project.create({
type: 'generic',
billingAccountId: 1,
@@ -130,7 +131,7 @@ describe('projectUpdatedKafkaHandler', () => {
it('should update lastActivityAt and lastActivityUserId columns in db', async () => {
await projectUpdatedKafkaHandler(mockedApp, topic, validPayload);
- const updatedProject = await models.Project.findById(project.id);
+ const updatedProject = await models.Project.findByPk(project.id);
expect(updatedProject.lastActivityUserId).to.be.eql('2');
expect(updatedProject.lastActivityAt).to.be.greaterThan(project.lastActivityAt);
});
diff --git a/src/middlewares/fieldLookupValidation.js b/src/middlewares/fieldLookupValidation.js
index 73d07a9c..b811d6cf 100644
--- a/src/middlewares/fieldLookupValidation.js
+++ b/src/middlewares/fieldLookupValidation.js
@@ -2,8 +2,8 @@ import _ from 'lodash';
/**
* Constructs a middleware the validates the existence of a record given a path to find in the req. For example, in
- * order to check for a ProductCategory being received in "req.body.param.category" you would construct this middleware
- * by calling this function with (models.ProductCategory, 'key', 'body.param.category', 'Category').
+ * order to check for a ProductCategory being received in "req.body.category" you would construct this middleware
+ * by calling this function with (models.ProductCategory, 'key', 'body.category', 'Category').
* Note that this also works for updates where the value might not be present in the request, in which the case
* the built middleware will continue without errors.
*
@@ -23,7 +23,7 @@ export default function (model, modelKey, path, errorEntityName) {
next();
} else {
const err = new Error(`${errorEntityName} not found for key "${value}"`);
- err.status = 422;
+ err.status = 400;
next(err);
}
});
diff --git a/src/middlewares/validateMilestoneTemplate.js b/src/middlewares/validateMilestoneTemplate.js
index 1aea3ace..eee1fd7e 100644
--- a/src/middlewares/validateMilestoneTemplate.js
+++ b/src/middlewares/validateMilestoneTemplate.js
@@ -18,11 +18,12 @@ async function validateReference(sourceObject) {
id: sourceObject.referenceId,
deletedAt: { $eq: null },
},
+ raw: true,
});
if (!productTemplate) {
const apiErr = new Error(
`Product template not found for product template id ${sourceObject.referenceId}`);
- apiErr.status = 422;
+ apiErr.status = 400;
throw apiErr;
}
}
@@ -40,12 +41,12 @@ const validateMilestoneTemplate = {
*/
// eslint-disable-next-line valid-jsdoc
validateRequestBody: (req, res, next) => {
- validateReference(req.body.param, req)
+ validateReference(req.body, req)
.then(() => {
- if (req.body.param.sourceReference) {
+ if (req.body.sourceReference) {
return validateReference({
- reference: req.body.param.sourceReference,
- referenceId: req.body.param.sourceReferenceId,
+ reference: req.body.sourceReference,
+ referenceId: req.body.sourceReferenceId,
});
}
@@ -71,34 +72,31 @@ const validateMilestoneTemplate = {
}
// Validate the filter
- const filter = util.parseQueryFilter(req.query.filter);
-
- // Save the parsed filter for later
- req.params.filter = filter;
+ const filter = req.query.filter;
if (!util.isValidFilter(filter, ['reference', 'referenceId'])) {
const apiErr = new Error('Only allowed to filter by reference and referenceId');
- apiErr.status = 422;
+ apiErr.status = 400;
return next(apiErr);
}
// Verify required filters are present
if (!filter.reference || !filter.referenceId) {
const apiErr = new Error('Please provide reference and referenceId filter parameters');
- apiErr.status = 422;
+ apiErr.status = 400;
return next(apiErr);
}
// Verify reference is a valid value
if (!_.includes(MILESTONE_TEMPLATE_REFERENCES, filter.reference)) {
const apiErr = new Error(`reference filter must be in ${MILESTONE_TEMPLATE_REFERENCES}`);
- apiErr.status = 422;
+ apiErr.status = 400;
return next(apiErr);
}
if (_.lt(filter.referenceId, 1)) {
const apiErr = new Error('referenceId filter must be a positive integer');
- apiErr.status = 422;
+ apiErr.status = 400;
return next(apiErr);
}
@@ -117,7 +115,7 @@ const validateMilestoneTemplate = {
*/
// eslint-disable-next-line valid-jsdoc
validateIdParam: (req, res, next) => {
- models.MilestoneTemplate.findById(req.params.milestoneTemplateId)
+ models.MilestoneTemplate.findByPk(req.params.milestoneTemplateId)
.then((milestoneTemplate) => {
if (!milestoneTemplate) {
const apiErr = new Error(
diff --git a/src/middlewares/validateTimeline.js b/src/middlewares/validateTimeline.js
index 60cfbb4c..4d10f4d7 100644
--- a/src/middlewares/validateTimeline.js
+++ b/src/middlewares/validateTimeline.js
@@ -27,7 +27,7 @@ async function validateReference(sourceObject, req, validateProjectExists) {
});
if (!project) {
const apiErr = new Error(`Project not found for project id ${req.params.projectId}`);
- apiErr.status = 422;
+ apiErr.status = 400;
throw apiErr;
}
}
@@ -45,7 +45,7 @@ async function validateReference(sourceObject, req, validateProjectExists) {
});
if (!product) {
const apiErr = new Error(`Product not found for product id ${sourceObject.referenceId}`);
- apiErr.status = 422;
+ apiErr.status = 400;
throw apiErr;
}
@@ -63,7 +63,7 @@ async function validateReference(sourceObject, req, validateProjectExists) {
});
if (!phase) {
const apiErr = new Error(`Phase not found for phase id ${sourceObject.referenceId}`);
- apiErr.status = 422;
+ apiErr.status = 400;
throw apiErr;
}
@@ -83,7 +83,7 @@ const validateTimeline = {
*/
// eslint-disable-next-line valid-jsdoc
validateTimelineRequestBody: (req, res, next) => {
- validateReference(req.body.param, req, true)
+ validateReference(req.body, req, true)
.then(next)
.catch(next);
},
@@ -100,34 +100,31 @@ const validateTimeline = {
// eslint-disable-next-line valid-jsdoc
validateTimelineQueryFilter: (req, res, next) => {
// Validate the filter
- const filter = util.parseQueryFilter(req.query.filter);
-
- // Save the parsed filter for later
- req.params.filter = filter;
+ const filter = req.query;
if (!util.isValidFilter(filter, ['reference', 'referenceId'])) {
const apiErr = new Error('Only allowed to filter by reference and referenceId');
- apiErr.status = 422;
+ apiErr.status = 400;
return next(apiErr);
}
// Verify required filters are present
if (!filter.reference || !filter.referenceId) {
const apiErr = new Error('Please provide reference and referenceId filter parameters');
- apiErr.status = 422;
+ apiErr.status = 400;
return next(apiErr);
}
// Verify reference is a valid value
if (!_.includes(TIMELINE_REFERENCES, filter.reference)) {
const apiErr = new Error(`reference filter must be in ${TIMELINE_REFERENCES}`);
- apiErr.status = 422;
+ apiErr.status = 400;
return next(apiErr);
}
if (_.lt(filter.referenceId, 1)) {
const apiErr = new Error('referenceId filter must be a positive integer');
- apiErr.status = 422;
+ apiErr.status = 400;
return next(apiErr);
}
@@ -146,7 +143,7 @@ const validateTimeline = {
*/
// eslint-disable-next-line valid-jsdoc
validateTimelineIdParam: (req, res, next) => {
- models.Timeline.findById(req.params.timelineId)
+ models.Timeline.findByPk(req.params.timelineId)
.then((timeline) => {
if (!timeline) {
const apiErr = new Error(`Timeline not found for timeline id ${req.params.timelineId}`);
diff --git a/src/models/buildingBlock.js b/src/models/buildingBlock.js
index abf160b9..a28762a4 100644
--- a/src/models/buildingBlock.js
+++ b/src/models/buildingBlock.js
@@ -37,19 +37,18 @@ module.exports = (sequelize, DataTypes) => {
* not returned.
*
* @param {Object} options find/findAll options
- * @param {Function} callback callback after hook
*/
- beforeFind: (options, callback) => {
+ afterFind: function removePrivateConfig(buildingBlocks, options) {
// ONLY FOR INTERNAL USAGE: don't use this option to return the data by API
if (!options.includePrivateConfigForInternalUsage) {
- // try to remove privateConfig from attributes
- const idx = options.attributes.indexOf('privateConfig');
- if (idx >= 0) {
- options.attributes.splice(idx, 1);
- }
+ // try to remove privateConfig from result
+ buildingBlocks.map((block) => {
+ const b = block;
+ delete b.privateConfig;
+ return b;
+ });
}
-
- return callback(null);
+ return buildingBlocks;
},
},
});
diff --git a/src/models/index.js b/src/models/index.js
index 59c8c9d2..0acc3b27 100644
--- a/src/models/index.js
+++ b/src/models/index.js
@@ -11,8 +11,24 @@ pg.defaults.parseInt8 = true;
delete pg.native;
Sequelize.cls = cls.createNamespace('tc.micro.service');
+// Sequelize.useCLS(cls.createNamespace('tc.micro.service'));
+
+const Op = Sequelize.Op;
+const operatorsAliases = {
+ $gt: Op.gt,
+ $gte: Op.gte,
+ $lt: Op.lt,
+ $between: Op.between,
+ $eq: Op.eq,
+ $ne: Op.ne,
+ $or: Op.or,
+ $and: Op.and,
+ $in: Op.in,
+ $contains: Op.contains,
+};
const sequelize = new Sequelize(config.get('dbConfig.masterUrl'), {
+ operatorsAliases,
logging: false,
dialectOptions: {
ssl: false,
diff --git a/src/models/milestone.js b/src/models/milestone.js
index dc31cf24..644f7b0c 100644
--- a/src/models/milestone.js
+++ b/src/models/milestone.js
@@ -84,52 +84,6 @@ module.exports = (sequelize, DataTypes) => {
updatedAt: 'updatedAt',
createdAt: 'createdAt',
deletedAt: 'deletedAt',
- classMethods: {
- /**
- * Get total duration of the given timeline by summing up individual milestone durations
- * @param timelineId the id of timeline
- */
- getTimelineDuration(timelineId) {
- const where = { timelineId, hidden: false };
- return this.findAll({
- where,
- order: [['order', 'asc']],
- attributes: ['id', 'duration', 'startDate', 'endDate', 'actualStartDate', 'completionDate'],
- raw: true,
- })
- .then((milestones) => {
- let scheduledDuration = 0;
- let completedDuration = 0;
- let duration = 0;
- let progress = 0;
- if (milestones) {
- const fMilestone = milestones[0];
- const lMilestone = milestones[milestones.length - 1];
- const startDate = fMilestone.actualStartDate ? fMilestone.actualStartDate : fMilestone.startDate;
- const endDate = lMilestone.completionDate ? lMilestone.completionDate : lMilestone.endDate;
- duration = moment.utc(endDate).diff(moment.utc(startDate), 'days') + 1;
- milestones.forEach((m) => {
- if (m.completionDate !== null) {
- let mDuration = 0;
- if (m.actualStartDate !== null) {
- mDuration = moment.utc(m.completionDate).diff(moment.utc(m.actualStartDate), 'days') + 1;
- } else {
- mDuration = moment.utc(m.completionDate).diff(moment.utc(m.startDate), 'days') + 1;
- }
- scheduledDuration += mDuration;
- completedDuration += mDuration;
- } else {
- scheduledDuration += m.duration;
- }
- });
- if (scheduledDuration > 0) {
- progress = Math.round((completedDuration / scheduledDuration) * 100);
- }
- }
- return Promise.resolve({ duration, progress });
- });
- },
- },
hooks: {
afterCreate: (milestone, options) => models.StatusHistory.create({
reference: STATUS_HISTORY_REFERENCES.MILESTONE,
@@ -180,5 +134,50 @@ module.exports = (sequelize, DataTypes) => {
},
});
+ /**
+ * Get total duration of the given timeline by summing up individual milestone durations
+ * @param timelineId the id of timeline
+ */
+ Milestone.getTimelineDuration = (timelineId) => {
+ const where = { timelineId, hidden: false };
+ return Milestone.findAll({
+ where,
+ order: [['order', 'asc']],
+ attributes: ['id', 'duration', 'startDate', 'endDate', 'actualStartDate', 'completionDate'],
+ raw: true,
+ })
+ .then((milestones) => {
+ let scheduledDuration = 0;
+ let completedDuration = 0;
+ let duration = 0;
+ let progress = 0;
+ if (milestones) {
+ const fMilestone = milestones[0];
+ const lMilestone = milestones[milestones.length - 1];
+ const startDate = fMilestone.actualStartDate ? fMilestone.actualStartDate : fMilestone.startDate;
+ const endDate = lMilestone.completionDate ? lMilestone.completionDate : lMilestone.endDate;
+ duration = moment.utc(endDate).diff(moment.utc(startDate), 'days') + 1;
+ milestones.forEach((m) => {
+ if (m.completionDate !== null) {
+ let mDuration = 0;
+ if (m.actualStartDate !== null) {
+ mDuration = moment.utc(m.completionDate).diff(moment.utc(m.actualStartDate), 'days') + 1;
+ } else {
+ mDuration = moment.utc(m.completionDate).diff(moment.utc(m.startDate), 'days') + 1;
+ }
+ scheduledDuration += mDuration;
+ completedDuration += mDuration;
+ } else {
+ scheduledDuration += m.duration;
+ }
+ });
+ if (scheduledDuration > 0) {
+ progress = Math.round((completedDuration / scheduledDuration) * 100);
+ }
+ }
+ return Promise.resolve({ duration, progress });
+ });
+ };
+
return Milestone;
};
diff --git a/src/models/phaseProduct.js b/src/models/phaseProduct.js
index 04f97479..d102b0af 100644
--- a/src/models/phaseProduct.js
+++ b/src/models/phaseProduct.js
@@ -26,37 +26,35 @@ module.exports = function definePhaseProduct(sequelize, DataTypes) {
createdAt: 'createdAt',
deletedAt: 'deletedAt',
indexes: [],
- classMethods: {
- getActivePhaseProducts(phaseId) {
- return this.findAll({
- where: {
- deletedAt: { $eq: null },
- phaseId,
- },
- raw: true,
- });
- },
- /**
- * Search Phase Products
- * @param {Object} parameters the replacements for sequelize
- * - projectId id of the project
- * - phaseId id of phase
- * @param {Object} log the request log
- * @return {Object} the result rows and count
- */
- async search(parameters = {}, log) {
- const whereQuery = 'phase_products."projectId"= :projectId AND phase_products."phaseId" = :phaseId';
- const dbQuery = `SELECT * FROM phase_products WHERE ${whereQuery}`;
- return sequelize.query(dbQuery,
- { type: sequelize.QueryTypes.SELECT,
- replacements: parameters,
- logging: (str) => { log.debug(str); },
- raw: true,
- })
- .then(phases => ({ rows: phases, count: phases.length }));
- },
+ });
+
+ PhaseProduct.getActivePhaseProducts = phaseId => PhaseProduct.findAll({
+ where: {
+ deletedAt: { $eq: null },
+ phaseId,
},
+ raw: true,
});
+ /**
+ * Search Phase Products
+ * @param {Object} parameters the replacements for sequelize
+ * - projectId id of the project
+ * - phaseId id of phase
+ * @param {Object} log the request log
+ * @return {Object} the result rows and count
+ */
+ PhaseProduct.search = async (parameters = {}, log) => {
+ const whereQuery = 'phase_products."projectId"= :projectId AND phase_products."phaseId" = :phaseId';
+ const dbQuery = `SELECT * FROM phase_products WHERE ${whereQuery}`;
+ return sequelize.query(dbQuery,
+ { type: sequelize.QueryTypes.SELECT,
+ replacements: parameters,
+ logging: (str) => { log.debug(str); },
+ raw: true,
+ })
+ .then(phases => ({ rows: phases, count: phases.length }));
+ };
+
return PhaseProduct;
};
diff --git a/src/models/project.js b/src/models/project.js
index 86c8a07c..3e5c9bb3 100644
--- a/src/models/project.js
+++ b/src/models/project.js
@@ -60,142 +60,158 @@ module.exports = function defineProject(sequelize, DataTypes) {
{ fields: ['status'] },
{ fields: ['directProjectId'] },
],
- classMethods: {
- /**
- * Get direct project id
- * @param id the id of project
- */
- getDirectProjectId(id) {
- return this.findById(id, {
- attributes: ['directProjectId'],
- raw: true,
- })
- .then(res => res.directProjectId);
- },
- associate: (models) => {
- Project.hasMany(models.ProjectMember, { as: 'members', foreignKey: 'projectId' });
- Project.hasMany(models.ProjectAttachment, { as: 'attachments', foreignKey: 'projectId' });
- Project.hasMany(models.ProjectPhase, { as: 'phases', foreignKey: 'projectId' });
- Project.hasMany(models.ProjectMemberInvite, { as: 'invites', foreignKey: 'projectId' });
- Project.hasMany(models.ScopeChangeRequest, { as: 'scopeChangeRequests', foreignKey: 'projectId' });
- Project.hasMany(models.WorkStream, { as: 'workStreams', foreignKey: 'projectId' });
- },
+ });
- /**
- * Search keyword in name, description, details.utm.code (To be deprecated)
- * @param parameters the parameters
- * - filters: the filters contains keyword
- * - order: the order
- * - limit: the limit
- * - offset: the offset
- * - attributes: the attributes to get
- * @param log the request log
- * @return the result rows and count
- */
- searchText(parameters, log) {
- // special handling for keyword filter
- let query = '1=1 ';
- const replacements = {};
- if (_.has(parameters.filters, 'id')) {
- if (_.isObject(parameters.filters.id)) {
- if (parameters.filters.id.$in.length === 0) {
- parameters.filters.id.$in.push(-1);
- }
- query += 'AND projects.id IN(:id) ';
- replacements.id = parameters.filters.id.$in;
- } else if (_.isString(parameters.filters.id) || _.isNumber(parameters.filters.id)) {
- query += 'AND id = :id ';
- replacements.id = parameters.filters.id;
- }
- }
- if (_.has(parameters.filters, 'status')) {
- const statusFilter = parameters.filters.status;
- if (_.isObject(statusFilter)) {
- query += 'AND projects.status IN (:status) ';
- replacements.status = statusFilter.$in;
- } else if (_.isString(statusFilter)) {
- query += 'AND projects.status = :status';
- replacements.status = statusFilter;
- }
- }
- if (_.has(parameters.filters, 'type')) {
- query += 'AND projects.type = :type ';
- replacements.type = parameters.filters.type;
- }
- if (_.has(parameters.filters, 'keyword')) {
- query += 'AND projects."projectFullText" ~ lower(:keyword)';
- replacements.keyword = parameters.filters.keyword;
- }
+ Project.associate = (models) => {
+ Project.hasMany(models.ProjectMember, { as: 'members', foreignKey: 'projectId' });
+ Project.hasMany(models.ProjectAttachment, { as: 'attachments', foreignKey: 'projectId' });
+ Project.hasMany(models.ProjectPhase, { as: 'phases', foreignKey: 'projectId' });
+ Project.hasMany(models.ProjectMemberInvite, { as: 'invites', foreignKey: 'projectId' });
+ Project.hasMany(models.ScopeChangeRequest, { as: 'scopeChangeRequests', foreignKey: 'projectId' });
+ Project.hasMany(models.WorkStream, { as: 'workStreams', foreignKey: 'projectId' });
+ };
- let joinQuery = '';
- if (_.has(parameters.filters, 'userId') || _.has(parameters.filters, 'email')) {
- query += ` AND (members."userId" = :userId
- OR invites."userId" = :userId
- OR invites."email" = :email) GROUP BY projects.id`;
+ /**
+ * Get direct project id
+ * @param id the id of project
+ */
+ Project.getDirectProjectId = id => Project.findByPk(id, {
+ attributes: ['directProjectId'],
+ raw: true,
+ })
+ .then(res => res.directProjectId);
- joinQuery = `LEFT OUTER JOIN project_members AS members ON projects.id = members."projectId"
- LEFT OUTER JOIN project_member_invites AS invites ON projects.id = invites."projectId"`;
- replacements.userId = parameters.filters.userId;
- replacements.email = parameters.filters.email;
+ /**
+ * Search keyword in name, description, details.utm.code (To be deprecated)
+ * @param parameters the parameters
+ * - filters: the filters contains keyword
+ * - order: the order
+ * - limit: the limit
+ * - offset: the offset
+ * - attributes: the attributes to get
+ * @param log the request log
+ * @return the result rows and count
+ */
+ Project.searchText = (parameters, log) => {
+ // special handling for keyword filter
+ let query = '1=1 ';
+ const replacements = {
+ INVITE_STATUS_PENDING: INVITE_STATUS.PENDING,
+ };
+ if (_.has(parameters.filters, 'id')) {
+ if (_.isArray(parameters.filters.id)) {
+ if (parameters.filters.id.length === 0) {
+ parameters.filters.id.push(-1);
}
+ query += 'AND projects.id IN(:id) ';
+ replacements.id = parameters.filters.id;
+ } else if (_.isString(parameters.filters.id) || _.isNumber(parameters.filters.id)) {
+ query += 'AND projects.id = :id ';
+ replacements.id = parameters.filters.id;
+ }
+ }
+ if (_.has(parameters.filters, 'status')) {
+ const statusFilter = parameters.filters.status;
+ if (_.isObject(statusFilter)) {
+ query += 'AND projects.status IN (:status) ';
+ replacements.status = statusFilter.$in;
+ } else if (_.isString(statusFilter)) {
+ query += 'AND projects.status = :status';
+ replacements.status = statusFilter;
+ }
+ }
+ if (_.has(parameters.filters, 'type')) {
+ query += 'AND projects.type = :type ';
+ replacements.type = parameters.filters.type;
+ }
+ if (_.has(parameters.filters, 'keyword')) {
+ query += 'AND projects."projectFullText" ~ lower(:keyword)';
+ replacements.keyword = parameters.filters.keyword;
+ }
+ if (_.has(parameters.filters, 'name')) {
+ query += 'AND projects.name = :name ';
+ replacements.name = parameters.filters.name;
+ }
+ if (_.has(parameters.filters, 'code')) {
+ query += 'AND details -> \'utm\' ->> \'code\' = :code ';
+ replacements.code = parameters.filters.code;
+ }
+
+ let joinQuery = '';
+ if (_.has(parameters.filters, 'userId') || _.has(parameters.filters, 'email')) {
+ query += ` AND (
+ members."userId" = :userId AND members."deletedAt" IS NULL
+ OR (
+ invites.status = :INVITE_STATUS_PENDING AND
+ (invites."userId" = :userId OR invites."email" = :email)
+ )
+ ) GROUP BY projects.id`;
- let attributesStr = _.map(parameters.attributes, attr => `projects."${attr}"`);
- attributesStr = `${attributesStr.join(',')}`;
- const orderStr = `"${parameters.order[0][0]}" ${parameters.order[0][1]}`;
+ joinQuery = `LEFT OUTER JOIN project_members AS members ON projects.id = members."projectId"
+ LEFT OUTER JOIN project_member_invites AS invites ON projects.id = invites."projectId"`;
- // select count of projects
- return sequelize.query(`SELECT COUNT(1) FROM projects AS projects
+ replacements.userId = parameters.filters.userId;
+ replacements.email = parameters.filters.email;
+ }
+
+ let attributesStr = _.map(parameters.attributes, attr => `projects."${attr}"`);
+ attributesStr = `${attributesStr.join(',')}`;
+ const orderStr = `"${parameters.order[0][0]}" ${parameters.order[0][1]}`;
+
+ // select count of projects
+ return sequelize.query(`SELECT COUNT(1) FROM projects AS projects
+ ${joinQuery}
+ WHERE ${query}`,
+ { type: sequelize.QueryTypes.SELECT,
+ replacements,
+ logging: (str) => { log.debug(str); },
+ raw: true,
+ })
+ .then((fcount) => {
+ let count = fcount.length;
+ if (fcount.length === 1) {
+ count = fcount[0].count;
+ }
+
+ replacements.limit = parameters.limit;
+ replacements.offset = parameters.offset;
+ // select project attributes
+ return sequelize.query(`SELECT ${attributesStr} FROM projects AS projects
${joinQuery}
- WHERE ${query}`,
+ WHERE ${query} ORDER BY ` +
+ ` projects.${orderStr} LIMIT :limit OFFSET :offset`,
{ type: sequelize.QueryTypes.SELECT,
replacements,
logging: (str) => { log.debug(str); },
raw: true,
})
- .then((fcount) => {
- let count = fcount.length;
- if (fcount.length === 1) {
- count = fcount[0].count;
- }
+ .then(projects => ({ rows: projects, count }));
+ });
+ };
- replacements.limit = parameters.limit;
- replacements.offset = parameters.offset;
- // select project attributes
- return sequelize.query(`SELECT ${attributesStr} FROM projects AS projects
- ${joinQuery}
- WHERE ${query} ORDER BY ` +
- ` projects.${orderStr} LIMIT :limit OFFSET :offset`,
- { type: sequelize.QueryTypes.SELECT,
- replacements,
- logging: (str) => { log.debug(str); },
- raw: true,
- })
- .then(projects => ({ rows: projects, count }));
- });
- },
- findProjectRange(models, startId, endId, fields, raw = true) {
- return this.findAll({
- where: { id: { $between: [startId, endId] } },
- attributes: _.get(fields, 'projects', null),
- raw,
- include: [{
- model: models.ProjectPhase,
- as: 'phases',
- order: [['startDate', 'asc']],
- // where: phasesWhere,
- include: [{
- model: models.PhaseProduct,
- as: 'products',
- }],
- }, {
- model: models.ProjectMemberInvite,
- as: 'invites',
- where: { status: INVITE_STATUS.PENDING },
- }],
- });
- },
- },
+ Project.findProjectRange = (models, startId, endId, fields, raw = true) => Project.findAll({
+ where: { id: { $between: [startId, endId] } },
+ attributes: _.get(fields, 'projects', null),
+ raw,
+ include: [{
+ model: models.ProjectPhase,
+ as: 'phases',
+ order: [['startDate', 'asc']],
+ // where: phasesWhere,
+ include: [{
+ model: models.PhaseProduct,
+ as: 'products',
+ }],
+ }, {
+ model: models.ProjectMemberInvite,
+ as: 'invites',
+ where: { status: { $in: [INVITE_STATUS.PENDING, INVITE_STATUS.REQUESTED] } },
+ required: false,
+ }, {
+ model: models.ProjectAttachment,
+ as: 'attachments',
+ }],
});
return Project;
diff --git a/src/models/projectAttachment.js b/src/models/projectAttachment.js
index 53043b32..6e23deb9 100644
--- a/src/models/projectAttachment.js
+++ b/src/models/projectAttachment.js
@@ -24,43 +24,36 @@ module.exports = function defineProjectAttachment(sequelize, DataTypes) {
createdAt: 'createdAt',
deletedAt: 'deletedAt',
indexes: [],
- classMethods: {
- getActiveProjectAttachments(projectId) {
- return this.findAll({
- where: {
- deletedAt: { $eq: null },
- projectId,
- },
- raw: true,
- });
- },
+ });
+
+ ProjectAttachment.getActiveProjectAttachments = projectId => ProjectAttachment.findAll({
+ where: {
+ deletedAt: { $eq: null },
+ projectId,
+ },
+ raw: true,
+ });
- getAttachmentById(projectId, attachmentId) {
- return this.findOne({
- where: {
- projectId,
- id: attachmentId,
- },
- });
- },
+ ProjectAttachment.getAttachmentById = (projectId, attachmentId) => ProjectAttachment.findOne({
+ where: {
+ projectId,
+ id: attachmentId,
+ },
+ });
- getAttachmentsForUser(projectId, userId) {
- return this.findAll({
- where: {
- projectId,
- $or: [{
- createdBy: { $eq: userId },
- }, {
- allowedUsers: {
- $or: [
- { $contains: [userId] },
- { $eq: null },
- ],
- },
- }],
- },
- });
- },
+ ProjectAttachment.getAttachmentsForUser = (projectId, userId) => ProjectAttachment.findAll({
+ where: {
+ projectId,
+ $or: [{
+ createdBy: { $eq: userId },
+ }, {
+ allowedUsers: {
+ $or: [
+ { $contains: [userId] },
+ { $eq: null },
+ ],
+ },
+ }],
},
});
diff --git a/src/models/projectEstimation.js b/src/models/projectEstimation.js
index 5bc2a7ec..9ab0d8b9 100644
--- a/src/models/projectEstimation.js
+++ b/src/models/projectEstimation.js
@@ -1,33 +1,29 @@
-module.exports = function defineProjectHistory(sequelize, DataTypes) {
- const ProjectEstimation = sequelize.define(
- 'ProjectEstimation',
- {
- id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
- buildingBlockKey: { type: DataTypes.STRING, allowNull: false },
- conditions: { type: DataTypes.STRING, allowNull: false },
- price: { type: DataTypes.DOUBLE, allowNull: false },
- quantity: { type: DataTypes.INTEGER, allowNull: true },
- minTime: { type: DataTypes.INTEGER, allowNull: false },
- maxTime: { type: DataTypes.INTEGER, allowNull: false },
- metadata: { type: DataTypes.JSON, allowNull: false, defaultValue: {} },
- projectId: { type: DataTypes.BIGINT, allowNull: false },
+module.exports = function defineProjectEstimation(sequelize, DataTypes) {
+ const ProjectEstimation = sequelize.define('ProjectEstimation', {
+ id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
+ buildingBlockKey: { type: DataTypes.STRING, allowNull: false },
+ conditions: { type: DataTypes.STRING, allowNull: false },
+ price: { type: DataTypes.DOUBLE, allowNull: false },
+ quantity: { type: DataTypes.INTEGER, allowNull: true },
+ minTime: { type: DataTypes.INTEGER, allowNull: false },
+ maxTime: { type: DataTypes.INTEGER, allowNull: false },
+ metadata: { type: DataTypes.JSON, allowNull: false, defaultValue: {} },
+ projectId: { type: DataTypes.BIGINT, allowNull: false },
- deletedAt: { type: DataTypes.DATE, allowNull: true },
- createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
- updatedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
- deletedBy: DataTypes.BIGINT,
- createdBy: { type: DataTypes.INTEGER, allowNull: false },
- updatedBy: { type: DataTypes.INTEGER, allowNull: false },
- },
- {
- tableName: 'project_estimations',
- paranoid: true,
- timestamps: true,
- updatedAt: 'updatedAt',
- createdAt: 'createdAt',
- indexes: [],
- },
- );
+ deletedAt: { type: DataTypes.DATE, allowNull: true },
+ createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
+ updatedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
+ deletedBy: DataTypes.BIGINT,
+ createdBy: { type: DataTypes.INTEGER, allowNull: false },
+ updatedBy: { type: DataTypes.INTEGER, allowNull: false },
+ }, {
+ tableName: 'project_estimations',
+ paranoid: true,
+ timestamps: true,
+ updatedAt: 'updatedAt',
+ createdAt: 'createdAt',
+ indexes: [],
+ });
return ProjectEstimation;
};
diff --git a/src/models/projectEstimationItem.js b/src/models/projectEstimationItem.js
index 245333ec..0e0aeb87 100644
--- a/src/models/projectEstimationItem.js
+++ b/src/models/projectEstimationItem.js
@@ -80,12 +80,13 @@ module.exports = function defineProjectEstimationItem(sequelize, DataTypes) {
beforeFind: (options, callback) => {
// ONLY FOR INTERNAL USAGE: don't use this option to return the data by API
if (options.includeAllProjectEstimatinoItemsForInternalUsage) {
- return callback(null);
+ return callback ? callback(null) : null;
}
if (!options.reqUser || !options.members) {
- return callback(new Error(
- 'You must provide auth user and project members to get project estimation items'));
+ const err = new Error('You must provide auth user and project members to get project estimation items');
+ if (!callback) throw err;
+ return callback(err);
}
// find all project estimation item types which are allowed to be returned to the user
@@ -99,68 +100,65 @@ module.exports = function defineProjectEstimationItem(sequelize, DataTypes) {
// only return Project Estimation Types which are allowed to the user
options.where.type = allowedTypes; // eslint-disable-line no-param-reassign
- return callback(null);
+ return callback ? callback(null) : null;
},
},
- classMethods: {
- /**
- * Find all project estimation items for project
- *
- * TODO: this method can rewritten without using `models`
- * and using JOIN instead for retrieving ProjectEstimationTimes by projectId
- *
- * @param {Object} models all models
- * @param {Number} projectId project id
- * @param {Object} [options] options
- *
- * @returns {Promise} list of project estimation items
- */
- findAllByProject(models, projectId, options) {
- return models.ProjectEstimation.findAll({
- raw: true,
- where: {
- projectId,
- },
- }).then((estimations) => {
- const optionsCombined = _.assign({}, options);
- // update where to always filter by projectEstimationsIds of the project
- optionsCombined.where = _.assign({}, optionsCombined.where, {
- projectEstimationId: _.map(estimations, 'id'),
- });
+ },
+ );
- return this.findAll(optionsCombined);
- });
- },
+ /**
+ * Find all project estimation items for project
+ *
+ * TODO: this method can rewritten without using `models`
+ * and using JOIN instead for retrieving ProjectEstimationTimes by projectId
+ *
+ * @param {Object} models all models
+ * @param {Number} projectId project id
+ * @param {Object} [options] options
+ *
+ * @returns {Promise} list of project estimation items
+ */
+ ProjectEstimationItem.findAllByProject = (models, projectId, options) =>
+ models.ProjectEstimation.findAll({
+ raw: true,
+ where: {
+ projectId,
+ },
+ }).then((estimations) => {
+ const optionsCombined = _.assign({}, options);
+ // update where to always filter by projectEstimationsIds of the project
+ optionsCombined.where = _.assign({}, optionsCombined.where, {
+ projectEstimationId: _.map(estimations, 'id'),
+ });
- /**
- * Delete all project estimation items for project
- *
- * TODO: this method can rewritten without using `models`
- * and using JOIN instead for retrieving ProjectEstimationTimes by projectId
- *
- * @param {Object} models all models
- * @param {Number} projectId project id
- * @param {Object} reqUser user who makes the request
- * @param {Object} [options] options
- *
- * @returns {Promise} result of destroy query
- */
- deleteAllForProject(models, projectId, reqUser, options) {
- return this.findAllByProject(models, projectId, options)
- .then((estimationItems) => {
- const estimationItemsOptions = {
- where: {
- id: _.map(estimationItems, 'id'),
- },
- };
+ return ProjectEstimationItem.findAll(optionsCombined);
+ });
- return this.update({ deletedBy: reqUser.userId }, estimationItemsOptions)
- .then(() => this.destroy(estimationItemsOptions));
- });
- },
- },
- },
- );
+ /**
+ * Delete all project estimation items for project
+ *
+ * TODO: this method can rewritten without using `models`
+ * and using JOIN instead for retrieving ProjectEstimationTimes by projectId
+ *
+ * @param {Object} models all models
+ * @param {Number} projectId project id
+ * @param {Object} reqUser user who makes the request
+ * @param {Object} [options] options
+ *
+ * @returns {Promise} result of destroy query
+ */
+ ProjectEstimationItem.deleteAllForProject = (models, projectId, reqUser, options) =>
+ ProjectEstimationItem.findAllByProject(models, projectId, options)
+ .then((estimationItems) => {
+ const estimationItemsOptions = {
+ where: {
+ id: _.map(estimationItems, 'id'),
+ },
+ };
+
+ return ProjectEstimationItem.update({ deletedBy: reqUser.userId }, estimationItemsOptions)
+ .then(() => ProjectEstimationItem.destroy(estimationItemsOptions));
+ });
return ProjectEstimationItem;
};
diff --git a/src/models/projectMember.js b/src/models/projectMember.js
index 1003c017..d1ed93fb 100644
--- a/src/models/projectMember.js
+++ b/src/models/projectMember.js
@@ -32,28 +32,24 @@ module.exports = function defineProjectMember(sequelize, DataTypes) {
{ fields: ['userId'] },
{ fields: ['role'] },
],
- classMethods: {
- getProjectIdsForUser(userId) {
- return this.findAll({
- where: {
- deletedAt: { $eq: null },
- userId,
- },
- attributes: ['projectId'],
- raw: true,
- })
- .then(res => _.without(_.map(res, 'projectId'), null));
- },
- getActiveProjectMembers(projectId) {
- return this.findAll({
- where: {
- deletedAt: { $eq: null },
- projectId,
- },
- raw: true,
- });
- },
+ });
+
+ ProjectMember.getProjectIdsForUser = userId => ProjectMember.findAll({
+ where: {
+ deletedAt: { $eq: null },
+ userId,
+ },
+ attributes: ['projectId'],
+ raw: true,
+ })
+ .then(res => _.without(_.map(res, 'projectId'), null));
+
+ ProjectMember.getActiveProjectMembers = projectId => ProjectMember.findAll({
+ where: {
+ deletedAt: { $eq: null },
+ projectId,
},
+ raw: true,
});
return ProjectMember;
diff --git a/src/models/projectMemberInvite.js b/src/models/projectMemberInvite.js
index 3b1e51cb..bacee6cd 100644
--- a/src/models/projectMemberInvite.js
+++ b/src/models/projectMemberInvite.js
@@ -45,68 +45,67 @@ module.exports = function defineProjectMemberInvite(sequelize, DataTypes) {
{ fields: ['status'] },
{ fields: ['deletedAt'] },
],
- classMethods: {
- getPendingInvitesForProject(projectId) {
- return this.findAll({
- where: {
- projectId,
- status: INVITE_STATUS.PENDING,
- },
- raw: true,
- });
- },
- getPendingAndReguestedInvitesForProject(projectId) {
- return this.findAll({
- where: {
- projectId,
- status: { $in: [INVITE_STATUS.PENDING, INVITE_STATUS.REQUESTED] },
- },
- raw: true,
- });
- },
- getPendingInviteByEmailOrUserId(projectId, email, userId) {
- const where = { projectId, status: INVITE_STATUS.PENDING };
-
- if (email && userId) {
- _.assign(where, { $or: [
- { email: { $eq: email.toLowerCase() } },
- { userId: { $eq: userId } },
- ] });
- } else if (email) {
- _.assign(where, { email });
- } else if (userId) {
- _.assign(where, { userId });
- }
- return this.findOne({
- where,
- });
- },
- getRequestedInvite(projectId, userId) {
- const where = { projectId, status: INVITE_STATUS.REQUESTED };
+ });
- if (userId) {
- _.assign(where, { userId });
- }
- return this.findOne({
- where,
- });
- },
- getProjectInvitesForUser(email, userId) {
- const where = { status: INVITE_STATUS.PENDING };
+ ProjectMemberInvite.getPendingInvitesForProject = projectId => ProjectMemberInvite.findAll({
+ where: {
+ projectId,
+ status: INVITE_STATUS.PENDING,
+ },
+ raw: true,
+ });
- if (email && userId) {
- _.assign(where, { $or: [{ email: { $eq: email } }, { userId: { $eq: userId } }] });
- } else if (email) {
- _.assign(where, { email });
- } else if (userId) {
- _.assign(where, { userId });
- }
- return this.findAll({
- where,
- }).then(res => _.without(_.map(res, 'projectId'), null));
- },
+ ProjectMemberInvite.getPendingAndReguestedInvitesForProject = projectId => ProjectMemberInvite.findAll({
+ where: {
+ projectId,
+ status: { $in: [INVITE_STATUS.PENDING, INVITE_STATUS.REQUESTED] },
},
+ raw: true,
});
+ ProjectMemberInvite.getPendingInviteByEmailOrUserId = (projectId, email, userId) => {
+ const where = { projectId, status: INVITE_STATUS.PENDING };
+
+ if (email && userId) {
+ _.assign(where, { $or: [
+ { email: { $eq: email.toLowerCase() } },
+ { userId: { $eq: userId } },
+ ] });
+ } else if (email) {
+ _.assign(where, { email });
+ } else if (userId) {
+ _.assign(where, { userId });
+ }
+ return ProjectMemberInvite.findOne({
+ where,
+ });
+ };
+
+ ProjectMemberInvite.getRequestedInvite = (projectId, userId) => {
+ const where = { projectId, status: INVITE_STATUS.REQUESTED };
+
+ if (userId) {
+ _.assign(where, { userId });
+ }
+ return ProjectMemberInvite.findOne({
+ where,
+ });
+ };
+
+ ProjectMemberInvite.getProjectInvitesForUser = (email, userId) => {
+ const where = { status: INVITE_STATUS.PENDING };
+
+ if (email && userId) {
+ _.assign(where, { $or: [{ email: { $eq: email } }, { userId: { $eq: userId } }] });
+ } else if (email) {
+ _.assign(where, { email });
+ } else if (userId) {
+ _.assign(where, { userId });
+ }
+ return ProjectMemberInvite.findAll({
+ where,
+ }).then(res => _.without(_.map(res, 'projectId'), null));
+ };
+
return ProjectMemberInvite;
};
diff --git a/src/models/projectPhase.js b/src/models/projectPhase.js
index 4cd11a48..5c260e1d 100644
--- a/src/models/projectPhase.js
+++ b/src/models/projectPhase.js
@@ -30,56 +30,55 @@ module.exports = function defineProjectPhase(sequelize, DataTypes) {
createdAt: 'createdAt',
deletedAt: 'deletedAt',
indexes: [],
- classMethods: {
- getActiveProjectPhases(projectId) {
- return this.findAll({
- where: {
- deletedAt: { $eq: null },
- projectId,
- },
- raw: true,
- });
- },
- associate: (models) => {
- ProjectPhase.hasMany(models.PhaseProduct, { as: 'products', foreignKey: 'phaseId' });
- ProjectPhase.belongsToMany(models.WorkStream, { through: models.PhaseWorkStream, foreignKey: 'phaseId' });
- },
- /**
- * Search project phases
- * @param {Object} parameters the parameters
- * - sortField: the field that will be references when sorting
- * - sortType: ASC or DESC
- * - fields: the fields to retrieved
- * - projectId: the id of project
- * @param {Object} log the request log
- * @return {Object} the result rows and count
- */
- async search(parameters = {}, log) {
- // ordering
- const orderBy = [];
- if (_.has(parameters, 'sortField') && _.has(parameters, 'sortType')) {
- orderBy.push([parameters.sortField, parameters.sortType]);
- }
- // find options
- const options = {
- where: {
- projectId: parameters.projectId,
- },
- order: orderBy,
- logging: (str) => { log.debug(str); },
- };
- // select fields
- if (_.has(parameters, 'fields')) {
- _.set(options, 'attributes', parameters.fields.filter(e => e !== 'products'));
- if (parameters.fields.includes('products')) {
- _.set(options, 'include', [{ model: this.sequelize.models.PhaseProduct, as: 'products' }]);
- }
- }
+ });
- return this.findAll(options).then(phases => ({ rows: phases, count: phases.length }));
- },
+ ProjectPhase.getActiveProjectPhases = projectId => ProjectPhase.findAll({
+ where: {
+ deletedAt: { $eq: null },
+ projectId,
},
+ raw: true,
});
+ ProjectPhase.associate = (models) => {
+ ProjectPhase.hasMany(models.PhaseProduct, { as: 'products', foreignKey: 'phaseId' });
+ ProjectPhase.belongsToMany(models.WorkStream, { through: models.PhaseWorkStream, foreignKey: 'phaseId' });
+ };
+
+ /**
+ * Search project phases
+ * @param {Object} parameters the parameters
+ * - sortField: the field that will be references when sorting
+ * - sortType: ASC or DESC
+ * - fields: the fields to retrieved
+ * - projectId: the id of project
+ * @param {Object} log the request log
+ * @return {Object} the result rows and count
+ */
+ ProjectPhase.search = async (parameters = {}, log) => {
+ // ordering
+ const orderBy = [];
+ if (_.has(parameters, 'sortField') && _.has(parameters, 'sortType')) {
+ orderBy.push([parameters.sortField, parameters.sortType]);
+ }
+ // find options
+ const options = {
+ where: {
+ projectId: parameters.projectId,
+ },
+ order: orderBy,
+ logging: (str) => { log.debug(str); },
+ };
+ // select fields
+ if (_.has(parameters, 'fields')) {
+ _.set(options, 'attributes', parameters.fields.filter(e => e !== 'products'));
+ if (parameters.fields.includes('products')) {
+ _.set(options, 'include', [{ model: this.sequelize.models.PhaseProduct, as: 'products' }]);
+ }
+ }
+
+ return ProjectPhase.findAll(options).then(phases => ({ rows: phases, count: phases.length }));
+ };
+
return ProjectPhase;
};
diff --git a/src/models/projectSetting.js b/src/models/projectSetting.js
index f0437cc3..dd720264 100644
--- a/src/models/projectSetting.js
+++ b/src/models/projectSetting.js
@@ -64,14 +64,16 @@ module.exports = (sequelize, DataTypes) => {
beforeFind: (options, callback) => {
// ONLY FOR INTERNAL USAGE: don't use this option to return the data by API
if (options.includeAllProjectSettingsForInternalUsage) {
- return callback(null);
+ return callback ? callback(null) : null;
}
if (!options.reqUser || !options.members) {
- return callback(new Error('You must provide reqUser and project member to get project settings'));
+ const err = new Error('You must provide reqUser and project member to get project settings');
+ if (!callback) throw err;
+ return callback(err);
}
- return callback(null);
+ return callback ? callback(null) : null;
},
/**
@@ -84,7 +86,7 @@ module.exports = (sequelize, DataTypes) => {
afterFind: (results, options, callback) => {
// ONLY FOR INTERNAL USAGE: don't use this option to return the data by API
if (options.includeAllProjectSettingsForInternalUsage) {
- return callback(null);
+ return callback ? callback(null) : null;
}
// if we have an array of results form `findAll()` we are filtering results
@@ -98,10 +100,12 @@ module.exports = (sequelize, DataTypes) => {
// if we have one result from `find()` we check if user has permission for the record
} else if (results && !util.hasPermission(results.readPermission, options.reqUser, options.members)) {
- return callback(new Error('User doesn\'t have permission to access this record.'));
+ const err = new Error('User doesn\'t have permission to access this record.');
+ if (!callback) throw err;
+ return callback(err);
}
- return callback(null);
+ return callback ? callback(null) : null;
},
},
},
diff --git a/src/models/projectTemplate.js b/src/models/projectTemplate.js
index 6b115e92..2d14299c 100644
--- a/src/models/projectTemplate.js
+++ b/src/models/projectTemplate.js
@@ -36,19 +36,17 @@ module.exports = (sequelize, DataTypes) => {
updatedAt: 'updatedAt',
createdAt: 'createdAt',
deletedAt: 'deletedAt',
- classMethods: {
- getTemplate(templateId) {
- return this.findById(templateId, { raw: true })
- .then((template) => {
- const formRef = template.form;
- return formRef
- ? models.Form.findAll({ where: formRef, raw: true })
- .then(forms => Object.assign({}, template, { form: _.maxBy(forms, f => f.revision) }))
- : template;
- });
- },
- },
});
+ ProjectTemplate.getTemplate = templateId =>
+ ProjectTemplate.findByPk(templateId, { raw: true })
+ .then((template) => {
+ const formRef = template.form;
+ return formRef
+ ? models.Form.findAll({ where: formRef, raw: true })
+ .then(forms => Object.assign({}, template, { form: _.maxBy(forms, f => f.revision) }))
+ : template;
+ });
+
return ProjectTemplate;
};
diff --git a/src/models/scopeChangeRequest.js b/src/models/scopeChangeRequest.js
index c4615a1e..e527b0ca 100644
--- a/src/models/scopeChangeRequest.js
+++ b/src/models/scopeChangeRequest.js
@@ -28,41 +28,41 @@ module.exports = (sequelize, DataTypes) => {
updatedAt: 'updatedAt',
createdAt: 'createdAt',
deletedAt: 'deletedAt',
- classMethods: {
- findScopeChangeRequest(projectId, { requestId, status }) {
- const where = {
- projectId,
- };
- if (status) {
- where.status = status;
- }
- if (requestId) {
- where.id = requestId;
- }
- return this.findOne({
- where,
- });
- },
- findPendingScopeChangeRequest(projectId) {
- return this.findScopeChangeRequest(
- projectId,
- { status: { $in: [SCOPE_CHANGE_REQ_STATUS.PENDING, SCOPE_CHANGE_REQ_STATUS.APPROVED] } },
- );
- },
- getProjectScopeChangeRequests(projectId, status) {
- const where = {
- projectId,
- };
- if (status) {
- where.status = status;
- }
- return this.findAll({
- where,
- raw: true,
- });
- },
- },
});
+ ScopeChangeRequest.findScopeChangeRequest = (projectId, { requestId, status }) => {
+ const where = {
+ projectId,
+ };
+ if (status) {
+ where.status = status;
+ }
+ if (requestId) {
+ where.id = requestId;
+ }
+ return ScopeChangeRequest.findOne({
+ where,
+ });
+ };
+
+ ScopeChangeRequest.findPendingScopeChangeRequest = projectId =>
+ ScopeChangeRequest.findScopeChangeRequest(
+ projectId,
+ { status: { $in: [SCOPE_CHANGE_REQ_STATUS.PENDING, SCOPE_CHANGE_REQ_STATUS.APPROVED] } },
+ );
+
+ ScopeChangeRequest.getProjectScopeChangeRequests = (projectId, status) => {
+ const where = {
+ projectId,
+ };
+ if (status) {
+ where.status = status;
+ }
+ return ScopeChangeRequest.findAll({
+ where,
+ raw: true,
+ });
+ };
+
return ScopeChangeRequest;
};
diff --git a/src/models/timeline.js b/src/models/timeline.js
index 5b9d6247..d9cce184 100644
--- a/src/models/timeline.js
+++ b/src/models/timeline.js
@@ -3,6 +3,8 @@
/**
* The Timeline model
*/
+import _ from 'lodash';
+
module.exports = (sequelize, DataTypes) => {
const Timeline = sequelize.define('Timeline', {
id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
@@ -25,12 +27,42 @@ module.exports = (sequelize, DataTypes) => {
updatedAt: 'updatedAt',
createdAt: 'createdAt',
deletedAt: 'deletedAt',
- classMethods: {
- associate: (models) => {
- Timeline.hasMany(models.Milestone, { as: 'milestones', foreignKey: 'timelineId', onDelete: 'cascade' });
- },
- },
});
+ Timeline.associate = (models) => {
+ Timeline.hasMany(models.Milestone, { as: 'milestones', foreignKey: 'timelineId', onDelete: 'cascade' });
+ };
+
+ /**
+ * Search keyword in name, description, details.utm.code (To be deprecated)
+ * @param filters: the filters contains reference & referenceId
+ * @param log the request log
+ * @return the result rows
+ */
+ Timeline.search = (filters, log) => {
+ // special handling for keyword filter
+ let query = '1=1 ';
+ const replacements = {};
+ if (_.has(filters, 'reference')) {
+ query += 'AND timelines.reference = :reference ';
+ replacements.reference = filters.reference;
+ }
+ if (_.has(filters, 'referenceId')) {
+ query += 'AND timelines."referenceId" = :referenceId';
+ replacements.referenceId = filters.referenceId;
+ }
+
+ // select timelines
+ return sequelize.query(`SELECT * FROM timelines AS timelines
+ WHERE ${query}`,
+ { type: sequelize.QueryTypes.SELECT,
+ replacements,
+ logging: (str) => { log.debug(str); },
+ raw: true,
+ })
+ .then(timelines => timelines);
+ };
+
+
return Timeline;
};
diff --git a/src/models/workStream.js b/src/models/workStream.js
index 35bfbf3b..d43628b8 100644
--- a/src/models/workStream.js
+++ b/src/models/workStream.js
@@ -32,12 +32,11 @@ module.exports = (sequelize, DataTypes) => {
createdAt: 'createdAt',
deletedAt: 'deletedAt',
indexes: [],
- classMethods: {
- associate: (models) => {
- WorkStream.belongsToMany(models.ProjectPhase, { through: models.PhaseWorkStream, foreignKey: 'workStreamId' });
- },
- },
});
+ WorkStream.associate = (models) => {
+ WorkStream.belongsToMany(models.ProjectPhase, { through: models.PhaseWorkStream, foreignKey: 'workStreamId' });
+ };
+
return WorkStream;
};
diff --git a/src/permissions/index.js b/src/permissions/index.js
index 9af747b9..f9c6fc96 100644
--- a/src/permissions/index.js
+++ b/src/permissions/index.js
@@ -21,14 +21,14 @@ module.exports = () => {
Authorizer.setPolicy('project.view', projectView);
Authorizer.setPolicy('project.edit', projectEdit);
Authorizer.setPolicy('project.delete', projectDelete);
- Authorizer.setPolicy('project.getMember', projectView);
Authorizer.setPolicy('project.addMember', projectView);
- Authorizer.setPolicy('project.listMembers', projectView);
+ Authorizer.setPolicy('project.viewMember', projectView);
Authorizer.setPolicy('project.removeMember', projectMemberDelete);
Authorizer.setPolicy('project.addAttachment', projectEdit);
Authorizer.setPolicy('project.updateAttachment', projectAttachmentUpdate);
Authorizer.setPolicy('project.removeAttachment', projectAttachmentUpdate);
Authorizer.setPolicy('project.downloadAttachment', projectAttachmentDownload);
+ Authorizer.setPolicy('project.listAttachment', projectView);
Authorizer.setPolicy('project.updateMember', projectEdit);
Authorizer.setPolicy('project.admin', projectAdmin);
diff --git a/src/permissions/projectSetting.edit.js b/src/permissions/projectSetting.edit.js
index e75a2855..0d9794c5 100644
--- a/src/permissions/projectSetting.edit.js
+++ b/src/permissions/projectSetting.edit.js
@@ -20,7 +20,7 @@ module.exports = freq => new Promise((resolve, reject) => {
projectMembers = members;
return Promise.resolve();
})
- .then(() => models.ProjectSetting.find({
+ .then(() => models.ProjectSetting.findOne({
where: { projectId, id: settingId },
raw: true,
includeAllProjectSettingsForInternalUsage: true,
diff --git a/src/routes/admin/es-create-index.js b/src/routes/admin/es-create-index.js
new file mode 100644
index 00000000..1d0e3048
--- /dev/null
+++ b/src/routes/admin/es-create-index.js
@@ -0,0 +1,49 @@
+/**
+ * Admin endpoint to create ES index.
+ *
+ * Waits until the operation is completed and returns result.
+ */
+import _ from 'lodash';
+import { middleware as tcMiddleware } from 'tc-core-library-js';
+import util from '../../util';
+import esUtils from '../../utils/es';
+import { INDEX_TO_DOC_TYPE } from '../../utils/es-config';
+
+const permissions = tcMiddleware.permissions;
+
+module.exports = [
+ permissions('project.admin'),
+ (req, res, next) => {
+ try {
+ const logger = req.log;
+ logger.debug('Entered Admin#createIndex');
+
+ const indexName = _.get(req, 'body.indexName');
+ if (!indexName) {
+ const apiErr = new Error('"indexName" is required.');
+ apiErr.status = 400;
+ throw apiErr;
+ }
+
+ const docType = _.get(req, 'body.param.docType', INDEX_TO_DOC_TYPE[indexName]);
+ if (!docType) {
+ const apiErr = new Error('Cannot find "docType" for the index.');
+ apiErr.status = 500;
+ throw apiErr;
+ }
+
+ logger.debug('indexName', indexName);
+ logger.debug('docType', docType);
+
+
+ const esClient = util.getElasticSearchClient();
+ esClient.indices.create(esUtils.buildCreateIndexRequest(indexName, docType))
+ .then(() => {
+ res.status(200).json({ message: 'Index successfully created.' });
+ })
+ .catch(next);
+ } catch (err) {
+ next(err);
+ }
+ },
+];
diff --git a/src/routes/admin/es-delete-index.js b/src/routes/admin/es-delete-index.js
new file mode 100644
index 00000000..da390253
--- /dev/null
+++ b/src/routes/admin/es-delete-index.js
@@ -0,0 +1,39 @@
+
+/**
+ * Admin endpoint to delete ES index.
+ *
+ * Waits until the operation is completed and returns result.
+ */
+import _ from 'lodash';
+import { middleware as tcMiddleware } from 'tc-core-library-js';
+import util from '../../util';
+
+const permissions = tcMiddleware.permissions;
+
+module.exports = [
+ permissions('project.admin'),
+ (req, res, next) => {
+ try {
+ const logger = req.log;
+ logger.debug('Entered Admin#deleteIndex');
+
+ const indexName = _.get(req, 'body.indexName');
+ logger.debug('indexName', indexName);
+
+ if (!indexName) {
+ const apiErr = new Error('"indexName" is required.');
+ apiErr.status = 400;
+ throw apiErr;
+ }
+
+ const esClient = util.getElasticSearchClient();
+ esClient.indices.delete({ index: indexName })
+ .then(() => {
+ res.status(200).json({ message: 'Index successfully deleted.' });
+ })
+ .catch(next);
+ } catch (err) {
+ next(err);
+ }
+ },
+];
diff --git a/src/routes/admin/es-fix-metadata-for-es.js b/src/routes/admin/es-fix-metadata-for-es.js
new file mode 100644
index 00000000..1859ef0c
--- /dev/null
+++ b/src/routes/admin/es-fix-metadata-for-es.js
@@ -0,0 +1,61 @@
+/**
+ * Admin endpoint to fix metadata in DB to be indexed in ES.
+ *
+ * Waits until the operation is completed and returns result.
+ */
+import { middleware as tcMiddleware } from 'tc-core-library-js';
+import fixMetadataForES from '../../utils/fixMetadataForES';
+
+const permissions = tcMiddleware.permissions;
+
+/**
+ * Create a simple logger to log into an array.
+ *
+ * @param {Object} defaultLogger default logger which should be used apart from logging to array
+ *
+ * @returns {Object} logger
+ */
+const createArrayLogger = (defaultLogger) => {
+ const loggerMethods = ['trace', 'debug', 'info', 'warn', 'error'];
+ const log = [];
+ const logger = {};
+
+ loggerMethods.forEach((method) => {
+ logger[method] = (message) => {
+ // log directly with the default logger first
+ defaultLogger[method](message);
+ // save the same message to the array
+ log.push({
+ level: method,
+ message,
+ });
+ };
+ });
+
+ logger.getLog = () => log;
+
+ return logger;
+};
+
+module.exports = [
+ permissions('project.admin'),
+ (req, res, next) => {
+ try {
+ const logger = req.log;
+ logger.debug('Entered Admin#fixMetadataForEs');
+
+ // this logger would use the default `logger` to log into console
+ // while saving the same log messages to an array, so we can return it in response
+ const arrayLogger = createArrayLogger(logger);
+
+ fixMetadataForES(arrayLogger)
+ .then(() => {
+ arrayLogger.info('Data has been successfully fixed in DB.');
+ res.status(200).json(arrayLogger.getLog());
+ })
+ .catch(next);
+ } catch (err) {
+ next(err);
+ }
+ },
+];
diff --git a/src/routes/admin/es-fix-projects-for-es.js b/src/routes/admin/es-fix-projects-for-es.js
new file mode 100644
index 00000000..44987fc7
--- /dev/null
+++ b/src/routes/admin/es-fix-projects-for-es.js
@@ -0,0 +1,61 @@
+/**
+ * Admin endpoint to fix project in DB to be indexed in ES.
+ *
+ * Waits until the operation is completed and returns result.
+ */
+import { middleware as tcMiddleware } from 'tc-core-library-js';
+import fixProjectsForES from '../../utils/fixProjectsForES';
+
+const permissions = tcMiddleware.permissions;
+
+/**
+ * Create a simple logger to log into an array.
+ *
+ * @param {Object} defaultLogger default logger which should be used apart from logging to array
+ *
+ * @returns {Object} logger
+ */
+const createArrayLogger = (defaultLogger) => {
+ const loggerMethods = ['trace', 'debug', 'info', 'warn', 'error'];
+ const log = [];
+ const logger = {};
+
+ loggerMethods.forEach((method) => {
+ logger[method] = (message) => {
+ // log directly with the default logger first
+ defaultLogger[method](message);
+ // save the same message to the array
+ log.push({
+ level: method,
+ message,
+ });
+ };
+ });
+
+ logger.getLog = () => log;
+
+ return logger;
+};
+
+module.exports = [
+ permissions('project.admin'),
+ (req, res, next) => {
+ try {
+ const logger = req.log;
+ logger.debug('Entered Admin#fixProjectsForEs');
+
+ // this logger would use the default `logger` to log into console
+ // while saving the same log messages to an array, so we can return it in response
+ const arrayLogger = createArrayLogger(logger);
+
+ fixProjectsForES(arrayLogger)
+ .then(() => {
+ arrayLogger.info('Data has been successfully fixed in DB.');
+ res.status(200).json(arrayLogger.getLog());
+ })
+ .catch(next);
+ } catch (err) {
+ next(err);
+ }
+ },
+];
diff --git a/src/routes/admin/es-migrate-from-db.js b/src/routes/admin/es-migrate-from-db.js
new file mode 100644
index 00000000..eec4a4a2
--- /dev/null
+++ b/src/routes/admin/es-migrate-from-db.js
@@ -0,0 +1,46 @@
+/**
+ * Admin endpoint migrate data from DB to ES index.
+ *
+ * Waits until the operation is completed and returns result.
+ */
+import _ from 'lodash';
+import config from 'config';
+import { middleware as tcMiddleware } from 'tc-core-library-js';
+import esUtils from '../../utils/es';
+
+const ES_METADATA_INDEX = config.get('elasticsearchConfig.metadataIndexName');
+
+const permissions = tcMiddleware.permissions;
+
+module.exports = [
+ permissions('project.admin'),
+ (req, res, next) => {
+ try {
+ const logger = req.log;
+ logger.debug('Entered Admin#migrateFromDb');
+
+ const indexName = _.get(req, 'body.indexName');
+ logger.debug('indexName', indexName);
+
+ if (!indexName) {
+ const apiErr = new Error('"indexName" is required.');
+ apiErr.status = 400;
+ throw apiErr;
+ }
+
+ if (indexName !== ES_METADATA_INDEX) {
+ const apiErr = new Error(`Only "indexName" === "${ES_METADATA_INDEX}" is supported for now.`);
+ apiErr.status = 400;
+ throw apiErr;
+ }
+
+ esUtils.indexMetadata()
+ .then(() => {
+ res.status(200).json({ message: 'Data has been successfully migrated.' });
+ })
+ .catch(next);
+ } catch (err) {
+ next(err);
+ }
+ },
+];
diff --git a/src/routes/admin/project-create-index.js b/src/routes/admin/project-create-index.js
deleted file mode 100644
index d4a00d4b..00000000
--- a/src/routes/admin/project-create-index.js
+++ /dev/null
@@ -1,367 +0,0 @@
-
-/* globals Promise */
-
-import _ from 'lodash';
-import config from 'config';
-import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../util';
-
-/**
-/**
- * API to handle retrieving a single project by id
- *
- * Permissions:
- * Only users that have access to the project can retrieve it.
- *
- */
-
-// var permissions = require('tc-core-library-js').middleware.permissions
-const permissions = tcMiddleware.permissions;
-const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName');
-const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType');
-
-/**
- * Get the request body for the specified index name
- * @private
- *
- * @param {String} indexName the index name
- * @param {String} docType document type
- * @return {Object} the request body for the specified index name
- */
-function getRequestBody(indexName, docType) {
- const projectMapping = {
- _all: { enabled: false },
- properties: {
- actualPrice: {
- type: 'double',
- },
- attachments: {
- type: 'nested',
- properties: {
- category: {
- type: 'string',
- index: 'not_analyzed',
- },
- contentType: {
- type: 'string',
- index: 'not_analyzed',
- },
- createdAt: {
- type: 'date',
- format: 'strict_date_optional_time||epoch_millis',
- },
- createdBy: {
- type: 'integer',
- },
- description: {
- type: 'string',
- },
- filePath: {
- type: 'string',
- },
- id: {
- type: 'long',
- },
- projectId: {
- type: 'long',
- },
- size: {
- type: 'double',
- },
- title: {
- type: 'string',
- },
- updatedAt: {
- type: 'date',
- format: 'strict_date_optional_time||epoch_millis',
- },
- updatedBy: {
- type: 'integer',
- },
- },
- },
- billingAccountId: {
- type: 'long',
- },
- bookmarks: {
- type: 'nested',
- properties: {
- address: {
- type: 'string',
- },
- title: {
- type: 'string',
- },
- },
- },
- cancelReason: {
- type: 'string',
- },
- challengeEligibility: {
- type: 'nested',
- properties: {
- groups: {
- type: 'long',
- },
- role: {
- type: 'string',
- index: 'not_analyzed',
- },
- users: {
- type: 'long',
- },
- },
- },
- createdAt: {
- type: 'date',
- format: 'strict_date_optional_time||epoch_millis',
- },
- createdBy: {
- type: 'integer',
- },
- description: {
- type: 'string',
- },
- details: {
- type: 'nested',
- properties: {
- TBD_features: {
- type: 'nested',
- properties: {
- description: {
- type: 'string',
- },
- id: {
- type: 'integer',
- },
- isCustom: {
- type: 'boolean',
- },
- title: {
- type: 'string',
- },
- },
- },
- TBD_usageDescription: {
- type: 'string',
- },
- appDefinition: {
- properties: {
- goal: {
- properties: {
- value: {
- type: 'string',
- },
- },
- },
- primaryTarget: {
- type: 'string',
- },
- users: {
- properties: {
- value: {
- type: 'string',
- },
- },
- },
- },
- },
- hideDiscussions: {
- type: 'boolean',
- },
- products: {
- type: 'string',
- },
- summary: {
- type: 'string',
- },
- utm: {
- type: 'nested',
- properties: {
- code: {
- type: 'string',
- },
- },
- },
- },
- },
- directProjectId: {
- type: 'long',
- },
- estimatedPrice: {
- type: 'double',
- },
- external: {
- properties: {
- data: {
- type: 'string',
- },
- id: {
- type: 'string',
- index: 'not_analyzed',
- },
- type: {
- type: 'string',
- index: 'not_analyzed',
- },
- },
- },
- id: {
- type: 'long',
- },
- members: {
- type: 'nested',
- properties: {
- createdAt: {
- type: 'date',
- format: 'strict_date_optional_time||epoch_millis',
- },
- createdBy: {
- type: 'integer',
- },
- email: {
- type: 'string',
- index: 'not_analyzed',
- },
- firstName: {
- type: 'string',
- },
- handle: {
- type: 'string',
- index: 'not_analyzed',
- },
- id: {
- type: 'long',
- },
- isPrimary: {
- type: 'boolean',
- },
- lastName: {
- type: 'string',
- },
- projectId: {
- type: 'long',
- },
- role: {
- type: 'string',
- index: 'not_analyzed',
- },
- updatedAt: {
- type: 'date',
- format: 'strict_date_optional_time||epoch_millis',
- },
- updatedBy: {
- type: 'integer',
- },
- lastActivityAt: {
- type: 'date',
- format: 'strict_date_optional_time||epoch_millis',
- },
- lastActivityUserId: {
- type: 'string',
- },
- userId: {
- type: 'long',
- },
- },
- },
- invites: {
- type: 'nested',
- properties: {
- createdAt: {
- type: 'date',
- format: 'strict_date_optional_time||epoch_millis',
- },
- createdBy: {
- type: 'integer',
- },
- email: {
- type: 'string',
- index: 'not_analyzed',
- },
- id: {
- type: 'long',
- },
- role: {
- type: 'string',
- index: 'not_analyzed',
- },
- updatedAt: {
- type: 'date',
- format: 'strict_date_optional_time||epoch_millis',
- },
- updatedBy: {
- type: 'integer',
- },
- userId: {
- type: 'long',
- },
- },
- },
- name: {
- type: 'string',
- },
- status: {
- type: 'string',
- index: 'not_analyzed',
- },
- terms: {
- type: 'integer',
- },
- type: {
- type: 'string',
- index: 'not_analyzed',
- },
- updatedAt: {
- type: 'date',
- format: 'strict_date_optional_time||epoch_millis',
- },
- updatedBy: {
- type: 'integer',
- },
- utm: {
- properties: {
- campaign: {
- type: 'string',
- },
- medium: {
- type: 'string',
- },
- source: {
- type: 'string',
- },
- },
- },
- },
- };
- const result = {
- index: indexName,
- updateAllTypes: true,
- body: {
- mappings: { },
- },
- };
- result.body.mappings[docType] = projectMapping;
- return result;
-}
-
-
-module.exports = [
- permissions('project.admin'),
- /**
- * GET projects/{projectId}
- * Get a project by id
- */
- (req, res, next) => { // eslint-disable-line no-unused-vars
- const logger = req.log;
- logger.debug('Entered Admin#createIndex');
- const indexName = _.get(req, 'body.param.indexName', ES_PROJECT_INDEX);
- const docType = _.get(req, 'body.param.docType', ES_PROJECT_TYPE);
- logger.debug('indexName', indexName);
- logger.debug('docType', docType);
-
- const esClient = util.getElasticSearchClient();
- esClient.indices.create(getRequestBody(indexName, docType));
- res.status(200).json(util.wrapResponse(req.id, { message: 'Create index request successfully submitted' }));
- },
-];
diff --git a/src/routes/admin/project-delete-index.js b/src/routes/admin/project-delete-index.js
deleted file mode 100644
index eb72ccc6..00000000
--- a/src/routes/admin/project-delete-index.js
+++ /dev/null
@@ -1,45 +0,0 @@
-
-/* globals Promise */
-
-import _ from 'lodash';
-import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../util';
-
-/**
-/**
- * API to handle retrieving a single project by id
- *
- * Permissions:
- * Only users that have access to the project can retrieve it.
- *
- */
-
-// var permissions = require('tc-core-library-js').middleware.permissions
-const permissions = tcMiddleware.permissions;
-
-module.exports = [
- permissions('project.admin'),
- /**
- * GET projects/{projectId}
- * Get a project by id
- */
- (req, res, next) => { // eslint-disable-line no-unused-vars
- const logger = req.log;
- logger.debug('Entered Admin#deleteIndex');
- const indexName = _.get(req, 'body.param.indexName');
- logger.debug('indexName', indexName);
- if (!indexName) {
- const apiErr = new Error('indexName is required');
- apiErr.status = 400;
- return Promise.reject(apiErr);
- }
-
- const esClient = util.getElasticSearchClient();
- esClient.indices.delete({
- index: indexName,
- // we would want to ignore no such index error
- ignore: [404],
- });
- return res.status(200).json(util.wrapResponse(req.id, { message: 'Delete index request successfully submitted' }));
- },
-];
diff --git a/src/routes/admin/project-index-create.js b/src/routes/admin/project-index-create.js
index 1d405aa0..a08bf87d 100644
--- a/src/routes/admin/project-index-create.js
+++ b/src/routes/admin/project-index-create.js
@@ -97,10 +97,6 @@ module.exports = [
projectResponses.map((p) => {
if (p) {
body.push({ index: { _index: indexName, _type: docType, _id: p.id } });
- // TEMPORARY FIX: should fix ES mapping instead and reindex all the projects instead
- if (typeof _.get(p, 'details.taasDefinition.team.skills') !== 'string') {
- _.set(p, 'details.taasDefinition.team.skills', '');
- }
body.push(p);
}
// dummy return
@@ -112,9 +108,9 @@ module.exports = [
logger.trace('body[length-1]', body[body.length - 1]);
}
- res.status(200).json(util.wrapResponse(req.id, {
+ res.status(200).json({
message: `Reindex request successfully submitted for ${body.length / 2} projects`,
- }));
+ });
// bulk index
eClient.bulk({
body,
diff --git a/src/routes/admin/project-index-delete.js b/src/routes/admin/project-index-delete.js
index 54f1c403..53ec9cf0 100644
--- a/src/routes/admin/project-index-delete.js
+++ b/src/routes/admin/project-index-delete.js
@@ -78,7 +78,7 @@ module.exports = [
.catch((error) => {
logger.error(`Error in deleting indexes for project (projectId: ${projectIdStart}-${projectIdEnd})`, error);
});
- res.status(200).json(util.wrapResponse(req.id, { message: 'Delete index request successfully submitted' }));
+ res.status(200).json({ message: 'Delete index request successfully submitted' });
});
})
.catch(err => next(err));
diff --git a/src/routes/attachments/create.js b/src/routes/attachments/create.js
index 99d8069c..be69ab48 100644
--- a/src/routes/attachments/create.js
+++ b/src/routes/attachments/create.js
@@ -11,23 +11,21 @@ import path from 'path';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import models from '../../models';
import util from '../../util';
-import { EVENT } from '../../constants';
+import { EVENT, RESOURCES } from '../../constants';
const permissions = tcMiddleware.permissions;
const addAttachmentValidations = {
- body: {
- param: Joi.object().keys({
- title: Joi.string().required(),
- description: Joi.string().optional().allow(null).allow(''),
- category: Joi.string().optional().allow(null).allow(''),
- size: Joi.number().optional(),
- filePath: Joi.string().required(),
- s3Bucket: Joi.string().required(),
- contentType: Joi.string().required(),
- allowedUsers: Joi.array(Joi.number().integer().positive()).allow(null).default(null),
- }).required(),
- },
+ body: Joi.object().keys({
+ title: Joi.string().required(),
+ description: Joi.string().optional().allow(null).allow(''),
+ category: Joi.string().optional().allow(null).allow(''),
+ size: Joi.number().optional(),
+ filePath: Joi.string().required(),
+ s3Bucket: Joi.string().required(),
+ contentType: Joi.string().required(),
+ allowedUsers: Joi.array().items(Joi.number().integer().positive()).allow(null).default(null),
+ }).required(),
};
module.exports = [
@@ -39,7 +37,7 @@ module.exports = [
* In development mode we have to mock the ec2 file transfer and file service calls
*/
(req, res, next) => {
- const data = req.body.param;
+ const data = req.body;
// default values
const projectId = req.params.projectId;
const allowedUsers = data.allowedUsers;
@@ -140,8 +138,14 @@ module.exports = [
newAttachment,
{ correlationId: req.id },
);
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED, { req, attachment: newAttachment });
- res.status(201).json(util.wrapResponse(req.id, response, 1, 201));
+
+ // emit the event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED,
+ RESOURCES.ATTACHMENT,
+ newAttachment);
+ res.status(201).json(response);
accept();
}
});
@@ -156,8 +160,14 @@ module.exports = [
newAttachment,
{ correlationId: req.id },
);
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED, { req, attachment: newAttachment });
- res.status(201).json(util.wrapResponse(req.id, response, 1, 201));
+ // emit the event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED,
+ RESOURCES.ATTACHMENT,
+ newAttachment);
+
+ res.status(201).json(response);
return Promise.resolve();
})
.catch((error) => {
diff --git a/src/routes/attachments/create.spec.js b/src/routes/attachments/create.spec.js
index 5cbae3fb..e7e02940 100644
--- a/src/routes/attachments/create.spec.js
+++ b/src/routes/attachments/create.spec.js
@@ -7,7 +7,7 @@ import models from '../../models';
import util from '../../util';
import testUtil from '../../tests/util';
import busApi from '../../services/busApi';
-import { BUS_API_EVENT } from '../../constants';
+import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants';
const should = chai.should();
@@ -103,29 +103,29 @@ describe('Project Attachments', () => {
describe('POST /projects/{id}/attachments/', () => {
it('should return 403 if user does not have permissions', (done) => {
request(server)
- .post(`/v4/projects/${project1.id}/attachments/`)
+ .post(`/v5/projects/${project1.id}/attachments/`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(403, done);
});
it('should return 201 return attachment record', (done) => {
request(server)
- .post(`/v4/projects/${project1.id}/attachments/`)
+ .post(`/v5/projects/${project1.id}/attachments/`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
postSpy.should.have.been.calledOnce;
getSpy.should.have.been.calledOnce;
@@ -150,13 +150,13 @@ describe('Project Attachments', () => {
createEventSpy = sandbox.spy(busApi, 'createEvent');
});
- it('sends single BUS_API_EVENT.PROJECT_FILES_UPDATED message when attachment added', (done) => {
+ it('sends send correct BUS API messages when attachment added', (done) => {
request(server)
- .post(`/v4/projects/${project1.id}/attachments/`)
+ .post(`/v5/projects/${project1.id}/attachments/`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: body })
+ .send(body)
.expect(201)
.end((err) => {
if (err) {
@@ -164,15 +164,27 @@ describe('Project Attachments', () => {
} else {
// Wait for app message handler to complete
testUtil.wait(() => {
- createEventSpy.calledTwice.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_FILE_UPLOADED);
- createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_FILES_UPDATED, sinon.match({
+ createEventSpy.calledThrice.should.be.true;
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_ADDED, sinon.match({
+ resource: RESOURCES.ATTACHMENT,
+ title: body.title,
+ description: body.description,
+ category: body.category,
+ contentType: body.contentType,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_FILE_UPLOADED)
+ .should.be.true;
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_FILES_UPDATED, sinon.match({
projectId: project1.id,
projectName: project1.name,
projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`,
userId: 40051333,
initiatorUserId: 40051333,
})).should.be.true;
+
done();
});
}
diff --git a/src/routes/attachments/delete.js b/src/routes/attachments/delete.js
index 12e766da..99dca3a1 100644
--- a/src/routes/attachments/delete.js
+++ b/src/routes/attachments/delete.js
@@ -7,8 +7,9 @@ import {
} from 'tc-core-library-js';
import config from 'config';
import models from '../../models';
+import util from '../../util';
import fileService from '../../services/fileService';
-import { EVENT } from '../../constants';
+import { EVENT, RESOURCES } from '../../constants';
/**
* API to delete a project member.
@@ -55,7 +56,12 @@ module.exports = [
pattachment,
{ correlationId: req.id },
);
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_REMOVED, { req, pattachment });
+ // emit the event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_REMOVED,
+ RESOURCES.ATTACHMENT,
+ pattachment);
res.status(204).json({});
})
.catch(err => next(err));
diff --git a/src/routes/attachments/delete.spec.js b/src/routes/attachments/delete.spec.js
index bf73d585..d31d1718 100644
--- a/src/routes/attachments/delete.spec.js
+++ b/src/routes/attachments/delete.spec.js
@@ -9,8 +9,9 @@ import util from '../../util';
import server from '../../app';
import testUtil from '../../tests/util';
import busApi from '../../services/busApi';
-import { BUS_API_EVENT } from '../../constants';
+import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants';
+const should = chai.should(); // eslint-disable-line no-unused-vars
describe('Project Attachments delete', () => {
let project1;
@@ -73,21 +74,21 @@ describe('Project Attachments delete', () => {
it('should return 403 if user does not have permissions', (done) => {
request(server)
- .delete(`/v4/projects/${project1.id}/attachments/${attachment.id}`)
+ .delete(`/v5/projects/${project1.id}/attachments/${attachment.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
- .send({ param: { userId: 1, projectId: project1.id, role: 'customer' } })
+ .send({ userId: 1, projectId: project1.id, role: 'customer' })
.expect(403, done);
});
it('should return 404 if attachment was not found', (done) => {
request(server)
- .delete(`/v4/projects/${project1.id}/attachments/8888888`)
+ .delete(`/v5/projects/${project1.id}/attachments/8888888`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: { userId: 1, projectId: project1.id, role: 'customer' } })
+ .send({ userId: 1, projectId: project1.id, role: 'customer' })
.expect(404, done);
});
@@ -109,7 +110,7 @@ describe('Project Attachments delete', () => {
const deleteSpy = sinon.spy(mockHttpClient, 'delete');
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .delete(`/v4/projects/${project1.id}/attachments/${attachment.id}`)
+ .delete(`/v5/projects/${project1.id}/attachments/${attachment.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -130,13 +131,13 @@ describe('Project Attachments delete', () => {
if (!res) {
throw new Error('Should found the entity');
} else {
- deleteSpy.should.have.been.calledOnce;
+ deleteSpy.calledOnce.should.be.true;
chai.assert.isNotNull(res.deletedAt);
chai.assert.isNotNull(res.deletedBy);
request(server)
- .get(`/v4/projects/${project1.id}/attachments/${attachment.id}`)
+ .get(`/v5/projects/${project1.id}/attachments/${attachment.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -149,18 +150,18 @@ describe('Project Attachments delete', () => {
it('should return 204 if ADMIN deletes the attachment successfully', (done) => {
request(server)
- .delete(`/v4/projects/${project1.id}/attachments/${attachment.id}`)
+ .delete(`/v5/projects/${project1.id}/attachments/${attachment.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: { userId: 1, projectId: project1.id, role: 'customer' } })
+ .send({ userId: 1, projectId: project1.id, role: 'customer' })
.expect(204, done)
.end((err) => {
if (err) {
done(err);
} else {
request(server)
- .get(`/v4/projects/${project1.id}/attachments/${attachment.id}`)
+ .get(`/v5/projects/${project1.id}/attachments/${attachment.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -181,9 +182,9 @@ describe('Project Attachments delete', () => {
createEventSpy = sandbox.spy(busApi, 'createEvent');
});
- it('sends single BUS_API_EVENT.PROJECT_FILES_UPDATED message when attachment deleted', (done) => {
+ it('sends send correct BUS API messages when attachment deleted', (done) => {
request(server)
- .delete(`/v4/projects/${project1.id}/attachments/${attachment.id}`)
+ .delete(`/v5/projects/${project1.id}/attachments/${attachment.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -194,14 +195,22 @@ describe('Project Attachments delete', () => {
} else {
// Wait for app message handler to complete
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.calledWith(BUS_API_EVENT.PROJECT_FILES_UPDATED, sinon.match({
+ createEventSpy.calledTwice.should.be.true;
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_REMOVED, sinon.match({
+ resource: RESOURCES.ATTACHMENT,
+ id: attachment.id,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_FILES_UPDATED, sinon.match({
projectId: project1.id,
projectName: project1.name,
projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`,
userId: 40051333,
initiatorUserId: 40051333,
})).should.be.true;
+
done();
});
}
diff --git a/src/routes/attachments/download.js b/src/routes/attachments/download.js
index 3b12793a..d925774a 100644
--- a/src/routes/attachments/download.js
+++ b/src/routes/attachments/download.js
@@ -11,40 +11,78 @@ import util from '../../util';
const permissions = tcMiddleware.permissions;
+const getFileDownloadUrl = (req, filePath) => {
+ if (process.env.NODE_ENV === 'development' || config.get('enableFileUpload') === false) {
+ return ['', 'dummy://url'];
+ }
+ return util.getFileDownloadUrl(req, filePath);
+};
+
module.exports = [
permissions('project.downloadAttachment'),
(req, res, next) => {
const projectId = _.parseInt(req.params.projectId);
const attachmentId = _.parseInt(req.params.id);
- models.ProjectAttachment.findOne(
- {
- where: {
- id: attachmentId,
- projectId,
+ util.fetchByIdFromES('attachments', {
+ query: {
+ nested: {
+ path: 'attachments',
+ query:
+ {
+ filtered: {
+ filter: {
+ bool: {
+ must: [
+ { term: { 'attachments.id': attachmentId } },
+ { term: { 'attachments.projectId': projectId } },
+ ],
+ },
+ },
+ },
+ },
+ inner_hits: {},
},
- })
- .then((attachment) => {
- if (!attachment) {
- const err = new Error('Record not found');
- err.status = 404;
- return Promise.reject(err);
- }
- if (process.env.NODE_ENV === 'development' && config.get('enableFileUpload') === 'false') {
- return ['dummy://url'];
+ },
+ })
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No attachment found in ES');
+ return models.ProjectAttachment.findOne(
+ {
+ where: {
+ id: attachmentId,
+ projectId,
+ },
+ })
+ .then((attachment) => {
+ if (!attachment) {
+ const err = new Error('Record not found');
+ err.status = 404;
+ return Promise.reject(err);
+ }
+ if (process.env.NODE_ENV === 'development' && config.get('enableFileUpload') === 'false') {
+ return ['dummy://url'];
+ }
+
+ return getFileDownloadUrl(req, attachment.filePath);
+ })
+ .catch((error) => {
+ req.log.error('Error fetching attachment', error);
+ const rerr = error;
+ rerr.status = rerr.status || 500;
+ next(rerr);
+ });
}
- return util.getFileDownloadUrl(req, attachment.filePath);
+ req.log.debug('attachment found in ES');
+ const attachment = data[0].inner_hits.attachments.hits.hits[0]._source; // eslint-disable-line no-underscore-dangle
+ return getFileDownloadUrl(req, attachment.filePath);
})
.then((result) => {
req.log.debug('getFileDownloadUrl result: ', JSON.stringify(result));
const url = result[1];
- res.status(200).json(util.wrapResponse(req.id, { url }));
+ return res.json({ url });
})
- .catch((error) => {
- req.log.error('Error fetching attachment', error);
- const rerr = error;
- rerr.status = rerr.status || 500;
- next(rerr);
- });
+ .catch(next);
},
];
diff --git a/src/routes/attachments/download.spec.js b/src/routes/attachments/download.spec.js
index 8cfc80cb..84ce6821 100644
--- a/src/routes/attachments/download.spec.js
+++ b/src/routes/attachments/download.spec.js
@@ -76,7 +76,7 @@ describe('Project Attachments download', () => {
it('should return 403 if USER does not have permissions', (done) => {
request(server)
- .get(`/v4/projects/${project1.id}/attachments/${attachment.id}`)
+ .get(`/v5/projects/${project1.id}/attachments/${attachment.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -86,7 +86,7 @@ describe('Project Attachments download', () => {
it('should return 403 if MANAGER does not have permissions', (done) => {
request(server)
- .get(`/v4/projects/${project1.id}/attachments/${attachment.id}`)
+ .get(`/v5/projects/${project1.id}/attachments/${attachment.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -96,7 +96,7 @@ describe('Project Attachments download', () => {
it('should return 404 if attachment was not found', (done) => {
request(server)
- .get(`/v4/projects/${project1.id}/attachments/8888888`)
+ .get(`/v5/projects/${project1.id}/attachments/8888888`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -106,7 +106,7 @@ describe('Project Attachments download', () => {
it('should return 200 when the CREATOR can download attachment successfully', (done) => {
request(server)
- .get(`/v4/projects/${project1.id}/attachments/${attachment.id}`)
+ .get(`/v5/projects/${project1.id}/attachments/${attachment.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -116,7 +116,7 @@ describe('Project Attachments download', () => {
it('should return 200 when the USER with permission can download attachment successfully', (done) => {
request(server)
- .get(`/v4/projects/${project1.id}/attachments/${attachment.id}`)
+ .get(`/v5/projects/${project1.id}/attachments/${attachment.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -126,7 +126,7 @@ describe('Project Attachments download', () => {
it('should return 200 when ADMIN can download attachment successfully', (done) => {
request(server)
- .get(`/v4/projects/${project1.id}/attachments/${attachment.id}`)
+ .get(`/v5/projects/${project1.id}/attachments/${attachment.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
diff --git a/src/routes/attachments/list.js b/src/routes/attachments/list.js
new file mode 100644
index 00000000..d77c7513
--- /dev/null
+++ b/src/routes/attachments/list.js
@@ -0,0 +1,65 @@
+import _ from 'lodash';
+import validate from 'express-validation';
+import Joi from 'joi';
+import { middleware as tcMiddleware } from 'tc-core-library-js';
+import models from '../../models';
+import util from '../../util';
+
+/**
+ * API to get project attachments.
+ *
+ */
+
+const permissions = tcMiddleware.permissions;
+
+const schema = {
+ params: {
+ projectId: Joi.number().integer().positive().required(),
+ },
+};
+
+module.exports = [
+ validate(schema),
+ permissions('project.listAttachment'),
+ (req, res, next) => {
+ const projectId = _.parseInt(req.params.projectId);
+
+ util.fetchByIdFromES('attachments', {
+ query: {
+ nested: {
+ path: 'attachments',
+ query:
+ {
+ filtered: {
+ filter: {
+ bool: {
+ must: [
+ { term: { 'attachments.projectId': projectId } },
+ ],
+ },
+ },
+ },
+ },
+ inner_hits: {},
+ },
+ },
+ })
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No attachment found in ES');
+ return models.ProjectAttachment.findAll({
+ where: {
+ projectId,
+ },
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ raw: true,
+ })
+ .then(attachments => res.json(attachments))
+ .catch(next);
+ }
+ req.log.debug('attachments found in ES');
+ return res.json(data[0].inner_hits.attachments.hits.hits.map(hit => hit._source)); // eslint-disable-line no-underscore-dangle
+ })
+ .catch(next);
+ },
+];
diff --git a/src/routes/attachments/list.spec.js b/src/routes/attachments/list.spec.js
new file mode 100644
index 00000000..07a1f90e
--- /dev/null
+++ b/src/routes/attachments/list.spec.js
@@ -0,0 +1,128 @@
+/* eslint-disable no-unused-expressions */
+import chai from 'chai';
+import request from 'supertest';
+
+import models from '../../models';
+import server from '../../app';
+import testUtil from '../../tests/util';
+
+const should = chai.should();
+
+describe('Project Attachments download', () => {
+ let project1;
+
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => {
+ models.Project.create({
+ type: 'generic',
+ directProjectId: 1,
+ billingAccountId: 1,
+ name: 'test1',
+ description: 'test project1',
+ status: 'draft',
+ details: {},
+ createdBy: 1,
+ updatedBy: 1,
+ lastActivityAt: 1,
+ lastActivityUserId: '1',
+ }).then((p) => {
+ project1 = p;
+ // create members
+ return models.ProjectMember.create({
+ userId: 40051332,
+ projectId: project1.id,
+ role: 'copilot',
+ isPrimary: true,
+ createdBy: 1,
+ updatedBy: 1,
+ }).then(() => models.ProjectAttachment.bulkCreate([{
+ projectId: project1.id,
+ title: 'test.txt',
+ description: 'blah',
+ contentType: 'application/unknown',
+ size: 12312,
+ category: null,
+ filePath: 'https://media.topcoder.com/projects/1/test.txt',
+ createdBy: testUtil.userIds.copilot,
+ updatedBy: 1,
+ }, {
+ projectId: project1.id,
+ title: 'test2.txt',
+ description: 'blah 2',
+ contentType: 'application/unknown',
+ size: 12312,
+ category: null,
+ filePath: 'https://media.topcoder.com/projects/1/test2.txt',
+ createdBy: testUtil.userIds.copilot,
+ updatedBy: 1,
+ }]).then(() => done()));
+ });
+ });
+ });
+
+ after((done) => {
+ testUtil.clearDb(done);
+ });
+
+ describe('List /projects/{id}/attachments', () => {
+ it('should return 403 for anonymous user', (done) => {
+ request(server)
+ .get(`/v5/projects/${project1.id}/attachments`)
+ .expect(403, done);
+ });
+
+ it('should return 403 for member', (done) => {
+ request(server)
+ .get(`/v5/projects/${project1.id}/attachments`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.member}`,
+ })
+ .send()
+ .expect(403, done);
+ });
+
+ it('should return 200 for manager', (done) => {
+ request(server)
+ .get(`/v5/projects/${project1.id}/attachments`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.manager}`,
+ })
+ .send()
+ .expect(200, done);
+ });
+
+ it('should return 200 for copilot', (done) => {
+ request(server)
+ .get(`/v5/projects/${project1.id}/attachments`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.copilot}`,
+ })
+ .send()
+ .expect(200, done);
+ });
+
+ it('should return 200 for admin', (done) => {
+ request(server)
+ .get(`/v5/projects/${project1.id}/attachments`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.admin}`,
+ })
+ .send()
+ .expect(200)
+ .end((err, res) => {
+ const resJson = res.body;
+ resJson.should.have.length(2);
+ resJson[0].description.should.be.eql('blah');
+ resJson[0].filePath.should.be.eql('https://media.topcoder.com/projects/1/test.txt');
+ resJson[0].projectId.should.be.eql(project1.id);
+ should.exist(resJson[0].createdAt);
+ should.exist(resJson[0].updatedAt);
+ should.not.exist(resJson[0].deletedBy);
+ should.not.exist(resJson[0].deletedAt);
+
+ done();
+ });
+ });
+ });
+});
diff --git a/src/routes/attachments/update.js b/src/routes/attachments/update.js
index 0bf90738..54cc74d2 100644
--- a/src/routes/attachments/update.js
+++ b/src/routes/attachments/update.js
@@ -7,7 +7,7 @@ import {
} from 'tc-core-library-js';
import models from '../../models';
import util from '../../util';
-import { EVENT } from '../../constants';
+import { EVENT, RESOURCES } from '../../constants';
/**
* API to update a project member.
@@ -15,13 +15,11 @@ import { EVENT } from '../../constants';
const permissions = tcMiddleware.permissions;
const updateProjectAttachmentValidation = {
- body: {
- param: Joi.object().keys({
- title: Joi.string().required(),
- description: Joi.string().optional().allow(null).allow(''),
- allowedUsers: Joi.array(Joi.number().integer().positive()).allow(null).default(null),
- }),
- },
+ body: Joi.object().keys({
+ title: Joi.string().required(),
+ description: Joi.string().optional().allow(null).allow(''),
+ allowedUsers: Joi.array().items(Joi.number().integer().positive()).allow(null).default(null),
+ }),
};
module.exports = [
@@ -32,7 +30,7 @@ module.exports = [
* Update a attachment if the user has access
*/
(req, res, next) => {
- const updatedProps = req.body.param;
+ const updatedProps = req.body;
const projectId = _.parseInt(req.params.projectId);
const attachmentId = _.parseInt(req.params.id);
let previousValue;
@@ -56,14 +54,20 @@ module.exports = [
}
})).then((updated) => {
req.log.debug('updated project attachment', JSON.stringify(updated, null, 2));
- res.json(util.wrapResponse(req.id, updated));
+ res.json(updated);
// emit original and updated project information
req.app.services.pubsub.publish(
EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_UPDATED,
{ original: previousValue, updated: updated.get({ plain: true }) },
{ correlationId: req.id },
);
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_UPDATED, { req, original: previousValue, updated });
+
+ // emit the event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_UPDATED,
+ RESOURCES.ATTACHMENT,
+ updated.toJSON());
}).catch(err => next(err)));
},
];
diff --git a/src/routes/attachments/update.spec.js b/src/routes/attachments/update.spec.js
index 0796e22f..34957a42 100644
--- a/src/routes/attachments/update.spec.js
+++ b/src/routes/attachments/update.spec.js
@@ -7,7 +7,7 @@ import models from '../../models';
import server from '../../app';
import testUtil from '../../tests/util';
import busApi from '../../services/busApi';
-import { BUS_API_EVENT } from '../../constants';
+import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants';
const should = chai.should();
@@ -73,37 +73,37 @@ describe('Project Attachments update', () => {
it('should return 403 if user does not have permissions', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}/attachments/${attachment.id}`)
+ .patch(`/v5/projects/${project1.id}/attachments/${attachment.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
- .send({ param: { title: 'updated title', description: 'updated description' } })
+ .send({ title: 'updated title', description: 'updated description' })
.expect(403, done);
});
it('should return 404 if attachment was not found', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}/attachments/8888888`)
+ .patch(`/v5/projects/${project1.id}/attachments/8888888`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: { title: 'updated title', description: 'updated description' } })
+ .send({ title: 'updated title', description: 'updated description' })
.expect(404, done);
});
it('should return 200 if attachment was successfully updated', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}/attachments/${attachment.id}`)
+ .patch(`/v5/projects/${project1.id}/attachments/${attachment.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: { title: 'updated title', description: 'updated description' } })
+ .send({ title: 'updated title', description: 'updated description' })
.expect(200)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.title.should.equal('updated title');
resJson.description.should.equal('updated description');
@@ -114,17 +114,17 @@ describe('Project Attachments update', () => {
it('should return 200 if admin updates the attachment', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}/attachments/${attachment.id}`)
+ .patch(`/v5/projects/${project1.id}/attachments/${attachment.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: { title: 'updated title 1', description: 'updated description 1' } })
+ .send({ title: 'updated title 1', description: 'updated description 1' })
.expect(200)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.title.should.equal('updated title 1');
resJson.description.should.equal('updated description 1');
@@ -145,13 +145,13 @@ describe('Project Attachments update', () => {
createEventSpy = sandbox.stub(busApi, 'createEvent');
});
- it('sends single BUS_API_EVENT.PROJECT_FILES_UPDATED message when attachment updated', (done) => {
+ it('sends send correct BUS API messages when attachment updated', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}/attachments/${attachment.id}`)
+ .patch(`/v5/projects/${project1.id}/attachments/${attachment.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: { title: 'updated title', description: 'updated description' } })
+ .send({ title: 'updated title', description: 'updated description' })
.expect(200)
.end((err) => {
if (err) {
@@ -159,14 +159,23 @@ describe('Project Attachments update', () => {
} else {
// Wait for app message handler to complete
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.calledWith(BUS_API_EVENT.PROJECT_FILES_UPDATED, sinon.match({
+ createEventSpy.calledTwice.should.be.true;
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_UPDATED, sinon.match({
+ resource: RESOURCES.ATTACHMENT,
+ title: 'updated title',
+ description: 'updated description',
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_FILES_UPDATED, sinon.match({
projectId: project1.id,
projectName: project1.name,
projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`,
userId: 40051333,
initiatorUserId: 40051333,
})).should.be.true;
+
done();
});
}
diff --git a/src/routes/form/revision/create.js b/src/routes/form/revision/create.js
index c1f570a7..99c814f2 100644
--- a/src/routes/form/revision/create.js
+++ b/src/routes/form/revision/create.js
@@ -5,6 +5,7 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../../constants';
import util from '../../../util';
import models from '../../../models';
@@ -15,18 +16,16 @@ const schema = {
key: Joi.string().max(45).required(),
version: Joi.number().integer().positive().required(),
},
- body: {
- param: Joi.object().keys({
- config: Joi.object().required(),
+ body: Joi.object().keys({
+ config: Joi.object().required(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
};
module.exports = [
@@ -43,13 +42,13 @@ module.exports = [
if (form) {
const version = form ? form.version : 1;
const revision = form ? form.revision + 1 : 1;
- const entity = _.assign(req.body.param, {
+ const entity = _.assign(req.body, {
version,
revision,
createdBy: req.authUser.userId,
updatedBy: req.authUser.userId,
key: req.params.key,
- config: req.body.param.config,
+ config: req.body.config,
});
return models.Form.create(entity);
}
@@ -57,9 +56,12 @@ module.exports = [
apiErr.status = 404;
return Promise.reject(apiErr);
}).then((createdEntity) => {
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE,
+ RESOURCES.FORM_REVISION,
+ createdEntity.toJSON());
// Omit deletedAt, deletedBy
- res.status(201).json(util.wrapResponse(
- req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201));
+ res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'));
})
.catch(next));
},
diff --git a/src/routes/form/revision/create.spec.js b/src/routes/form/revision/create.spec.js
index d23deaf7..3cfcf3c3 100644
--- a/src/routes/form/revision/create.spec.js
+++ b/src/routes/form/revision/create.spec.js
@@ -35,32 +35,32 @@ describe('CREATE Form Revision', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.Form.create(forms[0]))
- .then(() => models.Form.create(forms[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.Form.create(forms[0]))
+ .then(() => models.Form.create(forms[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('Post /projects/metadata/form/{key}/versions/{version}/revision', () => {
const body = {
- param: {
- config: {
- 'test create': 'test create',
- },
+ config: {
+ 'test create': 'test create',
},
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post('/v4/projects/metadata/form/dev/versions/1/revisions')
+ .post('/v5/projects/metadata/form/dev/versions/1/revisions')
.send(body)
.expect(403, done);
});
it('should return 404 if missing key', (done) => {
request(server)
- .post('/v4/projects/metadata/form/no-exist-key/versions/1/revisions')
+ .post('/v5/projects/metadata/form/no-exist-key/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -71,7 +71,7 @@ describe('CREATE Form Revision', () => {
it('should return 404 if missing version', (done) => {
request(server)
- .post('/v4/projects/metadata/form/dev/versions/100/revisions')
+ .post('/v5/projects/metadata/form/dev/versions/100/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -80,26 +80,24 @@ describe('CREATE Form Revision', () => {
.expect(404, done);
});
- it('should return 422 if missing config', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- config: undefined,
- }),
- };
+ it('should return 400 if missing config', (done) => {
+ const invalidBody = _.assign({}, body, {
+ config: undefined,
+ });
request(server)
- .post('/v4/projects/metadata/form/no-exist-key/versions/1/revisions')
+ .post('/v5/projects/metadata/form/no-exist-key/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 201 for admin', (done) => {
request(server)
- .post('/v4/projects/metadata/form/dev/versions/1/revisions')
+ .post('/v5/projects/metadata/form/dev/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -107,9 +105,9 @@ describe('CREATE Form Revision', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson.id);
- resJson.config.should.be.eql(body.param.config);
+ resJson.config.should.be.eql(body.config);
resJson.key.should.be.eql('dev');
resJson.revision.should.be.eql(3);
resJson.version.should.be.eql(1);
@@ -125,7 +123,7 @@ describe('CREATE Form Revision', () => {
it('should return 403 for member', (done) => {
request(server)
- .post('/v4/projects/metadata/form/dev/versions/1/revisions')
+ .post('/v5/projects/metadata/form/dev/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
diff --git a/src/routes/form/revision/delete.js b/src/routes/form/revision/delete.js
index 1abaf2ec..e06b9f80 100644
--- a/src/routes/form/revision/delete.js
+++ b/src/routes/form/revision/delete.js
@@ -2,8 +2,11 @@
* API to delete a revsion
*/
import validate from 'express-validation';
+import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../../constants';
+import util from '../../../util';
import models from '../../../models';
const permissions = tcMiddleware.permissions;
@@ -38,9 +41,12 @@ module.exports = [
return form.update({
deletedBy: req.authUser.userId,
});
- }).then((form) => {
- form.destroy();
- }).then(() => {
+ }).then(form => form.destroy())
+ .then((form) => {
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE,
+ RESOURCES.FORM_REVISION,
+ _.pick(form.toJSON(), 'id'));
res.status(204).end();
})
.catch(next));
diff --git a/src/routes/form/revision/delete.spec.js b/src/routes/form/revision/delete.spec.js
index 6bcd95c6..8aee319f 100644
--- a/src/routes/form/revision/delete.spec.js
+++ b/src/routes/form/revision/delete.spec.js
@@ -26,7 +26,7 @@ const expectAfterDelete = (err, next) => {
chai.assert.isNotNull(res.deletedBy);
request(server)
- .get('/v4/projects/metadata/form/dev/versions/1/revisions/1')
+ .get('/v5/projects/metadata/form/dev/versions/1/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -60,24 +60,25 @@ describe('DELETE form revision', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.Form.create(forms[0]))
- .then(() => models.Form.create(forms[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
-
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.Form.create(forms[0]))
+ .then(() => models.Form.create(forms[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('DELETE /projects/metadata/form/{key}/versions/{version}/revisions/{revision}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .delete('/v4/projects/metadata/form/dev/versions/1/revisions/1')
+ .delete('/v5/projects/metadata/form/dev/versions/1/revisions/1')
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .delete('/v4/projects/metadata/form/dev/versions/1/revisions/1')
+ .delete('/v5/projects/metadata/form/dev/versions/1/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -86,7 +87,7 @@ describe('DELETE form revision', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .delete('/v4/projects/metadata/form/dev/versions/1/revisions/1')
+ .delete('/v5/projects/metadata/form/dev/versions/1/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -95,7 +96,7 @@ describe('DELETE form revision', () => {
it('should return 403 for manager', (done) => {
request(server)
- .delete('/v4/projects/metadata/form/dev/versions/1/revisions/1')
+ .delete('/v5/projects/metadata/form/dev/versions/1/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -104,7 +105,7 @@ describe('DELETE form revision', () => {
it('should return 404 for non-existed key', (done) => {
request(server)
- .delete('/v4/projects/metadata/form/no-existed-key/versions/1/revisions/1')
+ .delete('/v5/projects/metadata/form/no-existed-key/versions/1/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -113,7 +114,7 @@ describe('DELETE form revision', () => {
it('should return 404 for non-existed version', (done) => {
request(server)
- .delete('/v4/projects/metadata/form/dev/versions/100/revisions/1')
+ .delete('/v5/projects/metadata/form/dev/versions/100/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -123,7 +124,7 @@ describe('DELETE form revision', () => {
it('should return 404 for non-existed revision', (done) => {
request(server)
- .delete('/v4/projects/metadata/form/dev/versions/1/revisions/100')
+ .delete('/v5/projects/metadata/form/dev/versions/1/revisions/100')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -132,7 +133,7 @@ describe('DELETE form revision', () => {
it('should return 204, for admin', (done) => {
request(server)
- .delete('/v4/projects/metadata/form/dev/versions/1/revisions/1')
+ .delete('/v5/projects/metadata/form/dev/versions/1/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -142,7 +143,7 @@ describe('DELETE form revision', () => {
it('should return 204, for connect admin', (done) => {
request(server)
- .delete('/v4/projects/metadata/form/dev/versions/1/revisions/1')
+ .delete('/v5/projects/metadata/form/dev/versions/1/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
diff --git a/src/routes/form/revision/get.js b/src/routes/form/revision/get.js
index 460331cf..cc0ec5d5 100644
--- a/src/routes/form/revision/get.js
+++ b/src/routes/form/revision/get.js
@@ -4,8 +4,8 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../../util';
import models from '../../../models';
+import util from '../../../util';
const permissions = tcMiddleware.permissions;
@@ -20,25 +20,58 @@ const schema = {
module.exports = [
validate(schema),
permissions('form.view'),
- (req, res, next) => models.Form.findOne({
- where: {
- key: req.params.key,
- version: req.params.version,
- revision: req.params.revision,
- },
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- })
- .then((form) => {
- // Not found
- if (!form) {
- const apiErr = new Error('Form not found for key' +
- ` ${req.params.key} version ${req.params.version} revision ${req.params.revision}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
- }
+ (req, res, next) => {
+ util.fetchByIdFromES('forms', {
+ query: {
+ nested: {
+ path: 'forms',
+ query:
+ {
+ filtered: {
+ filter: {
+ bool: {
+ must: [
+ { term: { 'forms.key': req.params.key } },
+ { term: { 'forms.version': req.params.version } },
+ { term: { 'forms.revision': req.params.revision } },
+ ],
+ },
+ },
+ },
+ },
+ inner_hits: {},
+ },
+ },
+ }, 'metadata')
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No form found in ES');
+ models.Form.findOne({
+ where: {
+ key: req.params.key,
+ version: req.params.version,
+ revision: req.params.revision,
+ },
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ })
+ .then((form) => {
+ // Not found
+ if (!form) {
+ const apiErr = new Error('Form not found for key' +
+ ` ${req.params.key} version ${req.params.version} revision ${req.params.revision}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
- res.json(util.wrapResponse(req.id, form));
- return Promise.resolve();
+ res.json(form);
+ return Promise.resolve();
+ })
+ .catch(next);
+ } else {
+ req.log.debug('forms found in ES');
+ res.json(data[0].inner_hits.forms.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle
+ }
})
- .catch(next),
+ .catch(next);
+ },
];
diff --git a/src/routes/form/revision/get.spec.js b/src/routes/form/revision/get.spec.js
index 9e2b218f..bdcdff23 100644
--- a/src/routes/form/revision/get.spec.js
+++ b/src/routes/form/revision/get.spec.js
@@ -34,24 +34,26 @@ describe('GET a particular revision of specific version Form', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.Form.create(forms[0]))
- .then(() => models.Form.create(forms[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.Form.create(forms[0]))
+ .then(() => models.Form.create(forms[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/form/dev/versions/{version}/revisions/{revision}', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions/1/revisions/2')
+ .get('/v5/projects/metadata/form/dev/versions/1/revisions/2')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
const form = forms[1];
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(form.key);
resJson.config.should.be.eql(form.config);
@@ -68,13 +70,13 @@ describe('GET a particular revision of specific version Form', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions/1/revisions/2')
+ .get('/v5/projects/metadata/form/dev/versions/1/revisions/2')
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions/1/revisions/2')
+ .get('/v5/projects/metadata/form/dev/versions/1/revisions/2')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -84,7 +86,7 @@ describe('GET a particular revision of specific version Form', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions/1/revisions/2')
+ .get('/v5/projects/metadata/form/dev/versions/1/revisions/2')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -94,7 +96,7 @@ describe('GET a particular revision of specific version Form', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions/1/revisions/2')
+ .get('/v5/projects/metadata/form/dev/versions/1/revisions/2')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -103,7 +105,7 @@ describe('GET a particular revision of specific version Form', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions/1/revisions/2')
+ .get('/v5/projects/metadata/form/dev/versions/1/revisions/2')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/form/revision/list.js b/src/routes/form/revision/list.js
index d4acc1d0..ab0ab9f9 100644
--- a/src/routes/form/revision/list.js
+++ b/src/routes/form/revision/list.js
@@ -4,8 +4,8 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../../util';
import models from '../../../models';
+import util from '../../../util';
const permissions = tcMiddleware.permissions;
@@ -19,23 +19,32 @@ const schema = {
module.exports = [
validate(schema),
permissions('form.view'),
- (req, res, next) => models.Form.findAll({
- where: {
- key: req.params.key,
- version: req.params.version,
- },
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- })
- .then((forms) => {
- // Not found
- if ((!forms) || (forms.length === 0)) {
- const apiErr = new Error(`Form not found for key ${req.params.key} version ${req.params.version}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
- }
+ (req, res, next) =>
+ util.fetchFromES('forms')
+ .then((data) => {
+ if (data.forms.length === 0) {
+ req.log.debug('No form found in ES');
+ models.Form.findAll({
+ where: {
+ key: req.params.key,
+ version: req.params.version,
+ },
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ }).then((forms) => {
+ // Not found
+ if ((!forms) || (forms.length === 0)) {
+ const apiErr = new Error(`Form not found for key ${req.params.key} version ${req.params.version}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
- res.json(util.wrapResponse(req.id, forms));
- return Promise.resolve();
- })
- .catch(next),
+ res.json(forms);
+ return Promise.resolve();
+ })
+ .catch(next);
+ } else {
+ req.log.debug('forms found in ES');
+ res.json(data.forms);
+ }
+ }).catch(next),
];
diff --git a/src/routes/form/revision/list.spec.js b/src/routes/form/revision/list.spec.js
index b2be931d..d239b03b 100644
--- a/src/routes/form/revision/list.spec.js
+++ b/src/routes/form/revision/list.spec.js
@@ -35,24 +35,26 @@ describe('LIST form revisions', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.Form.create(forms[0]))
- .then(() => models.Form.create(forms[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.Form.create(forms[0]))
+ .then(() => models.Form.create(forms[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/form/dev/versions/{version}/revisions', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions/1/revisions')
+ .get('/v5/projects/metadata/form/dev/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
const form = forms[0];
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
resJson[0].key.should.be.eql(form.key);
@@ -70,13 +72,13 @@ describe('LIST form revisions', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions/1/revisions')
+ .get('/v5/projects/metadata/form/dev/versions/1/revisions')
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions/1/revisions')
+ .get('/v5/projects/metadata/form/dev/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -86,7 +88,7 @@ describe('LIST form revisions', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions/1/revisions')
+ .get('/v5/projects/metadata/form/dev/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -96,7 +98,7 @@ describe('LIST form revisions', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions/1/revisions')
+ .get('/v5/projects/metadata/form/dev/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -105,7 +107,7 @@ describe('LIST form revisions', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions/1/revisions')
+ .get('/v5/projects/metadata/form/dev/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/form/version/create.js b/src/routes/form/version/create.js
index 2a16d113..00a16119 100644
--- a/src/routes/form/version/create.js
+++ b/src/routes/form/version/create.js
@@ -5,6 +5,7 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../../constants';
import util from '../../../util';
import models from '../../../models';
@@ -14,18 +15,18 @@ const schema = {
params: {
key: Joi.string().max(45).required(),
},
- body: {
- param: Joi.object().keys({
- config: Joi.object().required(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ body: Joi.object().keys({
+ config: Joi.object().required(),
+
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
+
};
module.exports = [
@@ -45,19 +46,22 @@ module.exports = [
latestVersion = latestVersionForm.version + 1;
}
- const entity = _.assign(req.body.param, {
+ const entity = _.assign(req.body, {
version: latestVersion,
revision: 1,
createdBy: req.authUser.userId,
updatedBy: req.authUser.userId,
key: req.params.key,
- config: req.body.param.config,
+ config: req.body.config,
});
return models.Form.create(entity);
}).then((createdEntity) => {
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE,
+ RESOURCES.FORM_VERSION,
+ createdEntity.toJSON());
// Omit deletedAt, deletedBy
- res.status(201).json(util.wrapResponse(
- req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201));
+ res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'));
})
.catch(next));
},
diff --git a/src/routes/form/version/create.spec.js b/src/routes/form/version/create.spec.js
index 68a27ba5..f94eb023 100644
--- a/src/routes/form/version/create.spec.js
+++ b/src/routes/form/version/create.spec.js
@@ -35,49 +35,56 @@ describe('CREATE Form version', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.Form.create(forms[0]))
- .then(() => models.Form.create(forms[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.Form.create(forms[0]))
+ .then(() => models.Form.create(forms[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
+
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.Form.create(forms[0]))
+ .then(() => models.Form.create(forms[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('Post /projects/metadata/form/{key}/versions/', () => {
const body = {
- param: {
- config: {
- 'test create': 'test create',
- },
+ config: {
+ 'test create': 'test create',
},
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post('/v4/projects/metadata/form/dev/versions')
+ .post('/v5/projects/metadata/form/dev/versions')
.send(body)
.expect(403, done);
});
- it('should return 422 if missing config', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- config: undefined,
- }),
- };
+ it('should return 400 if missing config', (done) => {
+ const invalidBody = _.assign({}, body, {
+ config: undefined,
+ });
request(server)
- .post('/v4/projects/metadata/form/dev/versions/')
+ .post('/v5/projects/metadata/form/dev/versions/')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 201 for admin', (done) => {
request(server)
- .post('/v4/projects/metadata/form/dev/versions/')
+ .post('/v5/projects/metadata/form/dev/versions/')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -85,9 +92,9 @@ describe('CREATE Form version', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson.id);
- resJson.config.should.be.eql(body.param.config);
+ resJson.config.should.be.eql(body.config);
resJson.key.should.be.eql('dev');
resJson.revision.should.be.eql(1);
resJson.version.should.be.eql(2);
@@ -103,7 +110,7 @@ describe('CREATE Form version', () => {
it('should return 403 for member', (done) => {
request(server)
- .post('/v4/projects/metadata/form/dev/versions/')
+ .post('/v5/projects/metadata/form/dev/versions/')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
diff --git a/src/routes/form/version/delete.js b/src/routes/form/version/delete.js
index cd195934..05359a37 100644
--- a/src/routes/form/version/delete.js
+++ b/src/routes/form/version/delete.js
@@ -2,8 +2,11 @@
* API to add a project type
*/
import validate from 'express-validation';
+import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../../constants';
+import util from '../../../util';
import models from '../../../models';
const permissions = tcMiddleware.permissions;
@@ -46,7 +49,20 @@ module.exports = [
key: req.params.key,
version: req.params.version,
},
- })).then(() => {
+ })).then(deleted => models.Form.findAll({
+ where: {
+ key: req.params.key,
+ version: req.params.version,
+ },
+ paranoid: false,
+ order: [['deletedAt', 'DESC']],
+ limit: deleted,
+ }))
+ .then((forms) => {
+ _.map(forms, form => util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE,
+ RESOURCES.FORM_VERSION,
+ _.pick(form.toJSON(), 'id')));
res.status(204).end();
})
.catch(next));
diff --git a/src/routes/form/version/delete.spec.js b/src/routes/form/version/delete.spec.js
index 0f3fd95e..8aff6a9b 100644
--- a/src/routes/form/version/delete.spec.js
+++ b/src/routes/form/version/delete.spec.js
@@ -56,24 +56,26 @@ describe('DELETE form version', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.Form.create(forms[0]))
- .then(() => models.Form.create(forms[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.Form.create(forms[0]))
+ .then(() => models.Form.create(forms[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('DELETE /projects/metadata/form/{key}/versions/{version}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .delete('/v4/projects/metadata/form/dev/versions/1')
+ .delete('/v5/projects/metadata/form/dev/versions/1')
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .delete('/v4/projects/metadata/form/dev/versions/1')
+ .delete('/v5/projects/metadata/form/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -82,7 +84,7 @@ describe('DELETE form version', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .delete('/v4/projects/metadata/form/dev/versions/1')
+ .delete('/v5/projects/metadata/form/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -91,7 +93,7 @@ describe('DELETE form version', () => {
it('should return 403 for manager', (done) => {
request(server)
- .delete('/v4/projects/metadata/form/dev/versions/1')
+ .delete('/v5/projects/metadata/form/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -100,7 +102,7 @@ describe('DELETE form version', () => {
it('should return 404 for non-existed key', (done) => {
request(server)
- .delete('/v4/projects/metadata/form/dev111/versions/1')
+ .delete('/v5/projects/metadata/form/dev111/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -109,7 +111,7 @@ describe('DELETE form version', () => {
it('should return 404 for non-existed version', (done) => {
request(server)
- .delete('/v4/projects/metadata/form/dev/versions/111')
+ .delete('/v5/projects/metadata/form/dev/versions/111')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -118,7 +120,7 @@ describe('DELETE form version', () => {
it('should return 204, for admin', (done) => {
request(server)
- .delete('/v4/projects/metadata/form/dev/versions/1')
+ .delete('/v5/projects/metadata/form/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -128,7 +130,7 @@ describe('DELETE form version', () => {
it('should return 204, for connect admin', (done) => {
request(server)
- .delete('/v4/projects/metadata/form/dev/versions/1')
+ .delete('/v5/projects/metadata/form/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
diff --git a/src/routes/form/version/get.js b/src/routes/form/version/get.js
index e68c6ac3..0779b9f5 100644
--- a/src/routes/form/version/get.js
+++ b/src/routes/form/version/get.js
@@ -5,8 +5,8 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../../util';
import models from '../../../models';
+import util from '../../../util';
const permissions = tcMiddleware.permissions;
@@ -19,16 +19,47 @@ const schema = {
module.exports = [
validate(schema),
permissions('form.view'),
- (req, res, next) =>
- models.Form.latestRevisionOfLatestVersion(req.params.key)
- .then((form) => {
- if (form == null) {
- const apiErr = new Error(`Form not found for key ${req.params.key} version ${req.params.version}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
- }
- res.json(util.wrapResponse(req.id, form));
- return Promise.resolve();
- })
- .catch(next),
+ (req, res, next) => {
+ util.fetchByIdFromES('forms', {
+ query: {
+ nested: {
+ path: 'forms',
+ query:
+ {
+ filtered: {
+ filter: {
+ bool: {
+ must: [
+ { term: { 'forms.key': req.params.key } },
+ ],
+ },
+ },
+ },
+ },
+ inner_hits: {},
+ },
+ },
+ sort: { 'forms.version': 'desc' },
+ }, 'metadata')
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No form found in ES');
+ models.Form.latestRevisionOfLatestVersion(req.params.key)
+ .then((form) => {
+ if (form == null) {
+ const apiErr = new Error(`Form not found for key ${req.params.key} version ${req.params.version}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
+ res.json(form);
+ return Promise.resolve();
+ })
+ .catch(next);
+ } else {
+ req.log.debug('forms found in ES');
+ res.json(data[0].inner_hits.forms.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle
+ }
+ })
+ .catch(next);
+ },
];
diff --git a/src/routes/form/version/get.spec.js b/src/routes/form/version/get.spec.js
index 64abd727..326d4ef7 100644
--- a/src/routes/form/version/get.spec.js
+++ b/src/routes/form/version/get.spec.js
@@ -54,25 +54,27 @@ describe('GET a latest version of specific key of Form', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.Form.create(forms[0]))
- .then(() => models.Form.create(forms[1]))
- .then(() => models.Form.create(forms[2]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.Form.create(forms[0]))
+ .then(() => models.Form.create(forms[1]))
+ .then(() => models.Form.create(forms[2]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/form/dev/versions/{version}', () => {
it('should return 200 and correct version for admin', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev')
+ .get('/v5/projects/metadata/form/dev')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
const form = forms[2];
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(form.key);
resJson.config.should.be.eql(form.config);
resJson.version.should.be.eql(form.version);
@@ -88,13 +90,13 @@ describe('GET a latest version of specific key of Form', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev')
+ .get('/v5/projects/metadata/form/dev')
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev')
+ .get('/v5/projects/metadata/form/dev')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -104,7 +106,7 @@ describe('GET a latest version of specific key of Form', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev')
+ .get('/v5/projects/metadata/form/dev')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -114,7 +116,7 @@ describe('GET a latest version of specific key of Form', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev')
+ .get('/v5/projects/metadata/form/dev')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -123,7 +125,7 @@ describe('GET a latest version of specific key of Form', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev')
+ .get('/v5/projects/metadata/form/dev')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/form/version/getVersion.js b/src/routes/form/version/getVersion.js
index 6399ceb0..d4167150 100644
--- a/src/routes/form/version/getVersion.js
+++ b/src/routes/form/version/getVersion.js
@@ -4,9 +4,11 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+
import util from '../../../util';
import models from '../../../models';
+
const permissions = tcMiddleware.permissions;
const schema = {
@@ -19,16 +21,48 @@ const schema = {
module.exports = [
validate(schema),
permissions('form.view'),
- (req, res, next) => models.Form.findOneWithLatestRevision(req.params)
- .then((form) => {
- // Not found
- if (!form) {
- const apiErr = new Error(`Form not found for key ${req.params.key} version ${req.params.version}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
+ (req, res, next) => {
+ util.fetchByIdFromES('forms', {
+ query: {
+ nested: {
+ path: 'forms',
+ query: {
+ filtered: {
+ filter: {
+ bool: {
+ must: [
+ { term: { 'forms.key': req.params.key } },
+ { term: { 'forms.version': req.params.version } },
+ ],
+ },
+ },
+ },
+ },
+ inner_hits: {},
+ },
+ },
+ sort: { 'forms.revision': 'desc' },
+ }, 'metadata')
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No form found in ES');
+ return models.Form.findOneWithLatestRevision(req.params)
+ .then((form) => {
+ // Not found
+ if (!form) {
+ const apiErr = new Error(`Form not found for key ${req.params.key} version ${req.params.version}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
+ res.json(form);
+ return Promise.resolve();
+ })
+ .catch(next);
}
- res.json(util.wrapResponse(req.id, form));
+ req.log.debug('forms found in ES');
+ res.json(data[0].inner_hits.forms.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle
return Promise.resolve();
})
- .catch(next),
+ .catch(next);
+ },
];
diff --git a/src/routes/form/version/getVersion.spec.js b/src/routes/form/version/getVersion.spec.js
index 60b3f2ec..14ac9096 100644
--- a/src/routes/form/version/getVersion.spec.js
+++ b/src/routes/form/version/getVersion.spec.js
@@ -44,25 +44,27 @@ describe('GET a particular version of specific key of Form', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.Form.create(forms[0]))
- .then(() => models.Form.create(forms[1]))
- .then(() => models.Form.create(forms[2]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.Form.create(forms[0]))
+ .then(() => models.Form.create(forms[1]))
+ .then(() => models.Form.create(forms[2]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/form/dev/versions/{version}', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions/1')
+ .get('/v5/projects/metadata/form/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
const form = forms[0];
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(form.key);
resJson.config.should.be.eql(form.config);
@@ -79,13 +81,13 @@ describe('GET a particular version of specific key of Form', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions/1')
+ .get('/v5/projects/metadata/form/dev/versions/1')
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev')
+ .get('/v5/projects/metadata/form/dev')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -95,7 +97,7 @@ describe('GET a particular version of specific key of Form', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions/1')
+ .get('/v5/projects/metadata/form/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -105,7 +107,7 @@ describe('GET a particular version of specific key of Form', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions/1')
+ .get('/v5/projects/metadata/form/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -114,7 +116,7 @@ describe('GET a particular version of specific key of Form', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions/1')
+ .get('/v5/projects/metadata/form/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/form/version/list.js b/src/routes/form/version/list.js
index 1186c55c..383cec6f 100644
--- a/src/routes/form/version/list.js
+++ b/src/routes/form/version/list.js
@@ -4,8 +4,8 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../../util';
import models from '../../../models';
+import util from '../../../util';
const permissions = tcMiddleware.permissions;
@@ -18,30 +18,40 @@ const schema = {
module.exports = [
validate(schema),
permissions('form.view'),
- (req, res, next) => models.Form.findAll({
- where: {
- key: req.params.key,
- },
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- })
- .then((forms) => {
- // Not found
- if ((!forms) || (forms.length === 0)) {
- const apiErr = new Error(`Form not found for key ${req.params.key}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
- }
+ (req, res, next) =>
+ util.fetchFromES('forms')
+ .then((data) => {
+ if (data.forms.length === 0) {
+ req.log.debug('No form found in ES');
+ models.Form.findAll({
+ where: {
+ key: req.params.key,
+ },
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ })
+ .then((forms) => {
+ // Not found
+ if ((!forms) || (forms.length === 0)) {
+ const apiErr = new Error(`Form not found for key ${req.params.key}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
- const latestForms = {};
- forms.forEach((element) => {
- const isNewerRevision = (latestForms[element.version] != null) &&
- (latestForms[element.version].revision < element.revision);
- if ((latestForms[element.version] == null) || isNewerRevision) {
- latestForms[element.version] = element;
- }
- });
- res.json(util.wrapResponse(req.id, Object.values(latestForms)));
- return Promise.resolve();
- })
- .catch(next),
+ const latestForms = {};
+ forms.forEach((element) => {
+ const isNewerRevision = (latestForms[element.version] != null) &&
+ (latestForms[element.version].revision < element.revision);
+ if ((latestForms[element.version] == null) || isNewerRevision) {
+ latestForms[element.version] = element;
+ }
+ });
+ res.json(Object.values(latestForms));
+ return Promise.resolve();
+ })
+ .catch(next);
+ } else {
+ req.log.debug('forms found in ES');
+ res.json(data.forms);
+ }
+ }).catch(next),
];
diff --git a/src/routes/form/version/list.spec.js b/src/routes/form/version/list.spec.js
index 019a0a72..14977b5e 100644
--- a/src/routes/form/version/list.spec.js
+++ b/src/routes/form/version/list.spec.js
@@ -35,24 +35,26 @@ describe('LIST form versions', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.Form.create(forms[0]))
- .then(() => models.Form.create(forms[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.Form.create(forms[0]))
+ .then(() => models.Form.create(forms[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/form/dev/versions/{version}', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions')
+ .get('/v5/projects/metadata/form/dev/versions')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
const form = forms[0];
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
resJson[0].key.should.be.eql(form.key);
@@ -70,13 +72,13 @@ describe('LIST form versions', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions')
+ .get('/v5/projects/metadata/form/dev/versions')
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions')
+ .get('/v5/projects/metadata/form/dev/versions')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -86,7 +88,7 @@ describe('LIST form versions', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions')
+ .get('/v5/projects/metadata/form/dev/versions')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -96,7 +98,7 @@ describe('LIST form versions', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions')
+ .get('/v5/projects/metadata/form/dev/versions')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -105,7 +107,7 @@ describe('LIST form versions', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/projects/metadata/form/dev/versions')
+ .get('/v5/projects/metadata/form/dev/versions')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/form/version/update.js b/src/routes/form/version/update.js
index ebb7294d..b484f830 100644
--- a/src/routes/form/version/update.js
+++ b/src/routes/form/version/update.js
@@ -7,6 +7,7 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../../constants';
import util from '../../../util';
import models from '../../../models';
@@ -17,18 +18,18 @@ const schema = {
version: Joi.number().integer().positive().required(),
key: Joi.string().max(45).required(),
},
- body: {
- param: Joi.object().keys({
- config: Joi.object().required(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ body: Joi.object().keys({
+ config: Joi.object().required(),
+
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
+
};
module.exports = [
@@ -60,14 +61,17 @@ module.exports = [
createdBy: req.authUser.userId,
updatedBy: req.authUser.userId,
key: req.params.key,
- config: req.body.param.config,
+ config: req.body.config,
};
return models.Form.create(entity);
})
.then((createdEntity) => {
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE,
+ RESOURCES.FORM_VERSION,
+ createdEntity.toJSON());
// Omit deletedAt, deletedBy
- res.status(201).json(util.wrapResponse(
- req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201));
+ res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'));
})
.catch(next));
},
diff --git a/src/routes/form/version/update.spec.js b/src/routes/form/version/update.spec.js
index 4f66e975..0bce1fd7 100644
--- a/src/routes/form/version/update.spec.js
+++ b/src/routes/form/version/update.spec.js
@@ -35,48 +35,46 @@ describe('UPDATE Form version', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.Form.create(forms[0]))
- .then(() => models.Form.create(forms[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.Form.create(forms[0]))
+ .then(() => models.Form.create(forms[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('Post /projects/metadata/form/{key}/versions/{version}', () => {
const body = {
- param: {
- config: {
- 'test create': 'test create',
- },
+ config: {
+ 'test create': 'test create',
},
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .patch('/v4/projects/metadata/form/dev/versions/1')
+ .patch('/v5/projects/metadata/form/dev/versions/1')
.send(body)
.expect(403, done);
});
- it('should return 422 if missing config', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- config: undefined,
- }),
- };
+ it('should return 400 if missing config', (done) => {
+ const invalidBody = _.assign({}, body, {
+ config: undefined,
+ });
request(server)
- .patch('/v4/projects/metadata/form/dev/versions/1')
+ .patch('/v5/projects/metadata/form/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 201 for admin', (done) => {
request(server)
- .patch('/v4/projects/metadata/form/dev/versions/1')
+ .patch('/v5/projects/metadata/form/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -84,9 +82,9 @@ describe('UPDATE Form version', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson.id);
- resJson.config.should.be.eql(body.param.config);
+ resJson.config.should.be.eql(body.config);
resJson.key.should.be.eql('dev');
resJson.revision.should.be.eql(3);
resJson.version.should.be.eql(1);
@@ -102,7 +100,7 @@ describe('UPDATE Form version', () => {
it('should return 403 for member', (done) => {
request(server)
- .patch('/v4/projects/metadata/form/dev/versions/1')
+ .patch('/v5/projects/metadata/form/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
diff --git a/src/routes/index.js b/src/routes/index.js
index cd578fd1..275ce470 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -10,7 +10,7 @@ const router = Router();
const apiVersion = config.apiVersion;
validate.options({
- status: 422,
+ status: 400,
flatten: true,
allowUnknownBody: false,
});
@@ -46,315 +46,313 @@ router.all(
},
);
-router.route('/v4/projects/metadata/projectTemplates')
+router.route('/v5/projects/metadata/projectTemplates')
.get(require('./projectTemplates/list'));
-router.route('/v4/projects/metadata/projectTemplates/:templateId(\\d+)')
+router.route('/v5/projects/metadata/projectTemplates/:templateId(\\d+)')
.get(require('./projectTemplates/get'));
-router.route('/v4/projects/metadata/productTemplates')
+router.route('/v5/projects/metadata/productTemplates')
.get(require('./productTemplates/list'));
-router.route('/v4/projects/metadata/productTemplates/:templateId(\\d+)')
+router.route('/v5/projects/metadata/productTemplates/:templateId(\\d+)')
.get(require('./productTemplates/get'));
-router.route('/v4/projects/metadata/productTemplates/:templateId(\\d+)/upgrade')
+router.route('/v5/projects/metadata/productTemplates/:templateId(\\d+)/upgrade')
.post(require('./productTemplates/upgrade'));
-router.route('/v4/projects/metadata/projectTypes')
+router.route('/v5/projects/metadata/projectTypes')
.get(require('./projectTypes/list'));
-router.route('/v4/projects/metadata/projectTypes/:key')
+router.route('/v5/projects/metadata/projectTypes/:key')
.get(require('./projectTypes/get'));
-router.route('/v4/projects/metadata/projectTemplates/:templateId(\\d+)/upgrade')
+router.route('/v5/projects/metadata/projectTemplates/:templateId(\\d+)/upgrade')
.post(require('./projectTemplates/upgrade'));
-router.route('/v4/projects/metadata/orgConfig')
+router.route('/v5/projects/metadata/orgConfig')
.get(require('./orgConfig/list'));
-router.route('/v4/projects/metadata/orgConfig/:id(\\d+)')
+router.route('/v5/projects/metadata/orgConfig/:id(\\d+)')
.get(require('./orgConfig/get'));
-router.route('/v4/projects/metadata/productCategories')
+router.route('/v5/projects/metadata/productCategories')
.get(require('./productCategories/list'));
-router.route('/v4/projects/metadata/productCategories/:key')
+router.route('/v5/projects/metadata/productCategories/:key')
.get(require('./productCategories/get'));
-router.route('/v4/projects/metadata/workManagementPermission')
+router.route('/v5/projects/metadata/workManagementPermission')
.get(require('./workManagementPermissions/list'));
-router.route('/v4/projects/metadata/workManagementPermission/:id')
+router.route('/v5/projects/metadata/workManagementPermission/:id')
.get(require('./workManagementPermissions/get'));
-router.use('/v4/projects/metadata', compression());
-router.route('/v4/projects/metadata')
+
+router.use('/v5/projects/metadata', compression());
+router.route('/v5/projects/metadata')
.get(require('./metadata/list'));
// Register all the routes
-router.use('/v4/projects', compression());
-router.route('/v4/projects')
+router.use('/v5/projects', compression());
+router.route('/v5/projects')
.post(require('./projects/create'))
.get(require('./projects/list'));
-router.use('/v4/projects/db', compression());
-router.route('/v4/projects/db')
- .get(require('./projects/list-db'));
-
-router.route('/v4/projects/admin/es/project/createIndex')
- .post(require('./admin/project-create-index'));
-router.route('/v4/projects/admin/es/project/deleteIndex')
- .delete(require('./admin/project-delete-index'));
-router.route('/v4/projects/admin/es/project/index')
+router.route('/v5/projects/admin/es/createIndex')
+ .post(require('./admin/es-create-index'));
+router.route('/v5/projects/admin/es/deleteIndex')
+ .delete(require('./admin/es-delete-index'));
+router.route('/v5/projects/admin/es/migrateFromDb')
+ .patch(require('./admin/es-migrate-from-db'));
+router.route('/v5/projects/admin/es/fixMetadataForEs')
+ .patch(require('./admin/es-fix-metadata-for-es'));
+router.route('/v5/projects/admin/es/fixProjectsForEs')
+ .patch(require('./admin/es-fix-projects-for-es'));
+router.route('/v5/projects/admin/es/project/index')
.post(require('./admin/project-index-create'));
-router.route('/v4/projects/admin/es/project/remove')
+router.route('/v5/projects/admin/es/project/remove')
.delete(require('./admin/project-index-delete'));
-router.route('/v4/projects/:projectId(\\d+)')
+router.route('/v5/projects/:projectId(\\d+)')
.get(require('./projects/get'))
.patch(require('./projects/update'))
.delete(require('./projects/delete'));
-router.route('/v4/projects/:projectId(\\d+)/scopeChangeRequests')
+router.route('/v5/projects/:projectId(\\d+)/scopeChangeRequests')
.post(require('./scopeChangeRequests/create'));
// .get(require('./scopeChangeRequests/list'));
-router.route('/v4/projects/:projectId(\\d+)/scopeChangeRequests/:requestId(\\d+)')
+router.route('/v5/projects/:projectId(\\d+)/scopeChangeRequests/:requestId(\\d+)')
// .get(require('./scopeChangeRequests/get'))
.patch(require('./scopeChangeRequests/update'));
// .delete(require('./scopeChangeRequests/delete'));
-router.route('/v4/projects/:projectId(\\d+)/members')
+router.route('/v5/projects/:projectId(\\d+)/members')
.get(require('./projectMembers/list'))
.post(require('./projectMembers/create'));
-router.route('/v4/projects/:projectId(\\d+)/members/:id(\\d+)')
+router.route('/v5/projects/:projectId(\\d+)/members/:id(\\d+)')
.get(require('./projectMembers/get'))
.delete(require('./projectMembers/delete'))
.patch(require('./projectMembers/update'));
// Permissions
-router.route('/v4/projects/:projectId(\\d+)/permissions')
+router.route('/v5/projects/:projectId(\\d+)/permissions')
.get(require('./permissions/get'));
-router.route('/v4/projects/:projectId(\\d+)/attachments')
- .post(require('./attachments/create'));
+router.route('/v5/projects/:projectId(\\d+)/attachments')
+ .post(require('./attachments/create'))
+ .get(require('./attachments/list'));
-router.route('/v4/projects/:projectId(\\d+)/attachments/:id(\\d+)')
+router.route('/v5/projects/:projectId(\\d+)/attachments/:id(\\d+)')
.get(require('./attachments/download'))
.patch(require('./attachments/update'))
.delete(require('./attachments/delete'));
-router.route('/v4/projects/:projectId(\\d+)/upgrade')
+router.route('/v5/projects/:projectId(\\d+)/upgrade')
.post(require('./projectUpgrade/create'));
-router.route('/v4/projects/metadata/projectTemplates')
+router.route('/v5/projects/metadata/projectTemplates')
.post(require('./projectTemplates/create'));
-router.route('/v4/projects/metadata/projectTemplates/:templateId(\\d+)')
+router.route('/v5/projects/metadata/projectTemplates/:templateId(\\d+)')
.patch(require('./projectTemplates/update'))
.delete(require('./projectTemplates/delete'));
-router.route('/v4/projects/metadata/productTemplates')
+router.route('/v5/projects/metadata/productTemplates')
.post(require('./productTemplates/create'));
-router.route('/v4/projects/metadata/productTemplates/:templateId(\\d+)')
+router.route('/v5/projects/metadata/productTemplates/:templateId(\\d+)')
.patch(require('./productTemplates/update'))
.delete(require('./productTemplates/delete'));
-router.route('/v4/projects/:projectId(\\d+)/phases')
+router.route('/v5/projects/:projectId(\\d+)/phases')
.get(require('./phases/list'))
.post(require('./phases/create'));
-router.route('/v4/projects/:projectId(\\d+)/phases/db')
- .get(require('./phases/list-db'));
-
-router.route('/v4/projects/:projectId(\\d+)/phases/:phaseId(\\d+)')
+router.route('/v5/projects/:projectId(\\d+)/phases/:phaseId(\\d+)')
.get(require('./phases/get'))
.patch(require('./phases/update'))
.delete(require('./phases/delete'));
-router.route('/v4/projects/:projectId(\\d+)/phases/:phaseId(\\d+)/products')
+router.route('/v5/projects/:projectId(\\d+)/phases/:phaseId(\\d+)/products')
.get(require('./phaseProducts/list'))
.post(require('./phaseProducts/create'));
-router.route('/v4/projects/:projectId(\\d+)/phases/:phaseId(\\d+)/products/db')
- .get(require('./phaseProducts/list-db'));
-
-router.route('/v4/projects/:projectId(\\d+)/phases/:phaseId(\\d+)/products/:productId(\\d+)')
+router.route('/v5/projects/:projectId(\\d+)/phases/:phaseId(\\d+)/products/:productId(\\d+)')
.get(require('./phaseProducts/get'))
.patch(require('./phaseProducts/update'))
.delete(require('./phaseProducts/delete'));
-router.route('/v4/projects/metadata/productCategories')
+router.route('/v5/projects/metadata/productCategories')
.post(require('./productCategories/create'));
-router.route('/v4/projects/metadata/productCategories/:key')
+router.route('/v5/projects/metadata/productCategories/:key')
.patch(require('./productCategories/update'))
.delete(require('./productCategories/delete'));
-router.route('/v4/projects/metadata/workManagementPermission')
+router.route('/v5/projects/metadata/workManagementPermission')
.post(require('./workManagementPermissions/create'));
-router.route('/v4/projects/metadata/workManagementPermission/:id')
+router.route('/v5/projects/metadata/workManagementPermission/:id')
.patch(require('./workManagementPermissions/update'))
.delete(require('./workManagementPermissions/delete'));
-router.route('/v4/projects/metadata/projectTypes')
+router.route('/v5/projects/metadata/projectTypes')
.post(require('./projectTypes/create'));
-router.route('/v4/projects/metadata/projectTypes/:key')
+router.route('/v5/projects/metadata/projectTypes/:key')
.patch(require('./projectTypes/update'))
.delete(require('./projectTypes/delete'));
-router.route('/v4/timelines')
+router.route('/v5/timelines')
.post(require('./timelines/create'))
.get(require('./timelines/list'));
-router.route('/v4/timelines/:timelineId(\\d+)')
+router.route('/v5/timelines/:timelineId(\\d+)')
.get(require('./timelines/get'))
.patch(require('./timelines/update'))
.delete(require('./timelines/delete'));
-router.route('/v4/timelines/:timelineId(\\d+)/milestones')
+router.route('/v5/timelines/:timelineId(\\d+)/milestones')
.post(require('./milestones/create'))
.get(require('./milestones/list'));
-router.route('/v4/timelines/:timelineId(\\d+)/milestones/:milestoneId(\\d+)')
+router.route('/v5/timelines/:timelineId(\\d+)/milestones/:milestoneId(\\d+)')
.get(require('./milestones/get'))
.patch(require('./milestones/update'))
.delete(require('./milestones/delete'));
-router.route('/v4/timelines/metadata/milestoneTemplates')
+router.route('/v5/timelines/metadata/milestoneTemplates')
.post(require('./milestoneTemplates/create'))
.get(require('./milestoneTemplates/list'));
-router.route('/v4/timelines/metadata/milestoneTemplates/clone')
+router.route('/v5/timelines/metadata/milestoneTemplates/clone')
.post(require('./milestoneTemplates/clone'));
-router.route('/v4/timelines/metadata/milestoneTemplates/:milestoneTemplateId(\\d+)')
+router.route('/v5/timelines/metadata/milestoneTemplates/:milestoneTemplateId(\\d+)')
.get(require('./milestoneTemplates/get'))
.patch(require('./milestoneTemplates/update'))
.delete(require('./milestoneTemplates/delete'));
-router.route('/v4/projects/:projectId(\\d+)/members/invites')
- .get(require('./projectMemberInvites/list'));
-
-router.route('/v4/projects/:projectId(\\d+)/members/invite')
+router.route('/v5/projects/:projectId(\\d+)/members/invite')
.post(require('./projectMemberInvites/create'))
.put(require('./projectMemberInvites/update'))
.get(require('./projectMemberInvites/get'));
-router.route('/v4/projects/metadata/orgConfig')
+router.route('/v5/projects/:projectId(\\d+)/members/invites')
+ .get(require('./projectMemberInvites/list'));
+
+router.route('/v5/projects/metadata/orgConfig')
.post(require('./orgConfig/create'));
-router.route('/v4/projects/metadata/orgConfig/:id(\\d+)')
+router.route('/v5/projects/metadata/orgConfig/:id(\\d+)')
.patch(require('./orgConfig/update'))
.delete(require('./orgConfig/delete'));
// form
-router.route('/v4/projects/metadata/form/:key/versions/:version(\\d+)/revisions/:revision(\\d+)')
+router.route('/v5/projects/metadata/form/:key/versions/:version(\\d+)/revisions/:revision(\\d+)')
.get(require('./form/revision/get'))
.delete(require('./form/revision/delete'));
-router.route('/v4/projects/metadata/form/:key/versions/:version(\\d+)/revisions')
+router.route('/v5/projects/metadata/form/:key/versions/:version(\\d+)/revisions')
.get(require('./form/revision/list'))
.post(require('./form/revision/create'));
-router.route('/v4/projects/metadata/form/:key')
+router.route('/v5/projects/metadata/form/:key')
.get(require('./form/version/get'));
-router.route('/v4/projects/metadata/form/:key/versions')
+router.route('/v5/projects/metadata/form/:key/versions')
.get(require('./form/version/list'))
.post(require('./form/version/create'));
-router.route('/v4/projects/metadata/form/:key/versions/:version(\\d+)')
+router.route('/v5/projects/metadata/form/:key/versions/:version(\\d+)')
.get(require('./form/version/getVersion'))
.patch(require('./form/version/update'))
.delete(require('./form/version/delete'));
// price config
-router.route('/v4/projects/metadata/priceConfig/:key/versions/:version(\\d+)/revisions/:revision(\\d+)')
+router.route('/v5/projects/metadata/priceConfig/:key/versions/:version(\\d+)/revisions/:revision(\\d+)')
.get(require('./priceConfig/revision/get'))
.delete(require('./priceConfig/revision/delete'));
-router.route('/v4/projects/metadata/priceConfig/:key/versions/:version(\\d+)/revisions')
+router.route('/v5/projects/metadata/priceConfig/:key/versions/:version(\\d+)/revisions')
.get(require('./priceConfig/revision/list'))
.post(require('./priceConfig/revision/create'));
-router.route('/v4/projects/metadata/priceConfig/:key')
+router.route('/v5/projects/metadata/priceConfig/:key')
.get(require('./priceConfig/version/get'));
-router.route('/v4/projects/metadata/priceConfig/:key/versions')
+router.route('/v5/projects/metadata/priceConfig/:key/versions')
.get(require('./priceConfig/version/list'))
.post(require('./priceConfig/version/create'));
-router.route('/v4/projects/metadata/priceConfig/:key/versions/:version(\\d+)')
+router.route('/v5/projects/metadata/priceConfig/:key/versions/:version(\\d+)')
.get(require('./priceConfig/version/getVersion'))
.patch(require('./priceConfig/version/update'))
.delete(require('./priceConfig/version/delete'));
// plan config
-router.route('/v4/projects/metadata/planConfig/:key/versions/:version(\\d+)/revisions/:revision(\\d+)')
+router.route('/v5/projects/metadata/planConfig/:key/versions/:version(\\d+)/revisions/:revision(\\d+)')
.get(require('./planConfig/revision/get'))
.delete(require('./planConfig/revision/delete'));
-router.route('/v4/projects/metadata/planConfig/:key/versions/:version(\\d+)/revisions')
+router.route('/v5/projects/metadata/planConfig/:key/versions/:version(\\d+)/revisions')
.get(require('./planConfig/revision/list'))
.post(require('./planConfig/revision/create'));
-router.route('/v4/projects/metadata/planConfig/:key')
+router.route('/v5/projects/metadata/planConfig/:key')
.get(require('./planConfig/version/get'));
-router.route('/v4/projects/metadata/planConfig/:key/versions')
+router.route('/v5/projects/metadata/planConfig/:key/versions')
.get(require('./planConfig/version/list'))
.post(require('./planConfig/version/create'));
-router.route('/v4/projects/metadata/planConfig/:key/versions/:version(\\d+)')
+router.route('/v5/projects/metadata/planConfig/:key/versions/:version(\\d+)')
.get(require('./planConfig/version/getVersion'))
.patch(require('./planConfig/version/update'))
.delete(require('./planConfig/version/delete'));
// work streams
-router.route('/v4/projects/:projectId(\\d+)/workstreams')
+router.route('/v5/projects/:projectId(\\d+)/workstreams')
.get(require('./workStreams/list'))
.post(require('./workStreams/create'));
-router.route('/v4/projects/:projectId(\\d+)/workstreams/:id(\\d+)')
+router.route('/v5/projects/:projectId(\\d+)/workstreams/:id(\\d+)')
.get(require('./workStreams/get'))
.patch(require('./workStreams/update'))
.delete(require('./workStreams/delete'));
// works
-router.route('/v4/projects/:projectId(\\d+)/workstreams/:workStreamId(\\d+)/works')
+router.route('/v5/projects/:projectId(\\d+)/workstreams/:workStreamId(\\d+)/works')
.get(require('./works/list'))
.post(require('./works/create'));
-router.route('/v4/projects/:projectId(\\d+)/workstreams/:workStreamId(\\d+)/works/:id(\\d+)')
+router.route('/v5/projects/:projectId(\\d+)/workstreams/:workStreamId(\\d+)/works/:id(\\d+)')
.get(require('./works/get'))
.patch(require('./works/update'))
.delete(require('./works/delete'));
// work items
-router.route('/v4/projects/:projectId(\\d+)/workstreams/:workStreamId(\\d+)/works/:workId(\\d+)/workitems')
+router.route('/v5/projects/:projectId(\\d+)/workstreams/:workStreamId(\\d+)/works/:workId(\\d+)/workitems')
.get(require('./workItems/list'))
.post(require('./workItems/create'));
-router.route('/v4/projects/:projectId(\\d+)/workstreams/:workStreamId(\\d+)/works/:workId(\\d+)/workitems/:id(\\d+)')
+router.route('/v5/projects/:projectId(\\d+)/workstreams/:workStreamId(\\d+)/works/:workId(\\d+)/workitems/:id(\\d+)')
.get(require('./workItems/get'))
.patch(require('./workItems/update'))
.delete(require('./workItems/delete'));
-router.route('/v4/projects/:projectId/reports')
+router.route('/v5/projects/:projectId/reports')
.get(require('./projectReports/getReport'));
// Project Settings
-router.route('/v4/projects/:projectId(\\d+)/settings/:id(\\d+)')
+router.route('/v5/projects/:projectId(\\d+)/settings/:id(\\d+)')
.patch(require('./projectSettings/update'))
.delete(require('./projectSettings/delete'));
-router.route('/v4/projects/:projectId(\\d+)/settings')
+router.route('/v5/projects/:projectId(\\d+)/settings')
.get(require('./projectSettings/list'))
.post(require('./projectSettings/create'));
// Project Estimation Items
-router.route('/v4/projects/:projectId(\\d+)/estimations/:estimationId(\\d+)/items')
+router.route('/v5/projects/:projectId(\\d+)/estimations/:estimationId(\\d+)/items')
.get(require('./projectEstimationItems/list'));
// register error handler
@@ -362,35 +360,26 @@ router.use((err, req, res, next) => { // eslint-disable-line no-unused-vars
// DO NOT REMOVE next arg.. even though eslint
// complains that it is not being used.
const content = {};
- let httpStatus = err.status || 500;
// specific for validation errors
if (err instanceof validate.ValidationError) {
content.message = `${err.message}: ${err.toJSON()}`;
- httpStatus = err.status;
} else {
content.message = err.message;
}
- const body = {
- id: req.id,
- result: {
- success: false,
- status: httpStatus,
- content,
- },
- };
// dvalidateelopment error handler
// will print stacktrace
if (_.indexOf(['development', 'test', 'qa'], process.env.NODE_ENV) > -1) {
- body.result.debug = err.stack;
+ // body.result.debug = err.stack;
+ content.debug = err.stack;
if (err.details) {
- body.result.details = err.details;
+ content.details = err.details;
}
}
const rerr = err;
rerr.status = rerr.status || 500;
req.log.error(rerr);
- res.status(rerr.status).send(body);
+ res.status(rerr.status).send(content);
});
// catch 404 and forward to error handler
diff --git a/src/routes/metadata/list.js b/src/routes/metadata/list.js
index 6ce46915..0d2e77de 100644
--- a/src/routes/metadata/list.js
+++ b/src/routes/metadata/list.js
@@ -2,11 +2,24 @@
* API to list all metadata
*/
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import _ from 'lodash';
import Joi from 'joi';
import validate from 'express-validation';
-import util from '../../util';
+
import models from '../../models';
+import util from '../../util';
+const metadataProperties = [
+ 'productTemplates',
+ 'forms',
+ 'projectTemplates',
+ 'planConfigs',
+ 'priceConfigs',
+ 'projectTypes',
+ 'productCategories',
+ 'milestoneTemplates',
+ 'buildingBlocks',
+];
const permissions = tcMiddleware.permissions;
const schema = {
@@ -69,89 +82,145 @@ function getUsedModel() {
});
}
+/**
+ * Fetch metadata from database
+ *
+ * @param {boolean} includeAllReferred when user query with includeAllReferred, return result with all used version of
+ *
+ * @return {object} metadata from database
+ */
+function loadMetadataFromDb(includeAllReferred) {
+ const query = {
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ raw: true,
+ };
+ const projectProductTemplateQuery = {
+ where: {
+ deletedAt: { $eq: null },
+ disabled: false,
+ },
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ raw: true,
+ };
+ // when user query with includeAllReferred, return result with all used version of
+ // Form, PriceConfig, PlanConfig
+ if (includeAllReferred) {
+ let usedModelMap;
+ let latestVersion;
+ return getUsedModel()
+ .then((modelUsed) => {
+ // found all latest version & used in project template version record
+ // for Form, PriceConfig, PlanConfig
+ usedModelMap = modelUsed;
+ return Promise.all([
+ models.Form.latestVersionIncludeUsed(usedModelMap.form),
+ models.PriceConfig.latestVersionIncludeUsed(usedModelMap.priceConfig),
+ models.PlanConfig.latestVersionIncludeUsed(usedModelMap.planConfig),
+ ]);
+ }).then((latestVersionModels) => {
+ latestVersion = latestVersionModels;
+ return Promise.all([
+ models.ProjectTemplate.findAll(projectProductTemplateQuery),
+ models.ProductTemplate.findAll(projectProductTemplateQuery),
+ models.MilestoneTemplate.findAll(query),
+ models.ProjectType.findAll(query),
+ models.ProductCategory.findAll(query),
+ Promise.resolve(latestVersion[0]),
+ Promise.resolve(latestVersion[1]),
+ Promise.resolve(latestVersion[2]),
+ ]);
+ }).then(queryAllResult => ({
+ projectTemplates: queryAllResult[0],
+ productTemplates: queryAllResult[1],
+ milestoneTemplates: queryAllResult[2],
+ projectTypes: queryAllResult[3],
+ productCategories: queryAllResult[4],
+ forms: queryAllResult[5],
+ priceConfigs: queryAllResult[6],
+ planConfigs: queryAllResult[7],
+ }),
+ );
+ }
+ return Promise.all([
+ models.ProjectTemplate.findAll(projectProductTemplateQuery),
+ models.ProductTemplate.findAll(projectProductTemplateQuery),
+ models.MilestoneTemplate.findAll(query),
+ models.ProjectType.findAll(query),
+ models.ProductCategory.findAll(query),
+ models.Form.latestVersion(),
+ models.PriceConfig.latestVersion(),
+ models.PlanConfig.latestVersion(),
+ models.BuildingBlock.findAll(query),
+ ]).then(results => ({
+ projectTemplates: results[0],
+ productTemplates: results[1],
+ milestoneTemplates: results[2],
+ projectTypes: results[3],
+ productCategories: results[4],
+ forms: results[5],
+ priceConfigs: results[6],
+ planConfigs: results[7],
+ buildingBlocks: results[8],
+ }));
+}
module.exports = [
validate(schema),
permissions('metadata.list'),
(req, res, next) => {
- const query = {
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- raw: true,
- };
- const projectProductTemplateQuery = {
- where: {
- deletedAt: { $eq: null },
- disabled: false,
- },
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- raw: true,
- };
+ // As we are generally return all the data from metadata ES index we just get all the index data
+ // instead of creating a detailed request to get each type of object
+ // There are few reasons for this:
+ // + getting all the index works much faster than making detailed request:
+ // ~2.5 seconds using detailed query vs 0.15 seconds without query (including JS filtering)
+ // + making request we have to get data from `inner_hits` and specify `size` which is by default is `3`
+ // otherwise we wouldn't get all the data, but we want to get all the data
+ // Disadvantage:
+ // - we have to filter disabled Project Templates and Product Templates by JS
+ util.fetchFromES(null, null, 'metadata')
+ .then((data) => {
+ const esDataToReturn = _.pick(data, metadataProperties);
+ // if some metadata properties are not returned from ES, then initialize such properties with empty array
+ // for consistency
+ metadataProperties.forEach((prop) => {
+ if (!esDataToReturn[prop]) {
+ esDataToReturn[prop] = [];
+ }
+ });
+
+ // return only non-disabled Project Templates
+ if (esDataToReturn.projectTemplates && esDataToReturn.projectTemplates.length > 0) {
+ esDataToReturn.projectTemplates = _.filter(esDataToReturn.projectTemplates, { disabled: false });
+ }
+
+ // return only non-disabled Product Templates
+ if (esDataToReturn.productTemplates && esDataToReturn.productTemplates.length > 0) {
+ esDataToReturn.productTemplates = _.filter(esDataToReturn.productTemplates, { disabled: false });
+ }
+
+ // WARNING: `BuildingBlock` model contains sensitive data!
+ //
+ // We should NEVER return `privateConfig` property for `buildingBlocks`.
+ // For the DB we use hooks to always clear it out, see `src/models/buildingBlock.js`.
+ // For the ES so far we should always remember about it and filter it out.
+ if (esDataToReturn.buildingBlocks && esDataToReturn.buildingBlocks.length > 0) {
+ esDataToReturn.buildingBlocks = _.map(
+ esDataToReturn.buildingBlocks,
+ buildingBlock => _.omit(buildingBlock, 'privateConfig'),
+ );
+ }
+
+ // check if any data is returned from ES
+ const hasDataInES = _.some(esDataToReturn, propData => propData && propData.length > 0);
+
+ if (hasDataInES) {
+ req.log.debug('Metadata is found in ES');
+ return res.json(esDataToReturn);
+ }
- // when user query with includeAllReferred, return result with all used version of
- // Form, PriceConfig, PlanConfig
- if (req.query.includeAllReferred) {
- let usedModelMap;
- let latestVersion;
- return getUsedModel()
- .then((modelUsed) => {
- // found all latest version & used in project template version record
- // for Form, PriceConfig, PlanConfig
- usedModelMap = modelUsed;
- return Promise.all([
- models.Form.latestVersionIncludeUsed(usedModelMap.form),
- models.PriceConfig.latestVersionIncludeUsed(usedModelMap.priceConfig),
- models.PlanConfig.latestVersionIncludeUsed(usedModelMap.planConfig),
- ]);
- }).then((latestVersionModels) => {
- latestVersion = latestVersionModels;
- return Promise.all([
- models.ProjectTemplate.findAll(projectProductTemplateQuery),
- models.ProductTemplate.findAll(projectProductTemplateQuery),
- models.MilestoneTemplate.findAll(query),
- models.ProjectType.findAll(query),
- models.ProductCategory.findAll(query),
- Promise.resolve(latestVersion[0]),
- Promise.resolve(latestVersion[1]),
- Promise.resolve(latestVersion[2]),
- ]);
- }).then((queryAllResult) => {
- res.json(util.wrapResponse(req.id, {
- projectTemplates: queryAllResult[0],
- productTemplates: queryAllResult[1],
- milestoneTemplates: queryAllResult[2],
- projectTypes: queryAllResult[3],
- productCategories: queryAllResult[4],
- forms: queryAllResult[5],
- priceConfigs: queryAllResult[6],
- planConfigs: queryAllResult[7],
- }));
- })
- .catch(next);
- }
- return Promise.all([
- models.ProjectTemplate.findAll(projectProductTemplateQuery),
- models.ProductTemplate.findAll(projectProductTemplateQuery),
- models.MilestoneTemplate.findAll(query),
- models.ProjectType.findAll(query),
- models.ProductCategory.findAll(query),
- models.Form.latestVersion(),
- models.PriceConfig.latestVersion(),
- models.PlanConfig.latestVersion(),
- models.BuildingBlock.findAll(query),
- ])
- .then((results) => {
- res.json(util.wrapResponse(req.id, {
- projectTemplates: results[0],
- productTemplates: results[1],
- milestoneTemplates: results[2],
- projectTypes: results[3],
- productCategories: results[4],
- forms: results[5],
- priceConfigs: results[6],
- planConfigs: results[7],
- buildingBlocks: results[8],
- }));
- })
- .catch(next);
+ req.log.debug('Metadata is not found in ES');
+ return loadMetadataFromDb(req.query.includeAllReferred).then(dbDataToReturn => res.json(dbDataToReturn));
+ })
+ .catch(next);
},
];
diff --git a/src/routes/metadata/list.spec.js b/src/routes/metadata/list.spec.js
index 38326fcf..67185f65 100644
--- a/src/routes/metadata/list.spec.js
+++ b/src/routes/metadata/list.spec.js
@@ -3,12 +3,18 @@
*/
import chai from 'chai';
import request from 'supertest';
+import config from 'config';
+import _ from 'lodash';
import models from '../../models';
import server from '../../app';
import testUtil from '../../tests/util';
const should = chai.should();
+const expect = chai.expect;
+
+const ES_METADATA_INDEX = config.get('elasticsearchConfig.metadataIndexName');
+const ES_METADATA_TYPE = config.get('elasticsearchConfig.metadataDocType');
const projectTemplates = [
{
@@ -204,8 +210,36 @@ const buildingBlocks = [
},
];
-describe('GET all metadata', () => {
- beforeEach(() => testUtil.clearDb()
+const getObjToIndex = (items) => {
+ const toIndex = _(items).map((item) => {
+ const json = _.omit(item.toJSON(), 'deletedAt', 'deletedBy');
+
+ // setup ES markers. check these for equality with "from ES" to confirm that these records well pulled from ES
+ if (json.description !== undefined) {
+ json.description = 'from ES';
+ } else if (json.info != null) {
+ json.info = 'from ES';
+ } else if (json.details != null) {
+ json.details = 'from ES';
+ } else if (json.config != null) {
+ if (json.config.sections != null) {
+ json.config.sections[0].description = 'from ES';
+ } else if (json.hello != null) {
+ json.hello = 'from ES';
+ }
+ }
+ // end of ES markers
+
+ return json;
+ }).value();
+
+ return toIndex;
+};
+
+describe('GET all metadata from DB', () => {
+ before((done) => {
+ testUtil.clearES()
+ .then(() => testUtil.clearDb())
.then(() => models.ProjectTemplate.bulkCreate(projectTemplates))
.then(() => models.ProductTemplate.bulkCreate(productTemplates))
.then(() => models.MilestoneTemplate.bulkCreate(milestoneTemplates))
@@ -214,20 +248,24 @@ describe('GET all metadata', () => {
.then(() => models.Form.bulkCreate(forms))
.then(() => models.PriceConfig.bulkCreate(priceConfigs))
.then(() => models.PlanConfig.bulkCreate(planConfigs))
- .then(() => models.BuildingBlock.bulkCreate(buildingBlocks)),
- );
- after(testUtil.clearDb);
+ .then(() => models.BuildingBlock.bulkCreate(buildingBlocks))
+ .then(() => done());
+ });
+
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/projects/metadata')
+ .get('/v5/projects/metadata')
.expect(403, done);
});
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/projects/metadata')
+ .get('/v5/projects/metadata')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -237,13 +275,13 @@ describe('GET all metadata', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/projects/metadata')
+ .get('/v5/projects/metadata')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.projectTemplates.should.have.length(1);
resJson.productTemplates.should.have.length(1);
@@ -264,13 +302,13 @@ describe('GET all metadata', () => {
it('should return all used model when request with includeAllReferred query', (done) => {
request(server)
- .get('/v4/projects/metadata?includeAllReferred=true')
+ .get('/v5/projects/metadata?includeAllReferred=true')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.projectTemplates.should.have.length(1);
resJson.productTemplates.should.have.length(1);
@@ -286,7 +324,7 @@ describe('GET all metadata', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/projects/metadata')
+ .get('/v5/projects/metadata')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -296,7 +334,7 @@ describe('GET all metadata', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/projects/metadata')
+ .get('/v5/projects/metadata')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -305,7 +343,7 @@ describe('GET all metadata', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/projects/metadata')
+ .get('/v5/projects/metadata')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -314,7 +352,7 @@ describe('GET all metadata', () => {
it('should return correct building blocks for admin', (done) => {
request(server)
- .get('/v4/projects/metadata')
+ .get('/v5/projects/metadata')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -323,7 +361,7 @@ describe('GET all metadata', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
should.exist(resJson.buildingBlocks);
resJson.buildingBlocks.length.should.be.eql(2);
@@ -337,3 +375,122 @@ describe('GET all metadata', () => {
});
});
});
+
+describe('GET all metadata from ES', () => {
+ before((done) => {
+ const esData = {};
+
+ testUtil.clearES()
+ .then(() => testUtil.clearDb())
+ .then(() => models.ProjectTemplate.bulkCreate(projectTemplates, { returning: true }))
+ .then((created) => { esData.projectTemplates = getObjToIndex(created); })
+ .then(() => models.ProductTemplate.bulkCreate(productTemplates, { returning: true }))
+ .then((created) => { esData.productTemplates = getObjToIndex(created); })
+ .then(() => models.MilestoneTemplate.bulkCreate(milestoneTemplates, { returning: true }))
+ .then((created) => { esData.milestoneTemplates = getObjToIndex(created); })
+ .then(() => models.ProjectType.bulkCreate(projectTypes, { returning: true }))
+ .then((created) => { esData.projectTypes = getObjToIndex(created); })
+ .then(() => models.ProductCategory.bulkCreate(productCategories, { returning: true }))
+ .then((created) => { esData.productCategories = getObjToIndex(created); })
+ .then(() => models.Form.bulkCreate(forms, { returning: true }))
+ .then((created) => {
+ // only index form with key `productKey 1`
+ const v2Form = _(created).filter(c => c.key === 'productKey 1');
+ esData.forms = getObjToIndex(v2Form);
+ })
+ .then(() => models.PriceConfig.bulkCreate(priceConfigs, { returning: true }))
+ .then((created) => {
+ // only index latest versions
+ const v2PriceConfigs = _(created).filter(c => c.version === 2);
+ esData.priceConfigs = getObjToIndex(v2PriceConfigs);
+ })
+ .then(() => models.PlanConfig.bulkCreate(planConfigs, { returning: true }))
+ .then((created) => {
+ // only index latest versions
+ const v2PlanConfigs = _(created).filter(c => c.version === 2);
+ esData.planConfigs = getObjToIndex(v2PlanConfigs);
+ })
+ .then(() => models.BuildingBlock.bulkCreate(buildingBlocks, { returning: true }))
+ .then((created) => { esData.buildingBlocks = getObjToIndex(created); })
+ .then(() => server.services.es.index({
+ index: ES_METADATA_INDEX,
+ type: ES_METADATA_TYPE,
+ body: esData,
+ }))
+ .then(() => done());
+ });
+
+ after((done) => {
+ testUtil.clearES().then(() => testUtil.clearDb(done));
+ });
+
+ describe('GET /projects/metadata', () => {
+ it('should return 200 for admin from ES', (done) => {
+ request(server)
+ .get('/v5/projects/metadata')
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.admin}`,
+ })
+ .expect(200)
+ .end((err, res) => {
+ const resJson = res.body;
+ should.exist(resJson);
+ resJson.projectTemplates.should.have.length(1);
+ resJson.projectTemplates[0].info.should.eql('from ES');
+
+ resJson.productTemplates.should.have.length(1);
+ resJson.productTemplates[0].details.should.eql('from ES');
+
+ resJson.milestoneTemplates.should.have.length(1);
+ resJson.milestoneTemplates[0].description.should.eql('from ES');
+
+ resJson.projectTypes.should.have.length(1);
+ resJson.projectTypes[0].info.should.eql('from ES');
+
+ resJson.productCategories.should.have.length(1);
+ resJson.productCategories[0].info.should.eql('from ES');
+
+ resJson.forms.should.have.length(1);
+ resJson.forms[0].key.should.eql('productKey 1');
+ resJson.forms[0].config.sections.should.have.length(1);
+ resJson.forms[0].config.sections[0].description.should.eql('from ES');
+
+ resJson.planConfigs.should.have.length(1);
+ resJson.priceConfigs.should.have.length(1);
+
+ resJson.forms[0].version.should.be.eql(2);
+ resJson.planConfigs[0].version.should.be.eql(2);
+ resJson.priceConfigs[0].version.should.be.eql(2);
+
+ done();
+ });
+ });
+
+ it('should return correct building blocks for admin from ES', (done) => {
+ request(server)
+ .get('/v5/projects/metadata')
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.copilot}`,
+ })
+ .expect(200)
+ .end((err, res) => {
+ if (err) {
+ done(err);
+ } else {
+ const resJson = res.body;
+ should.exist(resJson);
+ should.exist(resJson.buildingBlocks);
+ resJson.buildingBlocks.length.should.be.eql(2);
+ should.not.exist(resJson.buildingBlocks[0].privateConfig);
+ should.not.exist(resJson.buildingBlocks[1].privateConfig);
+
+ // ES doesn't guarantee order if sort order isn't specified. Current implementation doesn't specify sort order
+ expect(_.some(resJson.buildingBlocks, { key: 'key1' })).to.be.true; // eslint-disable-line no-unused-expressions
+ expect(_.some(resJson.buildingBlocks, { key: 'key2' })).to.be.true; // eslint-disable-line no-unused-expressions
+
+ done();
+ }
+ });
+ });
+ });
+});
diff --git a/src/routes/milestoneTemplates/clone.js b/src/routes/milestoneTemplates/clone.js
index 147f6dd1..26662879 100644
--- a/src/routes/milestoneTemplates/clone.js
+++ b/src/routes/milestoneTemplates/clone.js
@@ -7,20 +7,18 @@ import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import util from '../../util';
import models from '../../models';
-import { MILESTONE_TEMPLATE_REFERENCES } from '../../constants';
+import { EVENT, RESOURCES, MILESTONE_TEMPLATE_REFERENCES } from '../../constants';
import validateMilestoneTemplate from '../../middlewares/validateMilestoneTemplate';
const permissions = tcMiddleware.permissions;
const schema = {
- body: {
- param: Joi.object().keys({
- sourceReference: Joi.string().valid(_.values(MILESTONE_TEMPLATE_REFERENCES)).required(),
- sourceReferenceId: Joi.number().integer().positive().required(),
- reference: Joi.string().valid(_.values(MILESTONE_TEMPLATE_REFERENCES)).required(),
- referenceId: Joi.number().integer().positive().required(),
- }).required(),
- },
+ body: Joi.object().keys({
+ sourceReference: Joi.string().valid(_.values(MILESTONE_TEMPLATE_REFERENCES)).required(),
+ sourceReferenceId: Joi.number().integer().positive().required(),
+ reference: Joi.string().valid(_.values(MILESTONE_TEMPLATE_REFERENCES)).required(),
+ referenceId: Joi.number().integer().positive().required(),
+ }).required(),
};
module.exports = [
@@ -34,8 +32,8 @@ module.exports = [
// Find the product template
models.MilestoneTemplate.findAll({
where: {
- reference: req.body.param.sourceReference,
- referenceId: req.body.param.sourceReferenceId,
+ reference: req.body.sourceReference,
+ referenceId: req.body.sourceReferenceId,
},
attributes: { exclude: ['id', 'deletedAt', 'createdAt', 'updatedAt', 'deletedBy'] },
raw: true,
@@ -43,8 +41,8 @@ module.exports = [
.then((milestoneTemplatesToClone) => {
const newMilestoneTemplates = _.cloneDeep(milestoneTemplatesToClone);
_.each(newMilestoneTemplates, (milestone) => {
- milestone.reference = req.body.param.reference; // eslint-disable-line no-param-reassign
- milestone.referenceId = req.body.param.referenceId; // eslint-disable-line no-param-reassign
+ milestone.reference = req.body.reference; // eslint-disable-line no-param-reassign
+ milestone.referenceId = req.body.referenceId; // eslint-disable-line no-param-reassign
milestone.createdBy = req.authUser.userId; // eslint-disable-line no-param-reassign
milestone.updatedBy = req.authUser.userId; // eslint-disable-line no-param-reassign
});
@@ -53,8 +51,8 @@ module.exports = [
.then(() => { // eslint-disable-line arrow-body-style
return models.MilestoneTemplate.findAll({
where: {
- reference: req.body.param.reference,
- referenceId: req.body.param.referenceId,
+ reference: req.body.reference,
+ referenceId: req.body.referenceId,
},
attributes: { exclude: ['deletedAt', 'deletedBy'] },
raw: true,
@@ -66,8 +64,15 @@ module.exports = [
}),
)
.then(() => {
+ // emit the event
+ _.map(result, r => util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.MILESTONE_TEMPLATE_ADDED,
+ RESOURCES.MILESTONE_TEMPLATE,
+ r));
+
// Write to response
- res.status(201).json(util.wrapResponse(req.id, result, result.length, 201));
+ res.status(201).json(result);
})
.catch(next);
},
diff --git a/src/routes/milestoneTemplates/clone.spec.js b/src/routes/milestoneTemplates/clone.spec.js
index a0f9bbbd..4f233354 100644
--- a/src/routes/milestoneTemplates/clone.spec.js
+++ b/src/routes/milestoneTemplates/clone.spec.js
@@ -113,32 +113,34 @@ const milestoneTemplates = [
];
describe('CLONE milestone template', () => {
- beforeEach(() => testUtil.clearDb()
+ beforeEach((done) => {
+ testUtil.clearDb()
.then(() => models.ProductTemplate.bulkCreate(productTemplates))
- .then(() => models.MilestoneTemplate.bulkCreate(milestoneTemplates)),
+ .then(() => { models.MilestoneTemplate.bulkCreate(milestoneTemplates).then(() => done()); });
+ },
);
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('POST /timelines/metadata/milestoneTemplates/clone', () => {
const body = {
- param: {
- sourceReference: 'productTemplate',
- sourceReferenceId: 1,
- reference: 'productTemplate',
- referenceId: 2,
- },
+ sourceReference: 'productTemplate',
+ sourceReferenceId: 1,
+ reference: 'productTemplate',
+ referenceId: 2,
};
it('should return 403 if user is not authenticated/clone', (done) => {
request(server)
- .post('/v4/timelines/metadata/milestoneTemplates/clone')
+ .post('/v5/timelines/metadata/milestoneTemplates/clone')
.send(body)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .post('/v4/timelines/metadata/milestoneTemplates/clone')
+ .post('/v5/timelines/metadata/milestoneTemplates/clone')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -148,7 +150,7 @@ describe('CLONE milestone template', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .post('/v4/timelines/metadata/milestoneTemplates/clone')
+ .post('/v5/timelines/metadata/milestoneTemplates/clone')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -158,7 +160,7 @@ describe('CLONE milestone template', () => {
it('should return 403 for manager', (done) => {
request(server)
- .post('/v4/timelines/metadata/milestoneTemplates/clone')
+ .post('/v5/timelines/metadata/milestoneTemplates/clone')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -166,73 +168,67 @@ describe('CLONE milestone template', () => {
.expect(403, done);
});
- it('should return 422 for non-existent product template', (done) => {
+ it('should return 400 for non-existent product template', (done) => {
const invalidBody = {
- param: {
- sourceReference: 'productTemplate',
- sourceReferenceId: 1,
- reference: 'productTemplate',
- referenceId: 2000,
- },
+ sourceReference: 'productTemplate',
+ sourceReferenceId: 1,
+ reference: 'productTemplate',
+ referenceId: 2000,
};
request(server)
- .post('/v4/timelines/metadata/milestoneTemplates/clone')
+ .post('/v5/timelines/metadata/milestoneTemplates/clone')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for non-existent source product template', (done) => {
+ it('should return 400 for non-existent source product template', (done) => {
const invalidBody = {
- param: {
- sourceReference: 'product',
- sourceReferenceId: 1000,
- reference: 'product',
- referenceId: 2,
- },
+ sourceReference: 'product',
+ sourceReferenceId: 1000,
+ reference: 'product',
+ referenceId: 2,
};
request(server)
- .post('/v4/timelines/metadata/milestoneTemplates/clone')
+ .post('/v5/timelines/metadata/milestoneTemplates/clone')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if missing sourceReference', (done) => {
+ it('should return 400 if missing sourceReference', (done) => {
const invalidBody = {
- param: {
- sourceReferenceId: 1000,
- reference: 'productTemplate',
- referenceId: 2,
- },
+ sourceReferenceId: 1000,
+ reference: 'productTemplate',
+ referenceId: 2,
};
request(server)
- .post('/v4/timelines/metadata/milestoneTemplates/clone')
+ .post('/v5/timelines/metadata/milestoneTemplates/clone')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 201 for admin', (done) => {
request(server)
- .post('/v4/timelines/metadata/milestoneTemplates/clone')
+ .post('/v5/timelines/metadata/milestoneTemplates/clone')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(body)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
should.not.equal(resJson[0].id, null);
resJson[0].name.should.be.eql(milestoneTemplates[0].name);
@@ -260,14 +256,14 @@ describe('CLONE milestone template', () => {
it('should return 201 for connect admin', (done) => {
request(server)
- .post('/v4/timelines/metadata/milestoneTemplates/clone')
+ .post('/v5/timelines/metadata/milestoneTemplates/clone')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
.send(body)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
resJson[0].createdBy.should.be.eql(40051336); // connect admin
resJson[0].updatedBy.should.be.eql(40051336); // connect admin
diff --git a/src/routes/milestoneTemplates/create.js b/src/routes/milestoneTemplates/create.js
index 32ab860a..d72f727a 100644
--- a/src/routes/milestoneTemplates/create.js
+++ b/src/routes/milestoneTemplates/create.js
@@ -9,35 +9,33 @@ import { middleware as tcMiddleware } from 'tc-core-library-js';
import util from '../../util';
import models from '../../models';
import validateMilestoneTemplate from '../../middlewares/validateMilestoneTemplate';
-import { MILESTONE_TEMPLATE_REFERENCES } from '../../constants';
+import { EVENT, RESOURCES, MILESTONE_TEMPLATE_REFERENCES } from '../../constants';
const permissions = tcMiddleware.permissions;
const schema = {
- body: {
- param: Joi.object().keys({
- id: Joi.any().strip(),
- name: Joi.string().max(255).required(),
- description: Joi.string().max(255),
- duration: Joi.number().integer().required(),
- type: Joi.string().max(45).required(),
- order: Joi.number().integer().required(),
- plannedText: Joi.string().max(512).required(),
- activeText: Joi.string().max(512).required(),
- completedText: Joi.string().max(512).required(),
- blockedText: Joi.string().max(512).required(),
- reference: Joi.string().valid(_.values(MILESTONE_TEMPLATE_REFERENCES)).required(),
- referenceId: Joi.number().integer().positive().required(),
- metadata: Joi.object().required(),
- hidden: Joi.boolean().optional(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ body: Joi.object().keys({
+ id: Joi.any().strip(),
+ name: Joi.string().max(255).required(),
+ description: Joi.string().max(255),
+ duration: Joi.number().integer().required(),
+ type: Joi.string().max(45).required(),
+ order: Joi.number().integer().required(),
+ plannedText: Joi.string().max(512).required(),
+ activeText: Joi.string().max(512).required(),
+ completedText: Joi.string().max(512).required(),
+ blockedText: Joi.string().max(512).required(),
+ reference: Joi.string().valid(_.values(MILESTONE_TEMPLATE_REFERENCES)).required(),
+ referenceId: Joi.number().integer().positive().required(),
+ metadata: Joi.object().required(),
+ hidden: Joi.boolean().optional(),
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
};
module.exports = [
@@ -45,12 +43,11 @@ module.exports = [
validateMilestoneTemplate.validateRequestBody,
permissions('milestoneTemplate.create'),
(req, res, next) => {
- const entity = _.assign(req.body.param, {
+ const entity = _.assign(req.body, {
createdBy: req.authUser.userId,
updatedBy: req.authUser.userId,
});
let result;
-
return models.sequelize.transaction(() =>
// Create the milestone template
models.MilestoneTemplate.create(entity)
@@ -68,11 +65,41 @@ module.exports = [
order: { $gte: result.order },
},
});
+ })
+ .then((updatedCount) => {
+ if (updatedCount) {
+ return models.MilestoneTemplate.findAll({
+ where: {
+ reference: result.reference,
+ referenceId: result.referenceId,
+ id: { $ne: result.id },
+ },
+ order: [['updatedAt', 'DESC']],
+ limit: updatedCount[0],
+ });
+ }
+ return Promise.resolve();
}),
)
- .then(() => {
+ .then((otherUpdated) => {
+ // emit the event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.MILESTONE_TEMPLATE_ADDED,
+ RESOURCES.MILESTONE_TEMPLATE,
+ result);
+
+ // emit the event for other milestone templates order updated
+ _.map(otherUpdated, milestoneTemplate =>
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.MILESTONE_TEMPLATE_UPDATED,
+ RESOURCES.MILESTONE_TEMPLATE,
+ _.assign(_.pick(milestoneTemplate.toJSON(), 'id', 'order', 'updatedBy', 'updatedAt'))),
+ );
+
// Write to response
- res.status(201).json(util.wrapResponse(req.id, result, 1, 201));
+ res.status(201).json(result);
})
.catch(next);
},
diff --git a/src/routes/milestoneTemplates/create.spec.js b/src/routes/milestoneTemplates/create.spec.js
index beacf71f..0b1bb3cb 100644
--- a/src/routes/milestoneTemplates/create.spec.js
+++ b/src/routes/milestoneTemplates/create.spec.js
@@ -94,41 +94,43 @@ const milestoneTemplates = [
];
describe('CREATE milestone template', () => {
- beforeEach(() => testUtil.clearDb()
+ beforeEach((done) => {
+ testUtil.clearDb()
.then(() => models.ProductTemplate.bulkCreate(productTemplates))
- .then(() => models.MilestoneTemplate.bulkCreate(milestoneTemplates)),
+ .then(() => { models.MilestoneTemplate.bulkCreate(milestoneTemplates).then(() => done()); });
+ },
);
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('POST /timelines/metadata/milestoneTemplates', () => {
const body = {
- param: {
- name: 'milestoneTemplate 3',
- description: 'description 3',
- duration: 33,
- type: 'type3',
- order: 1,
- plannedText: 'text to be shown in planned stage - 3',
- blockedText: 'text to be shown in blocked stage - 3',
- activeText: 'text to be shown in active stage - 3',
- completedText: 'text to be shown in completed stage - 3',
- hidden: true,
- reference: 'productTemplate',
- referenceId: 1,
- metadata: {},
- },
+ name: 'milestoneTemplate 3',
+ description: 'description 3',
+ duration: 33,
+ type: 'type3',
+ order: 1,
+ plannedText: 'text to be shown in planned stage - 3',
+ blockedText: 'text to be shown in blocked stage - 3',
+ activeText: 'text to be shown in active stage - 3',
+ completedText: 'text to be shown in completed stage - 3',
+ hidden: true,
+ reference: 'productTemplate',
+ referenceId: 1,
+ metadata: {},
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post('/v4/timelines/metadata/milestoneTemplates')
+ .post('/v5/timelines/metadata/milestoneTemplates')
.send(body)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .post('/v4/timelines/metadata/milestoneTemplates')
+ .post('/v5/timelines/metadata/milestoneTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -138,7 +140,7 @@ describe('CREATE milestone template', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .post('/v4/timelines/metadata/milestoneTemplates')
+ .post('/v5/timelines/metadata/milestoneTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -148,7 +150,7 @@ describe('CREATE milestone template', () => {
it('should return 403 for manager', (done) => {
request(server)
- .post('/v4/timelines/metadata/milestoneTemplates')
+ .post('/v5/timelines/metadata/milestoneTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -156,91 +158,81 @@ describe('CREATE milestone template', () => {
.expect(403, done);
});
- it('should return 422 for non-existed product template', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, { referenceId: 1000 }),
- };
+ it('should return 400 for non-existed product template', (done) => {
+ const invalidBody = _.assign({}, body, { referenceId: 1000 });
request(server)
- .post('/v4/timelines/metadata/milestoneTemplates')
+ .post('/v5/timelines/metadata/milestoneTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if missing name', (done) => {
+ it('should return 400 if missing name', (done) => {
const invalidBody = {
- param: {
- name: undefined,
- },
+ name: undefined,
};
request(server)
- .post('/v4/timelines/metadata/milestoneTemplates')
+ .post('/v5/timelines/metadata/milestoneTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if missing duration', (done) => {
+ it('should return 400 if missing duration', (done) => {
const invalidBody = {
- param: {
- duration: undefined,
- },
+ duration: undefined,
};
request(server)
- .post('/v4/timelines/metadata/milestoneTemplates')
+ .post('/v5/timelines/metadata/milestoneTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if missing type', (done) => {
+ it('should return 400 if missing type', (done) => {
const invalidBody = {
- param: {
- type: undefined,
- },
+ type: undefined,
};
request(server)
- .post('/v4/timelines/metadata/milestoneTemplates')
+ .post('/v5/timelines/metadata/milestoneTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if missing order', (done) => {
+ it('should return 400 if missing order', (done) => {
const invalidBody = {
- param: {
- order: undefined,
- },
+ order: undefined,
};
request(server)
- .post('/v4/timelines/metadata/milestoneTemplates')
+ .post('/v5/timelines/metadata/milestoneTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 201 for admin', (done) => {
request(server)
- .post('/v4/timelines/metadata/milestoneTemplates')
+ .post('/v5/timelines/metadata/milestoneTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -248,20 +240,20 @@ describe('CREATE milestone template', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson.id);
- resJson.name.should.be.eql(body.param.name);
- resJson.description.should.be.eql(body.param.description);
- resJson.duration.should.be.eql(body.param.duration);
- resJson.type.should.be.eql(body.param.type);
- resJson.order.should.be.eql(body.param.order);
- resJson.plannedText.should.be.eql(body.param.plannedText);
- resJson.blockedText.should.be.eql(body.param.blockedText);
- resJson.activeText.should.be.eql(body.param.activeText);
- resJson.completedText.should.be.eql(body.param.completedText);
- resJson.reference.should.be.eql(body.param.reference);
- resJson.referenceId.should.be.eql(body.param.referenceId);
- resJson.metadata.should.be.eql(body.param.metadata);
+ resJson.name.should.be.eql(body.name);
+ resJson.description.should.be.eql(body.description);
+ resJson.duration.should.be.eql(body.duration);
+ resJson.type.should.be.eql(body.type);
+ resJson.order.should.be.eql(body.order);
+ resJson.plannedText.should.be.eql(body.plannedText);
+ resJson.blockedText.should.be.eql(body.blockedText);
+ resJson.activeText.should.be.eql(body.activeText);
+ resJson.completedText.should.be.eql(body.completedText);
+ resJson.reference.should.be.eql(body.reference);
+ resJson.referenceId.should.be.eql(body.referenceId);
+ resJson.metadata.should.be.eql(body.metadata);
resJson.createdBy.should.be.eql(40051333); // admin
should.exist(resJson.createdAt);
@@ -273,8 +265,8 @@ describe('CREATE milestone template', () => {
// Verify 'order' of the other milestones
models.MilestoneTemplate.findAll({
where: {
- reference: body.param.reference,
- referenceId: body.param.referenceId,
+ reference: body.reference,
+ referenceId: body.referenceId,
},
}).then((milestones) => {
_.each(milestones, (milestone) => {
@@ -293,9 +285,9 @@ describe('CREATE milestone template', () => {
it('should return 201 for admin without optional fields', (done) => {
const minimalBody = _.cloneDeep(body);
- delete minimalBody.param.hidden;
+ delete minimalBody.hidden;
request(server)
- .post('/v4/timelines/metadata/milestoneTemplates')
+ .post('/v5/timelines/metadata/milestoneTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -303,7 +295,7 @@ describe('CREATE milestone template', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.hidden.should.be.eql(false); // default of hidden field
done();
});
@@ -311,7 +303,7 @@ describe('CREATE milestone template', () => {
it('should return 201 for connect admin', (done) => {
request(server)
- .post('/v4/timelines/metadata/milestoneTemplates')
+ .post('/v5/timelines/metadata/milestoneTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -319,7 +311,7 @@ describe('CREATE milestone template', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.createdBy.should.be.eql(40051336); // connect admin
resJson.updatedBy.should.be.eql(40051336); // connect admin
done();
diff --git a/src/routes/milestoneTemplates/delete.js b/src/routes/milestoneTemplates/delete.js
index 5cd0454b..0ab2f7e0 100644
--- a/src/routes/milestoneTemplates/delete.js
+++ b/src/routes/milestoneTemplates/delete.js
@@ -5,6 +5,8 @@ import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import models from '../../models';
+import util from '../../util';
+import { EVENT, RESOURCES } from '../../constants';
import validateMilestoneTemplate from '../../middlewares/validateMilestoneTemplate';
const permissions = tcMiddleware.permissions;
@@ -25,6 +27,13 @@ module.exports = [
.then(entity => entity.destroy()),
)
.then(() => {
+ // emit the event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.MILESTONE_TEMPLATE_REMOVED,
+ RESOURCES.MILESTONE_TEMPLATE,
+ { id: req.params.milestoneTemplateId });
+
res.status(204).end();
})
.catch(next),
diff --git a/src/routes/milestoneTemplates/delete.spec.js b/src/routes/milestoneTemplates/delete.spec.js
index 92a6dcb6..25f61ad4 100644
--- a/src/routes/milestoneTemplates/delete.spec.js
+++ b/src/routes/milestoneTemplates/delete.spec.js
@@ -26,7 +26,7 @@ const expectAfterDelete = (id, err, next) => {
chai.assert.isNotNull(res.deletedBy);
request(server)
- .get(`/v4/timelines/metadata/milestoneTemplates/${id}`)
+ .get(`/v5/timelines/metadata/milestoneTemplates/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -122,22 +122,26 @@ const milestoneTemplates = [
];
describe('DELETE milestone template', () => {
- beforeEach(() => testUtil.clearDb()
+ beforeEach((done) => {
+ testUtil.clearDb()
.then(() => models.ProductTemplate.bulkCreate(productTemplates))
- .then(() => models.MilestoneTemplate.bulkCreate(milestoneTemplates)),
+ .then(() => { models.MilestoneTemplate.bulkCreate(milestoneTemplates).then(() => done()); });
+ },
);
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('DELETE /timelines/metadata/milestoneTemplates/{milestoneTemplateId}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .delete('/v4/timelines/metadata/milestoneTemplates/1')
+ .delete('/v5/timelines/metadata/milestoneTemplates/1')
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .delete('/v4/timelines/metadata/milestoneTemplates/1')
+ .delete('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -146,7 +150,7 @@ describe('DELETE milestone template', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .delete('/v4/timelines/metadata/milestoneTemplates/1')
+ .delete('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -155,7 +159,7 @@ describe('DELETE milestone template', () => {
it('should return 403 for manager', (done) => {
request(server)
- .delete('/v4/timelines/metadata/milestoneTemplates/1')
+ .delete('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -164,7 +168,7 @@ describe('DELETE milestone template', () => {
it('should return 404 for non-existed milestone template', (done) => {
request(server)
- .delete('/v4/timelines/metadata/milestoneTemplates/444')
+ .delete('/v5/timelines/metadata/milestoneTemplates/444')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -173,25 +177,25 @@ describe('DELETE milestone template', () => {
it('should return 404 for deleted milestone template', (done) => {
request(server)
- .delete('/v4/timelines/metadata/milestoneTemplates/2')
+ .delete('/v5/timelines/metadata/milestoneTemplates/2')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(404, done);
});
- it('should return 422 for invalid milestoneTemplateId param', (done) => {
+ it('should return 400 for invalid milestoneTemplateId param', (done) => {
request(server)
- .delete('/v4/timelines/metadata/milestoneTemplates/0')
+ .delete('/v5/timelines/metadata/milestoneTemplates/0')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .expect(422, done);
+ .expect(400, done);
});
it('should return 204, for admin, if template was successfully removed', (done) => {
request(server)
- .delete('/v4/timelines/metadata/milestoneTemplates/1')
+ .delete('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -201,7 +205,7 @@ describe('DELETE milestone template', () => {
it('should return 204, for connect admin, if template was successfully removed', (done) => {
request(server)
- .delete('/v4/timelines/metadata/milestoneTemplates/1')
+ .delete('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
diff --git a/src/routes/milestoneTemplates/get.js b/src/routes/milestoneTemplates/get.js
index 1f6cd2f7..2efdb3d3 100644
--- a/src/routes/milestoneTemplates/get.js
+++ b/src/routes/milestoneTemplates/get.js
@@ -20,5 +20,25 @@ module.exports = [
validate(schema),
validateMilestoneTemplate.validateIdParam,
permissions('milestoneTemplate.view'),
- (req, res) => res.json(util.wrapResponse(req.id, _.omit(req.milestoneTemplate.toJSON(), 'deletedAt', 'deletedBy'))),
+ (req, res, next) =>
+ util.fetchByIdFromES('milestoneTemplates', {
+ query: {
+ nested: {
+ path: 'milestoneTemplates',
+ query: {
+ match: { 'milestoneTemplates.id': req.params.milestoneTemplateId },
+ },
+ inner_hits: {},
+ },
+ },
+ }, 'metadata')
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No milestoneTemplate found in ES');
+ return res.json(_.omit(req.milestoneTemplate.toJSON(), 'deletedAt', 'deletedBy'));
+ }
+ req.log.debug('milestoneTemplate found in ES');
+ return res.json(data[0].inner_hits.milestoneTemplates.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle
+ })
+ .catch(next),
];
diff --git a/src/routes/milestoneTemplates/get.spec.js b/src/routes/milestoneTemplates/get.spec.js
index 50f31370..42aada94 100644
--- a/src/routes/milestoneTemplates/get.spec.js
+++ b/src/routes/milestoneTemplates/get.spec.js
@@ -97,22 +97,26 @@ const milestoneTemplates = [
];
describe('GET milestone template', () => {
- beforeEach(() => testUtil.clearDb()
+ beforeEach((done) => {
+ testUtil.clearDb()
.then(() => models.ProductTemplate.bulkCreate(productTemplates))
- .then(() => models.MilestoneTemplate.bulkCreate(milestoneTemplates)),
+ .then(() => { models.MilestoneTemplate.bulkCreate(milestoneTemplates).then(() => done()); });
+ },
);
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /timelines/metadata/milestoneTemplates/{milestoneTemplateId}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/timelines/metadata/milestoneTemplates/1')
+ .get('/v5/timelines/metadata/milestoneTemplates/1')
.expect(403, done);
});
it('should return 404 for non-existed milestone template', (done) => {
request(server)
- .get('/v4/timelines/metadata/milestoneTemplates/1111')
+ .get('/v5/timelines/metadata/milestoneTemplates/1111')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -121,7 +125,7 @@ describe('GET milestone template', () => {
it('should return 404 for deleted milestone template', (done) => {
request(server)
- .get('/v4/timelines/metadata/milestoneTemplates/2')
+ .get('/v5/timelines/metadata/milestoneTemplates/2')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -130,13 +134,13 @@ describe('GET milestone template', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/timelines/metadata/milestoneTemplates/1')
+ .get('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(milestoneTemplates[0].id);
resJson.name.should.be.eql(milestoneTemplates[0].name);
resJson.duration.should.be.eql(milestoneTemplates[0].duration);
@@ -163,7 +167,7 @@ describe('GET milestone template', () => {
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/timelines/metadata/milestoneTemplates/1')
+ .get('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -173,7 +177,7 @@ describe('GET milestone template', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/timelines/metadata/milestoneTemplates/1')
+ .get('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -183,7 +187,7 @@ describe('GET milestone template', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/timelines/metadata/milestoneTemplates/1')
+ .get('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -192,7 +196,7 @@ describe('GET milestone template', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/timelines/metadata/milestoneTemplates/1')
+ .get('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/milestoneTemplates/list.js b/src/routes/milestoneTemplates/list.js
index 64d93acf..572b8364 100644
--- a/src/routes/milestoneTemplates/list.js
+++ b/src/routes/milestoneTemplates/list.js
@@ -23,22 +23,79 @@ module.exports = [
];
if (sort && _.indexOf(sortableProps, sort) < 0) {
const apiErr = new Error('Invalid sort criteria');
- apiErr.status = 422;
+ apiErr.status = 400;
return next(apiErr);
}
const sortColumnAndOrder = sort.split(' ');
// Get all milestone templates
- const where = req.params.filter || {};
- return models.MilestoneTemplate.findAll({
- where,
- order: [sortColumnAndOrder],
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- raw: true,
+ let where = {};
+ let esTerms = [];
+ if (req.query.reference) {
+ where = _.assign(where, {
+ reference: req.query.reference,
+ });
+ esTerms = _.concat(esTerms, {
+ term: { 'milestoneTemplates.reference': req.query.reference },
+ });
+ }
+
+ if (req.query.referenceId) {
+ where = _.assign(where, {
+ referenceId: req.query.referenceId,
+ });
+ esTerms = _.concat(esTerms, {
+ term: { 'milestoneTemplates.referenceId': req.query.referenceId },
+ });
+ }
+
+ let query = {};
+ if (esTerms.length > 0) {
+ query = {
+ query: {
+ nested: {
+ path: 'milestoneTemplates',
+ query:
+ {
+ filtered: {
+ filter: {
+ bool: {
+ must: esTerms,
+ },
+ },
+ },
+ },
+ inner_hits: {},
+ },
+ },
+ };
+ }
+
+ return util.fetchFromES('milestoneTemplates', query)
+ .then((data) => {
+ let milestoneTemplates = _.isArray(data.milestoneTemplates) ?
+ data.milestoneTemplates : data.milestoneTemplates.hits.hits;
+ if (milestoneTemplates.length === 0) {
+ req.log.debug('No milestoneTemplate found in ES');
+ return models.MilestoneTemplate.findAll({
+ where,
+ order: [sortColumnAndOrder],
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ raw: true,
+ })
+ .then(result => res.json(result))
+ .catch(next);
+ }
+ req.log.debug('milestoneTemplates found in ES');
+ // Get the milestoneTemplates
+ milestoneTemplates = _.map(milestoneTemplates, (m) => {
+ if (m._source) return m._source; // eslint-disable-line no-underscore-dangle
+ return m;
+ });
+ // Sort
+ milestoneTemplates = _.orderBy(milestoneTemplates, [sortColumnAndOrder[0]], [sortColumnAndOrder[1]]);
+ return res.json(milestoneTemplates);
})
- .then((milestoneTemplates) => {
- res.json(util.wrapResponse(req.id, milestoneTemplates));
- })
- .catch(next);
+ .catch(next);
},
];
diff --git a/src/routes/milestoneTemplates/list.spec.js b/src/routes/milestoneTemplates/list.spec.js
index 465a388f..5a4b1567 100644
--- a/src/routes/milestoneTemplates/list.spec.js
+++ b/src/routes/milestoneTemplates/list.spec.js
@@ -113,46 +113,53 @@ const milestoneTemplates = [
];
describe('LIST milestone template', () => {
- beforeEach(() => testUtil.clearDb()
+ before((done) => {
+ testUtil.clearES(done);
+ });
+ beforeEach((done) => {
+ testUtil.clearDb()
.then(() => models.ProductTemplate.bulkCreate(productTemplates))
- .then(() => models.MilestoneTemplate.bulkCreate(milestoneTemplates)),
+ .then(() => { models.MilestoneTemplate.bulkCreate(milestoneTemplates).then(() => done()); });
+ },
);
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /timelines/metadata/milestoneTemplates', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/timelines/metadata/milestoneTemplates')
+ .get('/v5/timelines/metadata/milestoneTemplates')
.expect(403, done);
});
- it('should return 422 for invalid sort column', (done) => {
+ it('should return 400 for invalid sort column', (done) => {
request(server)
- .get('/v4/timelines/metadata/milestoneTemplates?sort=id')
+ .get('/v5/timelines/metadata/milestoneTemplates?sort=id')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for invalid sort order', (done) => {
+ it('should return 400 for invalid sort order', (done) => {
request(server)
- .get('/v4/timelines/metadata/milestoneTemplates?sort=order%20invalid')
+ .get('/v5/timelines/metadata/milestoneTemplates?sort=order invalid')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .expect(422, done);
+ .expect(400, done);
});
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/timelines/metadata/milestoneTemplates')
+ .get('/v5/timelines/metadata/milestoneTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
resJson[0].id.should.be.eql(milestoneTemplates[0].id);
resJson[0].name.should.be.eql(milestoneTemplates[0].name);
@@ -178,9 +185,9 @@ describe('LIST milestone template', () => {
});
});
- it('should return 200 for connect admin with filter', (done) => {
+ it('should return 200 for connect admin with reference and referenceId filters', (done) => {
request(server)
- .get('/v4/timelines/metadata/milestoneTemplates?filter=reference%3DproductTemplate%26referenceId%3D1')
+ .get('/v5/timelines/metadata/milestoneTemplates?reference=productTemplate&referenceId=1')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -190,7 +197,7 @@ describe('LIST milestone template', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/timelines/metadata/milestoneTemplates')
+ .get('/v5/timelines/metadata/milestoneTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -200,7 +207,7 @@ describe('LIST milestone template', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/timelines/metadata/milestoneTemplates')
+ .get('/v5/timelines/metadata/milestoneTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -209,7 +216,7 @@ describe('LIST milestone template', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/timelines/metadata/milestoneTemplates')
+ .get('/v5/timelines/metadata/milestoneTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -218,13 +225,13 @@ describe('LIST milestone template', () => {
it('should return 200 with sort desc', (done) => {
request(server)
- .get('/v4/timelines/metadata/milestoneTemplates?sort=order%20desc')
+ .get('/v5/timelines/metadata/milestoneTemplates?sort=order%20desc')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
resJson[0].id.should.be.eql(2);
resJson[1].id.should.be.eql(1);
diff --git a/src/routes/milestoneTemplates/update.js b/src/routes/milestoneTemplates/update.js
index c9ce67e4..86d1c5e0 100644
--- a/src/routes/milestoneTemplates/update.js
+++ b/src/routes/milestoneTemplates/update.js
@@ -9,7 +9,7 @@ import { middleware as tcMiddleware } from 'tc-core-library-js';
import util from '../../util';
import models from '../../models';
import validateMilestoneTemplate from '../../middlewares/validateMilestoneTemplate';
-import { MILESTONE_TEMPLATE_REFERENCES } from '../../constants';
+import { EVENT, RESOURCES, MILESTONE_TEMPLATE_REFERENCES } from '../../constants';
const permissions = tcMiddleware.permissions;
@@ -17,31 +17,29 @@ const schema = {
params: {
milestoneTemplateId: Joi.number().integer().positive().required(),
},
- body: {
- param: Joi.object().keys({
- id: Joi.any().strip(),
- name: Joi.string().max(255).optional(),
- description: Joi.string().max(255),
- duration: Joi.number().integer().optional(),
- type: Joi.string().max(45).optional(),
- order: Joi.number().integer().optional(),
- plannedText: Joi.string().max(512).optional(),
- activeText: Joi.string().max(512).optional(),
- completedText: Joi.string().max(512).optional(),
- blockedText: Joi.string().max(512).optional(),
- productTemplateId: Joi.any().strip(),
- hidden: Joi.boolean().optional(),
- reference: Joi.string().valid(_.values(MILESTONE_TEMPLATE_REFERENCES)).required(),
- referenceId: Joi.number().integer().positive().required(),
- metadata: Joi.object().optional(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ body: Joi.object().keys({
+ id: Joi.any().strip(),
+ name: Joi.string().max(255).optional(),
+ description: Joi.string().max(255),
+ duration: Joi.number().integer().optional(),
+ type: Joi.string().max(45).optional(),
+ order: Joi.number().integer().optional(),
+ plannedText: Joi.string().max(512).optional(),
+ activeText: Joi.string().max(512).optional(),
+ completedText: Joi.string().max(512).optional(),
+ blockedText: Joi.string().max(512).optional(),
+ productTemplateId: Joi.any().strip(),
+ hidden: Joi.boolean().optional(),
+ reference: Joi.string().valid(_.values(MILESTONE_TEMPLATE_REFERENCES)).required(),
+ referenceId: Joi.number().integer().positive().required(),
+ metadata: Joi.object().optional(),
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
};
module.exports = [
@@ -50,7 +48,7 @@ module.exports = [
validateMilestoneTemplate.validateRequestBody,
permissions('milestoneTemplate.edit'),
(req, res, next) => {
- const entityToUpdate = _.assign(req.body.param, {
+ const entityToUpdate = _.assign(req.body, {
updatedBy: req.authUser.userId,
});
@@ -108,10 +106,42 @@ module.exports = [
},
});
});
+ })
+ .then((updatedCount) => {
+ if (updatedCount) {
+ return models.MilestoneTemplate.findAll({
+ where: {
+ reference: updated.reference,
+ referenceId: updated.referenceId,
+ id: { $ne: updated.id },
+ },
+ order: [['updatedAt', 'DESC']],
+ limit: updatedCount[0],
+ });
+ }
+ return Promise.resolve();
}),
)
- .then(() => {
- res.json(util.wrapResponse(req.id, updated));
+ .then((otherUpdated) => {
+ // emit the event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.MILESTONE_TEMPLATE_UPDATED,
+ RESOURCES.MILESTONE_TEMPLATE,
+ updated,
+ );
+
+ // emit the event for other milestone templates order updated
+ _.map(otherUpdated, milestoneTemplate =>
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.MILESTONE_TEMPLATE_UPDATED,
+ RESOURCES.MILESTONE_TEMPLATE,
+ milestoneTemplate.toJSON(),
+ ),
+ );
+
+ res.json(updated);
return Promise.resolve();
})
.catch(next);
diff --git a/src/routes/milestoneTemplates/update.spec.js b/src/routes/milestoneTemplates/update.spec.js
index 3de8c430..ef76a799 100644
--- a/src/routes/milestoneTemplates/update.spec.js
+++ b/src/routes/milestoneTemplates/update.spec.js
@@ -160,41 +160,44 @@ const milestoneTemplates = [
];
describe('UPDATE milestone template', () => {
- beforeEach(() => testUtil.clearDb()
+ beforeEach((done) => {
+ testUtil.clearDb()
.then(() => models.ProductTemplate.bulkCreate(productTemplates))
- .then(() => models.MilestoneTemplate.bulkCreate(milestoneTemplates)),
+ .then(() => { models.MilestoneTemplate.bulkCreate(milestoneTemplates).then(() => done()); });
+ },
);
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
+
describe('PATCH /timelines/metadata/milestoneTemplates/{milestoneTemplateId}', () => {
const body = {
- param: {
- name: 'milestoneTemplate 1-updated',
- description: 'description-updated',
- duration: 6,
- type: 'type1-updated',
- order: 5,
- plannedText: 'text to be shown in planned stage',
- blockedText: 'text to be shown in blocked stage',
- activeText: 'text to be shown in active stage',
- completedText: 'text to be shown in completed stage',
- hidden: true,
- reference: 'productTemplate',
- referenceId: 1,
- metadata: {},
- },
+ name: 'milestoneTemplate 1-updated',
+ description: 'description-updated',
+ duration: 6,
+ type: 'type1-updated',
+ order: 5,
+ plannedText: 'text to be shown in planned stage',
+ blockedText: 'text to be shown in blocked stage',
+ activeText: 'text to be shown in active stage',
+ completedText: 'text to be shown in completed stage',
+ hidden: true,
+ reference: 'productTemplate',
+ referenceId: 1,
+ metadata: {},
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/1')
+ .patch('/v5/timelines/metadata/milestoneTemplates/1')
.send(body)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/1')
+ .patch('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -204,7 +207,7 @@ describe('UPDATE milestone template', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/1')
+ .patch('/v5/timelines/metadata/milestoneTemplates/1')
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
@@ -214,7 +217,7 @@ describe('UPDATE milestone template', () => {
it('should return 403 for manager', (done) => {
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/1')
+ .patch('/v5/timelines/metadata/milestoneTemplates/1')
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
@@ -224,7 +227,7 @@ describe('UPDATE milestone template', () => {
it('should return 404 for non-existed milestone template', (done) => {
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/111')
+ .patch('/v5/timelines/metadata/milestoneTemplates/111')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -234,7 +237,7 @@ describe('UPDATE milestone template', () => {
it('should return 404 for deleted milestone template', (done) => {
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/4')
+ .patch('/v5/timelines/metadata/milestoneTemplates/4')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -244,27 +247,27 @@ describe('UPDATE milestone template', () => {
it('should return 200 for admin', (done) => {
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/1')
+ .patch('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(body)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(1);
- resJson.name.should.be.eql(body.param.name);
- resJson.description.should.be.eql(body.param.description);
- resJson.duration.should.be.eql(body.param.duration);
- resJson.type.should.be.eql(body.param.type);
- resJson.order.should.be.eql(body.param.order);
- resJson.plannedText.should.be.eql(body.param.plannedText);
- resJson.blockedText.should.be.eql(body.param.blockedText);
- resJson.activeText.should.be.eql(body.param.activeText);
- resJson.completedText.should.be.eql(body.param.completedText);
- resJson.reference.should.be.eql(body.param.reference);
- resJson.referenceId.should.be.eql(body.param.referenceId);
- resJson.metadata.should.be.eql(body.param.metadata);
+ resJson.name.should.be.eql(body.name);
+ resJson.description.should.be.eql(body.description);
+ resJson.duration.should.be.eql(body.duration);
+ resJson.type.should.be.eql(body.type);
+ resJson.order.should.be.eql(body.order);
+ resJson.plannedText.should.be.eql(body.plannedText);
+ resJson.blockedText.should.be.eql(body.blockedText);
+ resJson.activeText.should.be.eql(body.activeText);
+ resJson.completedText.should.be.eql(body.completedText);
+ resJson.reference.should.be.eql(body.reference);
+ resJson.referenceId.should.be.eql(body.referenceId);
+ resJson.metadata.should.be.eql(body.metadata);
should.exist(resJson.createdBy);
should.exist(resJson.createdAt);
@@ -282,25 +285,25 @@ describe('UPDATE milestone template', () => {
this.timeout(10000);
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/1')
+ .patch('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: _.assign({}, body.param, { order: 3 }) }) // 1 to 3
+ .send(_.assign({}, body, { order: 3 })) // 1 to 3
.expect(200)
.end(() => {
// Milestone 1: order 3
// Milestone 2: order 2 - 1 = 1
// Milestone 3: order 3 - 1 = 2
- models.MilestoneTemplate.findById(1)
+ models.MilestoneTemplate.findByPk(1)
.then((milestone) => {
milestone.order.should.be.eql(3);
})
- .then(() => models.MilestoneTemplate.findById(2))
+ .then(() => models.MilestoneTemplate.findByPk(2))
.then((milestone) => {
milestone.order.should.be.eql(1);
})
- .then(() => models.MilestoneTemplate.findById(3))
+ .then(() => models.MilestoneTemplate.findByPk(3))
.then((milestone) => {
milestone.order.should.be.eql(2);
@@ -314,25 +317,25 @@ describe('UPDATE milestone template', () => {
this.timeout(10000);
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/1')
+ .patch('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: _.assign({}, body.param, { order: 4 }) }) // 1 to 4
+ .send(_.assign({}, body, { order: 4 })) // 1 to 4
.expect(200)
.end(() => {
// Milestone 1: order 4
// Milestone 2: order 2
// Milestone 3: order 3
- models.MilestoneTemplate.findById(1)
+ models.MilestoneTemplate.findByPk(1)
.then((milestone) => {
milestone.order.should.be.eql(4);
})
- .then(() => models.MilestoneTemplate.findById(2))
+ .then(() => models.MilestoneTemplate.findByPk(2))
.then((milestone) => {
milestone.order.should.be.eql(2);
})
- .then(() => models.MilestoneTemplate.findById(3))
+ .then(() => models.MilestoneTemplate.findByPk(3))
.then((milestone) => {
milestone.order.should.be.eql(3);
@@ -346,25 +349,25 @@ describe('UPDATE milestone template', () => {
this.timeout(10000);
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/3')
+ .patch('/v5/timelines/metadata/milestoneTemplates/3')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: _.assign({}, body.param, { order: 1 }) }) // 3 to 1
+ .send(_.assign({}, body, { order: 1 })) // 3 to 1
.expect(200)
.end(() => {
// Milestone 1: order 2
// Milestone 2: order 3
// Milestone 3: order 1
- models.MilestoneTemplate.findById(1)
+ models.MilestoneTemplate.findByPk(1)
.then((milestone) => {
milestone.order.should.be.eql(2);
})
- .then(() => models.MilestoneTemplate.findById(2))
+ .then(() => models.MilestoneTemplate.findByPk(2))
.then((milestone) => {
milestone.order.should.be.eql(3);
})
- .then(() => models.MilestoneTemplate.findById(3))
+ .then(() => models.MilestoneTemplate.findByPk(3))
.then((milestone) => {
milestone.order.should.be.eql(1);
@@ -378,25 +381,25 @@ describe('UPDATE milestone template', () => {
this.timeout(10000);
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/3')
+ .patch('/v5/timelines/metadata/milestoneTemplates/3')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: _.assign({}, body.param, { order: 0 }) }) // 3 to 0
+ .send(_.assign({}, body, { order: 0 })) // 3 to 0
.expect(200)
.end(() => {
// Milestone 1: order 1
// Milestone 2: order 2
// Milestone 3: order 0
- models.MilestoneTemplate.findById(1)
+ models.MilestoneTemplate.findByPk(1)
.then((milestone) => {
milestone.order.should.be.eql(1);
})
- .then(() => models.MilestoneTemplate.findById(2))
+ .then(() => models.MilestoneTemplate.findByPk(2))
.then((milestone) => {
milestone.order.should.be.eql(2);
})
- .then(() => models.MilestoneTemplate.findById(3))
+ .then(() => models.MilestoneTemplate.findByPk(3))
.then((milestone) => {
milestone.order.should.be.eql(0);
@@ -407,9 +410,9 @@ describe('UPDATE milestone template', () => {
it('should return 200 for missing name', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.name;
+ delete partialBody.name;
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/1')
+ .patch('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -419,9 +422,9 @@ describe('UPDATE milestone template', () => {
it('should return 200 for missing type', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.type;
+ delete partialBody.type;
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/1')
+ .patch('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -431,9 +434,9 @@ describe('UPDATE milestone template', () => {
it('should return 200 for missing duration', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.duration;
+ delete partialBody.duration;
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/1')
+ .patch('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -443,9 +446,9 @@ describe('UPDATE milestone template', () => {
it('should return 200 for missing order', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.order;
+ delete partialBody.order;
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/1')
+ .patch('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -455,9 +458,9 @@ describe('UPDATE milestone template', () => {
it('should return 200 for missing plannedText', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.plannedText;
+ delete partialBody.plannedText;
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/1')
+ .patch('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -467,9 +470,9 @@ describe('UPDATE milestone template', () => {
it('should return 200 for missing blockedText', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.blockedText;
+ delete partialBody.blockedText;
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/1')
+ .patch('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -479,9 +482,9 @@ describe('UPDATE milestone template', () => {
it('should return 200 for missing activeText', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.activeText;
+ delete partialBody.activeText;
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/1')
+ .patch('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -491,9 +494,9 @@ describe('UPDATE milestone template', () => {
it('should return 200 for missing completedText', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.completedText;
+ delete partialBody.completedText;
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/1')
+ .patch('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -503,9 +506,9 @@ describe('UPDATE milestone template', () => {
it('should return 200 for missing hidden field', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.hidden;
+ delete partialBody.hidden;
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/1')
+ .patch('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -515,7 +518,7 @@ describe('UPDATE milestone template', () => {
it('should return 200 for connect admin', (done) => {
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/1')
+ .patch('/v5/timelines/metadata/milestoneTemplates/1')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -526,48 +529,46 @@ describe('UPDATE milestone template', () => {
it('should return 200 for admin - updating metadata', (done) => {
const bodyWithMetadata = {
- param: {
- name: 'milestoneTemplate 5-updated',
- description: 'description-updated',
- duration: 6,
- type: 'type5-updated',
- order: 5,
- plannedText: 'text to be shown in planned stage',
- blockedText: 'text to be shown in blocked stage',
- activeText: 'text to be shown in active stage',
- completedText: 'text to be shown in completed stage',
- hidden: true,
- reference: 'productTemplate',
- referenceId: 1,
- metadata: {
- metadata1: {
- name: 'metadata 1 - update',
- details: {
- anyDetails: 'any details 1 - update',
- newDetails: 'new',
- },
- others: ['others new'],
+ name: 'milestoneTemplate 5-updated',
+ description: 'description-updated',
+ duration: 6,
+ type: 'type5-updated',
+ order: 5,
+ plannedText: 'text to be shown in planned stage',
+ blockedText: 'text to be shown in blocked stage',
+ activeText: 'text to be shown in active stage',
+ completedText: 'text to be shown in completed stage',
+ hidden: true,
+ reference: 'productTemplate',
+ referenceId: 1,
+ metadata: {
+ metadata1: {
+ name: 'metadata 1 - update',
+ details: {
+ anyDetails: 'any details 1 - update',
+ newDetails: 'new',
},
- metadata3: {
- name: 'metadata 3',
- details: {
- anyDetails: 'any details 3',
- },
- others: ['others 31', 'others 32'],
+ others: ['others new'],
+ },
+ metadata3: {
+ name: 'metadata 3',
+ details: {
+ anyDetails: 'any details 3',
},
+ others: ['others 31', 'others 32'],
},
},
};
request(server)
- .patch('/v4/timelines/metadata/milestoneTemplates/5')
+ .patch('/v5/timelines/metadata/milestoneTemplates/5')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(bodyWithMetadata)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.metadata.should.be.eql({
metadata1: {
name: 'metadata 1 - update',
diff --git a/src/routes/milestones/create.js b/src/routes/milestones/create.js
index 56b2242b..08cda372 100644
--- a/src/routes/milestones/create.js
+++ b/src/routes/milestones/create.js
@@ -9,7 +9,7 @@ import Sequelize from 'sequelize';
import util from '../../util';
import validateTimeline from '../../middlewares/validateTimeline';
import models from '../../models';
-import { EVENT } from '../../constants';
+import { EVENT, RESOURCES } from '../../constants';
const permissions = tcMiddleware.permissions;
@@ -17,33 +17,31 @@ const schema = {
params: {
timelineId: Joi.number().integer().positive().required(),
},
- body: {
- param: Joi.object().keys({
- id: Joi.any().strip(),
- name: Joi.string().max(255).required(),
- description: Joi.string().max(255),
- duration: Joi.number().integer().required(),
- startDate: Joi.date().required(),
- actualStartDate: Joi.date().allow(null),
- endDate: Joi.date().min(Joi.ref('startDate')).allow(null),
- completionDate: Joi.date().min(Joi.ref('startDate')).allow(null),
- status: Joi.string().max(45).required(),
- type: Joi.string().max(45).required(),
- details: Joi.object(),
- order: Joi.number().integer().required(),
- plannedText: Joi.string().max(512),
- activeText: Joi.string().max(512),
- completedText: Joi.string().max(512),
- blockedText: Joi.string().max(512),
- hidden: Joi.boolean().optional(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ body: Joi.object().keys({
+ id: Joi.any().strip(),
+ name: Joi.string().max(255).required(),
+ description: Joi.string().max(255),
+ duration: Joi.number().integer().required(),
+ startDate: Joi.date().required(),
+ actualStartDate: Joi.date().allow(null),
+ endDate: Joi.date().min(Joi.ref('startDate')).allow(null),
+ completionDate: Joi.date().min(Joi.ref('startDate')).allow(null),
+ status: Joi.string().max(45).required(),
+ type: Joi.string().max(45).required(),
+ details: Joi.object(),
+ order: Joi.number().integer().required(),
+ plannedText: Joi.string().max(512),
+ activeText: Joi.string().max(512),
+ completedText: Joi.string().max(512),
+ blockedText: Joi.string().max(512),
+ hidden: Joi.boolean().optional(),
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
};
module.exports = [
@@ -53,7 +51,7 @@ module.exports = [
validateTimeline.validateTimelineIdParam,
permissions('milestone.create'),
(req, res, next) => {
- const entity = _.assign(req.body.param, {
+ const entity = _.assign(req.body, {
createdBy: req.authUser.userId,
updatedBy: req.authUser.userId,
timelineId: req.params.timelineId,
@@ -62,12 +60,12 @@ module.exports = [
// Validate startDate is not earlier than timeline startDate
let error;
- if (req.body.param.startDate < req.timeline.startDate) {
+ if (req.body.startDate < req.timeline.startDate) {
error = 'Milestone startDate must not be before the timeline startDate';
}
if (error) {
const apiErr = new Error(error);
- apiErr.status = 422;
+ apiErr.status = 400;
return next(apiErr);
}
@@ -87,13 +85,23 @@ module.exports = [
order: { $gte: result.order },
},
});
+ })
+ .then((updatedCount) => {
+ if (updatedCount) {
+ return models.Milestone.findAll({
+ where: {
+ timelineId: result.timelineId,
+ id: { $ne: result.id },
+ order: { $gte: result.order + 1 },
+ },
+ order: [['updatedAt', 'DESC']],
+ limit: updatedCount[0],
+ });
+ }
+ return Promise.resolve();
}),
)
- .then(() => {
- // Do not send events for the updated milestones here,
- // because it will make 'version conflict' error in ES.
- // The order of the other milestones need to be updated in the MILESTONE_ADDED event handler
-
+ .then((otherUpdated) => {
// Send event to bus
req.log.debug('Sending event to RabbitMQ bus for milestone %d', result.id);
req.app.services.pubsub.publish(EVENT.ROUTING_KEY.MILESTONE_ADDED,
@@ -101,11 +109,37 @@ module.exports = [
{ correlationId: req.id },
);
- req.app.emit(EVENT.ROUTING_KEY.MILESTONE_ADDED,
- { req, created: result });
+ // NOTE So far this logic is implemented in RabbitMQ handler of MILESTONE_ADDED
+ // Even though we send this event to the Kafka, the "project-processor-es" shouldn't process it.
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.MILESTONE_ADDED,
+ RESOURCES.MILESTONE,
+ result);
+
+ // NOTE So far this logic is implemented in RabbitMQ handler of MILESTONE_ADDED
+ // Even though we send these events to the Kafka, the "project-processor-es" shouldn't process them.
+ //
+ // We don't process these event in "project-processor-es"
+ // because it will make 'version conflict' error in ES.
+ // The order of the other milestones need to be updated in the PROJECT_PHASE_UPDATED event handler
+ _.map(otherUpdated, milestone =>
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.MILESTONE_UPDATED,
+ RESOURCES.MILESTONE,
+ _.assign(_.pick(milestone.toJSON(), 'id', 'order', 'updatedBy', 'updatedAt')),
+ // Pass the same object as original milestone even though, their time has changed.
+ // So far we don't use time properties in the handler so it's ok. But in general, we should pass
+ // the original milestones. <- TODO
+ _.assign(_.pick(milestone.toJSON(), 'id', 'order', 'updatedBy', 'updatedAt')),
+ null, // no route
+ true, // don't send event to Notification Service as the main event here is updating one milestone
+ ),
+ );
// Write to the response
- res.status(201).json(util.wrapResponse(req.id, result, 1, 201));
+ res.status(201).json(result);
return Promise.resolve();
})
.catch(next);
diff --git a/src/routes/milestones/create.spec.js b/src/routes/milestones/create.spec.js
index 26fb8e29..271e1d2f 100644
--- a/src/routes/milestones/create.spec.js
+++ b/src/routes/milestones/create.spec.js
@@ -10,7 +10,7 @@ import server from '../../app';
import testUtil from '../../tests/util';
import models from '../../models';
import busApi from '../../services/busApi';
-import { EVENT, BUS_API_EVENT } from '../../constants';
+import { EVENT, RESOURCES, BUS_API_EVENT, CONNECT_NOTIFICATION_EVENT } from '../../constants';
const should = chai.should();
@@ -203,48 +203,48 @@ describe('CREATE milestone', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('POST /timelines/{timelineId}/milestones', () => {
const body = {
- param: {
- name: 'milestone 4',
- description: 'description 4',
- duration: 4,
- startDate: '2018-05-05T00:00:00.000Z',
- endDate: '2018-05-07T00:00:00.000Z',
- completionDate: '2018-05-08T00:00:00.000Z',
- status: 'draft',
- type: 'type4',
- details: {
- detail1: {
- subDetail1C: 4,
- },
- detail2: [
- 3,
- 4,
- 5,
- ],
+ name: 'milestone 4',
+ description: 'description 4',
+ duration: 4,
+ startDate: '2018-05-05T00:00:00.000Z',
+ endDate: '2018-05-07T00:00:00.000Z',
+ completionDate: '2018-05-08T00:00:00.000Z',
+ status: 'draft',
+ type: 'type4',
+ details: {
+ detail1: {
+ subDetail1C: 4,
},
- order: 2,
- plannedText: 'plannedText 4',
- activeText: 'activeText 4',
- completedText: 'completedText 4',
- blockedText: 'blockedText 4',
- hidden: true,
+ detail2: [
+ 3,
+ 4,
+ 5,
+ ],
},
+ order: 2,
+ plannedText: 'plannedText 4',
+ activeText: 'activeText 4',
+ completedText: 'completedText 4',
+ blockedText: 'blockedText 4',
+ hidden: true,
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post('/v4/timelines/1/milestones')
+ .post('/v5/timelines/1/milestones')
.send(body)
.expect(403, done);
});
it('should return 403 for member who is not in the project', (done) => {
request(server)
- .post('/v4/timelines/1/milestones')
+ .post('/v5/timelines/1/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -252,141 +252,127 @@ describe('CREATE milestone', () => {
.expect(403, done);
});
- it('should return 422 if missing name', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- name: undefined,
- }),
- };
+ it('should return 400 if missing name', (done) => {
+ const invalidBody = _.assign({}, body, {
+ name: undefined,
+ });
request(server)
- .post('/v4/timelines/1/milestones')
+ .post('/v5/timelines/1/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if missing duration', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- duration: undefined,
- }),
- };
+ it('should return 400 if missing duration', (done) => {
+ const invalidBody = _.assign({}, body, {
+ duration: undefined,
+ });
request(server)
- .post('/v4/timelines/1/milestones')
+ .post('/v5/timelines/1/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if missing type', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- type: undefined,
- }),
- };
+ it('should return 400 if missing type', (done) => {
+ const invalidBody = _.assign({}, body, {
+ type: undefined,
+ });
request(server)
- .post('/v4/timelines/1/milestones')
+ .post('/v5/timelines/1/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if missing order', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- order: undefined,
- }),
- };
+ it('should return 400 if missing order', (done) => {
+ const invalidBody = _.assign({}, body, {
+ order: undefined,
+ });
request(server)
- .post('/v4/timelines/1/milestones')
+ .post('/v5/timelines/1/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if startDate is after endDate', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- startDate: '2018-05-29T00:00:00.000Z',
- endDate: '2018-05-28T00:00:00.000Z',
- }),
- };
+ it('should return 400 if startDate is after endDate', (done) => {
+ const invalidBody = _.assign({}, body, {
+ startDate: '2018-05-29T00:00:00.000Z',
+ endDate: '2018-05-28T00:00:00.000Z',
+ });
request(server)
- .post('/v4/timelines/1/milestones')
+ .post('/v5/timelines/1/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if startDate is after completionDate', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- startDate: '2018-05-29T00:00:00.000Z',
- completionDate: '2018-05-28T00:00:00.000Z',
- }),
- };
+ it('should return 400 if startDate is after completionDate', (done) => {
+ const invalidBody = _.assign({}, body, {
+ startDate: '2018-05-29T00:00:00.000Z',
+ completionDate: '2018-05-28T00:00:00.000Z',
+ });
request(server)
- .post('/v4/timelines/1/milestones')
+ .post('/v5/timelines/1/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if startDate is before the timeline startDate', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- startDate: '2018-05-01T00:00:00.000Z',
- }),
- };
+ it('should return 400 if startDate is before the timeline startDate', (done) => {
+ const invalidBody = _.assign({}, body, {
+ startDate: '2018-05-01T00:00:00.000Z',
+ });
request(server)
- .post('/v4/timelines/1/milestones')
+ .post('/v5/timelines/1/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if invalid timelineId param', (done) => {
+ it('should return 400 if invalid timelineId param', (done) => {
request(server)
- .post('/v4/timelines/0/milestones')
+ .post('/v5/timelines/0/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(body)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 404 if timeline does not exist', (done) => {
request(server)
- .post('/v4/timelines/1000/milestones')
+ .post('/v5/timelines/1000/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -397,7 +383,7 @@ describe('CREATE milestone', () => {
it('should return 404 if timeline was deleted', (done) => {
request(server)
- .post('/v4/timelines/3/milestones')
+ .post('/v5/timelines/3/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -408,7 +394,7 @@ describe('CREATE milestone', () => {
it('should return 201 for admin', (done) => {
request(server)
- .post('/v4/timelines/1/milestones')
+ .post('/v5/timelines/1/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -416,23 +402,23 @@ describe('CREATE milestone', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson.id);
- resJson.name.should.be.eql(body.param.name);
- resJson.description.should.be.eql(body.param.description);
- resJson.duration.should.be.eql(body.param.duration);
- resJson.startDate.should.be.eql(body.param.startDate);
- resJson.endDate.should.be.eql(body.param.endDate);
- resJson.completionDate.should.be.eql(body.param.completionDate);
- resJson.status.should.be.eql(body.param.status);
- resJson.type.should.be.eql(body.param.type);
- resJson.details.should.be.eql(body.param.details);
- resJson.order.should.be.eql(body.param.order);
- resJson.plannedText.should.be.eql(body.param.plannedText);
- resJson.activeText.should.be.eql(body.param.activeText);
- resJson.completedText.should.be.eql(body.param.completedText);
- resJson.blockedText.should.be.eql(body.param.blockedText);
- resJson.hidden.should.be.eql(body.param.hidden);
+ resJson.name.should.be.eql(body.name);
+ resJson.description.should.be.eql(body.description);
+ resJson.duration.should.be.eql(body.duration);
+ resJson.startDate.should.be.eql(body.startDate);
+ resJson.endDate.should.be.eql(body.endDate);
+ resJson.completionDate.should.be.eql(body.completionDate);
+ resJson.status.should.be.eql(body.status);
+ resJson.type.should.be.eql(body.type);
+ resJson.details.should.be.eql(body.details);
+ resJson.order.should.be.eql(body.order);
+ resJson.plannedText.should.be.eql(body.plannedText);
+ resJson.activeText.should.be.eql(body.activeText);
+ resJson.completedText.should.be.eql(body.completedText);
+ resJson.blockedText.should.be.eql(body.blockedText);
+ resJson.hidden.should.be.eql(body.hidden);
resJson.createdBy.should.be.eql(40051333); // admin
should.exist(resJson.createdAt);
@@ -473,7 +459,7 @@ describe('CREATE milestone', () => {
it('should return 201 for connect manager', (done) => {
request(server)
- .post('/v4/timelines/1/milestones')
+ .post('/v5/timelines/1/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -481,7 +467,7 @@ describe('CREATE milestone', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.createdBy.should.be.eql(40051334); // manager
resJson.updatedBy.should.be.eql(40051334); // manager
done();
@@ -490,7 +476,7 @@ describe('CREATE milestone', () => {
it('should return 201 for connect admin', (done) => {
request(server)
- .post('/v4/timelines/1/milestones')
+ .post('/v5/timelines/1/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -498,7 +484,7 @@ describe('CREATE milestone', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.createdBy.should.be.eql(40051336); // connect admin
resJson.updatedBy.should.be.eql(40051336); // connect admin
done();
@@ -507,7 +493,7 @@ describe('CREATE milestone', () => {
it('should return 201 for copilot', (done) => {
request(server)
- .post('/v4/timelines/1/milestones')
+ .post('/v5/timelines/1/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -515,7 +501,7 @@ describe('CREATE milestone', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.createdBy.should.be.eql(40051332); // copilot
resJson.updatedBy.should.be.eql(40051332); // copilot
done();
@@ -524,7 +510,7 @@ describe('CREATE milestone', () => {
it('should return 201 for member', (done) => {
request(server)
- .post('/v4/timelines/1/milestones')
+ .post('/v5/timelines/1/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -532,7 +518,7 @@ describe('CREATE milestone', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.createdBy.should.be.eql(40051331); // member
resJson.updatedBy.should.be.eql(40051331); // member
done();
@@ -556,9 +542,9 @@ describe('CREATE milestone', () => {
sandbox.restore();
});
- it('should send message BUS_API_EVENT.TIMELINE_ADJUSTED when milestone created', (done) => {
+ it('sends send correct BUS API messages milestone created', (done) => {
request(server)
- .post('/v4/timelines/1/milestones')
+ .post('/v5/timelines/1/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -570,14 +556,35 @@ describe('CREATE milestone', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
+ createEventSpy.callCount.should.be.eql(4);
+
+ // added a new milestone
createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_ADDED, sinon.match({
+ resource: RESOURCES.MILESTONE,
+ name: 'milestone 4',
+ description: 'description 4',
+ order: 2,
+ })).should.be.true;
+
+ // as order of the next milestones after the added one have been updated, we send events about their update
+ createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({
+ resource: RESOURCES.MILESTONE,
+ order: 3,
+ })).should.be.true;
+ createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({
+ resource: RESOURCES.MILESTONE,
+ order: 4,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MILESTONE_ADDED, sinon.match({
projectId: 1,
projectName: 'test1',
projectUrl: 'https://local.topcoder-dev.com/projects/1',
userId: 40051332,
initiatorUserId: 40051332,
})).should.be.true;
+
done();
});
}
diff --git a/src/routes/milestones/delete.js b/src/routes/milestones/delete.js
index c2cf2bce..ade643c7 100644
--- a/src/routes/milestones/delete.js
+++ b/src/routes/milestones/delete.js
@@ -5,7 +5,8 @@ import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import models from '../../models';
-import { EVENT } from '../../constants';
+import util from '../../util';
+import { EVENT, RESOURCES } from '../../constants';
import validateTimeline from '../../middlewares/validateTimeline';
const permissions = tcMiddleware.permissions;
@@ -54,8 +55,13 @@ module.exports = [
deleted,
{ correlationId: req.id },
);
- req.app.emit(EVENT.ROUTING_KEY.MILESTONE_REMOVED,
- { req, deleted });
+
+ // emit the event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.MILESTONE_REMOVED,
+ RESOURCES.MILESTONE,
+ { id: deleted.id });
// Write to response
res.status(204).end();
diff --git a/src/routes/milestones/delete.spec.js b/src/routes/milestones/delete.spec.js
index 5b237d1d..a505e0d5 100644
--- a/src/routes/milestones/delete.spec.js
+++ b/src/routes/milestones/delete.spec.js
@@ -9,9 +9,11 @@ import chai from 'chai';
import models from '../../models';
import server from '../../app';
import testUtil from '../../tests/util';
-import { EVENT, BUS_API_EVENT } from '../../constants';
+import { EVENT, RESOURCES, BUS_API_EVENT, CONNECT_NOTIFICATION_EVENT } from '../../constants';
import busApi from '../../services/busApi';
+const should = chai.should(); // eslint-disable-line no-unused-vars
+
const expectAfterDelete = (timelineId, id, err, next) => {
if (err) throw err;
models.Milestone.findOne({
@@ -29,7 +31,7 @@ const expectAfterDelete = (timelineId, id, err, next) => {
chai.assert.isNotNull(res.deletedBy);
request(server)
- .get(`/v4/timelines/${timelineId}/milestones/${id}`)
+ .get(`/v5/timelines/${timelineId}/milestones/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -39,6 +41,9 @@ const expectAfterDelete = (timelineId, id, err, next) => {
};
describe('DELETE milestone', () => {
+ before((done) => {
+ testUtil.clearES(done);
+ });
beforeEach((done) => {
testUtil.clearDb()
.then(() => {
@@ -156,7 +161,7 @@ describe('DELETE milestone', () => {
deletedAt: '2018-05-14T00:00:00.000Z',
},
]))
- .then(() => {
+ .then(() =>
// Create milestones
models.Milestone.bulkCreate([
{
@@ -216,25 +221,27 @@ describe('DELETE milestone', () => {
deletedBy: 1,
deletedAt: '2018-05-04T00:00:00.000Z',
},
- ])
- .then(() => done());
- });
+ ]))
+ .then(() => done());
});
});
});
- after(testUtil.clearDb);
+
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('DELETE /timelines/{timelineId}/milestones/{milestoneId}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .delete('/v4/timelines/1/milestones/1')
+ .delete('/v5/timelines/1/milestones/1')
.expect(403, done);
});
it('should return 403 for member who is not in the project', (done) => {
request(server)
- .delete('/v4/timelines/1/milestones/1')
+ .delete('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -243,7 +250,7 @@ describe('DELETE milestone', () => {
it('should return 403 for member who is not in the project (timeline refers to a phase)', (done) => {
request(server)
- .delete('/v4/timelines/2/milestones/1')
+ .delete('/v5/timelines/2/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -252,7 +259,7 @@ describe('DELETE milestone', () => {
it('should return 404 for non-existed timeline', (done) => {
request(server)
- .delete('/v4/timelines/1234/milestones/1')
+ .delete('/v5/timelines/1234/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -261,7 +268,7 @@ describe('DELETE milestone', () => {
it('should return 404 for deleted timeline', (done) => {
request(server)
- .delete('/v4/timelines/3/milestones/1')
+ .delete('/v5/timelines/3/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -270,7 +277,7 @@ describe('DELETE milestone', () => {
it('should return 404 for non-existed milestone', (done) => {
request(server)
- .delete('/v4/timelines/1/milestones/100')
+ .delete('/v5/timelines/1/milestones/100')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -279,34 +286,34 @@ describe('DELETE milestone', () => {
it('should return 404 for deleted milestone', (done) => {
request(server)
- .delete('/v4/timelines/1/milestones/3')
+ .delete('/v5/timelines/1/milestones/3')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(404, done);
});
- it('should return 422 for invalid timelineId param', (done) => {
+ it('should return 400 for invalid timelineId param', (done) => {
request(server)
- .delete('/v4/timelines/0/milestones/1')
+ .delete('/v5/timelines/0/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for invalid milestoneId param', (done) => {
+ it('should return 400 for invalid milestoneId param', (done) => {
request(server)
- .delete('/v4/timelines/1/milestones/0')
+ .delete('/v5/timelines/1/milestones/0')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .expect(422, done);
+ .expect(400, done);
});
it('should return 204, for admin, if timeline was successfully removed', (done) => {
request(server)
- .delete('/v4/timelines/1/milestones/1')
+ .delete('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -320,7 +327,7 @@ describe('DELETE milestone', () => {
it('should return 204, for connect admin, if timeline was successfully removed', (done) => {
request(server)
- .delete('/v4/timelines/1/milestones/1')
+ .delete('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -330,7 +337,7 @@ describe('DELETE milestone', () => {
it('should return 204, for connect manager, if timeline was successfully removed', (done) => {
request(server)
- .delete('/v4/timelines/1/milestones/1')
+ .delete('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -340,7 +347,7 @@ describe('DELETE milestone', () => {
it('should return 204, for copilot, if timeline was successfully removed', (done) => {
request(server)
- .delete('/v4/timelines/1/milestones/1')
+ .delete('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -350,7 +357,7 @@ describe('DELETE milestone', () => {
it('should return 204, for member, if timeline was successfully removed', (done) => {
request(server)
- .delete('/v4/timelines/1/milestones/1')
+ .delete('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -375,11 +382,9 @@ describe('DELETE milestone', () => {
sandbox.restore();
});
- // not testing fields separately as startDate is required parameter,
- // thus TIMELINE_ADJUSTED will be always sent
- it('should send message BUS_API_EVENT.TIMELINE_ADJUSTED when milestone removed', (done) => {
+ it('sends send correct BUS API messages when milestone removed', (done) => {
request(server)
- .delete('/v4/timelines/1/milestones/1')
+ .delete('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -389,14 +394,22 @@ describe('DELETE milestone', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
+ createEventSpy.callCount.should.be.eql(2);
+
createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_REMOVED, sinon.match({
+ resource: RESOURCES.MILESTONE,
+ id: 1,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MILESTONE_REMOVED, sinon.match({
projectId: 1,
projectName: 'test1',
projectUrl: 'https://local.topcoder-dev.com/projects/1',
userId: 40051332,
initiatorUserId: 40051332,
})).should.be.true;
+
done();
});
}
diff --git a/src/routes/milestones/get.js b/src/routes/milestones/get.js
index a4731321..5c33994d 100644
--- a/src/routes/milestones/get.js
+++ b/src/routes/milestones/get.js
@@ -30,20 +30,40 @@ module.exports = [
id: req.params.milestoneId,
};
- // Find the milestone
- models.Milestone.findOne({ where })
- .then((milestone) => {
- // Not found
- if (!milestone) {
- const apiErr = new Error(`Milestone not found for milestone id ${req.params.milestoneId}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
- }
+ util.fetchByIdFromES('milestones', {
+ query: {
+ nested: {
+ path: 'milestones',
+ query: {
+ match: { 'milestones.id': req.params.milestoneId },
+ },
+ inner_hits: {},
+ },
+ },
+ }, 'timeline')
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No milestone found in ES');
+ // Find the milestone
+ models.Milestone.findOne({ where })
+ .then((milestone) => {
+ // Not found
+ if (!milestone) {
+ const apiErr = new Error(`Milestone not found for milestone id ${req.params.milestoneId}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
- // Write to response
- res.json(util.wrapResponse(req.id, _.omit(milestone.toJSON(), ['deletedBy', 'deletedAt'])));
- return Promise.resolve();
- })
- .catch(next);
+ // Write to response
+ res.json(_.omit(milestone.toJSON(), ['deletedBy', 'deletedAt']));
+ return Promise.resolve();
+ })
+ .catch(next);
+ } else {
+ req.log.debug('milestone found in ES');
+ res.json(data[0].inner_hits.milestones.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle
+ }
+ })
+ .catch(next);
},
];
diff --git a/src/routes/milestones/get.spec.js b/src/routes/milestones/get.spec.js
index 5ae3adf9..f541eb02 100644
--- a/src/routes/milestones/get.spec.js
+++ b/src/routes/milestones/get.spec.js
@@ -196,18 +196,20 @@ describe('GET milestone', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /timelines/{timelineId}/milestones/{milestoneId}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/timelines/1/milestones/1')
+ .get('/v5/timelines/1/milestones/1')
.expect(403, done);
});
it('should return 403 for member who is not in the project', (done) => {
request(server)
- .get('/v4/timelines/1/milestones/1')
+ .get('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -216,7 +218,7 @@ describe('GET milestone', () => {
it('should return 404 for non-existed timeline', (done) => {
request(server)
- .get('/v4/timelines/1234/milestones/1')
+ .get('/v5/timelines/1234/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -225,7 +227,7 @@ describe('GET milestone', () => {
it('should return 404 for deleted timeline', (done) => {
request(server)
- .get('/v4/timelines/3/milestones/1')
+ .get('/v5/timelines/3/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -234,7 +236,7 @@ describe('GET milestone', () => {
it('should return 404 for non-existed milestone', (done) => {
request(server)
- .get('/v4/timelines/1/milestones/1234')
+ .get('/v5/timelines/1/milestones/1234')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -243,40 +245,40 @@ describe('GET milestone', () => {
it('should return 404 for deleted milestone', (done) => {
request(server)
- .get('/v4/timelines/1/milestones/3')
+ .get('/v5/timelines/1/milestones/3')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(404, done);
});
- it('should return 422 for invalid timelineId param', (done) => {
+ it('should return 400 for invalid timelineId param', (done) => {
request(server)
- .get('/v4/timelines/0/milestones/3')
+ .get('/v5/timelines/0/milestones/3')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for invalid milestoneId param', (done) => {
+ it('should return 400 for invalid milestoneId param', (done) => {
request(server)
- .get('/v4/timelines/1/milestones/0')
+ .get('/v5/timelines/1/milestones/0')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .expect(422, done);
+ .expect(400, done);
});
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/timelines/1/milestones/1')
+ .get('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(1);
resJson.timelineId.should.be.eql(1);
resJson.name.should.be.eql('milestone 1');
@@ -319,7 +321,7 @@ describe('GET milestone', () => {
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/timelines/1/milestones/1')
+ .get('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -329,7 +331,7 @@ describe('GET milestone', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/timelines/1/milestones/1')
+ .get('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -339,7 +341,7 @@ describe('GET milestone', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/timelines/1/milestones/1')
+ .get('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -348,7 +350,7 @@ describe('GET milestone', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/timelines/1/milestones/1')
+ .get('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/milestones/list.js b/src/routes/milestones/list.js
index 16152f50..cad31a5a 100644
--- a/src/routes/milestones/list.js
+++ b/src/routes/milestones/list.js
@@ -37,7 +37,7 @@ module.exports = [
];
if (sort && _.indexOf(sortableProps, sort) < 0) {
const apiErr = new Error('Invalid sort criteria');
- apiErr.status = 422;
+ apiErr.status = 400;
return next(apiErr);
}
const sortColumnAndOrder = sort.split(' ');
@@ -49,12 +49,7 @@ module.exports = [
id: req.params.timelineId,
})
.then((doc) => {
- if (!doc) {
- const err = new Error(`Timeline not found for timeline id ${req.params.timelineId}`);
- err.status = 404;
- throw err;
- }
-
+ req.log.debug('milestone found in ES');
// Get the milestones
let milestones = _.isArray(doc._source.milestones) ? doc._source.milestones : []; // eslint-disable-line no-underscore-dangle
@@ -62,8 +57,19 @@ module.exports = [
milestones = _.orderBy(milestones, [sortColumnAndOrder[0]], [sortColumnAndOrder[1]]);
// Write to response
- res.json(util.wrapResponse(req.id, milestones, milestones.length));
+ res.json(milestones);
})
- .catch(err => next(err));
+ .catch((err) => {
+ if (err.status === 404) {
+ req.log.debug('No milestone found in ES');
+ // Load the milestones
+ return req.timeline.getMilestones()
+ .then(milestones =>
+ // Write to response
+ res.json(_.map(milestones, milestone => _.omit(milestone.toJSON(), ['deletedAt', 'deletedBy']))),
+ );
+ }
+ return next(err);
+ });
},
];
diff --git a/src/routes/milestones/list.spec.js b/src/routes/milestones/list.spec.js
index 5ab96537..e5ccab4a 100644
--- a/src/routes/milestones/list.spec.js
+++ b/src/routes/milestones/list.spec.js
@@ -3,7 +3,7 @@
*/
import chai from 'chai';
import request from 'supertest';
-import sleep from 'sleep';
+// import sleep from 'sleep';
import config from 'config';
import _ from 'lodash';
@@ -177,29 +177,31 @@ describe('LIST milestones', () => {
type: ES_TIMELINE_TYPE,
id: timelines[0].id,
body: timelines[0],
- })
- .then(() => {
- // sleep for some time, let elasticsearch indices be settled
- sleep.sleep(5);
- done();
- });
+ });
+ })
+ .then(() => {
+ // sleep for some time, let elasticsearch indices be settled
+ // sleep.sleep(5);
+ done();
});
});
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /timelines/{timelineId}/milestones', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/timelines')
+ .get('/v5/timelines')
.expect(403, done);
});
it('should return 403 for member with no accessible project', (done) => {
request(server)
- .get('/v4/timelines/1/milestones')
+ .get('/v5/timelines/1/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -208,40 +210,40 @@ describe('LIST milestones', () => {
it('should return 404 for not-existed timeline', (done) => {
request(server)
- .get('/v4/timelines/11/milestones')
+ .get('/v5/timelines/11/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
.expect(404, done);
});
- it('should return 422 for invalid sort column', (done) => {
+ it('should return 400 for invalid sort column', (done) => {
request(server)
- .get('/v4/timelines/1/milestones?sort=id%20asc')
+ .get('/v5/timelines/1/milestones?sort=id%20asc')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for invalid sort order', (done) => {
+ it('should return 400 for invalid sort order', (done) => {
request(server)
- .get('/v4/timelines/1/milestones?sort=order%20invalid')
+ .get('/v5/timelines/1/milestones?sort=order%20invalid')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
- .expect(422, done);
+ .expect(400, done);
});
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/timelines/1/milestones')
+ .get('/v5/timelines/1/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
resJson.forEach((milestone, index) => {
@@ -252,7 +254,7 @@ describe('LIST milestones', () => {
statusHistory.referenceId.should.be.eql(milestone.id);
});
- const m = _.omit(milestone, ['statusHistory']);
+ const m = _.omitBy(_.omit(milestone, ['statusHistory']), _.isNil);
m.should.be.eql(milestones[index]);
});
@@ -263,13 +265,13 @@ describe('LIST milestones', () => {
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/timelines/1/milestones')
+ .get('/v5/timelines/1/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
done();
@@ -278,13 +280,13 @@ describe('LIST milestones', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/timelines/1/milestones')
+ .get('/v5/timelines/1/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
done();
@@ -293,12 +295,12 @@ describe('LIST milestones', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/timelines/1/milestones')
+ .get('/v5/timelines/1/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
done();
@@ -307,12 +309,12 @@ describe('LIST milestones', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/timelines/1/milestones')
+ .get('/v5/timelines/1/milestones')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
done();
@@ -321,17 +323,17 @@ describe('LIST milestones', () => {
it('should return 200 with sort by order desc', (done) => {
request(server)
- .get('/v4/timelines/1/milestones?sort=order%20desc')
+ .get('/v5/timelines/1/milestones?sort=order%20desc')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
- const m1 = _.omit(resJson[0], ['statusHistory']);
- const m2 = _.omit(resJson[1], ['statusHistory']);
+ const m1 = _.omitBy(_.omit(resJson[0], ['statusHistory']), _.isNil);
+ const m2 = _.omitBy(_.omit(resJson[1], ['statusHistory']), _.isNil);
m1.should.be.eql(milestones[1]);
m2.should.be.eql(milestones[0]);
diff --git a/src/routes/milestones/update.js b/src/routes/milestones/update.js
index 90e5b29a..95c06edd 100644
--- a/src/routes/milestones/update.js
+++ b/src/routes/milestones/update.js
@@ -10,7 +10,7 @@ import Sequelize from 'sequelize';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import util from '../../util';
import validateTimeline from '../../middlewares/validateTimeline';
-import { EVENT, MILESTONE_STATUS, ADMIN_ROLES } from '../../constants';
+import { EVENT, RESOURCES, MILESTONE_STATUS, ADMIN_ROLES } from '../../constants';
import models from '../../models';
const permissions = tcMiddleware.permissions;
@@ -93,34 +93,32 @@ const schema = {
timelineId: Joi.number().integer().positive().required(),
milestoneId: Joi.number().integer().positive().required(),
},
- body: {
- param: Joi.object().keys({
- id: Joi.any().strip(),
- name: Joi.string().max(255).optional(),
- description: Joi.string().max(255),
- duration: Joi.number().integer().min(1).optional(),
- startDate: Joi.any().forbidden(),
- actualStartDate: Joi.date().allow(null),
- endDate: Joi.any().forbidden(),
- completionDate: Joi.date().allow(null),
- status: Joi.string().max(45).optional(),
- type: Joi.string().max(45).optional(),
- details: Joi.object(),
- order: Joi.number().integer().optional(),
- plannedText: Joi.string().max(512).optional(),
- activeText: Joi.string().max(512).optional(),
- completedText: Joi.string().max(512).optional(),
- blockedText: Joi.string().max(512).optional(),
- hidden: Joi.boolean().optional(),
- statusComment: Joi.string().when('status', { is: 'paused', then: Joi.required(), otherwise: Joi.optional() }),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ body: Joi.object().keys({
+ id: Joi.any().strip(),
+ name: Joi.string().max(255).optional(),
+ description: Joi.string().max(255),
+ duration: Joi.number().integer().min(1).optional(),
+ startDate: Joi.any().forbidden(),
+ actualStartDate: Joi.date().allow(null),
+ endDate: Joi.any().forbidden(),
+ completionDate: Joi.date().allow(null),
+ status: Joi.string().max(45).optional(),
+ type: Joi.string().max(45).optional(),
+ details: Joi.object(),
+ order: Joi.number().integer().optional(),
+ plannedText: Joi.string().max(512).optional(),
+ activeText: Joi.string().max(512).optional(),
+ completedText: Joi.string().max(512).optional(),
+ blockedText: Joi.string().max(512).optional(),
+ hidden: Joi.boolean().optional(),
+ statusComment: Joi.string().when('status', { is: 'paused', then: Joi.required(), otherwise: Joi.optional() }),
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
};
module.exports = [
@@ -134,7 +132,7 @@ module.exports = [
timelineId: req.params.timelineId,
id: req.params.milestoneId,
};
- const entityToUpdate = _.assign(req.body.param, {
+ const entityToUpdate = _.assign(req.body, {
updatedBy: req.authUser.userId,
timelineId: req.params.timelineId,
});
@@ -159,14 +157,14 @@ module.exports = [
if (entityToUpdate.status === MILESTONE_STATUS.PAUSED && !validStatuses.includes(milestone.status)) {
const validStatutesStr = validStatuses.join(', ');
const apiErr = new Error(`Milestone can only be paused from the next statuses: ${validStatutesStr}`);
- apiErr.status = 422;
+ apiErr.status = 400;
return Promise.reject(apiErr);
}
if (entityToUpdate.status === 'resume') {
if (milestone.status !== MILESTONE_STATUS.PAUSED) {
const apiErr = new Error('Milestone status isn\'t paused');
- apiErr.status = 422;
+ apiErr.status = 400;
return Promise.reject(apiErr);
}
const statusHistory = await models.StatusHistory.findAll({
@@ -202,7 +200,7 @@ module.exports = [
)
) {
const apiErr = new Error('The milestone completionDate should be greater or equal to actualStartDate.');
- apiErr.status = 422;
+ apiErr.status = 400;
return Promise.reject(apiErr);
}
@@ -344,15 +342,17 @@ module.exports = [
{ correlationId: req.id },
);
- // Do not send events for the the other milestones (updated order) here,
- // because it will make 'version conflict' error in ES.
- // The order of the other milestones need to be updated in the MILESTONE_UPDATED event above
-
- req.app.emit(EVENT.ROUTING_KEY.MILESTONE_UPDATED,
- { req, original, updated, cascadedUpdates });
+ // emit the event
+ // we cannot use `util.sendResourceToKafkaBus` as we have to pass a custom param `cascadedUpdates`
+ req.app.emit(EVENT.ROUTING_KEY.MILESTONE_UPDATED, {
+ req,
+ resource: _.assign({ resource: RESOURCES.MILESTONE }, updated),
+ originalResource: _.assign({ resource: RESOURCES.MILESTONE }, original),
+ cascadedUpdates,
+ });
// Write to response
- res.json(util.wrapResponse(req.id, updated));
+ res.json(updated);
return Promise.resolve();
})
.catch(next);
diff --git a/src/routes/milestones/update.spec.js b/src/routes/milestones/update.spec.js
index 253107a7..bf8b1c7e 100644
--- a/src/routes/milestones/update.spec.js
+++ b/src/routes/milestones/update.spec.js
@@ -11,7 +11,7 @@ import models from '../../models';
import server from '../../app';
import testUtil from '../../tests/util';
import busApi from '../../services/busApi';
-import { EVENT, MILESTONE_STATUS, BUS_API_EVENT } from '../../constants';
+import { EVENT, RESOURCES, MILESTONE_STATUS, BUS_API_EVENT, CONNECT_NOTIFICATION_EVENT } from '../../constants';
const should = chai.should();
@@ -252,48 +252,48 @@ describe('UPDATE Milestone', () => {
updatedAt: '2018-05-11T00:00:00.000Z',
},
])))
- .then(() => done());
+ .then(() => done());
});
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('PATCH /timelines/{timelineId}/milestones/{milestoneId}', () => {
const body = {
- param: {
- name: 'Milestone 1-updated',
- duration: 3,
- description: 'description-updated',
- status: 'draft',
- type: 'type1-updated',
- details: {
- detail1: {
- subDetail1A: 0,
- subDetail1C: 3,
- },
- detail2: [4],
- detail3: 3,
+ name: 'Milestone 1-updated',
+ duration: 3,
+ description: 'description-updated',
+ status: 'draft',
+ type: 'type1-updated',
+ details: {
+ detail1: {
+ subDetail1A: 0,
+ subDetail1C: 3,
},
- order: 1,
- plannedText: 'plannedText 1-updated',
- activeText: 'activeText 1-updated',
- completedText: 'completedText 1-updated',
- blockedText: 'blockedText 1-updated',
- hidden: true,
+ detail2: [4],
+ detail3: 3,
},
+ order: 1,
+ plannedText: 'plannedText 1-updated',
+ activeText: 'activeText 1-updated',
+ completedText: 'completedText 1-updated',
+ blockedText: 'blockedText 1-updated',
+ hidden: true,
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.send(body)
.expect(403, done);
});
it('should return 403 for member who is not in the project', (done) => {
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -303,9 +303,9 @@ describe('UPDATE Milestone', () => {
it('should return 403 for non-admin member updating the completionDate', (done) => {
const newBody = _.cloneDeep(body);
- newBody.param.completionDate = '2019-01-16T00:00:00.000Z';
+ newBody.completionDate = '2019-01-16T00:00:00.000Z';
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -315,9 +315,9 @@ describe('UPDATE Milestone', () => {
it('should return 403 for non-admin member updating the actualStartDate', (done) => {
const newBody = _.cloneDeep(body);
- newBody.param.actualStartDate = '2018-05-15T00:00:00.000Z';
+ newBody.actualStartDate = '2018-05-15T00:00:00.000Z';
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -327,7 +327,7 @@ describe('UPDATE Milestone', () => {
it('should return 404 for non-existed timeline', (done) => {
request(server)
- .patch('/v4/timelines/1234/milestones/1')
+ .patch('/v5/timelines/1234/milestones/1')
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
@@ -337,7 +337,7 @@ describe('UPDATE Milestone', () => {
it('should return 404 for deleted timeline', (done) => {
request(server)
- .patch('/v4/timelines/3/milestones/1')
+ .patch('/v5/timelines/3/milestones/1')
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
@@ -347,7 +347,7 @@ describe('UPDATE Milestone', () => {
it('should return 404 for non-existed Milestone', (done) => {
request(server)
- .patch('/v4/timelines/1/milestones/111')
+ .patch('/v5/timelines/1/milestones/111')
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
@@ -357,7 +357,7 @@ describe('UPDATE Milestone', () => {
it('should return 404 for deleted Milestone', (done) => {
request(server)
- .patch('/v4/timelines/1/milestones/5')
+ .patch('/v5/timelines/1/milestones/5')
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
@@ -365,31 +365,31 @@ describe('UPDATE Milestone', () => {
.expect(404, done);
});
- it('should return 422 for invalid timelineId param', (done) => {
+ it('should return 400 for invalid timelineId param', (done) => {
request(server)
- .patch('/v4/timelines/0/milestones/1')
+ .patch('/v5/timelines/0/milestones/1')
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for invalid milestoneId param', (done) => {
+ it('should return 400 for invalid milestoneId param', (done) => {
request(server)
- .patch('/v4/timelines/1/milestones/0')
+ .patch('/v5/timelines/1/milestones/0')
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .expect(422, done);
+ .expect(400, done);
});
it('should return 200 for missing name', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.name;
+ delete partialBody.name;
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -399,9 +399,9 @@ describe('UPDATE Milestone', () => {
it('should return 200 for missing type', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.type;
+ delete partialBody.type;
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -411,9 +411,9 @@ describe('UPDATE Milestone', () => {
it('should return 200 for missing duration', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.duration;
+ delete partialBody.duration;
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -423,9 +423,9 @@ describe('UPDATE Milestone', () => {
it('should return 200 for missing order', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.order;
+ delete partialBody.order;
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -435,9 +435,9 @@ describe('UPDATE Milestone', () => {
it('should return 200 for missing plannedText', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.plannedText;
+ delete partialBody.plannedText;
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -447,9 +447,9 @@ describe('UPDATE Milestone', () => {
it('should return 200 for missing blockedText', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.blockedText;
+ delete partialBody.blockedText;
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -459,9 +459,9 @@ describe('UPDATE Milestone', () => {
it('should return 200 for missing activeText', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.activeText;
+ delete partialBody.activeText;
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -471,9 +471,9 @@ describe('UPDATE Milestone', () => {
it('should return 200 for missing completedText', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.completedText;
+ delete partialBody.completedText;
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -483,9 +483,9 @@ describe('UPDATE Milestone', () => {
it('should return 200 for missing hidden field', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.hidden;
+ delete partialBody.hidden;
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -494,53 +494,51 @@ describe('UPDATE Milestone', () => {
});
['startDate', 'endDate'].forEach((field) => {
- it(`should return 422 if ${field} is present in the payload`, (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- [field]: '2018-07-01T00:00:00.000Z',
- }),
- };
+ it(`should return 400 if ${field} is present in the payload`, (done) => {
+ const invalidBody = _.assign({}, body, {
+ [field]: '2018-07-01T00:00:00.000Z',
+ });
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
});
it('should return 200 for admin', (done) => {
const newBody = _.cloneDeep(body);
- newBody.param.completionDate = '2018-05-15T00:00:00.000Z';
+ newBody.completionDate = '2018-05-15T00:00:00.000Z';
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(newBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson.id);
- resJson.name.should.be.eql(body.param.name);
- resJson.description.should.be.eql(body.param.description);
- resJson.duration.should.be.eql(body.param.duration);
- resJson.completionDate.should.be.eql(newBody.param.completionDate);
- resJson.status.should.be.eql(body.param.status);
- resJson.type.should.be.eql(body.param.type);
+ resJson.name.should.be.eql(body.name);
+ resJson.description.should.be.eql(body.description);
+ resJson.duration.should.be.eql(body.duration);
+ resJson.completionDate.should.be.eql(newBody.completionDate);
+ resJson.status.should.be.eql(body.status);
+ resJson.type.should.be.eql(body.type);
resJson.details.should.be.eql({
detail1: { subDetail1A: 0, subDetail1B: 2, subDetail1C: 3 },
detail2: [4],
detail3: 3,
});
- resJson.order.should.be.eql(body.param.order);
- resJson.plannedText.should.be.eql(body.param.plannedText);
- resJson.activeText.should.be.eql(body.param.activeText);
- resJson.completedText.should.be.eql(body.param.completedText);
- resJson.blockedText.should.be.eql(body.param.blockedText);
+ resJson.order.should.be.eql(body.order);
+ resJson.plannedText.should.be.eql(body.plannedText);
+ resJson.activeText.should.be.eql(body.activeText);
+ resJson.completedText.should.be.eql(body.completedText);
+ resJson.blockedText.should.be.eql(body.blockedText);
should.exist(resJson.createdBy);
should.exist(resJson.createdAt);
@@ -570,30 +568,30 @@ describe('UPDATE Milestone', () => {
this.timeout(10000);
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: _.assign({}, body.param, { order: 4 }) }) // 1 to 4
+ .send(_.assign({}, body, { order: 4 })) // 1 to 4
.expect(200)
.end(() => {
// Milestone 1: order 4
// Milestone 2: order 2 - 1 = 1
// Milestone 3: order 3 - 1 = 2
// Milestone 4: order 4 - 1 = 3
- models.Milestone.findById(1)
+ models.Milestone.findByPk(1)
.then((milestone) => {
milestone.order.should.be.eql(4);
})
- .then(() => models.Milestone.findById(2))
+ .then(() => models.Milestone.findByPk(2))
.then((milestone) => {
milestone.order.should.be.eql(1);
})
- .then(() => models.Milestone.findById(3))
+ .then(() => models.Milestone.findByPk(3))
.then((milestone) => {
milestone.order.should.be.eql(2);
})
- .then(() => models.Milestone.findById(4))
+ .then(() => models.Milestone.findByPk(4))
.then((milestone) => {
milestone.order.should.be.eql(3);
@@ -607,30 +605,30 @@ describe('UPDATE Milestone', () => {
this.timeout(10000);
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: _.assign({}, body.param, { order: 5 }) }) // 1 to 5
+ .send(_.assign({}, body, { order: 5 })) // 1 to 5
.expect(200)
.end(() => {
// Milestone 1: order 5
// Milestone 2: order 2
// Milestone 3: order 3
// Milestone 4: order 4
- models.Milestone.findById(1)
+ models.Milestone.findByPk(1)
.then((milestone) => {
milestone.order.should.be.eql(5);
})
- .then(() => models.Milestone.findById(2))
+ .then(() => models.Milestone.findByPk(2))
.then((milestone) => {
milestone.order.should.be.eql(2);
})
- .then(() => models.Milestone.findById(3))
+ .then(() => models.Milestone.findByPk(3))
.then((milestone) => {
milestone.order.should.be.eql(3);
})
- .then(() => models.Milestone.findById(4))
+ .then(() => models.Milestone.findByPk(4))
.then((milestone) => {
milestone.order.should.be.eql(4);
@@ -644,30 +642,30 @@ describe('UPDATE Milestone', () => {
this.timeout(10000);
request(server)
- .patch('/v4/timelines/1/milestones/4')
+ .patch('/v5/timelines/1/milestones/4')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: _.assign({}, body.param, { order: 2 }) }) // 4 to 2
+ .send(_.assign({}, body, { order: 2 })) // 4 to 2
.expect(200)
.end(() => {
// Milestone 1: order 1
// Milestone 2: order 3
// Milestone 3: order 4
// Milestone 4: order 2
- models.Milestone.findById(1)
+ models.Milestone.findByPk(1)
.then((milestone) => {
milestone.order.should.be.eql(1);
})
- .then(() => models.Milestone.findById(2))
+ .then(() => models.Milestone.findByPk(2))
.then((milestone) => {
milestone.order.should.be.eql(3);
})
- .then(() => models.Milestone.findById(3))
+ .then(() => models.Milestone.findByPk(3))
.then((milestone) => {
milestone.order.should.be.eql(4);
})
- .then(() => models.Milestone.findById(4))
+ .then(() => models.Milestone.findByPk(4))
.then((milestone) => {
milestone.order.should.be.eql(2);
@@ -681,30 +679,30 @@ describe('UPDATE Milestone', () => {
this.timeout(10000);
request(server)
- .patch('/v4/timelines/1/milestones/4')
+ .patch('/v5/timelines/1/milestones/4')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: _.assign({}, body.param, { order: 0 }) }) // 4 to 0
+ .send(_.assign({}, body, { order: 0 })) // 4 to 0
.expect(200)
.end(() => {
// Milestone 1: order 1
// Milestone 2: order 2
// Milestone 3: order 3
// Milestone 4: order 0
- models.Milestone.findById(1)
+ models.Milestone.findByPk(1)
.then((milestone) => {
milestone.order.should.be.eql(1);
})
- .then(() => models.Milestone.findById(2))
+ .then(() => models.Milestone.findByPk(2))
.then((milestone) => {
milestone.order.should.be.eql(2);
})
- .then(() => models.Milestone.findById(3))
+ .then(() => models.Milestone.findByPk(3))
.then((milestone) => {
milestone.order.should.be.eql(3);
})
- .then(() => models.Milestone.findById(4))
+ .then(() => models.Milestone.findByPk(4))
.then((milestone) => {
milestone.order.should.be.eql(0);
@@ -718,15 +716,15 @@ describe('UPDATE Milestone', () => {
this.timeout(10000);
request(server)
- .patch('/v4/timelines/2/milestones/6')
+ .patch('/v5/timelines/2/milestones/6')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: _.assign({}, body.param, { order: 0 }) }) // 1 to 0
+ .send(_.assign({}, body, { order: 0 })) // 1 to 0
.expect(200)
.end(() => {
// Milestone 6: order 0
- models.Milestone.findById(6)
+ models.Milestone.findByPk(6)
.then((milestone) => {
milestone.order.should.be.eql(0);
@@ -780,25 +778,25 @@ describe('UPDATE Milestone', () => {
])
.then(() => {
request(server)
- .patch('/v4/timelines/2/milestones/8')
+ .patch('/v5/timelines/2/milestones/8')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: _.assign({}, body.param, { order: 2 }) }) // 4 to 2
+ .send(_.assign({}, body, { order: 2 })) // 4 to 2
.expect(200)
.end(() => {
// Milestone 6: order 1 => 1
// Milestone 7: order 3 => 3
// Milestone 8: order 4 => 2
- models.Milestone.findById(6)
+ models.Milestone.findByPk(6)
.then((milestone) => {
milestone.order.should.be.eql(1);
})
- .then(() => models.Milestone.findById(7))
+ .then(() => models.Milestone.findByPk(7))
.then((milestone) => {
milestone.order.should.be.eql(3);
})
- .then(() => models.Milestone.findById(8))
+ .then(() => models.Milestone.findByPk(8))
.then((milestone) => {
milestone.order.should.be.eql(2);
@@ -853,25 +851,25 @@ describe('UPDATE Milestone', () => {
])
.then(() => {
request(server)
- .patch('/v4/timelines/2/milestones/8')
+ .patch('/v5/timelines/2/milestones/8')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: _.assign({}, body.param, { order: 2 }) }) // 4 to 2
+ .send(_.assign({}, body, { order: 2 })) // 4 to 2
.expect(200)
.end(() => {
// Milestone 6: order 1 => 1
// Milestone 7: order 2 => 3
// Milestone 8: order 4 => 2
- models.Milestone.findById(6)
+ models.Milestone.findByPk(6)
.then((milestone) => {
milestone.order.should.be.eql(1);
})
- .then(() => models.Milestone.findById(7))
+ .then(() => models.Milestone.findByPk(7))
.then((milestone) => {
milestone.order.should.be.eql(3);
})
- .then(() => models.Milestone.findById(8))
+ .then(() => models.Milestone.findByPk(8))
.then((milestone) => {
milestone.order.should.be.eql(2);
@@ -890,11 +888,11 @@ describe('UPDATE Milestone', () => {
.milliseconds(0);
request(server)
- .patch('/v4/timelines/1/milestones/2')
+ .patch('/v5/timelines/1/milestones/2')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: { status: MILESTONE_STATUS.ACTIVE } })
+ .send({ status: MILESTONE_STATUS.ACTIVE })
.expect(200)
.end(() => {
// Milestone 2: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-14T00:00:00.000Z'
@@ -904,7 +902,7 @@ describe('UPDATE Milestone', () => {
// endDate: null to today + 5 (5 = 3 + duration - 1)
// Milestone 4: startDate: '2018-05-14T00:00:00.000Z' to today + 6
// endDate: null to today + 8 (2 = 6 + duration - 1)
- models.Milestone.findById(2)
+ models.Milestone.findByPk(2)
.then((milestone) => {
should.exist(milestone.actualStartDate);
moment.utc(milestone.actualStartDate).diff(today, 'days').should.be.eql(0);
@@ -914,7 +912,7 @@ describe('UPDATE Milestone', () => {
// end date of the updated milestone should change, as delayed start caused scheduled to be delayed
moment.utc(milestone.endDate).diff(today, 'days').should.be.eql(0);
milestone.status.should.be.eql(MILESTONE_STATUS.ACTIVE);
- return models.Milestone.findById(3);
+ return models.Milestone.findByPk(3);
})
.then((milestone) => {
today.add('days', 1); // should have start date next to previous one's end date
@@ -922,7 +920,7 @@ describe('UPDATE Milestone', () => {
should.not.exist(milestone.actualStartDate);
today.add('days', milestone.duration - 1);
moment.utc(milestone.endDate).diff(today, 'days').should.be.eql(0);
- return models.Milestone.findById(4);
+ return models.Milestone.findByPk(4);
})
.then((milestone) => {
today.add('days', 1); // should have start date next to previous one's end date
@@ -944,20 +942,20 @@ describe('UPDATE Milestone', () => {
.milliseconds(0);
request(server)
- .patch('/v4/timelines/1/milestones/2')
+ .patch('/v5/timelines/1/milestones/2')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: _.assign({}, body.param, {
+ .send(_.assign({}, body, {
completionDate: '2018-05-18T00:00:00.000Z', order: undefined, duration: undefined,
- }) })
+ }))
.expect(200)
.end(() => {
// Milestone 3: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-19T00:00:00.000Z'
// endDate: null to '2018-05-21T00:00:00.000Z'
// Milestone 4: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-22T00:00:00.000Z'
// endDate: null to '2018-05-24T00:00:00.000Z'
- models.Milestone.findById(3)
+ models.Milestone.findByPk(3)
.then((milestone) => {
milestone.startDate.should.be.eql(new Date('2018-05-19T00:00:00.000Z'));
should.exist(milestone.actualStartDate);
@@ -965,7 +963,7 @@ describe('UPDATE Milestone', () => {
// milestone.actualStartDate.should.be.eql(today);
milestone.endDate.should.be.eql(new Date('2018-05-21T00:00:00.000Z'));
milestone.status.should.be.eql(MILESTONE_STATUS.ACTIVE);
- return models.Milestone.findById(4);
+ return models.Milestone.findByPk(4);
})
.then((milestone) => {
milestone.startDate.should.be.eql(new Date('2018-05-22T00:00:00.000Z'));
@@ -983,13 +981,13 @@ describe('UPDATE Milestone', () => {
this.timeout(10000);
request(server)
- .patch('/v4/timelines/1/milestones/2')
+ .patch('/v5/timelines/1/milestones/2')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: _.assign({}, body.param, {
+ .send(_.assign({}, body, {
completionDate: '2018-05-18T00:00:00.000Z', order: undefined, duration: undefined,
- }) })
+ }))
.expect(200)
.end(() => {
// Milestone 3: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-19T00:00:00.000Z'
@@ -997,7 +995,7 @@ describe('UPDATE Milestone', () => {
// Milestone 4: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-22T00:00:00.000Z'
// BELOW will be the new timeline's endDate
// endDate: null to '2018-05-24T00:00:00.000Z'
- models.Timeline.findById(1)
+ models.Timeline.findByPk(1)
.then((timeline) => {
// timeline start shouldn't change
timeline.startDate.should.be.eql(new Date('2018-05-02T00:00:00.000Z'));
@@ -1017,22 +1015,22 @@ describe('UPDATE Milestone', () => {
this.timeout(10000);
request(server)
- .patch('/v4/timelines/1/milestones/2')
+ .patch('/v5/timelines/1/milestones/2')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: _.assign({}, body.param, { duration: 5, order: undefined, completionDate: undefined }) })
+ .send(_.assign({}, body, { duration: 5, order: undefined, completionDate: undefined }))
.expect(200)
.end(() => {
// Milestone 3: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-19T00:00:00.000Z'
// endDate: null to '2018-05-21T00:00:00.000Z'
// Milestone 4: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-22T00:00:00.000Z'
// endDate: null to '2018-05-24T00:00:00.000Z'
- models.Milestone.findById(3)
+ models.Milestone.findByPk(3)
.then((milestone) => {
milestone.startDate.should.be.eql(new Date('2018-05-19T00:00:00.000Z'));
milestone.endDate.should.be.eql(new Date('2018-05-21T00:00:00.000Z'));
- return models.Milestone.findById(4);
+ return models.Milestone.findByPk(4);
})
.then((milestone) => {
milestone.startDate.should.be.eql(new Date('2018-05-22T00:00:00.000Z'));
@@ -1049,11 +1047,11 @@ describe('UPDATE Milestone', () => {
this.timeout(10000);
request(server)
- .patch('/v4/timelines/1/milestones/2')
+ .patch('/v5/timelines/1/milestones/2')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: _.assign({}, body.param, { duration: 5, order: undefined, completionDate: undefined }) })
+ .send(_.assign({}, body, { duration: 5, order: undefined, completionDate: undefined }))
.expect(200)
.end(() => {
// Milestone 3: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-19T00:00:00.000Z'
@@ -1061,7 +1059,7 @@ describe('UPDATE Milestone', () => {
// Milestone 4: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-22T00:00:00.000Z'
// BELOW will be the new timeline's endDate
// endDate: null to '2018-05-24T00:00:00.000Z'
- models.Timeline.findById(1)
+ models.Timeline.findByPk(1)
.then((timeline) => {
// timeline start shouldn't change
timeline.startDate.should.be.eql(new Date('2018-05-02T00:00:00.000Z'));
@@ -1077,7 +1075,7 @@ describe('UPDATE Milestone', () => {
it('should return 200 for connect admin', (done) => {
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -1088,9 +1086,9 @@ describe('UPDATE Milestone', () => {
it('should return 200 for admin updating the completionDate', (done) => {
const newBody = _.cloneDeep(body);
- newBody.param.completionDate = '2018-05-16T00:00:00.000Z';
+ newBody.completionDate = '2018-05-16T00:00:00.000Z';
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -1100,9 +1098,9 @@ describe('UPDATE Milestone', () => {
it('should return 200 for admin updating the actualStartDate', (done) => {
const newBody = _.cloneDeep(body);
- newBody.param.actualStartDate = '2018-05-15T00:00:00.000Z';
+ newBody.actualStartDate = '2018-05-15T00:00:00.000Z';
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -1112,7 +1110,7 @@ describe('UPDATE Milestone', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -1123,7 +1121,7 @@ describe('UPDATE Milestone', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -1134,7 +1132,7 @@ describe('UPDATE Milestone', () => {
it('should return 200 for member', (done) => {
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -1143,37 +1141,37 @@ describe('UPDATE Milestone', () => {
.end(done);
});
- it('should return 422 if try to pause and statusComment is missed', (done) => {
+ it('should return 400 if try to pause and statusComment is missed', (done) => {
const newBody = _.cloneDeep(body);
- newBody.param.status = 'paused';
+ newBody.status = 'paused';
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(newBody)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if try to pause not active milestone', (done) => {
+ it('should return 400 if try to pause not active milestone', (done) => {
const newBody = _.cloneDeep(body);
- newBody.param.status = 'paused';
- newBody.param.statusComment = 'milestone paused';
+ newBody.status = 'paused';
+ newBody.statusComment = 'milestone paused';
request(server)
- .patch('/v4/timelines/1/milestones/2')
+ .patch('/v5/timelines/1/milestones/2')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(newBody)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 200 if try to pause and should have one status history created', (done) => {
const newBody = _.cloneDeep(body);
- newBody.param.status = 'paused';
- newBody.param.statusComment = 'milestone paused';
+ newBody.status = 'paused';
+ newBody.statusComment = 'milestone paused';
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -1183,7 +1181,7 @@ describe('UPDATE Milestone', () => {
if (err) {
done(err);
} else {
- models.Milestone.findById(1).then((milestone) => {
+ models.Milestone.findByPk(1).then((milestone) => {
milestone.status.should.be.eql('paused');
return models.StatusHistory.findAll({
where: {
@@ -1202,23 +1200,23 @@ describe('UPDATE Milestone', () => {
});
});
- it('should return 422 if try to resume not paused milestone', (done) => {
+ it('should return 400 if try to resume not paused milestone', (done) => {
const newBody = _.cloneDeep(body);
- newBody.param.status = 'resume';
+ newBody.status = 'resume';
request(server)
- .patch('/v4/timelines/1/milestones/2')
+ .patch('/v5/timelines/1/milestones/2')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(newBody)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 200 if try to resume then status should update to last status and ' +
'should have one status history created', (done) => {
const newBody = _.cloneDeep(body);
- newBody.param.status = 'resume';
- newBody.param.statusComment = 'new comment';
+ newBody.status = 'resume';
+ newBody.statusComment = 'new comment';
models.Milestone.bulkCreate([
{
id: 7,
@@ -1247,12 +1245,12 @@ describe('UPDATE Milestone', () => {
createdAt: '2018-05-11T00:00:00.000Z',
updatedAt: '2018-05-11T00:00:00.000Z',
},
- ]).then(() => models.Milestone.findById(7)
+ ]).then(() => models.Milestone.findByPk(7)
// pause milestone before resume
.then(milestone => milestone.update(_.assign({}, milestone.toJSON(), { status: 'paused' }))),
).then(() => {
request(server)
- .patch('/v4/timelines/1/milestones/7')
+ .patch('/v5/timelines/1/milestones/7')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -1262,7 +1260,7 @@ describe('UPDATE Milestone', () => {
if (err) {
done(err);
} else {
- models.Milestone.findById(7).then((milestone) => {
+ models.Milestone.findByPk(7).then((milestone) => {
milestone.status.should.be.eql('active');
return models.StatusHistory.findAll({
@@ -1300,18 +1298,15 @@ describe('UPDATE Milestone', () => {
sandbox.restore();
});
- it('should send message BUS_API_EVENT.MILESTONE_WAITING_CUSTOMER when milestone duration updated', (done) => {
+ it('sends send correct BUS API messages when milestone details updated and waiting for customer', (done) => {
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- // duration: 1,
- details: {
- metadata: { waitingForCustomer: true },
- },
+ details: {
+ metadata: { waitingForCustomer: true },
},
})
.expect(200)
@@ -1320,34 +1315,40 @@ describe('UPDATE Milestone', () => {
done(err);
} else {
testUtil.wait(() => {
- // 5 milestones in total, so it would trigger 5 events
- // 4 MILESTONE_UPDATED events are for 4 non deleted milestones
- // 1 TIMELINE_ADJUSTED event, because timeline's end date updated
- createEventSpy.callCount.should.be.eql(2);
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({
+ createEventSpy.callCount.should.be.eql(3);
+
+ createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({
+ resource: RESOURCES.MILESTONE,
+ details: {
+ metadata: { waitingForCustomer: true },
+ },
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MILESTONE_UPDATED, sinon.match({
projectId: 1,
projectName: 'test1',
projectUrl: 'https://local.topcoder-dev.com/projects/1',
userId: 40051332,
initiatorUserId: 40051332,
})).should.be.true;
- createEventSpy.lastCall.calledWith(BUS_API_EVENT.MILESTONE_WAITING_CUSTOMER).should.be.true;
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MILESTONE_WAITING_CUSTOMER)
+ .should.be.true;
+
done();
});
}
});
});
- xit('should send message BUS_API_EVENT.TIMELINE_ADJUSTED when milestone duration updated', (done) => {
+ xit('should send message BUS_API_EVENT.MILESTONE_UPDATED when milestone duration updated', (done) => {
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- duration: 1,
- },
+ duration: 1,
})
.expect(200)
.end((err) => {
@@ -1358,15 +1359,11 @@ describe('UPDATE Milestone', () => {
// 5 milestones in total, so it would trigger 5 events
// 4 MILESTONE_UPDATED events are for 4 non deleted milestones
// 1 TIMELINE_ADJUSTED event, because timeline's end date updated
- createEventSpy.callCount.should.be.eql(5);
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({
- projectId: 1,
- projectName: 'test1',
- projectUrl: 'https://local.topcoder-dev.com/projects/1',
- userId: 40051332,
- initiatorUserId: 40051332,
- })).should.be.true;
- createEventSpy.lastCall.calledWith(BUS_API_EVENT.TIMELINE_ADJUSTED).should.be.true;
+ createEventSpy.calledOnce.should.be.true;
+ createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED,
+ sinon.match({ resource: RESOURCES.MILESTONE })).should.be.true;
+ createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED,
+ sinon.match({ duration: 1 })).should.be.true;
done();
});
}
@@ -1375,14 +1372,12 @@ describe('UPDATE Milestone', () => {
xit('should send message BUS_API_EVENT.MILESTONE_UPDATED when milestone status updated', (done) => {
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- status: 'reviewed',
- },
+ status: 'reviewed',
})
.expect(200)
.end((err) => {
@@ -1391,29 +1386,24 @@ describe('UPDATE Milestone', () => {
} else {
testUtil.wait(() => {
createEventSpy.calledOnce.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({
- projectId: 1,
- projectName: 'test1',
- projectUrl: 'https://local.topcoder-dev.com/projects/1',
- userId: 40051332,
- initiatorUserId: 40051332,
- })).should.be.true;
+ createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED,
+ sinon.match({ resource: RESOURCES.MILESTONE })).should.be.true;
+ createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED,
+ sinon.match({ status: 'reviewed' })).should.be.true;
done();
});
}
});
});
- it('should ONLY send message BUS_API_EVENT.MILESTONE_UPDATED when milestone order updated', (done) => {
+ it('should send correct BUS API messages when milestone order updated', (done) => {
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- order: 2,
- },
+ order: 2,
})
.expect(200)
.end((err) => {
@@ -1421,30 +1411,36 @@ describe('UPDATE Milestone', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({
+ resource: RESOURCES.MILESTONE,
+ order: 2,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MILESTONE_UPDATED, sinon.match({
projectId: 1,
projectName: 'test1',
projectUrl: 'https://local.topcoder-dev.com/projects/1',
userId: 40051332,
initiatorUserId: 40051332,
})).should.be.true;
+
done();
});
}
});
});
- it('should ONLY send message BUS_API_EVENT.MILESTONE_UPDATED when milestone plannedText updated', (done) => {
+ it('should send correct BUS API messages when milestone plannedText updated', (done) => {
request(server)
- .patch('/v4/timelines/1/milestones/1')
+ .patch('/v5/timelines/1/milestones/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- plannedText: 'new text',
- },
+ plannedText: 'new text',
})
.expect(200)
.end((err) => {
@@ -1452,14 +1448,22 @@ describe('UPDATE Milestone', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({
+ resource: RESOURCES.MILESTONE,
+ plannedText: 'new text',
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MILESTONE_UPDATED, sinon.match({
projectId: 1,
projectName: 'test1',
projectUrl: 'https://local.topcoder-dev.com/projects/1',
userId: 40051332,
initiatorUserId: 40051332,
})).should.be.true;
+
done();
});
}
diff --git a/src/routes/orgConfig/create.js b/src/routes/orgConfig/create.js
index 6b53d6d2..41e4968f 100644
--- a/src/routes/orgConfig/create.js
+++ b/src/routes/orgConfig/create.js
@@ -5,53 +5,57 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../constants';
import util from '../../util';
import models from '../../models';
const permissions = tcMiddleware.permissions;
const schema = {
- body: {
- param: Joi.object().keys({
- id: Joi.any().strip(),
- orgId: Joi.string().max(45).required(),
- configName: Joi.string().max(45).required(),
- configValue: Joi.string().max(512),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+
+ body: Joi.object().keys({
+ id: Joi.any().strip(),
+ orgId: Joi.string().max(45).required(),
+ configName: Joi.string().max(45).required(),
+ configValue: Joi.string().max(512),
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
+
};
module.exports = [
validate(schema),
permissions('orgConfig.create'),
(req, res, next) => {
- const entity = _.assign(req.body.param, {
+ const entity = _.assign(req.body, {
createdBy: req.authUser.userId,
updatedBy: req.authUser.userId,
});
// Check if duplicated key
- return models.OrgConfig.findOne({ where: { orgId: req.body.param.orgId, configName: req.body.param.configName } })
+ return models.OrgConfig.findOne({ where: { orgId: req.body.orgId, configName: req.body.configName } })
.then((existing) => {
if (existing) {
- const apiErr = new Error(`Organization config exists for orgId ${req.body.param.orgId}
- and configName ${req.body.param.configName}`);
- apiErr.status = 422;
+ const apiErr = new Error(`Organization config exists for orgId ${req.body.orgId}
+ and configName ${req.body.configName}`);
+ apiErr.status = 400;
return Promise.reject(apiErr);
}
// Create
return models.OrgConfig.create(entity);
}).then((createdEntity) => {
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE,
+ RESOURCES.ORG_CONFIG,
+ createdEntity.toJSON());
// Omit deletedAt, deletedBy
- res.status(201).json(util.wrapResponse(
- req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201));
+ res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'));
})
.catch(next);
},
diff --git a/src/routes/orgConfig/create.spec.js b/src/routes/orgConfig/create.spec.js
index c13a7521..b5113015 100644
--- a/src/routes/orgConfig/create.spec.js
+++ b/src/routes/orgConfig/create.spec.js
@@ -12,36 +12,37 @@ import models from '../../models';
const should = chai.should();
describe('CREATE organization config', () => {
- beforeEach(() => testUtil.clearDb()
- .then(() => models.OrgConfig.create({
- orgId: 'ORG1',
- configName: 'project_catefory_url',
- configValue: 'http://localhost/url',
- createdBy: 1,
- updatedBy: 1,
- })).then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.OrgConfig.create({
+ orgId: 'ORG1',
+ configName: 'project_catefory_url',
+ configValue: 'http://localhost/url',
+ createdBy: 1,
+ updatedBy: 1,
+ }).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('POST /orgConfig', () => {
const body = {
- param: {
- orgId: 'ORG2',
- configName: 'project_catefory_url',
- configValue: 'http://localhost/url',
- },
+ orgId: 'ORG2',
+ configName: 'project_catefory_url',
+ configValue: 'http://localhost/url',
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post('/v4/projects/metadata/orgConfig')
+ .post('/v5/projects/metadata/orgConfig')
.send(body)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .post('/v4/projects/metadata/orgConfig')
+ .post('/v5/projects/metadata/orgConfig')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -51,7 +52,7 @@ describe('CREATE organization config', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .post('/v4/projects/metadata/orgConfig')
+ .post('/v5/projects/metadata/orgConfig')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -61,7 +62,7 @@ describe('CREATE organization config', () => {
it('should return 403 for manager', (done) => {
request(server)
- .post('/v4/projects/metadata/orgConfig')
+ .post('/v5/projects/metadata/orgConfig')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -69,52 +70,52 @@ describe('CREATE organization config', () => {
.expect(403, done);
});
- it('should return 422 for missing orgId', (done) => {
+ it('should return 400 for missing orgId', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.orgId;
+ delete invalidBody.orgId;
request(server)
- .post('/v4/projects/metadata/orgConfig')
+ .post('/v5/projects/metadata/orgConfig')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for missing configName', (done) => {
+ it('should return 400 for missing configName', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.configName;
+ delete invalidBody.configName;
request(server)
- .post('/v4/projects/metadata/orgConfig')
+ .post('/v5/projects/metadata/orgConfig')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for duplicated orgId and configName', (done) => {
+ it('should return 400 for duplicated orgId and configName', (done) => {
const invalidBody = _.cloneDeep(body);
- invalidBody.param.orgId = 'ORG1';
- invalidBody.param.configName = 'project_catefory_url';
+ invalidBody.orgId = 'ORG1';
+ invalidBody.configName = 'project_catefory_url';
request(server)
- .post('/v4/projects/metadata/orgConfig')
+ .post('/v5/projects/metadata/orgConfig')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 201 for admin', (done) => {
request(server)
- .post('/v4/projects/metadata/orgConfig')
+ .post('/v5/projects/metadata/orgConfig')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -122,10 +123,10 @@ describe('CREATE organization config', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
- resJson.orgId.should.be.eql(body.param.orgId);
- resJson.configName.should.be.eql(body.param.configName);
- resJson.configValue.should.be.eql(body.param.configValue);
+ const resJson = res.body;
+ resJson.orgId.should.be.eql(body.orgId);
+ resJson.configName.should.be.eql(body.configName);
+ resJson.configValue.should.be.eql(body.configValue);
resJson.createdBy.should.be.eql(40051333); // admin
should.exist(resJson.createdAt);
@@ -140,7 +141,7 @@ describe('CREATE organization config', () => {
it('should return 201 for connect admin', (done) => {
request(server)
- .post('/v4/projects/metadata/orgConfig')
+ .post('/v5/projects/metadata/orgConfig')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -148,10 +149,10 @@ describe('CREATE organization config', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
- resJson.orgId.should.be.eql(body.param.orgId);
- resJson.configName.should.be.eql(body.param.configName);
- resJson.configValue.should.be.eql(body.param.configValue);
+ const resJson = res.body;
+ resJson.orgId.should.be.eql(body.orgId);
+ resJson.configName.should.be.eql(body.configName);
+ resJson.configValue.should.be.eql(body.configValue);
resJson.createdBy.should.be.eql(40051336); // connect admin
resJson.updatedBy.should.be.eql(40051336); // connect admin
done();
diff --git a/src/routes/orgConfig/delete.js b/src/routes/orgConfig/delete.js
index d33e7608..91c59bb4 100644
--- a/src/routes/orgConfig/delete.js
+++ b/src/routes/orgConfig/delete.js
@@ -2,8 +2,11 @@
* API to delete a organization config
*/
import validate from 'express-validation';
+import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../constants';
+import util from '../../util';
import models from '../../models';
const permissions = tcMiddleware.permissions;
@@ -19,7 +22,7 @@ module.exports = [
permissions('orgConfig.delete'),
(req, res, next) =>
models.sequelize.transaction(() =>
- models.OrgConfig.findById(req.params.id)
+ models.OrgConfig.findByPk(req.params.id)
.then((entity) => {
if (!entity) {
const apiErr = new Error(`Organization config not found for id ${req.params.id}`);
@@ -30,7 +33,11 @@ module.exports = [
return entity.update({ deletedBy: req.authUser.userId });
})
.then(entity => entity.destroy()))
- .then(() => {
+ .then((entity) => {
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE,
+ RESOURCES.ORG_CONFIG,
+ _.pick(entity.toJSON(), 'id'));
res.status(204).end();
})
.catch(next),
diff --git a/src/routes/orgConfig/delete.spec.js b/src/routes/orgConfig/delete.spec.js
index 0bc6aefc..e4a234b5 100644
--- a/src/routes/orgConfig/delete.spec.js
+++ b/src/routes/orgConfig/delete.spec.js
@@ -24,7 +24,7 @@ const expectAfterDelete = (id, err, next) => {
chai.assert.isNotNull(res.deletedBy);
request(server)
- .get(`/v4/projects/metadata/orgConfig/${id}`)
+ .get(`/v5/projects/metadata/orgConfig/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -36,28 +36,31 @@ const expectAfterDelete = (id, err, next) => {
describe('DELETE organization config', () => {
const id = 1;
- beforeEach(() => testUtil.clearDb()
- .then(() => models.OrgConfig.create({
- id: 1,
- orgId: 'ORG1',
- configName: 'project_category_url',
- configValue: '/projects/1',
- createdBy: 1,
- updatedBy: 1,
- })).then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.OrgConfig.create({
+ id: 1,
+ orgId: 'ORG1',
+ configName: 'project_category_url',
+ configValue: '/projects/1',
+ createdBy: 1,
+ updatedBy: 1,
+ }).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('DELETE /orgConfig/{id}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .delete(`/v4/projects/metadata/orgConfig/${id}`)
+ .delete(`/v5/projects/metadata/orgConfig/${id}`)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .delete(`/v4/projects/metadata/orgConfig/${id}`)
+ .delete(`/v5/projects/metadata/orgConfig/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -66,7 +69,7 @@ describe('DELETE organization config', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .delete(`/v4/projects/metadata/orgConfig/${id}`)
+ .delete(`/v5/projects/metadata/orgConfig/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -75,7 +78,7 @@ describe('DELETE organization config', () => {
it('should return 403 for manager', (done) => {
request(server)
- .delete(`/v4/projects/metadata/orgConfig/${id}`)
+ .delete(`/v5/projects/metadata/orgConfig/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -84,7 +87,7 @@ describe('DELETE organization config', () => {
it('should return 404 for non-existed config', (done) => {
request(server)
- .delete('/v4/projects/metadata/orgConfig/not_existed')
+ .delete('/v5/projects/metadata/orgConfig/not_existed')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -95,7 +98,7 @@ describe('DELETE organization config', () => {
models.OrgConfig.destroy({ where: { id } })
.then(() => {
request(server)
- .delete(`/v4/projects/metadata/orgConfig/${id}`)
+ .delete(`/v5/projects/metadata/orgConfig/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -105,7 +108,7 @@ describe('DELETE organization config', () => {
it('should return 204, for admin, if config was successfully removed', (done) => {
request(server)
- .delete(`/v4/projects/metadata/orgConfig/${id}`)
+ .delete(`/v5/projects/metadata/orgConfig/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -115,7 +118,7 @@ describe('DELETE organization config', () => {
it('should return 204, for connect admin, if config was successfully removed', (done) => {
request(server)
- .delete(`/v4/projects/metadata/orgConfig/${id}`)
+ .delete(`/v5/projects/metadata/orgConfig/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
diff --git a/src/routes/orgConfig/get.js b/src/routes/orgConfig/get.js
index 5c14779b..5bd8d582 100644
--- a/src/routes/orgConfig/get.js
+++ b/src/routes/orgConfig/get.js
@@ -4,8 +4,8 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../util';
import models from '../../models';
+import util from '../../util';
const permissions = tcMiddleware.permissions;
@@ -18,22 +18,46 @@ const schema = {
module.exports = [
validate(schema),
permissions('orgConfig.view'),
- (req, res, next) => models.OrgConfig.findOne({
- where: {
- id: req.params.id,
- },
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- })
- .then((orgConfig) => {
- // Not found
- if (!orgConfig) {
- const apiErr = new Error(`Organization config not found for id ${req.params.id}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
- }
+ (req, res, next) => {
+ util.fetchByIdFromES('orgConfigs', {
+ query: {
+ nested: {
+ path: 'orgConfigs',
+ query: {
+ match: { 'orgConfigs.id': req.params.id },
+ },
+ inner_hits: {},
+ },
+ },
+ }, 'metadata')
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No orgConfig found in ES');
+ models.OrgConfig.findOne({
+ where: {
+ id: req.params.id,
+ },
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ })
+ .then((orgConfig) => {
+ // Not found
+ if (!orgConfig) {
+ const apiErr = new Error(`Organization config not found for id ${req.params.id}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
- res.json(util.wrapResponse(req.id, orgConfig));
- return Promise.resolve();
+ res.json(orgConfig);
+ return Promise.resolve();
+ })
+ .catch(next);
+ } else {
+ req.log.debug('orgConfigs found in ES');
+ res.json(data[0].inner_hits.orgConfigs.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle
+ }
})
- .catch(next),
+ .catch(next);
+ },
+
+
];
diff --git a/src/routes/orgConfig/get.spec.js b/src/routes/orgConfig/get.spec.js
index 5bafd4c0..eddd8b0a 100644
--- a/src/routes/orgConfig/get.spec.js
+++ b/src/routes/orgConfig/get.spec.js
@@ -22,16 +22,18 @@ describe('GET organization config', () => {
const id = config.id;
- beforeEach(() => testUtil.clearDb()
- .then(() => models.OrgConfig.create(config))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.OrgConfig.create(config).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /orgConfig/{id}', () => {
it('should return 404 for non-existed config', (done) => {
request(server)
- .get('/v4/projects/metadata/orgConfig/1234')
+ .get('/v5/projects/metadata/orgConfig/1234')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -42,7 +44,7 @@ describe('GET organization config', () => {
models.OrgConfig.destroy({ where: { id } })
.then(() => {
request(server)
- .get(`/v4/projects/metadata/orgConfig/${id}`)
+ .get(`/v5/projects/metadata/orgConfig/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -52,13 +54,13 @@ describe('GET organization config', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get(`/v4/projects/metadata/orgConfig/${id}`)
+ .get(`/v5/projects/metadata/orgConfig/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(config.id);
resJson.orgId.should.be.eql(config.orgId);
resJson.configName.should.be.eql(config.configName);
@@ -76,13 +78,13 @@ describe('GET organization config', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get(`/v4/projects/metadata/orgConfig/${id}`)
+ .get(`/v5/projects/metadata/orgConfig/${id}`)
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get(`/v4/projects/metadata/orgConfig/${id}`)
+ .get(`/v5/projects/metadata/orgConfig/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -92,7 +94,7 @@ describe('GET organization config', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get(`/v4/projects/metadata/orgConfig/${id}`)
+ .get(`/v5/projects/metadata/orgConfig/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -102,7 +104,7 @@ describe('GET organization config', () => {
it('should return 200 for member', (done) => {
request(server)
- .get(`/v4/projects/metadata/orgConfig/${id}`)
+ .get(`/v5/projects/metadata/orgConfig/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -111,7 +113,7 @@ describe('GET organization config', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get(`/v4/projects/metadata/orgConfig/${id}`)
+ .get(`/v5/projects/metadata/orgConfig/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/orgConfig/list.js b/src/routes/orgConfig/list.js
index 0c8222f0..6e5fb747 100644
--- a/src/routes/orgConfig/list.js
+++ b/src/routes/orgConfig/list.js
@@ -3,6 +3,7 @@
*/
import validate from 'express-validation';
import Joi from 'joi';
+import _ from 'lodash';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import models from '../../models';
import util from '../../util';
@@ -11,7 +12,8 @@ const permissions = tcMiddleware.permissions;
const schema = {
query: {
- filter: Joi.string().required(),
+ orgId: Joi.string().required(),
+ configName: Joi.string().optional(),
},
};
@@ -20,25 +22,63 @@ module.exports = [
permissions('orgConfig.view'),
(req, res, next) => {
// handle filters
- const filters = util.parseQueryFilter(req.query.filter);
+ const filters = req.query;
// Throw error if orgId is not present in filter
if (!filters.orgId) {
- return next(util.buildApiError('Missing filter orgId', 422));
+ next(util.buildApiError('Missing filter orgId', 400));
}
if (!util.isValidFilter(filters, ['orgId', 'configName'])) {
- return util.handleError('Invalid filters', null, req, next);
+ util.handleError('Invalid filters', null, req, next);
}
req.log.debug(filters);
- // Get all organization config
- const where = filters || {};
- return models.OrgConfig.findAll({
- where,
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- raw: true,
- })
- .then((orgConfigs) => {
- res.json(util.wrapResponse(req.id, orgConfigs));
- })
- .catch(next);
+ const orgIds = filters.orgId.split(',');
+
+ // build filter query for ES
+ const must = [{
+ terms: {
+ 'orgConfigs.orgId': orgIds,
+ },
+ }];
+ if (filters.configName) {
+ must.push({
+ term: {
+ 'orgConfigs.configName': filters.configName,
+ },
+ });
+ }
+
+ util.fetchFromES('orgConfigs', {
+ query: {
+ nested: {
+ path: 'orgConfigs',
+ query: {
+ bool: {
+ must,
+ },
+ },
+ inner_hits: {},
+ },
+ },
+ }, 'metadata')
+ .then((data) => {
+ if (data.orgConfigs.length === 0) {
+ req.log.debug('No orgConfig found in ES');
+
+ // Get all organization config
+ const where = filters ? _.assign({}, filters, { orgId: { $in: orgIds } }) : {};
+ models.OrgConfig.findAll({
+ where,
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ raw: true,
+ })
+ .then((orgConfigs) => {
+ res.json(orgConfigs);
+ })
+ .catch(next);
+ } else {
+ req.log.debug('orgConfigs found in ES');
+ res.json(data.orgConfigs.hits.hits.map(hit => hit._source)); // eslint-disable-line no-underscore-dangle
+ }
+ });
},
];
diff --git a/src/routes/orgConfig/list.spec.js b/src/routes/orgConfig/list.spec.js
index 060a1a79..831a4898 100644
--- a/src/routes/orgConfig/list.spec.js
+++ b/src/routes/orgConfig/list.spec.js
@@ -7,122 +7,202 @@ import request from 'supertest';
import models from '../../models';
import server from '../../app';
import testUtil from '../../tests/util';
+import esUtils from '../../utils/es';
const should = chai.should();
+const validateOrgConfig = (resJson, orgConfig) => {
+ resJson.id.should.be.eql(orgConfig.id);
+ resJson.orgId.should.be.eql(orgConfig.orgId);
+ resJson.configName.should.be.eql(orgConfig.configName);
+ resJson.configValue.should.be.eql(orgConfig.configValue);
+ should.exist(resJson.createdAt);
+ resJson.updatedBy.should.be.eql(orgConfig.updatedBy);
+ should.exist(resJson.updatedAt);
+ should.not.exist(resJson.deletedBy);
+ should.not.exist(resJson.deletedAt);
+};
+
+const orgConfigPath = '/v5/projects/metadata/orgConfig';
+
+const configs = [
+ {
+ id: 1,
+ orgId: 'ORG1',
+ configName: 'project_category_url',
+ configValue: '/projects/1',
+ createdBy: 1,
+ updatedBy: 1,
+ },
+ {
+ id: 2,
+ orgId: 'ORG2',
+ configName: 'project_catalog_url',
+ configValue: '/projects/2',
+ createdBy: 1,
+ updatedBy: 1,
+ },
+ {
+ id: 3,
+ orgId: 'ORG3',
+ configName: 'project_catalog_url',
+ configValue: '/projects/3',
+ createdBy: 1,
+ updatedBy: 1,
+ },
+ {
+ id: 4,
+ orgId: 'ORG4',
+ configName: 'project_catalog_url',
+ configValue: '/projects/4',
+ createdBy: 1,
+ updatedBy: 1,
+ },
+];
+
describe('LIST organization config', () => {
- const orgConfigPath = '/v4/projects/metadata/orgConfig';
- const configs = [
- {
- id: 1,
- orgId: 'ORG1',
- configName: 'project_category_url',
- configValue: '/projects/1',
- createdBy: 1,
- updatedBy: 1,
- },
- {
- id: 2,
- orgId: 'ORG1',
- configName: 'project_catalog_url',
- configValue: '/projects/2',
- createdBy: 1,
- updatedBy: 1,
- },
- ];
-
- beforeEach(() => testUtil.clearDb()
- .then(() => models.OrgConfig.bulkCreate(configs)),
- );
- after(testUtil.clearDb);
+ after((done) => {
+ // clear data after tests in DB and ES
+ testUtil.clearDb()
+ .then(() => testUtil.clearES())
+ .then(done);
+ });
describe('GET /orgConfig', () => {
- it('should return 200 for admin with filter', (done) => {
- request(server)
- .get(`${orgConfigPath}?filter=orgId%3Din%28${configs[0].orgId}%29%26configName=${configs[0].configName}`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.admin}`,
- })
- .expect(200)
- .end((err, res) => {
- const config = configs[0];
-
- const resJson = res.body.result.content;
- resJson.should.have.length(1);
- resJson[0].id.should.be.eql(config.id);
- resJson[0].orgId.should.be.eql(config.orgId);
- resJson[0].configName.should.be.eql(config.configName);
- resJson[0].configValue.should.be.eql(config.configValue);
- should.exist(resJson[0].createdAt);
- resJson[0].updatedBy.should.be.eql(config.updatedBy);
- should.exist(resJson[0].updatedAt);
- should.not.exist(resJson[0].deletedBy);
- should.not.exist(resJson[0].deletedAt);
-
- done();
- });
- });
+ describe('permissions', () => {
+ it('should return 403 if user is not authenticated with filter', (done) => {
+ request(server)
+ .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`)
+ .expect(403, done);
+ });
- it('should return 403 if user is not authenticated with filter', (done) => {
- request(server)
- .get(`${orgConfigPath}?filter=orgId%3Din%28${configs[0].orgId}%29%26configName=${configs[0].configName}`)
- .expect(403, done);
- });
+ it('should return 200 for connect admin with filter', (done) => {
+ request(server)
+ .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
+ })
+ .expect(200, done);
+ });
- it('should return 200 for connect admin with filter', (done) => {
- request(server)
- .get(`${orgConfigPath}?filter=orgId%3Din%28${configs[0].orgId}%29%26configName=${configs[0].configName}`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
- })
- .expect(200)
- .end(done);
- });
+ it('should return 200 for connect manager with filter', (done) => {
+ request(server)
+ .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.manager}`,
+ })
+ .expect(200, done);
+ });
- it('should return 200 for connect manager with filter', (done) => {
- request(server)
- .get(`${orgConfigPath}?filter=orgId%3Din%28${configs[0].orgId}%29%26configName=${configs[0].configName}`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.manager}`,
- })
- .expect(200)
- .end(done);
- });
+ it('should return 200 for member with filter', (done) => {
+ request(server)
+ .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.member}`,
+ })
+ .expect(200, done);
+ });
- it('should return 200 for member with filter', (done) => {
- request(server)
- .get(`${orgConfigPath}?filter=orgId%3Din%28${configs[0].orgId}%29%26configName=${configs[0].configName}`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.member}`,
- })
- .expect(200, done);
- });
+ it('should return 200 for copilot with filter', (done) => {
+ request(server)
+ .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.copilot}`,
+ })
+ .expect(200, done);
+ });
- it('should return 200 for copilot with filter', (done) => {
- request(server)
- .get(`${orgConfigPath}?filter=orgId%3Din%28${configs[0].orgId}%29%26configName=${configs[0].configName}`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.copilot}`,
- })
- .expect(200, done);
- });
+ it('should return 400 without filter query param', (done) => {
+ request(server)
+ .get(`${orgConfigPath}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.admin}`,
+ })
+ .expect(400, done);
+ });
- it('should return 422 without filter query param', (done) => {
- request(server)
- .get(`${orgConfigPath}`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.admin}`,
- })
- .expect(422, done);
+ it('should return 400 with filter query param but without orgId defined', (done) => {
+ request(server)
+ .get(`${orgConfigPath}?filter=configName=${configs[0].configName}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.admin}`,
+ })
+ .expect(400, done);
+ });
});
- it('should return 422 with filter query param but without orgId defined', (done) => {
- request(server)
- .get(`${orgConfigPath}?filter=configName=${configs[0].configName}`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.admin}`,
- })
- .expect(422, done);
+ // we are testing all the endpoints with the same data when it comes from DB and ES
+ ['ES', 'DB'].forEach((dataSource) => {
+ describe(`data from ${dataSource}`, () => {
+ before((done) => {
+ // clear data in DB and ES before tests
+ testUtil.clearDb()
+ .then(() => testUtil.clearES())
+ // create data in DB first
+ .then(() => models.OrgConfig.bulkCreate(configs))
+ .then(() => {
+ // if we want to test data in ES, then we index data from DB to ES
+ // and clear data in DB after that, so we only have data in ES
+ if (dataSource === 'ES') {
+ return esUtils.indexMetadata()
+ .then(() => testUtil.clearDb());
+ }
+ return Promise.resolve();
+ })
+ .then(() => done());
+ });
+
+ it(`should get one record for admin with filter (${dataSource})`, (done) => {
+ request(server)
+ .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.admin}`,
+ })
+ .expect(200)
+ .end((err, res) => {
+ if (err) {
+ return done(err);
+ }
+
+ const config1 = configs[0];
+
+ const resJson = res.body;
+ resJson.should.have.length(1);
+ validateOrgConfig(resJson[0], config1);
+
+ return done();
+ });
+ });
+
+ it(`should return 2 records for admin with filter by multiple orgId (${dataSource})`, (done) => {
+ request(server)
+ .get(`${orgConfigPath}?orgId=${configs[0].orgId},${configs[1].orgId}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.admin}`,
+ })
+ .expect(200)
+ .end((err, res) => {
+ if (err) {
+ return done(err);
+ }
+
+ const config1 = configs[0];
+ const config2 = configs[1];
+
+ const resJson = res.body;
+ resJson.should.have.length(2);
+ resJson.forEach((result) => {
+ if (result.id === 1) {
+ validateOrgConfig(result, config1);
+ } else {
+ validateOrgConfig(result, config2);
+ }
+ });
+
+ return done();
+ });
+ });
+ });
});
});
});
diff --git a/src/routes/orgConfig/update.js b/src/routes/orgConfig/update.js
index 7bc2b52d..df39c1de 100644
--- a/src/routes/orgConfig/update.js
+++ b/src/routes/orgConfig/update.js
@@ -5,6 +5,7 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../constants';
import util from '../../util';
import models from '../../models';
@@ -14,27 +15,25 @@ const schema = {
params: {
id: Joi.number().integer().positive().required(),
},
- body: {
- param: Joi.object().keys({
- id: Joi.any().strip(),
- orgId: Joi.string().max(45).optional(),
- configName: Joi.string().max(45).optional(),
- configValue: Joi.string().max(512).optional(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ body: Joi.object().keys({
+ id: Joi.any().strip(),
+ orgId: Joi.string().max(45).optional(),
+ configName: Joi.string().max(45).optional(),
+ configValue: Joi.string().max(512).optional(),
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
};
module.exports = [
validate(schema),
permissions('orgConfig.edit'),
(req, res, next) => {
- const entityToUpdate = _.assign(req.body.param, {
+ const entityToUpdate = _.assign(req.body, {
updatedBy: req.authUser.userId,
});
@@ -55,7 +54,12 @@ module.exports = [
return orgConfig.update(entityToUpdate);
})
.then((orgConfig) => {
- res.json(util.wrapResponse(req.id, orgConfig));
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE,
+ RESOURCES.ORG_CONFIG,
+ orgConfig.get({ plain: true }),
+ );
+ res.json(orgConfig);
return Promise.resolve();
})
.catch(next);
diff --git a/src/routes/orgConfig/update.spec.js b/src/routes/orgConfig/update.spec.js
index e1f18672..f25c98bb 100644
--- a/src/routes/orgConfig/update.spec.js
+++ b/src/routes/orgConfig/update.spec.js
@@ -22,32 +22,32 @@ describe('UPDATE organization config', () => {
};
const id = config.id;
- beforeEach(() => testUtil.clearDb()
- .then(() => models.OrgConfig.create(config))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.OrgConfig.create(config).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('PATCH /orgConfig/{id}', () => {
const body = {
- param: {
- id: 1,
- orgId: 'ORG2',
- configName: 'project_category_url_update',
- configValue: '/projects/2',
- },
+ id: 1,
+ orgId: 'ORG2',
+ configName: 'project_category_url_update',
+ configValue: '/projects/2',
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .patch(`/v4/projects/metadata/orgConfig/${id}`)
+ .patch(`/v5/projects/metadata/orgConfig/${id}`)
.send(body)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .patch(`/v4/projects/metadata/orgConfig/${id}`)
+ .patch(`/v5/projects/metadata/orgConfig/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -57,7 +57,7 @@ describe('UPDATE organization config', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .patch(`/v4/projects/metadata/orgConfig/${id}`)
+ .patch(`/v5/projects/metadata/orgConfig/${id}`)
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
@@ -67,7 +67,7 @@ describe('UPDATE organization config', () => {
it('should return 403 for manager', (done) => {
request(server)
- .patch(`/v4/projects/metadata/orgConfig/${id}`)
+ .patch(`/v5/projects/metadata/orgConfig/${id}`)
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
@@ -77,7 +77,7 @@ describe('UPDATE organization config', () => {
it('should return 404 for non-existed config', (done) => {
request(server)
- .patch('/v4/projects/metadata/orgConfig/1234')
+ .patch('/v5/projects/metadata/orgConfig/1234')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -87,9 +87,12 @@ describe('UPDATE organization config', () => {
it('should return 404 for deleted config', (done) => {
models.OrgConfig.destroy({ where: { id } })
+ // we should clear ES, otherwise deleted config would be returned by ES
+ // TODO we should create an alternative way to test it, as all the data is "cached" in ES now
+ .then(() => testUtil.clearES())
.then(() => {
request(server)
- .patch(`/v4/projects/metadata/orgConfig/${id}`)
+ .patch(`/v5/projects/metadata/orgConfig/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -100,21 +103,21 @@ describe('UPDATE organization config', () => {
it('should return 200 for admin configValue updated', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.orgId;
- delete partialBody.param.configName;
+ delete partialBody.orgId;
+ delete partialBody.configName;
request(server)
- .patch(`/v4/projects/metadata/orgConfig/${id}`)
+ .patch(`/v5/projects/metadata/orgConfig/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(partialBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(id);
resJson.orgId.should.be.eql(config.orgId);
resJson.configName.should.be.eql(config.configName);
- resJson.configValue.should.be.eql(partialBody.param.configValue);
+ resJson.configValue.should.be.eql(partialBody.configValue);
resJson.createdBy.should.be.eql(config.createdBy);
resJson.createdBy.should.be.eql(config.createdBy); // should not update createdAt
resJson.updatedBy.should.be.eql(40051333); // admin
@@ -128,19 +131,19 @@ describe('UPDATE organization config', () => {
it('should return 200 for admin orgId updated', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.configName;
- delete partialBody.param.configValue;
+ delete partialBody.configName;
+ delete partialBody.configValue;
request(server)
- .patch(`/v4/projects/metadata/orgConfig/${id}`)
+ .patch(`/v5/projects/metadata/orgConfig/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(partialBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(id);
- resJson.orgId.should.be.eql(partialBody.param.orgId);
+ resJson.orgId.should.be.eql(partialBody.orgId);
resJson.configName.should.be.eql(config.configName);
resJson.configValue.should.be.eql(config.configValue);
resJson.createdBy.should.be.eql(config.createdBy);
@@ -156,20 +159,20 @@ describe('UPDATE organization config', () => {
it('should return 200 for admin configName updated', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.orgId;
- delete partialBody.param.configValue;
+ delete partialBody.orgId;
+ delete partialBody.configValue;
request(server)
- .patch(`/v4/projects/metadata/orgConfig/${id}`)
+ .patch(`/v5/projects/metadata/orgConfig/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(partialBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(id);
resJson.orgId.should.be.eql(config.orgId);
- resJson.configName.should.be.eql(partialBody.param.configName);
+ resJson.configName.should.be.eql(partialBody.configName);
resJson.configValue.should.be.eql(config.configValue);
resJson.createdBy.should.be.eql(config.createdBy);
resJson.createdBy.should.be.eql(config.createdBy); // should not update createdAt
@@ -184,18 +187,18 @@ describe('UPDATE organization config', () => {
it('should return 200 for admin all fields updated', (done) => {
request(server)
- .patch(`/v4/projects/metadata/orgConfig/${id}`)
+ .patch(`/v5/projects/metadata/orgConfig/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(body)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(id);
- resJson.orgId.should.be.eql(body.param.orgId);
- resJson.configName.should.be.eql(body.param.configName);
- resJson.configValue.should.be.eql(body.param.configValue);
+ resJson.orgId.should.be.eql(body.orgId);
+ resJson.configName.should.be.eql(body.configName);
+ resJson.configValue.should.be.eql(body.configValue);
resJson.createdBy.should.be.eql(config.createdBy); // should not update createdAt
resJson.updatedBy.should.be.eql(40051333); // admin
should.exist(resJson.updatedAt);
@@ -208,18 +211,18 @@ describe('UPDATE organization config', () => {
it('should return 200 for connect admin', (done) => {
request(server)
- .patch(`/v4/projects/metadata/orgConfig/${id}`)
+ .patch(`/v5/projects/metadata/orgConfig/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
.send(body)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(id);
- resJson.orgId.should.be.eql(body.param.orgId);
- resJson.configName.should.be.eql(body.param.configName);
- resJson.configValue.should.be.eql(body.param.configValue);
+ resJson.orgId.should.be.eql(body.orgId);
+ resJson.configName.should.be.eql(body.configName);
+ resJson.configValue.should.be.eql(body.configValue);
resJson.createdBy.should.be.eql(config.createdBy); // should not update createdAt
resJson.updatedBy.should.be.eql(40051336); // connect admin
done();
diff --git a/src/routes/permissions/get.js b/src/routes/permissions/get.js
index 01ea6fc4..d22acbee 100644
--- a/src/routes/permissions/get.js
+++ b/src/routes/permissions/get.js
@@ -58,7 +58,7 @@ module.exports = [
}
});
- res.json(util.wrapResponse(req.id, allowPermissions));
+ res.json(allowPermissions);
})
.catch(next);
},
diff --git a/src/routes/permissions/get.spec.js b/src/routes/permissions/get.spec.js
index 06e23482..57f7d62e 100644
--- a/src/routes/permissions/get.spec.js
+++ b/src/routes/permissions/get.spec.js
@@ -118,18 +118,20 @@ describe('GET permissions', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/{projectId}/permissions', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/permissions`)
+ .get(`/v5/projects/${projectId}/permissions`)
.expect(403, done);
});
it('should return 403 for non-member', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/permissions`)
+ .get(`/v5/projects/${projectId}/permissions`)
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -138,7 +140,7 @@ describe('GET permissions', () => {
it('should return 404 for non-existed project', (done) => {
request(server)
- .get('/v4/projects/9999/permissions')
+ .get('/v5/projects/9999/permissions')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -157,13 +159,13 @@ describe('GET permissions', () => {
})
.then((p) => {
request(server)
- .get(`/v4/projects/${p.id}/permissions`)
+ .get(`/v5/projects/${p.id}/permissions`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.be.empty;
done();
});
@@ -172,13 +174,13 @@ describe('GET permissions', () => {
it('should return 200 for connect admin - no permission', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/permissions`)
+ .get(`/v5/projects/${projectId}/permissions`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.not.have.all.keys(permissions[0].policy, permissions[1].policy);
done();
});
@@ -186,13 +188,13 @@ describe('GET permissions', () => {
it('should return 200 for copilot - has both no-permission and permission', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/permissions`)
+ .get(`/v5/projects/${projectId}/permissions`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.all.keys(permissions[1].policy);
resJson.should.not.have.all.keys(permissions[0].policy);
done();
@@ -201,13 +203,13 @@ describe('GET permissions', () => {
it('should return 200 for admin - has both permission and no-permission', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/permissions`)
+ .get(`/v5/projects/${projectId}/permissions`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.all.keys(permissions[0].policy);
resJson.should.not.have.all.keys(permissions[1].policy);
done();
@@ -216,13 +218,13 @@ describe('GET permissions', () => {
it('should return 200 for manager - has permissions', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/permissions`)
+ .get(`/v5/projects/${projectId}/permissions`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.all.keys(permissions[0].policy, permissions[1].policy);
done();
});
diff --git a/src/routes/phaseProducts/create.js b/src/routes/phaseProducts/create.js
index 10887006..4bfb9bc3 100644
--- a/src/routes/phaseProducts/create.js
+++ b/src/routes/phaseProducts/create.js
@@ -6,23 +6,21 @@ import Joi from 'joi';
import models from '../../models';
import util from '../../util';
-import { EVENT } from '../../constants';
+import { EVENT, RESOURCES } from '../../constants';
const permissions = require('tc-core-library-js').middleware.permissions;
const addPhaseProductValidations = {
- body: {
- param: Joi.object().keys({
- name: Joi.string().required(),
- type: Joi.string().required(),
- templateId: Joi.number().positive().optional(),
- directProjectId: Joi.number().positive().optional(),
- billingAccountId: Joi.number().positive().optional(),
- estimatedPrice: Joi.number().positive().optional(),
- actualPrice: Joi.number().positive().optional(),
- details: Joi.any().optional(),
- }).required(),
- },
+ body: Joi.object().keys({
+ name: Joi.string().required(),
+ type: Joi.string().required(),
+ templateId: Joi.number().positive().optional(),
+ directProjectId: Joi.number().positive().optional(),
+ billingAccountId: Joi.number().positive().optional(),
+ estimatedPrice: Joi.number().positive().optional(),
+ actualPrice: Joi.number().positive().optional(),
+ details: Joi.any().optional(),
+ }).required(),
};
module.exports = [
@@ -35,7 +33,7 @@ module.exports = [
const projectId = _.parseInt(req.params.projectId);
const phaseId = _.parseInt(req.params.phaseId);
- const data = req.body.param;
+ const data = req.body;
// default values
_.assign(data, {
createdBy: req.authUser.userId,
@@ -107,10 +105,14 @@ module.exports = [
newPhaseProduct,
{ correlationId: req.id },
);
- req.log.debug('Sending event to Kafka bus for phase product %d', newPhaseProduct.id);
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_ADDED, { req, created: newPhaseProduct });
+ // emit the event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_ADDED,
+ RESOURCES.PHASE_PRODUCT,
+ newPhaseProduct);
- res.status(201).json(util.wrapResponse(req.id, newPhaseProduct, 1, 201));
+ res.status(201).json(newPhaseProduct);
})
.catch((err) => { next(err); });
},
diff --git a/src/routes/phaseProducts/create.spec.js b/src/routes/phaseProducts/create.spec.js
index b8b462f8..e987ed42 100644
--- a/src/routes/phaseProducts/create.spec.js
+++ b/src/routes/phaseProducts/create.spec.js
@@ -7,6 +7,7 @@ import server from '../../app';
import models from '../../models';
import testUtil from '../../tests/util';
import busApi from '../../services/busApi';
+import { RESOURCES, BUS_API_EVENT } from '../../constants';
const should = chai.should();
@@ -101,114 +102,114 @@ describe('Phase Products', () => {
describe('POST /projects/{projectId}/phases/{phaseId}/products', () => {
it('should return 403 if user does not have permissions (non team member)', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/phases/${phaseId}/products`)
+ .post(`/v5/projects/${projectId}/phases/${phaseId}/products`)
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(403, done);
});
it('should return 403 if user does not have permissions (customer)', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/phases/${phaseId}/products`)
+ .post(`/v5/projects/${projectId}/phases/${phaseId}/products`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(403, done);
});
- it('should return 422 when name not provided', (done) => {
+ it('should return 400 when name not provided', (done) => {
const reqBody = _.cloneDeep(body);
delete reqBody.name;
request(server)
- .post(`/v4/projects/${projectId}/phases/${phaseId}/products`)
+ .post(`/v5/projects/${projectId}/phases/${phaseId}/products`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: reqBody })
+ .send(reqBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 when type not provided', (done) => {
+ it('should return 400 when type not provided', (done) => {
const reqBody = _.cloneDeep(body);
delete reqBody.type;
request(server)
- .post(`/v4/projects/${projectId}/phases/${phaseId}/products`)
+ .post(`/v5/projects/${projectId}/phases/${phaseId}/products`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: reqBody })
+ .send(reqBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 when estimatedPrice is negative', (done) => {
+ it('should return 400 when estimatedPrice is negative', (done) => {
const reqBody = _.cloneDeep(body);
reqBody.estimatedPrice = -20;
request(server)
- .post(`/v4/projects/${projectId}/phases/${phaseId}/products`)
+ .post(`/v5/projects/${projectId}/phases/${phaseId}/products`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: reqBody })
+ .send(reqBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 when actualPrice is negative', (done) => {
+ it('should return 400 when actualPrice is negative', (done) => {
const reqBody = _.cloneDeep(body);
reqBody.actualPrice = -20;
request(server)
- .post(`/v4/projects/${projectId}/phases/${phaseId}/products`)
+ .post(`/v5/projects/${projectId}/phases/${phaseId}/products`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: reqBody })
+ .send(reqBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 404 when project is not found', (done) => {
request(server)
- .post(`/v4/projects/99999/phases/${phaseId}/products`)
+ .post(`/v5/projects/99999/phases/${phaseId}/products`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(404, done);
});
it('should return 404 when project phase is not found', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/phases/99999/products`)
+ .post(`/v5/projects/${projectId}/phases/99999/products`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(404, done);
});
it('should return 201 if payload is valid', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/phases/${phaseId}/products`)
+ .post(`/v5/projects/${projectId}/phases/${phaseId}/products`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.name.should.be.eql(body.name);
resJson.type.should.be.eql(body.type);
@@ -222,11 +223,11 @@ describe('Phase Products', () => {
it('should return 201 if requested by admin', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/phases/${phaseId}/products`)
+ .post(`/v5/projects/${projectId}/phases/${phaseId}/products`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(201)
.end(done);
@@ -243,11 +244,11 @@ describe('Phase Products', () => {
updatedBy: 1,
}).then(() => {
request(server)
- .post(`/v4/projects/${projectId}/phases/${phaseId}/products`)
+ .post(`/v5/projects/${projectId}/phases/${phaseId}/products`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(201)
.end(done);
@@ -256,11 +257,11 @@ describe('Phase Products', () => {
it('should return 403 if requested by manager which is not a member', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/phases/${phaseId}/products`)
+ .post(`/v5/projects/${projectId}/phases/${phaseId}/products`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(403)
.end(done);
@@ -271,11 +272,11 @@ describe('Phase Products', () => {
where: { userId: testUtil.userIds.copilot, projectId },
}).then(() => {
request(server)
- .post(`/v4/projects/${projectId}/phases/${phaseId}/products`)
+ .post(`/v5/projects/${projectId}/phases/${phaseId}/products`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(403)
.end(done);
@@ -299,13 +300,13 @@ describe('Phase Products', () => {
sandbox.restore();
});
- it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when product phase created', (done) => {
+ it('should send correct BUS API messages when product phase created', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/phases/${phaseId}/products`)
+ .post(`/v5/projects/${projectId}/phases/${phaseId}/products`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(201)
.end((err) => {
@@ -313,7 +314,12 @@ describe('Phase Products', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.notCalled.should.be.true;
+ createEventSpy.callCount.should.be.eql(1);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, sinon.match({
+ resource: RESOURCES.PHASE_PRODUCT,
+ })).should.be.true;
+
done();
});
}
diff --git a/src/routes/phaseProducts/delete.js b/src/routes/phaseProducts/delete.js
index b8efbb39..c30138bb 100644
--- a/src/routes/phaseProducts/delete.js
+++ b/src/routes/phaseProducts/delete.js
@@ -3,7 +3,8 @@
import _ from 'lodash';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import models from '../../models';
-import { EVENT } from '../../constants';
+import util from '../../util';
+import { EVENT, RESOURCES } from '../../constants';
const permissions = tcMiddleware.permissions;
@@ -45,7 +46,12 @@ module.exports = [
deleted,
{ correlationId: req.id },
);
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_REMOVED, { req, deleted });
+ // emit the event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_REMOVED,
+ RESOURCES.PHASE_PRODUCT,
+ _.pick(deleted.toJSON(), 'id'));
res.status(204).json({});
})
diff --git a/src/routes/phaseProducts/delete.spec.js b/src/routes/phaseProducts/delete.spec.js
index 03db9a9d..c6e7c358 100644
--- a/src/routes/phaseProducts/delete.spec.js
+++ b/src/routes/phaseProducts/delete.spec.js
@@ -7,32 +7,29 @@ import server from '../../app';
import models from '../../models';
import testUtil from '../../tests/util';
import busApi from '../../services/busApi';
+import { BUS_API_EVENT, RESOURCES } from '../../constants';
+
+const should = chai.should(); // eslint-disable-line no-unused-vars
const expectAfterDelete = (projectId, phaseId, id, err, next) => {
if (err) throw err;
setTimeout(() =>
- models.PhaseProduct.findOne({
- where: {
- id,
- projectId,
- phaseId,
- },
- paranoid: false,
- })
+ models.PhaseProduct.findOne({
+ where: {
+ id,
+ projectId,
+ phaseId,
+ },
+ paranoid: false,
+ })
.then((res) => {
if (!res) {
throw new Error('Should found the entity');
} else {
chai.assert.isNotNull(res.deletedAt);
chai.assert.isNotNull(res.deletedBy);
-
- request(server)
- .get(`/v4/projects/${projectId}/phases/${phaseId}/products/${id}`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.admin}`,
- })
- .expect(404, next);
}
+ next();
}), 500);
};
const body = {
@@ -134,7 +131,7 @@ describe('Phase Products', () => {
describe('DELETE /projects/{id}/phases/{phaseId}/products/{productId}', () => {
it('should return 403 when user have no permission (non team member)', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -144,7 +141,7 @@ describe('Phase Products', () => {
it('should return 403 when user have no permission (customer)', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -154,7 +151,7 @@ describe('Phase Products', () => {
it('should return 404 when no project with specific projectId', (done) => {
request(server)
- .delete(`/v4/projects/999/phases/${phaseId}/products/${productId}`)
+ .delete(`/v5/projects/999/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -164,7 +161,7 @@ describe('Phase Products', () => {
it('should return 404 when no phase with specific phaseId', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/phases/99999/products/${productId}`)
+ .delete(`/v5/projects/${projectId}/phases/99999/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -174,7 +171,7 @@ describe('Phase Products', () => {
it('should return 404 when no product with specific productId', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/phases/${phaseId}/products/99999`)
+ .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/99999`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -184,7 +181,7 @@ describe('Phase Products', () => {
it('should return 204 when user have project permission', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -194,7 +191,7 @@ describe('Phase Products', () => {
it('should return 204 if requested by admin', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -213,7 +210,7 @@ describe('Phase Products', () => {
updatedBy: 1,
}).then(() => {
request(server)
- .delete(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -224,7 +221,7 @@ describe('Phase Products', () => {
it('should return 403 if requested by manager which is not a member', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -237,7 +234,7 @@ describe('Phase Products', () => {
where: { userId: testUtil.userIds.copilot, projectId },
}).then(() => {
request(server)
- .delete(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -263,9 +260,9 @@ describe('Phase Products', () => {
sandbox.restore();
});
- it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when product phase removed', (done) => {
+ it('should send correct BUS API messages when product phase removed', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -275,7 +272,12 @@ describe('Phase Products', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.notCalled.should.be.true;
+ createEventSpy.callCount.should.be.eql(1);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_DELETED, sinon.match({
+ resource: RESOURCES.PHASE_PRODUCT,
+ })).should.be.true;
+
done();
});
}
diff --git a/src/routes/phaseProducts/get.js b/src/routes/phaseProducts/get.js
index 30aa2ed4..a2eca0c7 100644
--- a/src/routes/phaseProducts/get.js
+++ b/src/routes/phaseProducts/get.js
@@ -15,22 +15,63 @@ module.exports = [
const phaseId = _.parseInt(req.params.phaseId);
const productId = _.parseInt(req.params.productId);
- return models.PhaseProduct.findOne({
- where: {
- id: productId,
- projectId,
- phaseId,
+ // Get project from ES
+ return util.fetchByIdFromES('phaseProducts', {
+ query: {
+ nested: {
+ path: 'phases',
+ query:
+ {
+ filtered: {
+ filter: {
+ bool: {
+ must: [
+ { term: { 'phases.id': phaseId } },
+ { term: { 'phases.projectId': projectId } },
+ ],
+ },
+ },
+ },
+ },
+ inner_hits: {},
+ },
},
- }).then((product) => {
- if (!product) {
- // handle 404
- const err = new Error('phase product not found for project id ' +
- `${projectId}, phase id ${phaseId} and product id ${productId}`);
- err.status = 404;
- throw err;
- } else {
- res.json(util.wrapResponse(req.id, product));
- }
- }).catch(err => next(err));
+ })
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No phase product found in ES');
+ return models.PhaseProduct.findOne({
+ where: {
+ id: productId,
+ projectId,
+ phaseId,
+ },
+ }).then((product) => {
+ if (!product) {
+ // handle 404
+ const err = new Error('phase product not found for project id ' +
+ `${projectId}, phase id ${phaseId} and product id ${productId}`);
+ err.status = 404;
+ throw err;
+ } else {
+ res.json(product);
+ }
+ }).catch(err => next(err));
+ }
+ req.log.debug('phase product found in ES');
+ // Get the phases
+ const phases = data[0].inner_hits.phases.hits.hits[0]._source; // eslint-disable-line no-underscore-dangle
+ const product = _.isArray(phases.products) ? _.find(phases.products, p => p.id === productId) : {};
+ if (!product) {
+ // handle 404
+ const err = new Error('phase product not found for project id ' +
+ `${projectId}, phase id ${phaseId} and product id ${productId}`);
+ err.status = 404;
+ throw err;
+ }
+
+ return res.json(product);
+ })
+ .catch(err => next(err));
},
];
diff --git a/src/routes/phaseProducts/get.spec.js b/src/routes/phaseProducts/get.spec.js
index a71d8a7f..2160fd61 100644
--- a/src/routes/phaseProducts/get.spec.js
+++ b/src/routes/phaseProducts/get.spec.js
@@ -107,7 +107,7 @@ describe('Phase Products', () => {
describe('GET /projects/{id}/phases/{phaseId}/products/{productId}', () => {
it('should return 403 when user have no permission (non team member)', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .get(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -117,7 +117,7 @@ describe('Phase Products', () => {
it('should return 404 when no project with specific projectId', (done) => {
request(server)
- .get(`/v4/projects/999/phases/${phaseId}/products/${productId}`)
+ .get(`/v5/projects/999/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -127,7 +127,7 @@ describe('Phase Products', () => {
it('should return 404 when no phase with specific phaseId', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/phases/99999/products/${productId}`)
+ .get(`/v5/projects/${projectId}/phases/99999/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -137,7 +137,7 @@ describe('Phase Products', () => {
it('should return 404 when no product with specific productId', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/phases/${phaseId}/products/99999`)
+ .get(`/v5/projects/${projectId}/phases/${phaseId}/products/99999`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -147,7 +147,7 @@ describe('Phase Products', () => {
it('should return 1 phase when user have project permission (customer)', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .get(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -157,7 +157,7 @@ describe('Phase Products', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.name.should.be.eql(body.name);
resJson.type.should.be.eql(body.type);
@@ -171,7 +171,7 @@ describe('Phase Products', () => {
it('should return 1 phase when user have project permission (copilot)', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .get(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -181,7 +181,7 @@ describe('Phase Products', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.name.should.be.eql(body.name);
resJson.type.should.be.eql(body.type);
diff --git a/src/routes/phaseProducts/list-db.js b/src/routes/phaseProducts/list-db.js
deleted file mode 100644
index d0eab2f5..00000000
--- a/src/routes/phaseProducts/list-db.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import _ from 'lodash';
-import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../util';
-import models from '../../models';
-
-const permissions = tcMiddleware.permissions;
-
-module.exports = [
- permissions('project.view'),
- async (req, res, next) => {
- const projectId = _.parseInt(req.params.projectId);
- const phaseId = _.parseInt(req.params.phaseId);
-
- // check if the project and phase are exist
- try {
- const countProject = await models.Project.count({ where: { id: projectId } });
- if (countProject === 0) {
- const apiErr = new Error(`active project not found for project id ${projectId}`);
- apiErr.status = 404;
- throw apiErr;
- }
-
- const countPhase = await models.ProjectPhase.count({ where: { id: phaseId } });
- if (countPhase === 0) {
- const apiErr = new Error(`active project phase not found for id ${phaseId}`);
- apiErr.status = 404;
- throw apiErr;
- }
- } catch (err) {
- return next(err);
- }
-
- const parameters = {
- projectId,
- phaseId,
- };
-
- try {
- const { rows, count } = await models.PhaseProduct.search(parameters, req.log);
- return res.json(util.wrapResponse(req.id, rows, count));
- } catch (err) {
- return next(err);
- }
- },
-];
diff --git a/src/routes/phaseProducts/list-db.spec.js b/src/routes/phaseProducts/list-db.spec.js
deleted file mode 100644
index eb32119c..00000000
--- a/src/routes/phaseProducts/list-db.spec.js
+++ /dev/null
@@ -1,188 +0,0 @@
-/* eslint-disable no-unused-expressions */
-import _ from 'lodash';
-import request from 'supertest';
-import chai from 'chai';
-import server from '../../app';
-import models from '../../models';
-import testUtil from '../../tests/util';
-
-const should = chai.should();
-
-const body = {
- name: 'test phase product',
- type: 'product1',
- estimatedPrice: 20.0,
- actualPrice: 1.23456,
- details: {
- message: 'This can be any json',
- },
- createdBy: 1,
- updatedBy: 1,
-};
-
-describe('Phase Products', () => {
- let projectId;
- let phaseId;
- let project;
- const memberUser = {
- handle: testUtil.getDecodedToken(testUtil.jwts.member).handle,
- userId: testUtil.getDecodedToken(testUtil.jwts.member).userId,
- firstName: 'fname',
- lastName: 'lName',
- email: 'some@abc.com',
- };
- const copilotUser = {
- handle: testUtil.getDecodedToken(testUtil.jwts.copilot).handle,
- userId: testUtil.getDecodedToken(testUtil.jwts.copilot).userId,
- firstName: 'fname',
- lastName: 'lName',
- email: 'some@abc.com',
- };
- before(function beforeHook(done) {
- this.timeout(10000);
- // mocks
- testUtil.clearDb()
- .then(() => {
- models.Project.create({
- type: 'generic',
- billingAccountId: 1,
- name: 'test1',
- description: 'test project1',
- status: 'draft',
- details: {},
- createdBy: 1,
- updatedBy: 1,
- lastActivityAt: 1,
- lastActivityUserId: '1',
- }).then((p) => {
- projectId = p.id;
- project = p.toJSON();
- // create members
- models.ProjectMember.bulkCreate([{
- id: 1,
- userId: copilotUser.userId,
- projectId,
- role: 'copilot',
- isPrimary: false,
- createdBy: 1,
- updatedBy: 1,
- }, {
- id: 2,
- userId: memberUser.userId,
- projectId,
- role: 'customer',
- isPrimary: true,
- createdBy: 1,
- updatedBy: 1,
- }]).then(() => {
- models.ProjectPhase.create({
- name: 'test project phase',
- status: 'active',
- startDate: '2018-05-15T00:00:00Z',
- endDate: '2018-05-15T12:00:00Z',
- budget: 20.0,
- progress: 1.23456,
- details: {
- message: 'This can be any json',
- },
- createdBy: 1,
- updatedBy: 1,
- projectId,
- }).then((phase) => {
- phaseId = phase.id;
- _.assign(body, { phaseId, projectId });
- project.lastActivityAt = 1;
- project.phases = [phase.toJSON()];
-
- models.PhaseProduct.create(body).then((product) => {
- project.phases[0].products = [product.toJSON()];
- project.lastActivityAt = 1;
- done();
- });
- });
- });
- });
- });
- });
-
- after((done) => {
- testUtil.clearDb(done);
- });
-
- describe('GET /projects/{id}/phases/{phaseId}/products/db', () => {
- it('should return 403 when user have no permission (non team member)', (done) => {
- request(server)
- .get(`/v4/projects/${projectId}/phases/${phaseId}/products/db`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.member2}`,
- })
- .send({ param: body })
- .expect('Content-Type', /json/)
- .expect(403, done);
- });
-
- it('should return 404 when no project with specific projectId', (done) => {
- request(server)
- .get(`/v4/projects/999/phases/${phaseId}/products/db`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.manager}`,
- })
- .send({ param: body })
- .expect('Content-Type', /json/)
- .expect(404, done);
- });
-
- it('should return 404 when no phase with specific phaseId', (done) => {
- request(server)
- .get(`/v4/projects/${projectId}/phases/99999/products/db`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.manager}`,
- })
- .send({ param: body })
- .expect('Content-Type', /json/)
- .expect(404, done);
- });
-
- it('should return 1 phase when user have project permission (customer)', (done) => {
- request(server)
- .get(`/v4/projects/${projectId}/phases/${phaseId}/products/db`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.member}`,
- })
- .send({ param: body })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const resJson = res.body.result.content;
- should.exist(resJson);
- resJson.should.have.lengthOf(1);
- done();
- }
- });
- });
-
- it('should return 1 phase when user have project permission (copilot)', (done) => {
- request(server)
- .get(`/v4/projects/${projectId}/phases/${phaseId}/products/db`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.copilot}`,
- })
- .send({ param: body })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const resJson = res.body.result.content;
- should.exist(resJson);
- resJson.should.have.lengthOf(1);
- done();
- }
- });
- });
- });
-});
diff --git a/src/routes/phaseProducts/list.js b/src/routes/phaseProducts/list.js
index 5899c425..842a0891 100644
--- a/src/routes/phaseProducts/list.js
+++ b/src/routes/phaseProducts/list.js
@@ -1,14 +1,36 @@
import _ from 'lodash';
-import config from 'config';
import util from '../../util';
+import models from '../../models';
-const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName');
-const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType');
+const permissions = require('tc-core-library-js').middleware.permissions;
-const eClient = util.getElasticSearchClient();
+const retrieveFromDB = async (req, res, next) => {
+ const projectId = _.parseInt(req.params.projectId);
+ const phaseId = _.parseInt(req.params.phaseId);
-const permissions = require('tc-core-library-js').middleware.permissions;
+ // check if the project and phase are exist
+ return models.ProjectPhase.findOne({
+ where: { id: phaseId, projectId },
+ raw: true,
+ }).then((countPhase) => {
+ if (!countPhase) {
+ const apiErr = new Error('project phase not found for project id ' +
+ `${projectId} and phase id ${phaseId}`);
+ apiErr.status = 404;
+ throw apiErr;
+ }
+
+ const parameters = {
+ projectId,
+ phaseId,
+ };
+
+ return models.PhaseProduct.search(parameters, req.log)
+ .then(({ rows }) => res.json(rows));
+ })
+ .catch(err => next(err));
+};
module.exports = [
// check permission
@@ -19,30 +41,37 @@ module.exports = [
const phaseId = _.parseInt(req.params.phaseId);
// Get project from ES
- eClient.get({ index: ES_PROJECT_INDEX, type: ES_PROJECT_TYPE, id: req.params.projectId })
- .then((doc) => {
- if (!doc) {
- const err = new Error(`active project not found for project id ${projectId}`);
- err.status = 404;
- throw err;
+ util.fetchByIdFromES('phaseProducts', {
+ query: {
+ nested: {
+ path: 'phases',
+ query:
+ {
+ filtered: {
+ filter: {
+ bool: {
+ must: [
+ { term: { 'phases.id': phaseId } },
+ { term: { 'phases.projectId': projectId } },
+ ],
+ },
+ },
+ },
+ },
+ inner_hits: {},
+ },
+ },
+ })
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No phase product found in ES');
+ return retrieveFromDB(req, res, next);
}
-
- // Get the phases
- let phases = _.isArray(doc._source.phases) ? doc._source.phases : []; // eslint-disable-line no-underscore-dangle
-
- // Get the phase by id
- phases = _.filter(phases, { id: phaseId });
- if (phases.length <= 0) {
- const err = new Error(`active project phase not found for phase id ${phaseId}`);
- err.status = 404;
- throw err;
- }
-
- // Get the products
- let products = phases[0].products;
- products = _.isArray(products) ? products : []; // eslint-disable-line no-underscore-dangle
-
- res.json(util.wrapResponse(req.id, products, products.length));
+ req.log.debug('phase product found in ES');
+ // Get the phases
+ const phases = data[0].inner_hits.phases.hits.hits[0]._source; // eslint-disable-line no-underscore-dangle
+ const products = _.isArray(phases.products) ? phases.products : [];
+ return res.json(products);
})
.catch(err => next(err));
},
diff --git a/src/routes/phaseProducts/list.spec.js b/src/routes/phaseProducts/list.spec.js
index 8dcd08d2..3a0fc37d 100644
--- a/src/routes/phaseProducts/list.spec.js
+++ b/src/routes/phaseProducts/list.spec.js
@@ -1,7 +1,7 @@
/* eslint-disable no-unused-expressions */
import _ from 'lodash';
import request from 'supertest';
-import sleep from 'sleep';
+// import sleep from 'sleep';
import chai from 'chai';
import config from 'config';
import server from '../../app';
@@ -111,7 +111,7 @@ describe('Phase Products', () => {
body: project,
}).then(() => {
// sleep for some time, let elasticsearch indices be settled
- sleep.sleep(5);
+ // sleep.sleep(5);
done();
});
});
@@ -128,51 +128,51 @@ describe('Phase Products', () => {
describe('GET /projects/{id}/phases/{phaseId}/products', () => {
it('should return 403 when user have no permission (non team member)', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/phases/${phaseId}/products`)
+ .get(`/v5/projects/${projectId}/phases/${phaseId}/products`)
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(403, done);
});
it('should return 404 when no project with specific projectId', (done) => {
request(server)
- .get(`/v4/projects/999/phases/${phaseId}/products`)
+ .get(`/v5/projects/999/phases/${phaseId}/products`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(404, done);
});
it('should return 404 when no phase with specific phaseId', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/phases/99999/products`)
+ .get(`/v5/projects/${projectId}/phases/99999/products`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(404, done);
});
it('should return 1 phase when user have project permission (customer)', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/phases/${phaseId}/products`)
+ .get(`/v5/projects/${projectId}/phases/${phaseId}/products`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(1);
done();
@@ -182,18 +182,18 @@ describe('Phase Products', () => {
it('should return 1 phase when user have project permission (copilot)', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/phases/${phaseId}/products`)
+ .get(`/v5/projects/${projectId}/phases/${phaseId}/products`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(1);
done();
diff --git a/src/routes/phaseProducts/update.js b/src/routes/phaseProducts/update.js
index 5039e658..7ae577da 100644
--- a/src/routes/phaseProducts/update.js
+++ b/src/routes/phaseProducts/update.js
@@ -5,24 +5,22 @@ import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import models from '../../models';
import util from '../../util';
-import { EVENT, ROUTES } from '../../constants';
+import { EVENT, RESOURCES, ROUTES } from '../../constants';
const permissions = tcMiddleware.permissions;
const updatePhaseProductValidation = {
- body: {
- param: Joi.object().keys({
- name: Joi.string().optional(),
- type: Joi.string().optional(),
- templateId: Joi.number().optional(),
- directProjectId: Joi.number().positive().optional(),
- billingAccountId: Joi.number().positive().optional(),
- estimatedPrice: Joi.number().positive().optional(),
- actualPrice: Joi.number().positive().optional(),
- details: Joi.any().optional(),
- }).required(),
- },
+ body: Joi.object().keys({
+ name: Joi.string().optional(),
+ type: Joi.string().optional(),
+ templateId: Joi.number().optional(),
+ directProjectId: Joi.number().positive().optional(),
+ billingAccountId: Joi.number().positive().optional(),
+ estimatedPrice: Joi.number().positive().optional(),
+ actualPrice: Joi.number().positive().optional(),
+ details: Joi.any().optional(),
+ }).required(),
};
@@ -37,7 +35,7 @@ module.exports = [
const phaseId = _.parseInt(req.params.phaseId);
const productId = _.parseInt(req.params.productId);
- const updatedProps = req.body.param;
+ const updatedProps = req.body;
updatedProps.updatedBy = req.authUser.userId;
let previousValue;
@@ -74,14 +72,17 @@ module.exports = [
{ original: previousValue, updated: updatedValue },
{ correlationId: req.id },
);
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED, {
+
+ // emit the event
+ util.sendResourceToKafkaBus(
req,
- original: previousValue,
- updated: updatedValue,
- route: ROUTES.PHASE_PRODUCTS.UPDATE,
- });
+ EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED,
+ RESOURCES.PHASE_PRODUCT,
+ updatedValue,
+ previousValue,
+ ROUTES.PHASE_PRODUCTS.UPDATE);
- res.json(util.wrapResponse(req.id, updated));
+ res.json(updated);
}).catch(err => next(err));
},
];
diff --git a/src/routes/phaseProducts/update.spec.js b/src/routes/phaseProducts/update.spec.js
index 5dcb8771..be892212 100644
--- a/src/routes/phaseProducts/update.spec.js
+++ b/src/routes/phaseProducts/update.spec.js
@@ -7,7 +7,7 @@ import server from '../../app';
import models from '../../models';
import testUtil from '../../tests/util';
import busApi from '../../services/busApi';
-import { BUS_API_EVENT } from '../../constants';
+import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants';
const should = chai.should();
@@ -120,89 +120,87 @@ describe('Phase Products', () => {
describe('PATCH /projects/{id}/phases/{phaseId}/products/{productId}', () => {
it('should return 403 when user have no permission (non team member)', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect('Content-Type', /json/)
.expect(403, done);
});
it('should return 403 when user have no permission (customer)', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect('Content-Type', /json/)
.expect(403, done);
});
it('should return 404 when no project with specific projectId', (done) => {
request(server)
- .patch(`/v4/projects/999/phases/${phaseId}/products/${productId}`)
+ .patch(`/v5/projects/999/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect('Content-Type', /json/)
.expect(404, done);
});
it('should return 404 when no phase with specific phaseId', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/99999/products/${productId}`)
+ .patch(`/v5/projects/${projectId}/phases/99999/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect('Content-Type', /json/)
.expect(404, done);
});
it('should return 404 when no product with specific productId', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/99999`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/99999`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect('Content-Type', /json/)
.expect(404, done);
});
- it('should return 422 when parameters are invalid', (done) => {
+ it('should return 400 when parameters are invalid', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/99999`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/99999`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- estimatedPrice: -15,
- },
+ estimatedPrice: -15,
})
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return updated product when user have permission and parameters are valid', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.name.should.be.eql(updateBody.name);
resJson.type.should.be.eql(updateBody.type);
@@ -216,11 +214,11 @@ describe('Phase Products', () => {
it('should return 200 if requested by admin', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect('Content-Type', /json/)
.expect(200)
.end(done);
@@ -237,11 +235,11 @@ describe('Phase Products', () => {
updatedBy: 1,
}).then(() => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect('Content-Type', /json/)
.expect(200)
.end(done);
@@ -250,11 +248,11 @@ describe('Phase Products', () => {
it('should return 403 if requested by manager which is not a member', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect('Content-Type', /json/)
.expect(403)
.end(done);
@@ -265,11 +263,11 @@ describe('Phase Products', () => {
where: { userId: testUtil.userIds.copilot, projectId },
}).then(() => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect('Content-Type', /json/)
.expect(403)
.end(done);
@@ -293,16 +291,14 @@ describe('Phase Products', () => {
sandbox.restore();
});
- it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when name updated', (done) => {
+ it('should send correct BUS API messages when name updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- name: 'new name',
- },
+ name: 'new name',
})
.expect('Content-Type', /json/)
.expect(200)
@@ -311,30 +307,36 @@ describe('Phase Products', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE_PRODUCT,
+ name: 'new name',
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
projectId: 1,
projectName: 'test1',
projectUrl: 'https://local.topcoder-dev.com/projects/1',
userId: 40051332,
initiatorUserId: 40051332,
})).should.be.true;
+
done();
});
}
});
});
- it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when estimatedPrice updated', (done) => {
+ it('should send correct BUS API messages when estimatedPrice updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- estimatedPrice: 123,
- },
+ estimatedPrice: 123,
})
.expect('Content-Type', /json/)
.expect(200)
@@ -343,30 +345,36 @@ describe('Phase Products', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE_PRODUCT,
+ estimatedPrice: 123,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
projectId: 1,
projectName: 'test1',
projectUrl: 'https://local.topcoder-dev.com/projects/1',
userId: 40051332,
initiatorUserId: 40051332,
})).should.be.true;
+
done();
});
}
});
});
- it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when actualPrice updated', (done) => {
+ it('should send correct BUS API messages when actualPrice updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- actualPrice: 123,
- },
+ actualPrice: 123,
})
.expect('Content-Type', /json/)
.expect(200)
@@ -375,30 +383,36 @@ describe('Phase Products', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE_PRODUCT,
+ actualPrice: 123,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
projectId: 1,
projectName: 'test1',
projectUrl: 'https://local.topcoder-dev.com/projects/1',
userId: 40051332,
initiatorUserId: 40051332,
})).should.be.true;
+
done();
});
}
});
});
- it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when details updated', (done) => {
+ it('should send correct BUS API messages when details updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- details: 'something',
- },
+ details: 'something',
})
.expect('Content-Type', /json/)
.expect(200)
@@ -407,31 +421,38 @@ describe('Phase Products', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledTwice.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PRODUCT_SPECIFICATION_MODIFIED);
- createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
+ createEventSpy.callCount.should.be.eql(3);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE_PRODUCT,
+ details: 'something',
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PRODUCT_SPECIFICATION_MODIFIED)
+ .should.be.true;
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
projectId: 1,
projectName: 'test1',
projectUrl: 'https://local.topcoder-dev.com/projects/1',
userId: 40051332,
initiatorUserId: 40051332,
})).should.be.true;
+
done();
});
}
});
});
- it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when type updated', (done) => {
+ it('should send correct BUS API messages when type updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- type: 'another type',
- },
+ type: 'another type',
})
.expect('Content-Type', /json/)
.expect(200)
@@ -440,7 +461,13 @@ describe('Phase Products', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.notCalled.should.be.true;
+ createEventSpy.callCount.should.be.eql(1);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE_PRODUCT,
+ type: 'another type',
+ })).should.be.true;
+
done();
});
}
diff --git a/src/routes/phases/create.js b/src/routes/phases/create.js
index e4a9b250..080e1915 100644
--- a/src/routes/phases/create.js
+++ b/src/routes/phases/create.js
@@ -5,29 +5,27 @@ import Sequelize from 'sequelize';
import models from '../../models';
import util from '../../util';
-import { EVENT, TIMELINE_REFERENCES } from '../../constants';
+import { EVENT, RESOURCES, TIMELINE_REFERENCES } from '../../constants';
const permissions = require('tc-core-library-js').middleware.permissions;
const addProjectPhaseValidations = {
- body: {
- param: Joi.object().keys({
- name: Joi.string().required(),
- description: Joi.string().optional(),
- requirements: Joi.string().optional(),
- status: Joi.string().required(),
- startDate: Joi.date().optional(),
- endDate: Joi.date().optional(),
- duration: Joi.number().min(0).optional(),
- budget: Joi.number().min(0).optional(),
- spentBudget: Joi.number().min(0).optional(),
- progress: Joi.number().min(0).optional(),
- details: Joi.any().optional(),
- order: Joi.number().integer().optional(),
- productTemplateId: Joi.number().integer().positive().optional(),
- }).required(),
- },
+ body: Joi.object().keys({
+ name: Joi.string().required(),
+ description: Joi.string().optional(),
+ requirements: Joi.string().optional(),
+ status: Joi.string().required(),
+ startDate: Joi.date().optional(),
+ endDate: Joi.date().optional(),
+ duration: Joi.number().min(0).optional(),
+ budget: Joi.number().min(0).optional(),
+ spentBudget: Joi.number().min(0).optional(),
+ progress: Joi.number().min(0).optional(),
+ details: Joi.any().optional(),
+ order: Joi.number().integer().optional(),
+ productTemplateId: Joi.number().integer().positive().optional(),
+ }).required(),
};
module.exports = [
@@ -37,7 +35,7 @@ module.exports = [
permissions('project.addProjectPhase'),
// do the real work
(req, res, next) => {
- const data = req.body.param;
+ const data = req.body;
// default values
const projectId = _.parseInt(req.params.projectId);
_.assign(data, {
@@ -47,6 +45,7 @@ module.exports = [
});
let newProjectPhase = null;
+ let otherUpdated = null;
models.sequelize.transaction(() => {
req.log.debug('Create Phase - Starting transaction');
return models.Project.findOne({
@@ -60,7 +59,7 @@ module.exports = [
}
if (data.startDate !== null && data.endDate !== null && data.startDate > data.endDate) {
const err = new Error('startDate must not be after endDate.');
- err.status = 422;
+ err.status = 400;
throw err;
}
return models.ProjectPhase
@@ -91,17 +90,32 @@ module.exports = [
},
});
})
- .then(() => {
+ .then((updatedCount) => {
+ if (updatedCount) {
+ return models.ProjectPhase.findAll({
+ where: {
+ projectId,
+ id: { $ne: newProjectPhase.id },
+ order: { $gte: newProjectPhase.order },
+ },
+ order: [['updatedAt', 'DESC']],
+ limit: updatedCount[0],
+ });
+ }
+ return Promise.resolve();
+ })
+ .then((_otherUpdated) => {
+ otherUpdated = _otherUpdated || [];
if (_.isNil(data.productTemplateId)) {
return Promise.resolve();
}
// Get the product template
- return models.ProductTemplate.findById(data.productTemplateId)
+ return models.ProductTemplate.findByPk(data.productTemplateId)
.then((productTemplate) => {
if (!productTemplate) {
const err = new Error(`Product template does not exist with id = ${data.productTemplateId}`);
- err.status = 422;
+ err.status = 400;
throw err;
}
@@ -130,10 +144,35 @@ module.exports = [
{ added: newProjectPhase, route: TIMELINE_REFERENCES.PHASE },
{ correlationId: req.id },
);
- req.log.debug('Sending event to Kafka bus for project phase %d', newProjectPhase.id);
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED, { req, created: newProjectPhase });
- res.status(201).json(util.wrapResponse(req.id, newProjectPhase, 1, 201));
+ // NOTE So far this logic is implemented in RabbitMQ handler of PROJECT_PHASE_UPDATED
+ // Even though we send this event to the Kafka, the "project-processor-es" shouldn't process it.
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED,
+ RESOURCES.PHASE,
+ newProjectPhase);
+
+ // NOTE So far this logic is implemented in RabbitMQ handler of PROJECT_PHASE_UPDATED
+ // Even though we send these events to the Kafka, the "project-processor-es" shouldn't process them.
+ //
+ // We don't process these event in "project-processor-es"
+ // because it will make 'version conflict' error in ES.
+ // The order of the other milestones need to be updated in the PROJECT_PHASE_UPDATED event handler
+ _.map(otherUpdated, phase =>
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED,
+ RESOURCES.PHASE,
+ _.assign(_.pick(phase.toJSON(), 'id', 'order', 'updatedBy', 'updatedAt')),
+ // Pass the same object as original phase even though, the order has changed.
+ // So far we don't use the order so it's ok. But in general, we should pass
+ // the original phases. <- TODO
+ _.assign(_.pick(phase.toJSON(), 'id', 'order', 'updatedBy', 'updatedAt'))),
+ true, // don't send event to Notification Service as the main event here is updating one phase
+ );
+
+ res.status(201).json(newProjectPhase);
})
.catch((err) => {
util.handleError('Error creating project phase', err, req, next);
diff --git a/src/routes/phases/create.spec.js b/src/routes/phases/create.spec.js
index 9b587ed6..106cac7a 100644
--- a/src/routes/phases/create.spec.js
+++ b/src/routes/phases/create.spec.js
@@ -12,14 +12,14 @@ import messageService from '../../services/messageService';
import RabbitMQService from '../../services/rabbitmq';
import mockRabbitMQ from '../../tests/mockRabbitMQ';
import {
- BUS_API_EVENT,
+ BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT,
} from '../../constants';
+const should = chai.should();
+
const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName');
const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType');
-const should = chai.should();
-
const body = {
name: 'test project phase',
description: 'test project phase description',
@@ -83,7 +83,7 @@ describe('Project Phases', () => {
.then(() => models.Project.create(project).then((p) => {
projectId = p.id;
projectName = p.name;
- // create members
+ // create members
return models.ProjectMember.bulkCreate([{
id: 1,
userId: copilotUser.userId,
@@ -145,116 +145,116 @@ describe('Project Phases', () => {
describe('POST /projects/{id}/phases/', () => {
it('should return 403 if user does not have permissions (non team member)', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/phases/`)
+ .post(`/v5/projects/${projectId}/phases/`)
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(403, done);
});
it('should return 403 if user does not have permissions (customer)', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/phases/`)
+ .post(`/v5/projects/${projectId}/phases/`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(403, done);
});
- it('should return 422 when name not provided', (done) => {
+ it('should return 400 when name not provided', (done) => {
const reqBody = _.cloneDeep(body);
delete reqBody.name;
request(server)
- .post(`/v4/projects/${projectId}/phases/`)
+ .post(`/v5/projects/${projectId}/phases/`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: reqBody })
+ .send(reqBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 when status not provided', (done) => {
+ it('should return 400 when status not provided', (done) => {
const reqBody = _.cloneDeep(body);
delete reqBody.status;
request(server)
- .post(`/v4/projects/${projectId}/phases/`)
+ .post(`/v5/projects/${projectId}/phases/`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: reqBody })
+ .send(reqBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 when startDate > endDate', (done) => {
+ it('should return 400 when startDate > endDate', (done) => {
const reqBody = _.cloneDeep(body);
reqBody.startDate = '2018-05-16T12:00:00';
request(server)
- .post(`/v4/projects/${projectId}/phases/`)
+ .post(`/v5/projects/${projectId}/phases/`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: reqBody })
+ .send(reqBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 when budget is negative', (done) => {
+ it('should return 400 when budget is negative', (done) => {
const reqBody = _.cloneDeep(body);
reqBody.budget = -20;
request(server)
- .post(`/v4/projects/${projectId}/phases/`)
+ .post(`/v5/projects/${projectId}/phases/`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: reqBody })
+ .send(reqBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 when progress is negative', (done) => {
+ it('should return 400 when progress is negative', (done) => {
const reqBody = _.cloneDeep(body);
reqBody.progress = -20;
request(server)
- .post(`/v4/projects/${projectId}/phases/`)
+ .post(`/v5/projects/${projectId}/phases/`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: reqBody })
+ .send(reqBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 404 when project is not found', (done) => {
request(server)
- .post('/v4/projects/99999/phases/')
+ .post('/v5/projects/99999/phases/')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(404, done);
});
it('should return 201 if payload is valid', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/phases/`)
+ .post(`/v5/projects/${projectId}/phases/`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
validatePhase(resJson, body);
done();
}
@@ -268,18 +268,18 @@ describe('Project Phases', () => {
bodyWithZeros.budget = 0.0;
bodyWithZeros.progress = 0.0;
request(server)
- .post(`/v4/projects/${projectId}/phases/`)
+ .post(`/v5/projects/${projectId}/phases/`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: bodyWithZeros })
+ .send(bodyWithZeros)
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
validatePhase(resJson, bodyWithZeros);
done();
}
@@ -288,18 +288,18 @@ describe('Project Phases', () => {
it('should return 201 if payload has order specified', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/phases/`)
+ .post(`/v5/projects/${projectId}/phases/`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: _.assign({ order: 1 }, body) })
+ .send(_.assign({ order: 1 }, body))
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
validatePhase(resJson, body);
resJson.order.should.be.eql(1);
@@ -307,15 +307,15 @@ describe('Project Phases', () => {
// Create second phase
request(server)
- .post(`/v4/projects/${projectId}/phases/`)
+ .post(`/v5/projects/${projectId}/phases/`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: _.assign({ order: 1 }, body) })
+ .send(_.assign({ order: 1 }, body))
.expect('Content-Type', /json/)
.expect(201)
.end((err2, res2) => {
- const resJson2 = res2.body.result.content;
+ const resJson2 = res2.body;
validatePhase(resJson2, body);
resJson2.order.should.be.eql(1);
@@ -331,18 +331,18 @@ describe('Project Phases', () => {
it('should return 201 if payload has productTemplateId specified', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/phases/`)
+ .post(`/v5/projects/${projectId}/phases/`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: _.assign({ productTemplateId }, body) })
+ .send(_.assign({ productTemplateId }, body))
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
validatePhase(resJson, body);
resJson.products.should.have.length(1);
@@ -359,11 +359,11 @@ describe('Project Phases', () => {
it('should return 201 if requested by admin', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/phases/`)
+ .post(`/v5/projects/${projectId}/phases/`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(201)
.end(done);
@@ -380,11 +380,11 @@ describe('Project Phases', () => {
updatedBy: 1,
}).then(() => {
request(server)
- .post(`/v4/projects/${projectId}/phases/`)
+ .post(`/v5/projects/${projectId}/phases/`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(201)
.end(done);
@@ -393,11 +393,11 @@ describe('Project Phases', () => {
it('should return 403 if requested by manager which is not a member', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/phases/`)
+ .post(`/v5/projects/${projectId}/phases/`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(403)
.end(done);
@@ -408,11 +408,11 @@ describe('Project Phases', () => {
where: { userId: testUtil.userIds.copilot, projectId },
}).then(() => {
request(server)
- .post(`/v4/projects/${projectId}/phases/`)
+ .post(`/v5/projects/${projectId}/phases/`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(403)
.end(done);
@@ -436,13 +436,13 @@ describe('Project Phases', () => {
sandbox.restore();
});
- it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when phase added', (done) => {
+ it('should send correct BUS API messages when phase added', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/phases/`)
+ .post(`/v5/projects/${projectId}/phases/`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(201)
.end((err) => {
@@ -450,14 +450,26 @@ describe('Project Phases', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, sinon.match({
+ resource: RESOURCES.PHASE,
+ name: body.name,
+ status: body.status,
+ budget: body.budget,
+ progress: body.progress,
+ projectId,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
projectId,
projectName,
projectUrl: `https://local.topcoder-dev.com/projects/${projectId}`,
userId: 40051332,
initiatorUserId: 40051332,
})).should.be.true;
+
done();
});
}
@@ -470,12 +482,12 @@ describe('Project Phases', () => {
let publishSpy;
let sandbox;
- before(async (done) => {
+ before((done) => {
// Wait for 500ms in order to wait for createEvent calls from previous tests to complete
testUtil.wait(done);
});
- beforeEach(async (done) => {
+ beforeEach(async () => {
sandbox = sinon.sandbox.create();
server.services.pubsub = new RabbitMQService(server.logger);
@@ -496,11 +508,11 @@ describe('Project Phases', () => {
},
});
- testUtil.wait(() => {
+ return new Promise(resolve => setTimeout(() => {
publishSpy = sandbox.spy(server.services.pubsub, 'publish');
createMessageSpy = sandbox.spy(messageService, 'createTopic');
- done();
- });
+ resolve();
+ }, 500));
});
afterEach(() => {
@@ -515,24 +527,16 @@ describe('Project Phases', () => {
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
post: () => Promise.resolve({
status: 200,
- data: {
- id: 'requesterId',
- version: 'v3',
- result: {
- success: true,
- status: 200,
- content: {},
- },
- },
+ data: {},
}),
});
sandbox.stub(messageService, 'getClient', () => mockHttpClient);
request(server)
- .post(`/v4/projects/${projectId}/phases/`)
+ .post(`/v5/projects/${projectId}/phases/`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(201)
.end((err) => {
diff --git a/src/routes/phases/delete.js b/src/routes/phases/delete.js
index bbe270eb..5d1657bf 100644
--- a/src/routes/phases/delete.js
+++ b/src/routes/phases/delete.js
@@ -3,7 +3,8 @@
import _ from 'lodash';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import models from '../../models';
-import { EVENT, TIMELINE_REFERENCES } from '../../constants';
+import util from '../../util';
+import { EVENT, RESOURCES, TIMELINE_REFERENCES } from '../../constants';
const permissions = tcMiddleware.permissions;
@@ -43,7 +44,14 @@ module.exports = [
{ deleted, route: TIMELINE_REFERENCES.PHASE },
{ correlationId: req.id },
);
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED, { req, deleted });
+
+ // emit event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED,
+ RESOURCES.PHASE,
+ deleted.toJSON(),
+ );
res.status(204).json({});
}).catch(err => next(err));
diff --git a/src/routes/phases/delete.spec.js b/src/routes/phases/delete.spec.js
index b6a0f042..e619ed79 100644
--- a/src/routes/phases/delete.spec.js
+++ b/src/routes/phases/delete.spec.js
@@ -13,8 +13,12 @@ import RabbitMQService from '../../services/rabbitmq';
import mockRabbitMQ from '../../tests/mockRabbitMQ';
import {
BUS_API_EVENT,
+ RESOURCES,
+ CONNECT_NOTIFICATION_EVENT,
} from '../../constants';
+const should = chai.should(); // eslint-disable-line no-unused-vars
+
const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName');
const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType');
@@ -34,14 +38,8 @@ const expectAfterDelete = (projectId, id, err, next) => {
} else {
chai.assert.isNotNull(res.deletedAt);
chai.assert.isNotNull(res.deletedBy);
-
- request(server)
- .get(`/v4/projects/${projectId}/phases/${id}`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.admin}`,
- })
- .expect(404, next);
}
+ next();
}), 500);
};
const body = {
@@ -60,8 +58,8 @@ const body = {
describe('Project Phases', () => {
let projectId;
- let projectName;
let phaseId;
+ let projectName;
const memberUser = {
handle: testUtil.getDecodedToken(testUtil.jwts.member).handle,
userId: testUtil.getDecodedToken(testUtil.jwts.member).userId,
@@ -139,7 +137,7 @@ describe('Project Phases', () => {
describe('DELETE /projects/{projectId}/phases/{phaseId}', () => {
it('should return 403 if user does not have permissions (non team member)', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .delete(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -149,7 +147,7 @@ describe('Project Phases', () => {
it('should return 403 if user does not have permissions (customer)', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .delete(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -159,7 +157,7 @@ describe('Project Phases', () => {
it('should return 404 when no project with specific projectId', (done) => {
request(server)
- .delete(`/v4/projects/999/phases/${phaseId}`)
+ .delete(`/v5/projects/999/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -169,7 +167,7 @@ describe('Project Phases', () => {
it('should return 404 when no phase with specific phaseId', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/phases/999`)
+ .delete(`/v5/projects/${projectId}/phases/999`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -179,7 +177,7 @@ describe('Project Phases', () => {
it('should return 204 when user have project permission', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .delete(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -189,7 +187,7 @@ describe('Project Phases', () => {
it('should return 204 if requested by admin', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .delete(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -208,7 +206,7 @@ describe('Project Phases', () => {
updatedBy: 1,
}).then(() => {
request(server)
- .delete(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .delete(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -219,7 +217,7 @@ describe('Project Phases', () => {
it('should return 403 if requested by manager which is not a member', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .delete(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -232,7 +230,7 @@ describe('Project Phases', () => {
where: { userId: testUtil.userIds.copilot, projectId },
}).then(() => {
request(server)
- .delete(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .delete(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -258,9 +256,9 @@ describe('Project Phases', () => {
sandbox.restore();
});
- it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when phase removed', (done) => {
+ it('should send correct BUS API messages when phase removed', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .delete(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -270,14 +268,22 @@ describe('Project Phases', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_DELETED, sinon.match({
+ resource: RESOURCES.PHASE,
+ id: phaseId,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
projectId,
projectName,
projectUrl: `https://local.topcoder-dev.com/projects/${projectId}`,
userId: 40051332,
initiatorUserId: 40051332,
})).should.be.true;
+
done();
});
}
@@ -291,12 +297,12 @@ describe('Project Phases', () => {
let publishSpy;
let sandbox;
- before(async (done) => {
+ before((done) => {
// Wait for 500ms in order to wait for createEvent calls from previous tests to complete
testUtil.wait(done);
});
- beforeEach(async (done) => {
+ beforeEach(async () => {
sandbox = sinon.sandbox.create();
server.services.pubsub = new RabbitMQService(server.logger);
@@ -317,13 +323,13 @@ describe('Project Phases', () => {
},
});
- testUtil.wait(() => {
+ return new Promise(resolve => setTimeout(() => {
publishSpy = sandbox.spy(server.services.pubsub, 'publish');
deleteTopicSpy = sandbox.spy(messageService, 'deleteTopic');
deletePostsSpy = sandbox.spy(messageService, 'deletePosts');
sandbox.stub(messageService, 'getTopicByTag', () => Promise.resolve(topic));
- done();
- });
+ resolve();
+ }, 500));
});
afterEach(() => {
@@ -340,7 +346,7 @@ describe('Project Phases', () => {
});
sandbox.stub(messageService, 'getClient', () => mockHttpClient);
request(server)
- .delete(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .delete(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
diff --git a/src/routes/phases/get.js b/src/routes/phases/get.js
index cee2c3f2..7ecb77a0 100644
--- a/src/routes/phases/get.js
+++ b/src/routes/phases/get.js
@@ -11,21 +11,51 @@ module.exports = [
(req, res, next) => {
const projectId = _.parseInt(req.params.projectId);
const phaseId = _.parseInt(req.params.phaseId);
- return models.ProjectPhase
- .findOne({
- where: { id: phaseId, projectId },
- raw: true,
- })
- .then((phase) => {
- if (!phase) {
- // handle 404
- const err = new Error('project phase not found for project id ' +
- `${projectId} and phase id ${phaseId}`);
- err.status = 404;
- throw err;
- }
- res.json(util.wrapResponse(req.id, phase));
- })
- .catch(err => next(err));
+
+ util.fetchByIdFromES('phases', {
+ query: {
+ nested: {
+ path: 'phases',
+ query:
+ {
+ filtered: {
+ filter: {
+ bool: {
+ must: [
+ { term: { 'phases.id': phaseId } },
+ { term: { 'phases.projectId': projectId } },
+ ],
+ },
+ },
+ },
+ },
+ inner_hits: {},
+ },
+ },
+ })
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No phase found in ES');
+ return models.ProjectPhase
+ .findOne({
+ where: { id: phaseId, projectId },
+ raw: true,
+ })
+ .then((phase) => {
+ if (!phase) {
+ // handle 404
+ const err = new Error('project phase not found for project id ' +
+ `${projectId} and phase id ${phaseId}`);
+ err.status = 404;
+ throw err;
+ }
+ res.json(phase);
+ })
+ .catch(err => next(err));
+ }
+ req.log.debug('phase found in ES');
+ return res.json(data[0].inner_hits.phases.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle
+ })
+ .catch(next);
},
];
diff --git a/src/routes/phases/get.spec.js b/src/routes/phases/get.spec.js
index f6cde663..e448f96b 100644
--- a/src/routes/phases/get.spec.js
+++ b/src/routes/phases/get.spec.js
@@ -42,6 +42,7 @@ describe('Project Phases', () => {
before((done) => {
// mocks
testUtil.clearDb()
+ .then(() => testUtil.clearES())
.then(() => {
models.Project.create({
type: 'generic',
@@ -91,7 +92,7 @@ describe('Project Phases', () => {
describe('GET /projects/{projectId}/phases/{phaseId}', () => {
it('should return 403 when user have no permission (non team member)', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .get(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -101,7 +102,7 @@ describe('Project Phases', () => {
it('should return 404 when no project with specific projectId', (done) => {
request(server)
- .get(`/v4/projects/999/phases/${phaseId}`)
+ .get(`/v5/projects/999/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -111,7 +112,7 @@ describe('Project Phases', () => {
it('should return 404 when no phase with specific phaseId', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/phases/999`)
+ .get(`/v5/projects/${projectId}/phases/999`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -121,7 +122,7 @@ describe('Project Phases', () => {
it('should return 1 phase when user have project permission (customer)', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .get(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -131,7 +132,7 @@ describe('Project Phases', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.name.should.be.eql('test project phase');
resJson.status.should.be.eql('active');
@@ -145,7 +146,7 @@ describe('Project Phases', () => {
it('should return 1 phase when user have project permission (copilot)', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .get(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -155,7 +156,7 @@ describe('Project Phases', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.name.should.be.eql('test project phase');
resJson.status.should.be.eql('active');
diff --git a/src/routes/phases/list-db.js b/src/routes/phases/list-db.js
deleted file mode 100644
index 60337cb1..00000000
--- a/src/routes/phases/list-db.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import _ from 'lodash';
-import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../util';
-import models from '../../models';
-
-const PHASE_ATTRIBUTES = _.keys(models.ProjectPhase.rawAttributes);
-const permissions = tcMiddleware.permissions;
-
-module.exports = [
- permissions('project.view'),
- async (req, res, next) => {
- const projectId = _.parseInt(req.params.projectId);
- // check if the project is exist
- try {
- const count = await models.Project.count({ where: { id: projectId } });
- if (count === 0) {
- const apiErr = new Error(`active project not found for project id ${projectId}`);
- apiErr.status = 404;
- throw apiErr;
- }
- } catch (err) {
- return next(err);
- }
-
- // Parse the fields string to determine what fields are to be returned
- const rawFields = req.query.fields ? decodeURIComponent(req.query.fields).split(',') : PHASE_ATTRIBUTES;
- let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'startDate';
- if (sort && sort.indexOf(' ') === -1) {
- sort += ' asc';
- }
- const sortableProps = [
- 'startDate asc', 'startDate desc',
- 'endDate asc', 'endDate desc',
- 'status asc', 'status desc',
- 'order asc', 'order desc',
- ];
- if (sort && _.indexOf(sortableProps, sort) < 0) {
- return util.handleError('Invalid sort criteria', null, req, next);
- }
-
- const sortParameters = sort.split(' ');
-
- const fields = _.union(
- _.intersection(rawFields, [...PHASE_ATTRIBUTES, 'products']),
- ['id'], // required fields
- );
-
- const parameters = {
- projectId,
- sortField: sortParameters[0],
- sortType: sortParameters[1],
- fields,
- };
-
- try {
- const { rows, count } = await models.ProjectPhase.search(parameters, req.log);
- return res.json(util.wrapResponse(req.id, rows, count));
- } catch (err) {
- return next(err);
- }
- },
-];
diff --git a/src/routes/phases/list-db.spec.js b/src/routes/phases/list-db.spec.js
deleted file mode 100644
index d5e7a3d9..00000000
--- a/src/routes/phases/list-db.spec.js
+++ /dev/null
@@ -1,205 +0,0 @@
-/* eslint-disable no-unused-expressions */
-import _ from 'lodash';
-import request from 'supertest';
-import chai from 'chai';
-import server from '../../app';
-import models from '../../models';
-import testUtil from '../../tests/util';
-
-const should = chai.should();
-
-const body = {
- name: 'test project phase',
- status: 'active',
- startDate: '2018-05-15T00:00:00Z',
- endDate: '2018-05-15T12:00:00Z',
- budget: 20.0,
- progress: 1.23456,
- details: {
- message: 'This can be any json',
- },
- createdBy: 1,
- updatedBy: 1,
-};
-
-const productBody = {
- name: 'test phase product',
- type: 'product1',
- estimatedPrice: 20.0,
- actualPrice: 1.23456,
- details: {
- message: 'This can be any json',
- },
- createdBy: 1,
- updatedBy: 1,
-};
-
-describe('Project Phases', () => {
- let projectId;
- let project;
- const memberUser = {
- handle: testUtil.getDecodedToken(testUtil.jwts.member).handle,
- userId: testUtil.getDecodedToken(testUtil.jwts.member).userId,
- firstName: 'fname',
- lastName: 'lName',
- email: 'some@abc.com',
- };
- const copilotUser = {
- handle: testUtil.getDecodedToken(testUtil.jwts.copilot).handle,
- userId: testUtil.getDecodedToken(testUtil.jwts.copilot).userId,
- firstName: 'fname',
- lastName: 'lName',
- email: 'some@abc.com',
- };
- before(function beforeHook(done) {
- this.timeout(10000);
- // mocks
- testUtil.clearDb()
- .then(() => {
- models.Project.create({
- type: 'generic',
- billingAccountId: 1,
- name: 'test1',
- description: 'test project1',
- status: 'draft',
- details: {},
- createdBy: 1,
- updatedBy: 1,
- lastActivityAt: 1,
- lastActivityUserId: '1',
- }).then((p) => {
- projectId = p.id;
- project = p.toJSON();
- // create members
- models.ProjectMember.bulkCreate([{
- id: 1,
- userId: copilotUser.userId,
- projectId,
- role: 'copilot',
- isPrimary: false,
- createdBy: 1,
- updatedBy: 1,
- }, {
- id: 2,
- userId: memberUser.userId,
- projectId,
- role: 'customer',
- isPrimary: true,
- createdBy: 1,
- updatedBy: 1,
- }]).then(() => {
- _.assign(body, { projectId });
- return models.ProjectPhase.create(body);
- }).then((phase) => {
- project.lastActivityAt = 1;
- project.phases = [phase.toJSON()];
- const phaseId = phase.id;
- _.assign(productBody, { phaseId, projectId });
-
- models.PhaseProduct.create(productBody).then((product) => {
- project.phases[0].products = [product.toJSON()];
- project.lastActivityAt = 1;
- done();
- });
- });
- });
- });
- });
-
- after((done) => {
- testUtil.clearDb(done);
- });
-
- describe('GET /projects/{id}/phases/db', () => {
- it('should return 403 when user have no permission (non team member)', (done) => {
- request(server)
- .get(`/v4/projects/${projectId}/phases/db`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.member2}`,
- })
- .send({ param: body })
- .expect('Content-Type', /json/)
- .expect(403, done);
- });
-
- it('should return 404 when no project with specific projectId', (done) => {
- request(server)
- .get('/v4/projects/999/phases/db')
- .set({
- Authorization: `Bearer ${testUtil.jwts.manager}`,
- })
- .send({ param: body })
- .expect('Content-Type', /json/)
- .expect(404, done);
- });
-
- it('should return 1 phase when user have project permission (customer)', (done) => {
- request(server)
- .get(`/v4/projects/${projectId}/phases/db`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.member}`,
- })
- .send({ param: body })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const resJson = res.body.result.content;
- should.exist(resJson);
- resJson.should.have.lengthOf(1);
- done();
- }
- });
- });
-
- it('should return 1 phase when user have project permission (copilot)', (done) => {
- request(server)
- .get(`/v4/projects/${projectId}/phases/db`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.copilot}`,
- })
- .send({ param: body })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const resJson = res.body.result.content;
- should.exist(resJson);
- resJson.should.have.lengthOf(1);
- done();
- }
- });
- });
-
- it('should return phase populated with 1 product when fields=products is used', (done) => {
- request(server)
- .get(`/v4/projects/${projectId}/phases/db?fields=products`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.copilot}`,
- })
- .send({ param: body })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const resJson = res.body.result.content;
- should.exist(resJson);
- resJson.should.have.lengthOf(1);
- resJson[0].should.have.property('products');
- resJson[0].products.should.be.a('array');
- resJson[0].products.should.have.lengthOf(1);
- resJson[0].products[0].should.have.property('projectId');
- resJson[0].products[0].projectId.should.equal(projectId);
- resJson[0].products[0].name.should.equal(productBody.name);
- done();
- }
- });
- });
- });
-});
diff --git a/src/routes/phases/list.js b/src/routes/phases/list.js
index e0084184..cd6aaabf 100644
--- a/src/routes/phases/list.js
+++ b/src/routes/phases/list.js
@@ -14,7 +14,6 @@ const PHASE_ATTRIBUTES = _.keys(models.ProjectPhase.rawAttributes);
const permissions = tcMiddleware.permissions;
-
module.exports = [
permissions('project.view'),
(req, res, next) => {
@@ -40,17 +39,12 @@ module.exports = [
// Get project from ES
return eClient.get({ index: ES_PROJECT_INDEX, type: ES_PROJECT_TYPE, id: req.params.projectId })
.then((doc) => {
- if (!doc) {
- const err = new Error(`active project not found for project id ${projectId}`);
- err.status = 404;
- throw err;
- }
-
+ req.log.debug('phases found in ES');
// Get the phases
let phases = _.isArray(doc._source.phases) ? doc._source.phases : []; // eslint-disable-line no-underscore-dangle
// Sort
- phases = _.sortBy(phases, [sortColumnAndOrder[0]], [sortColumnAndOrder[1]]);
+ phases = _.orderBy(phases, [sortColumnAndOrder[0]], [sortColumnAndOrder[1]]);
fields = _.intersection(fields, [...PHASE_ATTRIBUTES, 'products']);
if (_.indexOf(fields, 'id') < 0) {
@@ -59,8 +53,46 @@ module.exports = [
phases = _.map(phases, phase => _.pick(phase, fields));
- res.json(util.wrapResponse(req.id, phases, phases.length));
+ res.json(phases);
})
- .catch(err => next(err));
+ .catch((err) => {
+ if (err.status === 404) {
+ req.log.debug('No phases found in ES');
+ // Load the phases
+ return models.Project.findByPk(projectId, {
+ include: [{
+ model: models.ProjectPhase,
+ as: 'phases',
+ order: [['startDate', 'asc']],
+ include: [{
+ model: models.PhaseProduct,
+ as: 'products',
+ }],
+ }],
+ })
+ .then((project) => {
+ if (!project) {
+ const apiErr = new Error(`active project not found for project id ${projectId}`);
+ apiErr.status = 404;
+ return next(apiErr);
+ }
+
+ // Get the phases
+ let phases = _.isArray(project.phases) ? project.phases : [];
+
+ // Sort
+ phases = _.orderBy(phases, [sortColumnAndOrder[0]], [sortColumnAndOrder[1]]);
+
+ fields = _.intersection(fields, [...PHASE_ATTRIBUTES, 'products']);
+ if (_.indexOf(fields, 'id') < 0) {
+ fields.push('id');
+ }
+
+ // Write to response
+ return res.json(_.map(phases, p => _.omit(p.toJSON(), ['deletedAt', 'deletedBy'])));
+ });
+ }
+ return next(err);
+ });
},
];
diff --git a/src/routes/phases/list.spec.js b/src/routes/phases/list.spec.js
index 43a1d14f..815d4a8c 100644
--- a/src/routes/phases/list.spec.js
+++ b/src/routes/phases/list.spec.js
@@ -2,7 +2,7 @@
import _ from 'lodash';
import request from 'supertest';
import config from 'config';
-import sleep from 'sleep';
+// import sleep from 'sleep';
import chai from 'chai';
import server from '../../app';
import models from '../../models';
@@ -48,6 +48,7 @@ describe('Project Phases', () => {
this.timeout(10000);
// mocks
testUtil.clearDb()
+ .then(() => testUtil.clearES())
.then(() => {
models.Project.create({
type: 'generic',
@@ -95,7 +96,7 @@ describe('Project Phases', () => {
body: project,
}).then(() => {
// sleep for some time, let elasticsearch indices be settled
- sleep.sleep(5);
+ // sleep.sleep(5);
done();
});
});
@@ -110,40 +111,40 @@ describe('Project Phases', () => {
describe('GET /projects/{id}/phases/', () => {
it('should return 403 when user have no permission (non team member)', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/phases/`)
+ .get(`/v5/projects/${projectId}/phases/`)
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(403, done);
});
it('should return 404 when no project with specific projectId', (done) => {
request(server)
- .get('/v4/projects/999/phases/')
+ .get('/v5/projects/999/phases/')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(404, done);
});
it('should return 1 phase when user have project permission (customer)', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/phases/`)
+ .get(`/v5/projects/${projectId}/phases/`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(1);
done();
@@ -153,18 +154,18 @@ describe('Project Phases', () => {
it('should return 1 phase when user have project permission (copilot)', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/phases/`)
+ .get(`/v5/projects/${projectId}/phases/`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(1);
done();
diff --git a/src/routes/phases/update.js b/src/routes/phases/update.js
index 9fcfcfba..7bafa010 100644
--- a/src/routes/phases/update.js
+++ b/src/routes/phases/update.js
@@ -6,28 +6,26 @@ import Sequelize from 'sequelize';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import models from '../../models';
import util from '../../util';
-import { EVENT, ROUTES, TIMELINE_REFERENCES } from '../../constants';
+import { EVENT, RESOURCES, TIMELINE_REFERENCES, ROUTES } from '../../constants';
const permissions = tcMiddleware.permissions;
const updateProjectPhaseValidation = {
- body: {
- param: Joi.object().keys({
- name: Joi.string().optional(),
- description: Joi.string().optional(),
- requirements: Joi.string().optional(),
- status: Joi.string().optional(),
- startDate: Joi.date().optional(),
- endDate: Joi.date().optional(),
- duration: Joi.number().min(0).optional(),
- budget: Joi.number().min(0).optional(),
- spentBudget: Joi.number().min(0).optional(),
- progress: Joi.number().min(0).optional(),
- details: Joi.any().optional(),
- order: Joi.number().integer().optional(),
- }).required(),
- },
+ body: Joi.object().keys({
+ name: Joi.string().optional(),
+ description: Joi.string().optional(),
+ requirements: Joi.string().optional(),
+ status: Joi.string().optional(),
+ startDate: Joi.date().optional(),
+ endDate: Joi.date().optional(),
+ duration: Joi.number().min(0).optional(),
+ budget: Joi.number().min(0).optional(),
+ spentBudget: Joi.number().min(0).optional(),
+ progress: Joi.number().min(0).optional(),
+ details: Joi.any().optional(),
+ order: Joi.number().integer().optional(),
+ }).required(),
};
@@ -41,7 +39,7 @@ module.exports = [
const projectId = _.parseInt(req.params.projectId);
const phaseId = _.parseInt(req.params.phaseId);
- const updatedProps = req.body.param;
+ const updatedProps = req.body;
updatedProps.updatedBy = req.authUser.userId;
let previousValue;
@@ -153,20 +151,25 @@ module.exports = [
.then((allPhases) => {
req.log.debug('updated project phase', JSON.stringify(updated, null, 2));
+ const updatedValue = updated.get({ plain: true });
+
// emit original and updated project phase information
req.app.services.pubsub.publish(
EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED,
- { original: previousValue, updated, allPhases, route: TIMELINE_REFERENCES.PHASE },
+ { original: previousValue, updated: updatedValue, allPhases, route: TIMELINE_REFERENCES.PHASE },
{ correlationId: req.id },
);
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, {
+
+ // emit event
+ util.sendResourceToKafkaBus(
req,
- original: previousValue,
- updated: _.clone(updated.get({ plain: true })),
- route: ROUTES.PHASES.UPDATE,
- });
+ EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED,
+ RESOURCES.PHASE,
+ updatedValue,
+ previousValue,
+ ROUTES.PHASES.UPDATE);
- res.json(util.wrapResponse(req.id, updated));
+ res.json(updated);
})
.catch(err => next(err));
},
diff --git a/src/routes/phases/update.spec.js b/src/routes/phases/update.spec.js
index 2db36e68..b8b6910b 100644
--- a/src/routes/phases/update.spec.js
+++ b/src/routes/phases/update.spec.js
@@ -13,6 +13,8 @@ import RabbitMQService from '../../services/rabbitmq';
import mockRabbitMQ from '../../tests/mockRabbitMQ';
import {
BUS_API_EVENT,
+ RESOURCES,
+ CONNECT_NOTIFICATION_EVENT,
} from '../../constants';
const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName');
@@ -153,73 +155,69 @@ describe('Project Phases', () => {
describe('PATCH /projects/{projectId}/phases/{phaseId}', () => {
it('should return 403 if user does not have permissions (non team member)', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect('Content-Type', /json/)
.expect(403, done);
});
it('should return 403 if user does not have permissions (customer)', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect('Content-Type', /json/)
.expect(403, done);
});
it('should return 404 when no project with specific projectId', (done) => {
request(server)
- .patch(`/v4/projects/999/phases/${phaseId}`)
+ .patch(`/v5/projects/999/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect('Content-Type', /json/)
.expect(404, done);
});
it('should return 404 when no phase with specific phaseId', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/999`)
+ .patch(`/v5/projects/${projectId}/phases/999`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect('Content-Type', /json/)
.expect(404, done);
});
- it('should return 422 when parameters are invalid', (done) => {
+ it('should return 400 when parameters are invalid', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- progress: -15,
- },
+ progress: -15,
})
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 400 when startDate > endDate', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- endDate: '2018-05-13T00:00:00Z',
- },
+ endDate: '2018-05-13T00:00:00Z',
})
.expect('Content-Type', /json/)
.expect(400, done);
@@ -227,18 +225,18 @@ describe('Project Phases', () => {
it('should return updated phase when user have permission and parameters are valid', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
validatePhase(resJson, updateBody);
done();
}
@@ -252,18 +250,18 @@ describe('Project Phases', () => {
bodyWithZeros.budget = 0.0;
bodyWithZeros.progress = 0.0;
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: bodyWithZeros })
+ .send(bodyWithZeros)
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
validatePhase(resJson, bodyWithZeros);
done();
}
@@ -272,18 +270,18 @@ describe('Project Phases', () => {
it('should return updated phase if the order is specified', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: _.assign({ order: 1 }, updateBody) })
+ .send(_.assign({ order: 1 }, updateBody))
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
validatePhase(resJson, updateBody);
resJson.order.should.be.eql(1);
@@ -299,11 +297,11 @@ describe('Project Phases', () => {
it('should return 200 if requested by admin', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: _.assign({ order: 1 }, updateBody) })
+ .send(_.assign({ order: 1 }, updateBody))
.expect('Content-Type', /json/)
.expect(200)
.end(done);
@@ -320,11 +318,11 @@ describe('Project Phases', () => {
updatedBy: 1,
}).then(() => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
- .send({ param: _.assign({ order: 1 }, updateBody) })
+ .send(_.assign({ order: 1 }, updateBody))
.expect('Content-Type', /json/)
.expect(200)
.end(done);
@@ -333,11 +331,11 @@ describe('Project Phases', () => {
it('should return 403 if requested by manager which is not a member', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
- .send({ param: _.assign({ order: 1 }, updateBody) })
+ .send(_.assign({ order: 1 }, updateBody))
.expect('Content-Type', /json/)
.expect(403)
.end(done);
@@ -348,11 +346,11 @@ describe('Project Phases', () => {
where: { userId: testUtil.userIds.copilot, projectId },
}).then(() => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: _.assign({ order: 1 }, updateBody) })
+ .send(_.assign({ order: 1 }, updateBody))
.expect('Content-Type', /json/)
.expect(403)
.end(done);
@@ -363,29 +361,30 @@ describe('Project Phases', () => {
let createEventSpy;
const sandbox = sinon.sandbox.create();
+
before((done) => {
// Wait for 500ms in order to wait for createEvent calls from previous tests to complete
testUtil.wait(done);
});
+
beforeEach(() => {
createEventSpy = sandbox.spy(busApi, 'createEvent');
});
+
afterEach(() => {
sandbox.restore();
});
- it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when spentBudget updated', (done) => {
+ it('should send correct BUS API messages when spentBudget updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- spentBudget: 123,
- },
+ spentBudget: 123,
})
.expect('Content-Type', /json/)
.expect(200)
@@ -394,25 +393,31 @@ describe('Project Phases', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE,
+ id: phaseId,
+ updatedBy: testUtil.userIds.copilot,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PHASE_UPDATE_PAYMENT).should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATE_PAYMENT);
done();
});
}
});
});
- it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when progress updated', (done) => {
+ it('should send correct BUS API messages when progress updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- progress: 50,
- },
+ progress: 50,
})
.expect('Content-Type', /json/)
.expect(200)
@@ -421,26 +426,32 @@ describe('Project Phases', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.callCount.should.be.eql(2);
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATE_PROGRESS);
- createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_PROGRESS_MODIFIED);
+ createEventSpy.callCount.should.be.eql(3);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE,
+ id: phaseId,
+ updatedBy: testUtil.userIds.copilot,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PHASE_UPDATE_PROGRESS).should.be.true;
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PROGRESS_MODIFIED).should.be.true;
done();
});
}
});
});
- it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when details updated', (done) => {
+ it('should send correct BUS API messages when details updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- details: {
- text: 'something',
- },
+ details: {
+ text: 'something',
},
})
.expect('Content-Type', /json/)
@@ -450,24 +461,31 @@ describe('Project Phases', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATE_SCOPE);
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE,
+ id: phaseId,
+ updatedBy: testUtil.userIds.copilot,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PHASE_UPDATE_SCOPE).should.be.true;
+
done();
});
}
});
});
- it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when status updated (completed)', (done) => {
+ it('should send correct BUS API messages when status updated (completed)', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- status: 'completed',
- },
+ status: 'completed',
})
.expect('Content-Type', /json/)
.expect(200)
@@ -476,24 +494,31 @@ describe('Project Phases', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_TRANSITION_COMPLETED);
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE,
+ id: phaseId,
+ updatedBy: testUtil.userIds.copilot,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PHASE_TRANSITION_COMPLETED).should.be.true;
+
done();
});
}
});
});
- it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when status updated (active)', (done) => {
+ it('should send correct BUS API messages when status updated (active)', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId3}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId3}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- status: 'active',
- },
+ status: 'active',
})
.expect('Content-Type', /json/)
.expect(200)
@@ -502,24 +527,31 @@ describe('Project Phases', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_TRANSITION_ACTIVE);
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE,
+ id: phaseId3,
+ updatedBy: testUtil.userIds.copilot,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PHASE_TRANSITION_ACTIVE).should.be.true;
+
done();
});
}
});
});
- it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when budget updated', (done) => {
+ it('should send correct BUS API messages when budget updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- budget: 123,
- },
+ budget: 123,
})
.expect('Content-Type', /json/)
.expect(200)
@@ -528,23 +560,28 @@ describe('Project Phases', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.notCalled.should.be.true;
+ createEventSpy.callCount.should.be.eql(1);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE,
+ id: phaseId,
+ updatedBy: testUtil.userIds.copilot,
+ })).should.be.true;
+
done();
});
}
});
});
- it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when startDate updated', (done) => {
+ it('should send correct BUS API messages when startDate updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- startDate: 123,
- },
+ startDate: 123,
})
.expect('Content-Type', /json/)
.expect(200)
@@ -553,32 +590,38 @@ describe('Project Phases', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE,
+ id: phaseId,
+ updatedBy: testUtil.userIds.copilot,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
projectId,
projectName,
projectUrl: `https://local.topcoder-dev.com/projects/${projectId}`,
- // originalPhase: sinon.match(originalPhase),
- // updatedPhase: sinon.match(updatedPhase),
userId: 40051332,
initiatorUserId: 40051332,
})).should.be.true;
+
done();
});
}
});
});
- it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when duration updated', (done) => {
+
+ it('should send correct BUS API messages when duration updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- duration: 100,
- },
+ duration: 100,
})
.expect('Content-Type', /json/)
.expect(200)
@@ -587,30 +630,35 @@ describe('Project Phases', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({
+ duration: 100,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
projectId,
projectName,
projectUrl: `https://local.topcoder-dev.com/projects/${projectId}`,
userId: 40051332,
initiatorUserId: 40051332,
})).should.be.true;
+
done();
});
}
});
});
- it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when order updated', (done) => {
+ it('should send correct BUS API messages when order updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- order: 100,
- },
+ order: 100,
})
.expect('Content-Type', /json/)
.expect(200)
@@ -619,23 +667,30 @@ describe('Project Phases', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.notCalled.should.be.true;
+ createEventSpy.callCount.should.be.eql(1);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE,
+ id: phaseId,
+ updatedBy: testUtil.userIds.copilot,
+ })).should.be.true;
+
+ // NOTE: no other event should be called, as this phase doesn't move any other phases
+
done();
});
}
});
});
- it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when endDate updated', (done) => {
+ it('should send correct BUS API messages when endDate updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- endDate: new Date(),
- },
+ endDate: new Date(),
})
.expect('Content-Type', /json/)
.expect(200)
@@ -644,7 +699,14 @@ describe('Project Phases', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.notCalled.should.be.true;
+ createEventSpy.callCount.should.be.eql(1);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE,
+ id: phaseId,
+ updatedBy: testUtil.userIds.copilot,
+ })).should.be.true;
+
done();
});
}
@@ -657,12 +719,12 @@ describe('Project Phases', () => {
let publishSpy;
let sandbox;
- before(async (done) => {
+ before((done) => {
// Wait for 500ms in order to wait for createEvent calls from previous tests to complete
testUtil.wait(done);
});
- beforeEach(async (done) => {
+ beforeEach(async () => {
sandbox = sinon.sandbox.create();
server.services.pubsub = new RabbitMQService(server.logger);
@@ -683,12 +745,12 @@ describe('Project Phases', () => {
},
});
- testUtil.wait(() => {
+ return new Promise(resolve => setTimeout(() => {
publishSpy = sandbox.spy(server.services.pubsub, 'publish');
updateMessageSpy = sandbox.spy(messageService, 'updateTopic');
sandbox.stub(messageService, 'getTopicByTag', () => Promise.resolve(topic));
- done();
- });
+ resolve();
+ }, 500));
});
afterEach(() => {
@@ -703,24 +765,16 @@ describe('Project Phases', () => {
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
post: () => Promise.resolve({
status: 200,
- data: {
- id: 'requesterId',
- version: 'v3',
- result: {
- success: true,
- status: 200,
- content: {},
- },
- },
+ data: {},
}),
});
sandbox.stub(messageService, 'getClient', () => mockHttpClient);
request(server)
- .patch(`/v4/projects/${projectId}/phases/${phaseId}`)
+ .patch(`/v5/projects/${projectId}/phases/${phaseId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: _.assign(updateBody, { budget: 123 }) })
+ .send(_.assign(updateBody, { budget: 123 }))
.expect('Content-Type', /json/)
.expect(200)
.end((err) => {
diff --git a/src/routes/planConfig/revision/create.js b/src/routes/planConfig/revision/create.js
index 00bbd164..52c0cb0f 100644
--- a/src/routes/planConfig/revision/create.js
+++ b/src/routes/planConfig/revision/create.js
@@ -5,6 +5,7 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../../constants';
import util from '../../../util';
import models from '../../../models';
@@ -15,18 +16,16 @@ const schema = {
key: Joi.string().max(45).required(),
version: Joi.number().integer().positive().required(),
},
- body: {
- param: Joi.object().keys({
- config: Joi.object().required(),
+ body: Joi.object().keys({
+ config: Joi.object().required(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
};
module.exports = [
@@ -43,13 +42,13 @@ module.exports = [
if (planConfig) {
const version = planConfig ? planConfig.version : 1;
const revision = planConfig ? planConfig.revision + 1 : 1;
- const entity = _.assign(req.body.param, {
+ const entity = _.assign(req.body, {
version,
revision,
createdBy: req.authUser.userId,
updatedBy: req.authUser.userId,
key: req.params.key,
- config: req.body.param.config,
+ config: req.body.config,
});
return models.PlanConfig.create(entity);
}
@@ -57,9 +56,12 @@ module.exports = [
apiErr.status = 404;
return Promise.reject(apiErr);
}).then((createdEntity) => {
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE,
+ RESOURCES.PLAN_CONFIG_REVISION,
+ createdEntity.toJSON());
// Omit deletedAt, deletedBy
- res.status(201).json(util.wrapResponse(
- req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201));
+ res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'));
})
.catch(next));
},
diff --git a/src/routes/planConfig/revision/create.spec.js b/src/routes/planConfig/revision/create.spec.js
index 66a8121c..f1a92f83 100644
--- a/src/routes/planConfig/revision/create.spec.js
+++ b/src/routes/planConfig/revision/create.spec.js
@@ -35,32 +35,32 @@ describe('CREATE PlanConfig Revision', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.PlanConfig.create(planConfigs[0]))
- .then(() => models.PlanConfig.create(planConfigs[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.PlanConfig.create(planConfigs[0]))
+ .then(() => models.PlanConfig.create(planConfigs[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('Post /projects/metadata/planConfig/{key}/versions/{version}/revision', () => {
const body = {
- param: {
- config: {
- 'test create': 'test create',
- },
+ config: {
+ 'test create': 'test create',
},
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post('/v4/projects/metadata/planConfig/dev/versions/1/revisions')
+ .post('/v5/projects/metadata/planConfig/dev/versions/1/revisions')
.send(body)
.expect(403, done);
});
it('should return 404 if missing key', (done) => {
request(server)
- .post('/v4/projects/metadata/planConfig/no-exist-key/versions/1/revisions')
+ .post('/v5/projects/metadata/planConfig/no-exist-key/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -71,7 +71,7 @@ describe('CREATE PlanConfig Revision', () => {
it('should return 404 if missing version', (done) => {
request(server)
- .post('/v4/projects/metadata/planConfig/dev/versions/100/revisions')
+ .post('/v5/projects/metadata/planConfig/dev/versions/100/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -80,26 +80,24 @@ describe('CREATE PlanConfig Revision', () => {
.expect(404, done);
});
- it('should return 422 if missing config', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- config: undefined,
- }),
- };
+ it('should return 400 if missing config', (done) => {
+ const invalidBody = _.assign({}, body, {
+ config: undefined,
+ });
request(server)
- .post('/v4/projects/metadata/planConfig/no-exist-key/versions/1/revisions')
+ .post('/v5/projects/metadata/planConfig/no-exist-key/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 201 for admin', (done) => {
request(server)
- .post('/v4/projects/metadata/planConfig/dev/versions/1/revisions')
+ .post('/v5/projects/metadata/planConfig/dev/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -107,9 +105,9 @@ describe('CREATE PlanConfig Revision', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson.id);
- resJson.config.should.be.eql(body.param.config);
+ resJson.config.should.be.eql(body.config);
resJson.key.should.be.eql('dev');
resJson.revision.should.be.eql(3);
resJson.version.should.be.eql(1);
@@ -125,7 +123,7 @@ describe('CREATE PlanConfig Revision', () => {
it('should return 403 for member', (done) => {
request(server)
- .post('/v4/projects/metadata/planConfig/dev/versions/1/revisions')
+ .post('/v5/projects/metadata/planConfig/dev/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
diff --git a/src/routes/planConfig/revision/delete.js b/src/routes/planConfig/revision/delete.js
index 0c890678..8728e000 100644
--- a/src/routes/planConfig/revision/delete.js
+++ b/src/routes/planConfig/revision/delete.js
@@ -2,8 +2,11 @@
* API to delete a revsion
*/
import validate from 'express-validation';
+import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../../constants';
+import util from '../../../util';
import models from '../../../models';
const permissions = tcMiddleware.permissions;
@@ -38,9 +41,13 @@ module.exports = [
return planConfig.update({
deletedBy: req.authUser.userId,
});
- }).then((planConfig) => {
- planConfig.destroy();
- }).then(() => {
+ }).then(planConfig =>
+ planConfig.destroy(),
+ ).then((planConfig) => {
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE,
+ RESOURCES.PLAN_CONFIG_REVISION,
+ _.pick(planConfig.toJSON(), 'id'));
res.status(204).end();
})
.catch(next));
diff --git a/src/routes/planConfig/revision/delete.spec.js b/src/routes/planConfig/revision/delete.spec.js
index 80802235..1d33284f 100644
--- a/src/routes/planConfig/revision/delete.spec.js
+++ b/src/routes/planConfig/revision/delete.spec.js
@@ -26,7 +26,7 @@ const expectAfterDelete = (err, next) => {
chai.assert.isNotNull(res.deletedBy);
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions/1')
+ .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -60,24 +60,26 @@ describe('DELETE planConfig revision', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.PlanConfig.create(planConfigs[0]))
- .then(() => models.PlanConfig.create(planConfigs[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.PlanConfig.create(planConfigs[0]))
+ .then(() => models.PlanConfig.create(planConfigs[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('DELETE /projects/metadata/planConfig/{key}/versions/{version}/revisions/{revision}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .delete('/v4/projects/metadata/planConfig/dev/versions/1/revisions/1')
+ .delete('/v5/projects/metadata/planConfig/dev/versions/1/revisions/1')
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .delete('/v4/projects/metadata/planConfig/dev/versions/1/revisions/1')
+ .delete('/v5/projects/metadata/planConfig/dev/versions/1/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -86,7 +88,7 @@ describe('DELETE planConfig revision', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .delete('/v4/projects/metadata/planConfig/dev/versions/1/revisions/1')
+ .delete('/v5/projects/metadata/planConfig/dev/versions/1/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -95,7 +97,7 @@ describe('DELETE planConfig revision', () => {
it('should return 403 for manager', (done) => {
request(server)
- .delete('/v4/projects/metadata/planConfig/dev/versions/1/revisions/1')
+ .delete('/v5/projects/metadata/planConfig/dev/versions/1/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -104,7 +106,7 @@ describe('DELETE planConfig revision', () => {
it('should return 404 for non-existed key', (done) => {
request(server)
- .delete('/v4/projects/metadata/planConfig/no-existed-key/versions/1/revisions/1')
+ .delete('/v5/projects/metadata/planConfig/no-existed-key/versions/1/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -113,7 +115,7 @@ describe('DELETE planConfig revision', () => {
it('should return 404 for non-existed version', (done) => {
request(server)
- .delete('/v4/projects/metadata/planConfig/dev/versions/100/revisions/1')
+ .delete('/v5/projects/metadata/planConfig/dev/versions/100/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -123,7 +125,7 @@ describe('DELETE planConfig revision', () => {
it('should return 404 for non-existed revision', (done) => {
request(server)
- .delete('/v4/projects/metadata/planConfig/dev/versions/1/revisions/100')
+ .delete('/v5/projects/metadata/planConfig/dev/versions/1/revisions/100')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -132,7 +134,7 @@ describe('DELETE planConfig revision', () => {
it('should return 204, for admin', (done) => {
request(server)
- .delete('/v4/projects/metadata/planConfig/dev/versions/1/revisions/1')
+ .delete('/v5/projects/metadata/planConfig/dev/versions/1/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -142,7 +144,7 @@ describe('DELETE planConfig revision', () => {
it('should return 204, for connect admin', (done) => {
request(server)
- .delete('/v4/projects/metadata/planConfig/dev/versions/1/revisions/1')
+ .delete('/v5/projects/metadata/planConfig/dev/versions/1/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
diff --git a/src/routes/planConfig/revision/get.js b/src/routes/planConfig/revision/get.js
index 2f20a564..a038380d 100644
--- a/src/routes/planConfig/revision/get.js
+++ b/src/routes/planConfig/revision/get.js
@@ -4,8 +4,8 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../../util';
import models from '../../../models';
+import util from '../../../util';
const permissions = tcMiddleware.permissions;
@@ -20,25 +20,58 @@ const schema = {
module.exports = [
validate(schema),
permissions('planConfig.view'),
- (req, res, next) => models.PlanConfig.findOne({
- where: {
- key: req.params.key,
- version: req.params.version,
- revision: req.params.revision,
- },
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- })
- .then((planConfig) => {
- // Not found
- if (!planConfig) {
- const apiErr = new Error('PlanConfig not found for key' +
- `${req.params.key} version ${req.params.version} revision ${req.params.revision}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
- }
+ (req, res, next) => {
+ util.fetchByIdFromES('plan configs', {
+ query: {
+ nested: {
+ path: 'planConfigs',
+ query:
+ {
+ filtered: {
+ filter: {
+ bool: {
+ must: [
+ { term: { 'planConfigs.key': req.params.key } },
+ { term: { 'planConfigs.version': req.params.version } },
+ { term: { 'planConfigs.revision': req.params.revision } },
+ ],
+ },
+ },
+ },
+ },
+ inner_hits: {},
+ },
+ },
+ }, 'metadata')
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No plan config found in ES');
+ models.PlanConfig.findOne({
+ where: {
+ key: req.params.key,
+ version: req.params.version,
+ revision: req.params.revision,
+ },
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ })
+ .then((planConfig) => {
+ // Not found
+ if (!planConfig) {
+ const apiErr = new Error('PlanConfig not found for key' +
+ `${req.params.key} version ${req.params.version} revision ${req.params.revision}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
- res.json(util.wrapResponse(req.id, planConfig));
- return Promise.resolve();
+ res.json(planConfig);
+ return Promise.resolve();
+ })
+ .catch(next);
+ } else {
+ req.log.debug('plan config found in ES');
+ res.json(data[0].inner_hits.planConfigs.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle
+ }
})
- .catch(next),
+ .catch(next);
+ },
];
diff --git a/src/routes/planConfig/revision/get.spec.js b/src/routes/planConfig/revision/get.spec.js
index 874aefd8..a524b5da 100644
--- a/src/routes/planConfig/revision/get.spec.js
+++ b/src/routes/planConfig/revision/get.spec.js
@@ -34,24 +34,26 @@ describe('GET a particular revision of specific version PlanConfig', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.PlanConfig.create(planConfigs[0]))
- .then(() => models.PlanConfig.create(planConfigs[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.PlanConfig.create(planConfigs[0]))
+ .then(() => models.PlanConfig.create(planConfigs[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/planConfig/dev/versions/{version}/revisions/{revision}', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions/2')
+ .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions/2')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
const planConfig = planConfigs[1];
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(planConfig.key);
resJson.config.should.be.eql(planConfig.config);
@@ -68,13 +70,13 @@ describe('GET a particular revision of specific version PlanConfig', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions/2')
+ .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions/2')
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions/2')
+ .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions/2')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -84,7 +86,7 @@ describe('GET a particular revision of specific version PlanConfig', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions/2')
+ .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions/2')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -94,7 +96,7 @@ describe('GET a particular revision of specific version PlanConfig', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions/2')
+ .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions/2')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -103,7 +105,7 @@ describe('GET a particular revision of specific version PlanConfig', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions/2')
+ .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions/2')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/planConfig/revision/list.js b/src/routes/planConfig/revision/list.js
index a1bbb6ec..dc18059e 100644
--- a/src/routes/planConfig/revision/list.js
+++ b/src/routes/planConfig/revision/list.js
@@ -4,8 +4,8 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../../util';
import models from '../../../models';
+import util from '../../../util';
const permissions = tcMiddleware.permissions;
@@ -19,23 +19,34 @@ const schema = {
module.exports = [
validate(schema),
permissions('planConfig.view'),
- (req, res, next) => models.PlanConfig.findAll({
- where: {
- key: req.params.key,
- version: req.params.version,
- },
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- })
- .then((planConfigs) => {
- // Not found
- if ((!planConfigs) || (planConfigs.length === 0)) {
- const apiErr = new Error(`PlanConfig not found for key ${req.params.key} version ${req.params.version}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
- }
+ (req, res, next) =>
+
+ util.fetchFromES('planConfigs')
+ .then((data) => {
+ if (data.planConfigs.length === 0) {
+ req.log.debug('No planConfig found in ES');
+ models.PlanConfig.findAll({
+ where: {
+ key: req.params.key,
+ version: req.params.version,
+ },
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ })
+ .then((planConfigs) => {
+ // Not found
+ if ((!planConfigs) || (planConfigs.length === 0)) {
+ const apiErr = new Error(`PlanConfig not found for key ${req.params.key} version ${req.params.version}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
- res.json(util.wrapResponse(req.id, planConfigs));
- return Promise.resolve();
- })
- .catch(next),
+ res.json(planConfigs);
+ return Promise.resolve();
+ })
+ .catch(next);
+ } else {
+ req.log.debug('planConfigs found in ES');
+ res.json(data.planConfigs);
+ }
+ }).catch(next),
];
diff --git a/src/routes/planConfig/revision/list.spec.js b/src/routes/planConfig/revision/list.spec.js
index 0781018c..38201af8 100644
--- a/src/routes/planConfig/revision/list.spec.js
+++ b/src/routes/planConfig/revision/list.spec.js
@@ -35,24 +35,26 @@ describe('LIST planConfig revisions', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.PlanConfig.create(planConfigs[0]))
- .then(() => models.PlanConfig.create(planConfigs[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.PlanConfig.create(planConfigs[0]))
+ .then(() => models.PlanConfig.create(planConfigs[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/planConfig/dev/versions/{version}/revisions', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions')
+ .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
const planConfig = planConfigs[0];
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
resJson[0].key.should.be.eql(planConfig.key);
@@ -70,13 +72,13 @@ describe('LIST planConfig revisions', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions')
+ .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions')
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions')
+ .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -86,7 +88,7 @@ describe('LIST planConfig revisions', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions')
+ .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -96,7 +98,7 @@ describe('LIST planConfig revisions', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions')
+ .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -105,7 +107,7 @@ describe('LIST planConfig revisions', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions')
+ .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/planConfig/version/create.js b/src/routes/planConfig/version/create.js
index 50d4c09c..dc0526b1 100644
--- a/src/routes/planConfig/version/create.js
+++ b/src/routes/planConfig/version/create.js
@@ -5,6 +5,7 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../../constants';
import util from '../../../util';
import models from '../../../models';
@@ -14,18 +15,16 @@ const schema = {
params: {
key: Joi.string().max(45).required(),
},
- body: {
- param: Joi.object().keys({
- config: Joi.object().required(),
+ body: Joi.object().keys({
+ config: Joi.object().required(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
};
module.exports = [
@@ -45,19 +44,22 @@ module.exports = [
latestVersion = latestVersionPlanConfig.version + 1;
}
- const entity = _.assign(req.body.param, {
+ const entity = _.assign(req.body, {
version: latestVersion,
revision: 1,
createdBy: req.authUser.userId,
updatedBy: req.authUser.userId,
key: req.params.key,
- config: req.body.param.config,
+ config: req.body.config,
});
return models.PlanConfig.create(entity);
}).then((createdEntity) => {
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE,
+ RESOURCES.PLAN_CONFIG_VERSION,
+ createdEntity.toJSON());
// Omit deletedAt, deletedBy
- res.status(201).json(util.wrapResponse(
- req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201));
+ res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'));
})
.catch(next));
},
diff --git a/src/routes/planConfig/version/create.spec.js b/src/routes/planConfig/version/create.spec.js
index 80de2b60..d3c19562 100644
--- a/src/routes/planConfig/version/create.spec.js
+++ b/src/routes/planConfig/version/create.spec.js
@@ -35,49 +35,49 @@ describe('CREATE PlanConfig version', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.PlanConfig.create(planConfigs[0]))
- .then(() => models.PlanConfig.create(planConfigs[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.PlanConfig.create(planConfigs[0]))
+ .then(() => models.PlanConfig.create(planConfigs[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('Post /projects/metadata/planConfig/{key}/versions/', () => {
const body = {
- param: {
- config: {
- 'test create': 'test create',
- },
+ config: {
+ 'test create': 'test create',
},
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post('/v4/projects/metadata/planConfig/dev/versions')
+ .post('/v5/projects/metadata/planConfig/dev/versions')
.send(body)
- .expect(403, done);
+ .expect(403)
+ .end(done);
});
- it('should return 422 if missing config', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- config: undefined,
- }),
- };
+ it('should return 400 if missing config', (done) => {
+ const invalidBody = _.assign({}, body, {
+ config: undefined,
+ });
request(server)
- .post('/v4/projects/metadata/planConfig/dev/versions/')
+ .post('/v5/projects/metadata/planConfig/dev/versions/')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400)
+ .end(done);
});
it('should return 201 for admin', (done) => {
request(server)
- .post('/v4/projects/metadata/planConfig/dev/versions/')
+ .post('/v5/projects/metadata/planConfig/dev/versions/')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -85,9 +85,9 @@ describe('CREATE PlanConfig version', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson.id);
- resJson.config.should.be.eql(body.param.config);
+ resJson.config.should.be.eql(body.config);
resJson.key.should.be.eql('dev');
resJson.revision.should.be.eql(1);
resJson.version.should.be.eql(2);
@@ -103,12 +103,13 @@ describe('CREATE PlanConfig version', () => {
it('should return 403 for member', (done) => {
request(server)
- .post('/v4/projects/metadata/planConfig/dev/versions/')
+ .post('/v5/projects/metadata/planConfig/dev/versions/')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
.send(body)
- .expect(403, done);
+ .expect(403)
+ .end(done);
});
});
});
diff --git a/src/routes/planConfig/version/delete.js b/src/routes/planConfig/version/delete.js
index a6b141c9..493fbdec 100644
--- a/src/routes/planConfig/version/delete.js
+++ b/src/routes/planConfig/version/delete.js
@@ -2,8 +2,11 @@
* API to add a project type
*/
import validate from 'express-validation';
+import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../../constants';
+import util from '../../../util';
import models from '../../../models';
const permissions = tcMiddleware.permissions;
@@ -46,7 +49,21 @@ module.exports = [
key: req.params.key,
version: req.params.version,
},
- })).then(() => {
+ }))
+ .then(deleted => models.PlanConfig.findAll({
+ where: {
+ key: req.params.key,
+ version: req.params.version,
+ },
+ paranoid: false,
+ order: [['deletedAt', 'DESC']],
+ limit: deleted,
+ }))
+ .then((planConfigs) => {
+ _.map(planConfigs, planConfig => util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE,
+ RESOURCES.PLAN_CONFIG_VERSION,
+ _.pick(planConfig.toJSON(), 'id')));
res.status(204).end();
})
.catch(next));
diff --git a/src/routes/planConfig/version/delete.spec.js b/src/routes/planConfig/version/delete.spec.js
index c147fd8f..79097e62 100644
--- a/src/routes/planConfig/version/delete.spec.js
+++ b/src/routes/planConfig/version/delete.spec.js
@@ -56,69 +56,77 @@ describe('DELETE planConfig version', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.PlanConfig.create(planConfigs[0]))
- .then(() => models.PlanConfig.create(planConfigs[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.PlanConfig.create(planConfigs[0]))
+ .then(() => models.PlanConfig.create(planConfigs[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('DELETE /projects/metadata/planConfig/{key}/versions/{version}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .delete('/v4/projects/metadata/planConfig/dev/versions/1')
- .expect(403, done);
+ .delete('/v5/projects/metadata/planConfig/dev/versions/1')
+ .expect(403)
+ .end(done);
});
it('should return 403 for member', (done) => {
request(server)
- .delete('/v4/projects/metadata/planConfig/dev/versions/1')
+ .delete('/v5/projects/metadata/planConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
- .expect(403, done);
+ .expect(403)
+ .end(done);
});
it('should return 403 for copilot', (done) => {
request(server)
- .delete('/v4/projects/metadata/planConfig/dev/versions/1')
+ .delete('/v5/projects/metadata/planConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .expect(403, done);
+ .expect(403)
+ .end(done);
});
it('should return 403 for manager', (done) => {
request(server)
- .delete('/v4/projects/metadata/planConfig/dev/versions/1')
+ .delete('/v5/projects/metadata/planConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
- .expect(403, done);
+ .expect(403)
+ .end(done);
});
it('should return 404 for non-existed key', (done) => {
request(server)
- .delete('/v4/projects/metadata/planConfig/dev111/versions/1')
+ .delete('/v5/projects/metadata/planConfig/dev111/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .expect(404, done);
+ .expect(404)
+ .end(done);
});
it('should return 404 for non-existed version', (done) => {
request(server)
- .delete('/v4/projects/metadata/planConfig/dev/versions/111')
+ .delete('/v5/projects/metadata/planConfig/dev/versions/111')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .expect(404, done);
+ .expect(404)
+ .end(done);
});
it('should return 204, for admin', (done) => {
request(server)
- .delete('/v4/projects/metadata/planConfig/dev/versions/1')
+ .delete('/v5/projects/metadata/planConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -128,7 +136,7 @@ describe('DELETE planConfig version', () => {
it('should return 204, for connect admin', (done) => {
request(server)
- .delete('/v4/projects/metadata/planConfig/dev/versions/1')
+ .delete('/v5/projects/metadata/planConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
diff --git a/src/routes/planConfig/version/get.js b/src/routes/planConfig/version/get.js
index 4b336122..46fe1c7d 100644
--- a/src/routes/planConfig/version/get.js
+++ b/src/routes/planConfig/version/get.js
@@ -5,8 +5,8 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../../util';
import models from '../../../models';
+import util from '../../../util';
const permissions = tcMiddleware.permissions;
@@ -19,15 +19,49 @@ const schema = {
module.exports = [
validate(schema),
permissions('planConfig.view'),
- (req, res, next) => models.PlanConfig.latestRevisionOfLatestVersion(req.params.key)
- .then((form) => {
- if (form == null) {
- const apiErr = new Error(`PlanConfig not found for key ${req.params.key} version ${req.params.version}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
+ (req, res, next) => {
+ util.fetchByIdFromES('planConfigs', {
+ query: {
+ nested: {
+ path: 'planConfigs',
+ query:
+ {
+ filtered: {
+ filter: {
+ bool: {
+ must: [
+ { term: { 'planConfigs.key': req.params.key } },
+ ],
+ },
+ },
+ },
+ },
+ inner_hits: {},
+ },
+ },
+ sort: { 'planConfigs.version': 'desc' },
+ }, 'metadata')
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No planConfig found in ES');
+ models.PlanConfig.latestRevisionOfLatestVersion(req.params.key)
+ .then((planConfig) => {
+ if (planConfig == null) {
+ const apiErr = new Error(`PlanConfig not found for key ${req.params.key} version ${req.params.version}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
+ res.json(planConfig);
+ return Promise.resolve();
+ })
+ .catch(next);
+ } else {
+ req.log.debug('planConfigs found in ES');
+ res.json(data[0].inner_hits.planConfigs.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle
}
- res.json(util.wrapResponse(req.id, form));
- return Promise.resolve();
})
- .catch(next),
+ .catch(next);
+ },
+
+
];
diff --git a/src/routes/planConfig/version/get.spec.js b/src/routes/planConfig/version/get.spec.js
index f77d5cb3..a7455791 100644
--- a/src/routes/planConfig/version/get.spec.js
+++ b/src/routes/planConfig/version/get.spec.js
@@ -54,25 +54,27 @@ describe('GET a latest version of specific key of PlanConfig', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.PlanConfig.create(planConfigs[0]))
- .then(() => models.PlanConfig.create(planConfigs[1]))
- .then(() => models.PlanConfig.create(planConfigs[2]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.PlanConfig.create(planConfigs[0]))
+ .then(() => models.PlanConfig.create(planConfigs[1]))
+ .then(() => models.PlanConfig.create(planConfigs[2]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/planConfig/dev/versions/{version}', () => {
it('should return 200 and correct version for admin', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev')
+ .get('/v5/projects/metadata/planConfig/dev')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
const planConfig = planConfigs[2];
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(planConfig.key);
resJson.config.should.be.eql(planConfig.config);
@@ -89,13 +91,14 @@ describe('GET a latest version of specific key of PlanConfig', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev')
- .expect(403, done);
+ .get('/v5/projects/metadata/planConfig/dev')
+ .expect(403)
+ .end(done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev')
+ .get('/v5/projects/metadata/planConfig/dev')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -105,7 +108,7 @@ describe('GET a latest version of specific key of PlanConfig', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev')
+ .get('/v5/projects/metadata/planConfig/dev')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -115,20 +118,22 @@ describe('GET a latest version of specific key of PlanConfig', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev')
+ .get('/v5/projects/metadata/planConfig/dev')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
- .expect(200, done);
+ .expect(200)
+ .end(done);
});
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev')
+ .get('/v5/projects/metadata/planConfig/dev')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .expect(200, done);
+ .expect(200)
+ .end(done);
});
});
});
diff --git a/src/routes/planConfig/version/getVersion.js b/src/routes/planConfig/version/getVersion.js
index 1bf391dd..7673c3cf 100644
--- a/src/routes/planConfig/version/getVersion.js
+++ b/src/routes/planConfig/version/getVersion.js
@@ -4,8 +4,8 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../../util';
import models from '../../../models';
+import util from '../../../util';
const permissions = tcMiddleware.permissions;
@@ -19,16 +19,47 @@ const schema = {
module.exports = [
validate(schema),
permissions('planConfig.view'),
- (req, res, next) => models.PlanConfig.findOneWithLatestRevision(req.params)
- .then((planConfig) => {
- // Not found
- if (!planConfig) {
- const apiErr = new Error(`PlanConfig not found for key ${req.params.key} version ${req.params.version}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
- }
- res.json(util.wrapResponse(req.id, planConfig));
- return Promise.resolve();
- })
- .catch(next),
+ (req, res, next) =>
+ util.fetchByIdFromES('planConfigs', {
+ query: {
+ nested: {
+ path: 'planConfigs',
+ query: {
+ filtered: {
+ filter: {
+ bool: {
+ must: [
+ { term: { 'planConfigs.key': req.params.key } },
+ { term: { 'planConfigs.version': req.params.version } },
+ ],
+ },
+ },
+ },
+ },
+ inner_hits: {},
+ },
+ },
+ sort: { 'planConfigs.revision': 'desc' },
+ }, 'metadata')
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No planConfig found in ES');
+ return models.PlanConfig.findOneWithLatestRevision(req.params)
+ .then((planConfig) => {
+ // Not found
+ if (!planConfig) {
+ const apiErr = new Error(`PlanConfig not found for key ${req.params.key} version ${req.params.version}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
+ res.json(planConfig);
+ return Promise.resolve();
+ })
+ .catch(next);
+ }
+ req.log.debug('planConfigs found in ES');
+ res.json(data[0].inner_hits.planConfigs.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle
+ return Promise.resolve();
+ })
+ .catch(next),
];
diff --git a/src/routes/planConfig/version/getVersion.spec.js b/src/routes/planConfig/version/getVersion.spec.js
index ffca3809..3052a224 100644
--- a/src/routes/planConfig/version/getVersion.spec.js
+++ b/src/routes/planConfig/version/getVersion.spec.js
@@ -44,25 +44,27 @@ describe('GET a particular version of specific key of PlanConfig', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.PlanConfig.create(planConfigs[0]))
- .then(() => models.PlanConfig.create(planConfigs[1]))
- .then(() => models.PlanConfig.create(planConfigs[2]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.PlanConfig.create(planConfigs[0]))
+ .then(() => models.PlanConfig.create(planConfigs[1]))
+ .then(() => models.PlanConfig.create(planConfigs[2]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/planConfig/dev/versions/{version}', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions/1')
+ .get('/v5/projects/metadata/planConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
const planConfig = planConfigs[0];
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(planConfig.key);
resJson.config.should.be.eql(planConfig.config);
@@ -79,13 +81,13 @@ describe('GET a particular version of specific key of PlanConfig', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions/1')
+ .get('/v5/projects/metadata/planConfig/dev/versions/1')
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev')
+ .get('/v5/projects/metadata/planConfig/dev')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -95,7 +97,7 @@ describe('GET a particular version of specific key of PlanConfig', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions/1')
+ .get('/v5/projects/metadata/planConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -105,7 +107,7 @@ describe('GET a particular version of specific key of PlanConfig', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions/1')
+ .get('/v5/projects/metadata/planConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -114,7 +116,7 @@ describe('GET a particular version of specific key of PlanConfig', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions/1')
+ .get('/v5/projects/metadata/planConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/planConfig/version/list.js b/src/routes/planConfig/version/list.js
index 5624a981..66c857d9 100644
--- a/src/routes/planConfig/version/list.js
+++ b/src/routes/planConfig/version/list.js
@@ -4,8 +4,8 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../../util';
import models from '../../../models';
+import util from '../../../util';
const permissions = tcMiddleware.permissions;
@@ -18,30 +18,42 @@ const schema = {
module.exports = [
validate(schema),
permissions('planConfig.view'),
- (req, res, next) => models.PlanConfig.findAll({
- where: {
- key: req.params.key,
- },
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- })
- .then((planConfigs) => {
- // Not found
- if ((!planConfigs) || (planConfigs.length === 0)) {
- const apiErr = new Error(`PlanConfig not found for key ${req.params.key}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
- }
+ (req, res, next) =>
+ util.fetchFromES('planConfigs')
+ .then((data) => {
+ if (data.planConfigs.length === 0) {
+ req.log.debug('No planConfig found in ES');
+ models.PlanConfig.findAll({
+ where: {
+ key: req.params.key,
+ },
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ })
+ .then((planConfigs) => {
+ // Not found
+ if ((!planConfigs) || (planConfigs.length === 0)) {
+ const apiErr = new Error(`PlanConfig not found for key ${req.params.key}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
+
+ const latestPlanConfigs = {};
+ planConfigs.forEach((element) => {
+ const isNewerRevision = (latestPlanConfigs[element.version] != null) &&
+ (latestPlanConfigs[element.version].revision < element.revision);
+ if ((latestPlanConfigs[element.version] == null) || isNewerRevision) {
+ latestPlanConfigs[element.version] = element;
+ }
+ });
+ res.json(Object.values(latestPlanConfigs));
+ return Promise.resolve();
+ })
+ .catch(next);
+ } else {
+ req.log.debug('planConfigs found in ES');
+ res.json(data.planConfigs);
+ }
+ }).catch(next),
+
- const latestPlanConfigs = {};
- planConfigs.forEach((element) => {
- const isNewerRevision = (latestPlanConfigs[element.version] != null) &&
- (latestPlanConfigs[element.version].revision < element.revision);
- if ((latestPlanConfigs[element.version] == null) || isNewerRevision) {
- latestPlanConfigs[element.version] = element;
- }
- });
- res.json(util.wrapResponse(req.id, Object.values(latestPlanConfigs)));
- return Promise.resolve();
- })
- .catch(next),
];
diff --git a/src/routes/planConfig/version/list.spec.js b/src/routes/planConfig/version/list.spec.js
index 851a9577..8fc3ce1b 100644
--- a/src/routes/planConfig/version/list.spec.js
+++ b/src/routes/planConfig/version/list.spec.js
@@ -35,24 +35,26 @@ describe('LIST planConfig versions', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.PlanConfig.create(planConfigs[0]))
- .then(() => models.PlanConfig.create(planConfigs[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.PlanConfig.create(planConfigs[0]))
+ .then(() => models.PlanConfig.create(planConfigs[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/planConfig/dev/versions/{version}', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions')
+ .get('/v5/projects/metadata/planConfig/dev/versions')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
const planConfig = planConfigs[0];
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
resJson[0].key.should.be.eql(planConfig.key);
@@ -70,13 +72,13 @@ describe('LIST planConfig versions', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions')
+ .get('/v5/projects/metadata/planConfig/dev/versions')
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions')
+ .get('/v5/projects/metadata/planConfig/dev/versions')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -86,7 +88,7 @@ describe('LIST planConfig versions', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions')
+ .get('/v5/projects/metadata/planConfig/dev/versions')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -96,7 +98,7 @@ describe('LIST planConfig versions', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions')
+ .get('/v5/projects/metadata/planConfig/dev/versions')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -105,7 +107,7 @@ describe('LIST planConfig versions', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/projects/metadata/planConfig/dev/versions')
+ .get('/v5/projects/metadata/planConfig/dev/versions')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/planConfig/version/update.js b/src/routes/planConfig/version/update.js
index 44591e6c..b62b63c9 100644
--- a/src/routes/planConfig/version/update.js
+++ b/src/routes/planConfig/version/update.js
@@ -7,6 +7,7 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../../constants';
import util from '../../../util';
import models from '../../../models';
@@ -17,18 +18,16 @@ const schema = {
version: Joi.number().integer().positive().required(),
key: Joi.string().max(45).required(),
},
- body: {
- param: Joi.object().keys({
- config: Joi.object().required(),
+ body: Joi.object().keys({
+ config: Joi.object().required(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
};
module.exports = [
@@ -60,14 +59,17 @@ module.exports = [
createdBy: req.authUser.userId,
updatedBy: req.authUser.userId,
key: req.params.key,
- config: req.body.param.config,
+ config: req.body.config,
};
return models.PlanConfig.create(entity);
})
.then((createdEntity) => {
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE,
+ RESOURCES.PLAN_CONFIG_VERSION,
+ createdEntity.toJSON());
// Omit deletedAt, deletedBy
- res.status(201).json(util.wrapResponse(
- req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201));
+ res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'));
})
.catch(next));
},
diff --git a/src/routes/planConfig/version/update.spec.js b/src/routes/planConfig/version/update.spec.js
index 537b8c57..025b2b08 100644
--- a/src/routes/planConfig/version/update.spec.js
+++ b/src/routes/planConfig/version/update.spec.js
@@ -35,48 +35,48 @@ describe('UPDATE PlanConfig version', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.PlanConfig.create(planConfigs[0]))
- .then(() => models.PlanConfig.create(planConfigs[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.PlanConfig.create(planConfigs[0]))
+ .then(() => models.PlanConfig.create(planConfigs[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('Post /projects/metadata/planConfig/{key}/versions/{version}', () => {
const body = {
- param: {
- config: {
- 'test create': 'test create',
- },
+ config: {
+ 'test create': 'test create',
},
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .patch('/v4/projects/metadata/planConfig/dev/versions/1')
+ .patch('/v5/projects/metadata/planConfig/dev/versions/1')
.send(body)
- .expect(403, done);
+ .expect(403)
+ .end(done);
});
- it('should return 422 if missing config', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- config: undefined,
- }),
- };
+ it('should return 400 if missing config', (done) => {
+ const invalidBody = _.assign({}, body, {
+ config: undefined,
+ });
request(server)
- .patch('/v4/projects/metadata/planConfig/dev/versions/1')
+ .patch('/v5/projects/metadata/planConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400)
+ .end(done);
});
it('should return 201 for admin', (done) => {
request(server)
- .patch('/v4/projects/metadata/planConfig/dev/versions/1')
+ .patch('/v5/projects/metadata/planConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -84,9 +84,9 @@ describe('UPDATE PlanConfig version', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson.id);
- resJson.config.should.be.eql(body.param.config);
+ resJson.config.should.be.eql(body.config);
resJson.key.should.be.eql('dev');
resJson.revision.should.be.eql(3);
resJson.version.should.be.eql(1);
@@ -102,12 +102,13 @@ describe('UPDATE PlanConfig version', () => {
it('should return 403 for member', (done) => {
request(server)
- .patch('/v4/projects/metadata/planConfig/dev/versions/1')
+ .patch('/v5/projects/metadata/planConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
.send(body)
- .expect(403, done);
+ .expect(403)
+ .end(done);
});
});
});
diff --git a/src/routes/priceConfig/revision/create.js b/src/routes/priceConfig/revision/create.js
index 83790b82..294f176f 100644
--- a/src/routes/priceConfig/revision/create.js
+++ b/src/routes/priceConfig/revision/create.js
@@ -5,6 +5,7 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../../constants';
import util from '../../../util';
import models from '../../../models';
@@ -15,18 +16,16 @@ const schema = {
key: Joi.string().max(45).required(),
version: Joi.number().integer().positive().required(),
},
- body: {
- param: Joi.object().keys({
- config: Joi.object().required(),
+ body: Joi.object().keys({
+ config: Joi.object().required(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
};
module.exports = [
@@ -43,13 +42,13 @@ module.exports = [
if (priceConfig) {
const version = priceConfig ? priceConfig.version : 1;
const revision = priceConfig ? priceConfig.revision + 1 : 1;
- const entity = _.assign(req.body.param, {
+ const entity = _.assign(req.body, {
version,
revision,
createdBy: req.authUser.userId,
updatedBy: req.authUser.userId,
key: req.params.key,
- config: req.body.param.config,
+ config: req.body.config,
});
return models.PriceConfig.create(entity);
}
@@ -57,9 +56,12 @@ module.exports = [
apiErr.status = 404;
return Promise.reject(apiErr);
}).then((createdEntity) => {
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE,
+ RESOURCES.PRICE_CONFIG_REVISION,
+ createdEntity.toJSON());
// Omit deletedAt, deletedBy
- res.status(201).json(util.wrapResponse(
- req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201));
+ res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'));
})
.catch(next));
},
diff --git a/src/routes/priceConfig/revision/create.spec.js b/src/routes/priceConfig/revision/create.spec.js
index b082eddc..537f043f 100644
--- a/src/routes/priceConfig/revision/create.spec.js
+++ b/src/routes/priceConfig/revision/create.spec.js
@@ -35,32 +35,33 @@ describe('CREATE PriceConfig Revision', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.PriceConfig.create(priceConfigs[0]))
- .then(() => models.PriceConfig.create(priceConfigs[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.PriceConfig.create(priceConfigs[0]))
+ .then(() => models.PriceConfig.create(priceConfigs[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('Post /projects/metadata/priceConfig/{key}/versions/{version}/revision', () => {
const body = {
- param: {
- config: {
- 'test create': 'test create',
- },
+
+ config: {
+ 'test create': 'test create',
},
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post('/v4/projects/metadata/priceConfig/dev/versions/1/revisions')
+ .post('/v5/projects/metadata/priceConfig/dev/versions/1/revisions')
.send(body)
.expect(403, done);
});
it('should return 404 if missing key', (done) => {
request(server)
- .post('/v4/projects/metadata/priceConfig/no-exist-key/versions/1/revisions')
+ .post('/v5/projects/metadata/priceConfig/no-exist-key/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -71,7 +72,7 @@ describe('CREATE PriceConfig Revision', () => {
it('should return 404 if missing version', (done) => {
request(server)
- .post('/v4/projects/metadata/priceConfig/dev/versions/100/revisions')
+ .post('/v5/projects/metadata/priceConfig/dev/versions/100/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -80,26 +81,24 @@ describe('CREATE PriceConfig Revision', () => {
.expect(404, done);
});
- it('should return 422 if missing config', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- config: undefined,
- }),
- };
+ it('should return 400 if missing config', (done) => {
+ const invalidBody = _.assign({}, body, {
+ config: undefined,
+ });
request(server)
- .post('/v4/projects/metadata/priceConfig/no-exist-key/versions/1/revisions')
+ .post('/v5/projects/metadata/priceConfig/no-exist-key/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 201 for admin', (done) => {
request(server)
- .post('/v4/projects/metadata/priceConfig/dev/versions/1/revisions')
+ .post('/v5/projects/metadata/priceConfig/dev/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -107,9 +106,9 @@ describe('CREATE PriceConfig Revision', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson.id);
- resJson.config.should.be.eql(body.param.config);
+ resJson.config.should.be.eql(body.config);
resJson.key.should.be.eql('dev');
resJson.revision.should.be.eql(3);
resJson.version.should.be.eql(1);
@@ -125,7 +124,7 @@ describe('CREATE PriceConfig Revision', () => {
it('should return 403 for member', (done) => {
request(server)
- .post('/v4/projects/metadata/priceConfig/dev/versions/1/revisions')
+ .post('/v5/projects/metadata/priceConfig/dev/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
diff --git a/src/routes/priceConfig/revision/delete.js b/src/routes/priceConfig/revision/delete.js
index 3ce81743..a529ba58 100644
--- a/src/routes/priceConfig/revision/delete.js
+++ b/src/routes/priceConfig/revision/delete.js
@@ -2,8 +2,11 @@
* API to delete a revsion
*/
import validate from 'express-validation';
+import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../../constants';
+import util from '../../../util';
import models from '../../../models';
const permissions = tcMiddleware.permissions;
@@ -38,9 +41,13 @@ module.exports = [
return priceConfig.update({
deletedBy: req.authUser.userId,
});
- }).then((priceConfig) => {
- priceConfig.destroy();
- }).then(() => {
+ }).then(priceConfig =>
+ priceConfig.destroy(),
+ ).then((priceConfig) => {
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE,
+ RESOURCES.PRICE_CONFIG_REVISION,
+ _.pick(priceConfig.toJSON(), 'id'));
res.status(204).end();
})
.catch(next));
diff --git a/src/routes/priceConfig/revision/delete.spec.js b/src/routes/priceConfig/revision/delete.spec.js
index 0acc1491..4ab18cda 100644
--- a/src/routes/priceConfig/revision/delete.spec.js
+++ b/src/routes/priceConfig/revision/delete.spec.js
@@ -26,7 +26,7 @@ const expectAfterDelete = (err, next) => {
chai.assert.isNotNull(res.deletedBy);
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/1')
+ .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -60,24 +60,25 @@ describe('DELETE priceConfig revision', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.PriceConfig.create(priceConfigs[0]))
- .then(() => models.PriceConfig.create(priceConfigs[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
-
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.PriceConfig.create(priceConfigs[0]))
+ .then(() => models.PriceConfig.create(priceConfigs[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('DELETE /projects/metadata/priceConfig/{key}/versions/{version}/revisions/{revision}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .delete('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/1')
+ .delete('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/1')
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .delete('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/1')
+ .delete('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -86,7 +87,7 @@ describe('DELETE priceConfig revision', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .delete('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/1')
+ .delete('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -95,7 +96,7 @@ describe('DELETE priceConfig revision', () => {
it('should return 403 for manager', (done) => {
request(server)
- .delete('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/1')
+ .delete('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -104,7 +105,7 @@ describe('DELETE priceConfig revision', () => {
it('should return 404 for non-existed key', (done) => {
request(server)
- .delete('/v4/projects/metadata/priceConfig/no-existed-key/versions/1/revisions/1')
+ .delete('/v5/projects/metadata/priceConfig/no-existed-key/versions/1/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -113,7 +114,7 @@ describe('DELETE priceConfig revision', () => {
it('should return 404 for non-existed version', (done) => {
request(server)
- .delete('/v4/projects/metadata/priceConfig/dev/versions/100/revisions/1')
+ .delete('/v5/projects/metadata/priceConfig/dev/versions/100/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -123,7 +124,7 @@ describe('DELETE priceConfig revision', () => {
it('should return 404 for non-existed revision', (done) => {
request(server)
- .delete('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/100')
+ .delete('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/100')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -132,7 +133,7 @@ describe('DELETE priceConfig revision', () => {
it('should return 204, for admin', (done) => {
request(server)
- .delete('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/1')
+ .delete('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -142,7 +143,7 @@ describe('DELETE priceConfig revision', () => {
it('should return 204, for connect admin', (done) => {
request(server)
- .delete('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/1')
+ .delete('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
diff --git a/src/routes/priceConfig/revision/get.js b/src/routes/priceConfig/revision/get.js
index 081f051c..acd2da9a 100644
--- a/src/routes/priceConfig/revision/get.js
+++ b/src/routes/priceConfig/revision/get.js
@@ -4,8 +4,8 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../../util';
import models from '../../../models';
+import util from '../../../util';
const permissions = tcMiddleware.permissions;
@@ -20,25 +20,58 @@ const schema = {
module.exports = [
validate(schema),
permissions('priceConfig.view'),
- (req, res, next) => models.PriceConfig.findOne({
- where: {
- key: req.params.key,
- version: req.params.version,
- revision: req.params.revision,
- },
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- })
- .then((priceConfig) => {
- // Not found
- if (!priceConfig) {
- const apiErr = new Error('PriceConfig not found for key' +
- ` ${req.params.key} version ${req.params.version} revision ${req.params.revision}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
- }
+ (req, res, next) => {
+ util.fetchByIdFromES('priceConfigs', {
+ query: {
+ nested: {
+ path: 'priceConfigs',
+ query:
+ {
+ filtered: {
+ filter: {
+ bool: {
+ must: [
+ { term: { 'priceConfigs.key': req.params.key } },
+ { term: { 'priceConfigs.version': req.params.version } },
+ { term: { 'priceConfigs.revision': req.params.revision } },
+ ],
+ },
+ },
+ },
+ },
+ inner_hits: {},
+ },
+ },
+ }, 'metadata')
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No priceConfi found in ES');
+ models.PriceConfig.findOne({
+ where: {
+ key: req.params.key,
+ version: req.params.version,
+ revision: req.params.revision,
+ },
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ })
+ .then((priceConfig) => {
+ // Not found
+ if (!priceConfig) {
+ const apiErr = new Error('PriceConfig not found for key' +
+ ` ${req.params.key} version ${req.params.version} revision ${req.params.revision}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
- res.json(util.wrapResponse(req.id, priceConfig));
- return Promise.resolve();
+ res.json(priceConfig);
+ return Promise.resolve();
+ })
+ .catch(next);
+ } else {
+ req.log.debug('priceConfigs found in ES');
+ res.json(data[0].inner_hits.priceConfigs.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle
+ }
})
- .catch(next),
+ .catch(next);
+ },
];
diff --git a/src/routes/priceConfig/revision/get.spec.js b/src/routes/priceConfig/revision/get.spec.js
index 7830f440..65f6e72b 100644
--- a/src/routes/priceConfig/revision/get.spec.js
+++ b/src/routes/priceConfig/revision/get.spec.js
@@ -34,24 +34,26 @@ describe('GET a particular revision of specific version PriceConfig', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.PriceConfig.create(priceConfigs[0]))
- .then(() => models.PriceConfig.create(priceConfigs[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.PriceConfig.create(priceConfigs[0]))
+ .then(() => models.PriceConfig.create(priceConfigs[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/priceConfig/dev/versions/{version}/revisions/{revision}', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/2')
+ .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/2')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
const priceConfig = priceConfigs[1];
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(priceConfig.key);
resJson.config.should.be.eql(priceConfig.config);
@@ -68,13 +70,13 @@ describe('GET a particular revision of specific version PriceConfig', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/2')
+ .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/2')
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/2')
+ .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/2')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -84,7 +86,7 @@ describe('GET a particular revision of specific version PriceConfig', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/2')
+ .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/2')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -94,7 +96,7 @@ describe('GET a particular revision of specific version PriceConfig', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/2')
+ .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/2')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -103,7 +105,7 @@ describe('GET a particular revision of specific version PriceConfig', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/2')
+ .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/2')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/priceConfig/revision/list.js b/src/routes/priceConfig/revision/list.js
index 1d67a9d1..bbfa404d 100644
--- a/src/routes/priceConfig/revision/list.js
+++ b/src/routes/priceConfig/revision/list.js
@@ -4,8 +4,8 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../../util';
import models from '../../../models';
+import util from '../../../util';
const permissions = tcMiddleware.permissions;
@@ -19,23 +19,34 @@ const schema = {
module.exports = [
validate(schema),
permissions('priceConfig.view'),
- (req, res, next) => models.PriceConfig.findAll({
- where: {
- key: req.params.key,
- version: req.params.version,
- },
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- })
- .then((priceConfigs) => {
- // Not found
- if ((!priceConfigs) || (priceConfigs.length === 0)) {
- const apiErr = new Error(`PriceConfig not found for key ${req.params.key} version ${req.params.version}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
- }
+ (req, res, next) =>
+ util.fetchFromES('priceConfigs')
+ .then((data) => {
+ if (data.priceConfigs.length === 0) {
+ req.log.debug('No priceConfig found in ES');
+ models.PriceConfig.findAll({
+ where: {
+ key: req.params.key,
+ version: req.params.version,
+ },
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ })
+ .then((priceConfigs) => {
+ // Not found
+ if ((!priceConfigs) || (priceConfigs.length === 0)) {
+ const apiErr = new Error(`PriceConfig not found for key ${req.params.key} version ${req.params.version}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
+
+ res.json(priceConfigs);
+ return Promise.resolve();
+ })
+ .catch(next);
+ } else {
+ req.log.debug('priceConfigs found in ES');
+ res.json(data.priceConfigs);
+ }
+ }).catch(next),
- res.json(util.wrapResponse(req.id, priceConfigs));
- return Promise.resolve();
- })
- .catch(next),
];
diff --git a/src/routes/priceConfig/revision/list.spec.js b/src/routes/priceConfig/revision/list.spec.js
index 04363891..04623fda 100644
--- a/src/routes/priceConfig/revision/list.spec.js
+++ b/src/routes/priceConfig/revision/list.spec.js
@@ -35,24 +35,26 @@ describe('LIST priceConfig revisions', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.PriceConfig.create(priceConfigs[0]))
- .then(() => models.PriceConfig.create(priceConfigs[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.PriceConfig.create(priceConfigs[0]))
+ .then(() => models.PriceConfig.create(priceConfigs[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/priceConfig/dev/versions/{version}/revisions', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions')
+ .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
const priceConfig = priceConfigs[0];
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
resJson[0].key.should.be.eql(priceConfig.key);
@@ -70,13 +72,13 @@ describe('LIST priceConfig revisions', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions')
+ .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions')
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions')
+ .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -86,7 +88,7 @@ describe('LIST priceConfig revisions', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions')
+ .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -96,7 +98,7 @@ describe('LIST priceConfig revisions', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions')
+ .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -105,7 +107,7 @@ describe('LIST priceConfig revisions', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions')
+ .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/priceConfig/version/create.js b/src/routes/priceConfig/version/create.js
index d3c71335..504e9f7c 100644
--- a/src/routes/priceConfig/version/create.js
+++ b/src/routes/priceConfig/version/create.js
@@ -5,6 +5,7 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../../constants';
import util from '../../../util';
import models from '../../../models';
@@ -14,18 +15,18 @@ const schema = {
params: {
key: Joi.string().max(45).required(),
},
- body: {
- param: Joi.object().keys({
- config: Joi.object().required(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ body: Joi.object().keys({
+ config: Joi.object().required(),
+
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
+
};
module.exports = [
@@ -45,19 +46,22 @@ module.exports = [
latestVersion = latestVersionPriceConfig.version + 1;
}
- const entity = _.assign(req.body.param, {
+ const entity = _.assign(req.body, {
version: latestVersion,
revision: 1,
createdBy: req.authUser.userId,
updatedBy: req.authUser.userId,
key: req.params.key,
- config: req.body.param.config,
+ config: req.body.config,
});
return models.PriceConfig.create(entity);
}).then((createdEntity) => {
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE,
+ RESOURCES.PRICE_CONFIG_VERSION,
+ createdEntity.toJSON());
// Omit deletedAt, deletedBy
- res.status(201).json(util.wrapResponse(
- req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201));
+ res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'));
})
.catch(next));
},
diff --git a/src/routes/priceConfig/version/create.spec.js b/src/routes/priceConfig/version/create.spec.js
index 6ad58665..23a659e4 100644
--- a/src/routes/priceConfig/version/create.spec.js
+++ b/src/routes/priceConfig/version/create.spec.js
@@ -35,49 +35,47 @@ describe('CREATE PriceConfig version', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.PriceConfig.create(priceConfigs[0]))
- .then(() => models.PriceConfig.create(priceConfigs[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.PriceConfig.create(priceConfigs[0]))
+ .then(() => models.PriceConfig.create(priceConfigs[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('Post /projects/metadata/priceConfig/{key}/versions/', () => {
const body = {
- param: {
- config: {
- 'test create': 'test create',
- },
+ config: {
+ 'test create': 'test create',
},
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post('/v4/projects/metadata/priceConfig/dev/versions')
+ .post('/v5/projects/metadata/priceConfig/dev/versions')
.send(body)
.expect(403, done);
});
- it('should return 422 if missing config', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- config: undefined,
- }),
- };
+ it('should return 400 if missing config', (done) => {
+ const invalidBody = _.assign({}, body, {
+ config: undefined,
+ });
request(server)
- .post('/v4/projects/metadata/priceConfig/dev/versions/')
+ .post('/v5/projects/metadata/priceConfig/dev/versions/')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 201 for admin', (done) => {
request(server)
- .post('/v4/projects/metadata/priceConfig/dev/versions/')
+ .post('/v5/projects/metadata/priceConfig/dev/versions/')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -85,9 +83,9 @@ describe('CREATE PriceConfig version', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson.id);
- resJson.config.should.be.eql(body.param.config);
+ resJson.config.should.be.eql(body.config);
resJson.key.should.be.eql('dev');
resJson.revision.should.be.eql(1);
resJson.version.should.be.eql(2);
@@ -103,7 +101,7 @@ describe('CREATE PriceConfig version', () => {
it('should return 403 for member', (done) => {
request(server)
- .post('/v4/projects/metadata/priceConfig/dev/versions/')
+ .post('/v5/projects/metadata/priceConfig/dev/versions/')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
diff --git a/src/routes/priceConfig/version/delete.js b/src/routes/priceConfig/version/delete.js
index 3aa4bc66..3a9eaf28 100644
--- a/src/routes/priceConfig/version/delete.js
+++ b/src/routes/priceConfig/version/delete.js
@@ -2,8 +2,11 @@
* API to add a project type
*/
import validate from 'express-validation';
+import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../../constants';
+import util from '../../../util';
import models from '../../../models';
const permissions = tcMiddleware.permissions;
@@ -46,7 +49,21 @@ module.exports = [
key: req.params.key,
version: req.params.version,
},
- })).then(() => {
+ }))
+ .then(deleted => models.PriceConfig.findAll({
+ where: {
+ key: req.params.key,
+ version: req.params.version,
+ },
+ paranoid: false,
+ order: [['deletedAt', 'DESC']],
+ limit: deleted,
+ }))
+ .then((priceConfigs) => {
+ _.map(priceConfigs, priceConfig => util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE,
+ RESOURCES.PRICE_CONFIG_VERSION,
+ _.pick(priceConfig.toJSON(), 'id')));
res.status(204).end();
})
.catch(next));
diff --git a/src/routes/priceConfig/version/delete.spec.js b/src/routes/priceConfig/version/delete.spec.js
index 2bcac11f..534ffc82 100644
--- a/src/routes/priceConfig/version/delete.spec.js
+++ b/src/routes/priceConfig/version/delete.spec.js
@@ -56,24 +56,26 @@ describe('DELETE priceConfig version', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.PriceConfig.create(priceConfigs[0]))
- .then(() => models.PriceConfig.create(priceConfigs[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.PriceConfig.create(priceConfigs[0]))
+ .then(() => models.PriceConfig.create(priceConfigs[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('DELETE /projects/metadata/priceConfig/{key}/versions/{version}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .delete('/v4/projects/metadata/priceConfig/dev/versions/1')
+ .delete('/v5/projects/metadata/priceConfig/dev/versions/1')
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .delete('/v4/projects/metadata/priceConfig/dev/versions/1')
+ .delete('/v5/projects/metadata/priceConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -82,7 +84,7 @@ describe('DELETE priceConfig version', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .delete('/v4/projects/metadata/priceConfig/dev/versions/1')
+ .delete('/v5/projects/metadata/priceConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -91,7 +93,7 @@ describe('DELETE priceConfig version', () => {
it('should return 403 for manager', (done) => {
request(server)
- .delete('/v4/projects/metadata/priceConfig/dev/versions/1')
+ .delete('/v5/projects/metadata/priceConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -100,7 +102,7 @@ describe('DELETE priceConfig version', () => {
it('should return 404 for non-existed key', (done) => {
request(server)
- .delete('/v4/projects/metadata/priceConfig/dev111/versions/1')
+ .delete('/v5/projects/metadata/priceConfig/dev111/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -109,7 +111,7 @@ describe('DELETE priceConfig version', () => {
it('should return 404 for non-existed version', (done) => {
request(server)
- .delete('/v4/projects/metadata/priceConfig/dev/versions/111')
+ .delete('/v5/projects/metadata/priceConfig/dev/versions/111')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -118,7 +120,7 @@ describe('DELETE priceConfig version', () => {
it('should return 204, for admin', (done) => {
request(server)
- .delete('/v4/projects/metadata/priceConfig/dev/versions/1')
+ .delete('/v5/projects/metadata/priceConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -128,7 +130,7 @@ describe('DELETE priceConfig version', () => {
it('should return 204, for connect admin', (done) => {
request(server)
- .delete('/v4/projects/metadata/priceConfig/dev/versions/1')
+ .delete('/v5/projects/metadata/priceConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
diff --git a/src/routes/priceConfig/version/get.js b/src/routes/priceConfig/version/get.js
index da624c3b..d23b4bae 100644
--- a/src/routes/priceConfig/version/get.js
+++ b/src/routes/priceConfig/version/get.js
@@ -5,8 +5,8 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../../util';
import models from '../../../models';
+import util from '../../../util';
const permissions = tcMiddleware.permissions;
@@ -19,15 +19,48 @@ const schema = {
module.exports = [
validate(schema),
permissions('priceConfig.view'),
- (req, res, next) => models.PriceConfig.latestRevisionOfLatestVersion(req.params.key)
- .then((form) => {
- if (form == null) {
- const apiErr = new Error(`PriceConfig not found for key ${req.params.key} version ${req.params.version}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
- }
- res.json(util.wrapResponse(req.id, form));
- return Promise.resolve();
- })
-.catch(next),
+ (req, res, next) => {
+ util.fetchByIdFromES('priceConfigs', {
+ query: {
+ nested: {
+ path: 'priceConfigs',
+ query:
+ {
+ filtered: {
+ filter: {
+ bool: {
+ must: [
+ { term: { 'priceConfigs.key': req.params.key } },
+ ],
+ },
+ },
+ },
+ },
+ inner_hits: {},
+ },
+ },
+ sort: { 'priceConfigs.version': 'desc' },
+ }, 'metadata')
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No priceConfig found in ES');
+
+ models.PriceConfig.latestRevisionOfLatestVersion(req.params.key)
+ .then((priceConfig) => {
+ if (priceConfig == null) {
+ const apiErr = new Error(`PriceConfig not found for key ${req.params.key} version ${req.params.version}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
+ res.json(priceConfig);
+ return Promise.resolve();
+ })
+ .catch(next);
+ } else {
+ req.log.debug('priceConfigs found in ES');
+ res.json(data[0].inner_hits.priceConfigs.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle
+ }
+ })
+ .catch(next);
+ },
];
diff --git a/src/routes/priceConfig/version/get.spec.js b/src/routes/priceConfig/version/get.spec.js
index d76afd60..e7062502 100644
--- a/src/routes/priceConfig/version/get.spec.js
+++ b/src/routes/priceConfig/version/get.spec.js
@@ -54,25 +54,27 @@ describe('GET a latest version of specific key of PriceConfig', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.PriceConfig.create(priceConfigs[0]))
- .then(() => models.PriceConfig.create(priceConfigs[1]))
- .then(() => models.PriceConfig.create(priceConfigs[2]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.PriceConfig.create(priceConfigs[0]))
+ .then(() => models.PriceConfig.create(priceConfigs[1]))
+ .then(() => models.PriceConfig.create(priceConfigs[2]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/priceConfig/dev/versions/{version}', () => {
it('should return 200 and correct version for admin', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev')
+ .get('/v5/projects/metadata/priceConfig/dev')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
const priceConfig = priceConfigs[2];
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(priceConfig.key);
resJson.config.should.be.eql(priceConfig.config);
@@ -89,13 +91,13 @@ describe('GET a latest version of specific key of PriceConfig', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev')
+ .get('/v5/projects/metadata/priceConfig/dev')
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev')
+ .get('/v5/projects/metadata/priceConfig/dev')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -105,7 +107,7 @@ describe('GET a latest version of specific key of PriceConfig', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev')
+ .get('/v5/projects/metadata/priceConfig/dev')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -115,7 +117,7 @@ describe('GET a latest version of specific key of PriceConfig', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev')
+ .get('/v5/projects/metadata/priceConfig/dev')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -124,7 +126,7 @@ describe('GET a latest version of specific key of PriceConfig', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev')
+ .get('/v5/projects/metadata/priceConfig/dev')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/priceConfig/version/getVersion.js b/src/routes/priceConfig/version/getVersion.js
index 5ebaa1fe..c6b9120e 100644
--- a/src/routes/priceConfig/version/getVersion.js
+++ b/src/routes/priceConfig/version/getVersion.js
@@ -4,8 +4,8 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../../util';
import models from '../../../models';
+import util from '../../../util';
const permissions = tcMiddleware.permissions;
@@ -19,16 +19,48 @@ const schema = {
module.exports = [
validate(schema),
permissions('priceConfig.view'),
- (req, res, next) => models.PriceConfig.findOneWithLatestRevision(req.params)
- .then((priceConfig) => {
- // Not found
- if (!priceConfig) {
- const apiErr = new Error(`PriceConfig not found for key ${req.params.key} version ${req.params.version}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
+ (req, res, next) => {
+ util.fetchByIdFromES('priceConfigs', {
+ query: {
+ nested: {
+ path: 'priceConfigs',
+ query: {
+ filtered: {
+ filter: {
+ bool: {
+ must: [
+ { term: { 'priceConfigs.key': req.params.key } },
+ { term: { 'priceConfigs.version': req.params.version } },
+ ],
+ },
+ },
+ },
+ },
+ inner_hits: {},
+ },
+ },
+ sort: { 'priceConfigs.revision': 'desc' },
+ }, 'metadata')
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No priceConfig found in ES');
+ return models.PriceConfig.findOneWithLatestRevision(req.params)
+ .then((priceConfig) => {
+ // Not found
+ if (!priceConfig) {
+ const apiErr = new Error(`PriceConfig not found for key ${req.params.key} version ${req.params.version}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
+ res.json(priceConfig);
+ return Promise.resolve();
+ })
+ .catch(next);
}
- res.json(util.wrapResponse(req.id, priceConfig));
+ req.log.debug('priceConfigs found in ES');
+ res.json(data[0].inner_hits.priceConfigs.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle
return Promise.resolve();
})
- .catch(next),
+ .catch(next);
+ },
];
diff --git a/src/routes/priceConfig/version/getVersion.spec.js b/src/routes/priceConfig/version/getVersion.spec.js
index 04671910..7f693610 100644
--- a/src/routes/priceConfig/version/getVersion.spec.js
+++ b/src/routes/priceConfig/version/getVersion.spec.js
@@ -44,25 +44,27 @@ describe('GET a particular version of specific key of PriceConfig', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.PriceConfig.create(priceConfigs[0]))
- .then(() => models.PriceConfig.create(priceConfigs[1]))
- .then(() => models.PriceConfig.create(priceConfigs[2]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.PriceConfig.create(priceConfigs[0]))
+ .then(() => models.PriceConfig.create(priceConfigs[1]))
+ .then(() => models.PriceConfig.create(priceConfigs[2]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/priceConfig/dev/versions/{version}', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions/1')
+ .get('/v5/projects/metadata/priceConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
const priceConfig = priceConfigs[0];
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(priceConfig.key);
resJson.config.should.be.eql(priceConfig.config);
@@ -79,13 +81,13 @@ describe('GET a particular version of specific key of PriceConfig', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions/1')
+ .get('/v5/projects/metadata/priceConfig/dev/versions/1')
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev')
+ .get('/v5/projects/metadata/priceConfig/dev')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -95,7 +97,7 @@ describe('GET a particular version of specific key of PriceConfig', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions/1')
+ .get('/v5/projects/metadata/priceConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -105,7 +107,7 @@ describe('GET a particular version of specific key of PriceConfig', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions/1')
+ .get('/v5/projects/metadata/priceConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -114,7 +116,7 @@ describe('GET a particular version of specific key of PriceConfig', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions/1')
+ .get('/v5/projects/metadata/priceConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/priceConfig/version/list.js b/src/routes/priceConfig/version/list.js
index aa4e9e77..a8257e94 100644
--- a/src/routes/priceConfig/version/list.js
+++ b/src/routes/priceConfig/version/list.js
@@ -4,8 +4,8 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../../util';
import models from '../../../models';
+import util from '../../../util';
const permissions = tcMiddleware.permissions;
@@ -18,30 +18,40 @@ const schema = {
module.exports = [
validate(schema),
permissions('priceConfig.view'),
- (req, res, next) => models.PriceConfig.findAll({
- where: {
- key: req.params.key,
- },
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- })
- .then((priceConfigs) => {
- // Not found
- if ((!priceConfigs) || (priceConfigs.length === 0)) {
- const apiErr = new Error(`PriceConfig not found for key ${req.params.key}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
- }
+ (req, res, next) =>
+ util.fetchFromES('priceConfigs')
+ .then((data) => {
+ if (data.priceConfigs.length === 0) {
+ req.log.debug('No priceConfig found in ES');
+ models.PriceConfig.findAll({
+ where: {
+ key: req.params.key,
+ },
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ })
+ .then((priceConfigs) => {
+ // Not found
+ if ((!priceConfigs) || (priceConfigs.length === 0)) {
+ const apiErr = new Error(`PriceConfig not found for key ${req.params.key}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
- const latestPriceConfigs = {};
- priceConfigs.forEach((element) => {
- const isNewerRevision = (latestPriceConfigs[element.version] != null) &&
- (latestPriceConfigs[element.version].revision < element.revision);
- if ((latestPriceConfigs[element.version] == null) || isNewerRevision) {
- latestPriceConfigs[element.version] = element;
- }
- });
- res.json(util.wrapResponse(req.id, Object.values(latestPriceConfigs)));
- return Promise.resolve();
- })
- .catch(next),
+ const latestPriceConfigs = {};
+ priceConfigs.forEach((element) => {
+ const isNewerRevision = (latestPriceConfigs[element.version] != null) &&
+ (latestPriceConfigs[element.version].revision < element.revision);
+ if ((latestPriceConfigs[element.version] == null) || isNewerRevision) {
+ latestPriceConfigs[element.version] = element;
+ }
+ });
+ res.json(Object.values(latestPriceConfigs));
+ return Promise.resolve();
+ })
+ .catch(next);
+ } else {
+ req.log.debug('priceConfigs found in ES');
+ res.json(data.priceConfigs);
+ }
+ }).catch(next),
];
diff --git a/src/routes/priceConfig/version/list.spec.js b/src/routes/priceConfig/version/list.spec.js
index 2c58aca0..ec245406 100644
--- a/src/routes/priceConfig/version/list.spec.js
+++ b/src/routes/priceConfig/version/list.spec.js
@@ -35,24 +35,26 @@ describe('LIST priceConfig versions', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.PriceConfig.create(priceConfigs[0]))
- .then(() => models.PriceConfig.create(priceConfigs[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.PriceConfig.create(priceConfigs[0]))
+ .then(() => models.PriceConfig.create(priceConfigs[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/priceConfig/dev/versions/{version}', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions')
+ .get('/v5/projects/metadata/priceConfig/dev/versions')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
const priceConfig = priceConfigs[0];
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
resJson[0].key.should.be.eql(priceConfig.key);
@@ -70,13 +72,13 @@ describe('LIST priceConfig versions', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions')
+ .get('/v5/projects/metadata/priceConfig/dev/versions')
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions')
+ .get('/v5/projects/metadata/priceConfig/dev/versions')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -86,7 +88,7 @@ describe('LIST priceConfig versions', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions')
+ .get('/v5/projects/metadata/priceConfig/dev/versions')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -96,7 +98,7 @@ describe('LIST priceConfig versions', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions')
+ .get('/v5/projects/metadata/priceConfig/dev/versions')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -105,7 +107,7 @@ describe('LIST priceConfig versions', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/projects/metadata/priceConfig/dev/versions')
+ .get('/v5/projects/metadata/priceConfig/dev/versions')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/priceConfig/version/update.js b/src/routes/priceConfig/version/update.js
index 6b7a7e44..1a58138d 100644
--- a/src/routes/priceConfig/version/update.js
+++ b/src/routes/priceConfig/version/update.js
@@ -7,6 +7,7 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../../constants';
import util from '../../../util';
import models from '../../../models';
@@ -17,18 +18,18 @@ const schema = {
version: Joi.number().integer().positive().required(),
key: Joi.string().max(45).required(),
},
- body: {
- param: Joi.object().keys({
- config: Joi.object().required(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ body: Joi.object().keys({
+ config: Joi.object().required(),
+
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
+
};
module.exports = [
@@ -60,14 +61,17 @@ module.exports = [
createdBy: req.authUser.userId,
updatedBy: req.authUser.userId,
key: req.params.key,
- config: req.body.param.config,
+ config: req.body.config,
};
return models.PriceConfig.create(entity);
})
.then((createdEntity) => {
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE,
+ RESOURCES.PRICE_CONFIG_VERSION,
+ createdEntity.toJSON());
// Omit deletedAt, deletedBy
- res.status(201).json(util.wrapResponse(
- req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201));
+ res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'));
})
.catch(next));
},
diff --git a/src/routes/priceConfig/version/update.spec.js b/src/routes/priceConfig/version/update.spec.js
index 3a9cad6e..ebb4d853 100644
--- a/src/routes/priceConfig/version/update.spec.js
+++ b/src/routes/priceConfig/version/update.spec.js
@@ -35,48 +35,46 @@ describe('UPDATE PriceConfig version', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.PriceConfig.create(priceConfigs[0]))
- .then(() => models.PriceConfig.create(priceConfigs[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.PriceConfig.create(priceConfigs[0]))
+ .then(() => models.PriceConfig.create(priceConfigs[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('Post /projects/metadata/priceConfig/{key}/versions/{version}', () => {
const body = {
- param: {
- config: {
- 'test create': 'test create',
- },
+ config: {
+ 'test create': 'test create',
},
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .patch('/v4/projects/metadata/priceConfig/dev/versions/1')
+ .patch('/v5/projects/metadata/priceConfig/dev/versions/1')
.send(body)
.expect(403, done);
});
- it('should return 422 if missing config', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- config: undefined,
- }),
- };
+ it('should return 400 if missing config', (done) => {
+ const invalidBody = _.assign({}, body, {
+ config: undefined,
+ });
request(server)
- .patch('/v4/projects/metadata/priceConfig/dev/versions/1')
+ .patch('/v5/projects/metadata/priceConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 201 for admin', (done) => {
request(server)
- .patch('/v4/projects/metadata/priceConfig/dev/versions/1')
+ .patch('/v5/projects/metadata/priceConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -84,9 +82,9 @@ describe('UPDATE PriceConfig version', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson.id);
- resJson.config.should.be.eql(body.param.config);
+ resJson.config.should.be.eql(body.config);
resJson.key.should.be.eql('dev');
resJson.revision.should.be.eql(3);
resJson.version.should.be.eql(1);
@@ -102,7 +100,7 @@ describe('UPDATE PriceConfig version', () => {
it('should return 403 for member', (done) => {
request(server)
- .patch('/v4/projects/metadata/priceConfig/dev/versions/1')
+ .patch('/v5/projects/metadata/priceConfig/dev/versions/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
diff --git a/src/routes/productCategories/create.js b/src/routes/productCategories/create.js
index 152a7d2e..4abce161 100644
--- a/src/routes/productCategories/create.js
+++ b/src/routes/productCategories/create.js
@@ -5,56 +5,58 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../constants';
import util from '../../util';
import models from '../../models';
const permissions = tcMiddleware.permissions;
const schema = {
- body: {
- param: Joi.object().keys({
- key: Joi.string().max(45).required(),
- displayName: Joi.string().max(255).required(),
- icon: Joi.string().max(255).required(),
- question: Joi.string().max(255).required(),
- info: Joi.string().max(255).required(),
- aliases: Joi.array().required(),
- disabled: Joi.boolean().optional(),
- hidden: Joi.boolean().optional(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ body: Joi.object().keys({
+ key: Joi.string().max(45).required(),
+ displayName: Joi.string().max(255).required(),
+ icon: Joi.string().max(255).required(),
+ question: Joi.string().max(255).required(),
+ info: Joi.string().max(255).required(),
+ aliases: Joi.array().required(),
+ disabled: Joi.boolean().optional(),
+ hidden: Joi.boolean().optional(),
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
};
module.exports = [
validate(schema),
permissions('productCategory.create'),
(req, res, next) => {
- const entity = _.assign(req.body.param, {
+ const entity = _.assign(req.body, {
createdBy: req.authUser.userId,
updatedBy: req.authUser.userId,
});
// Check if duplicated key
- return models.ProductCategory.findById(req.body.param.key, { paranoid: false })
+ return models.ProductCategory.findByPk(req.body.key, { paranoid: false })
.then((existing) => {
if (existing) {
- const apiErr = new Error(`Product category already exists (may be deleted) for key "${req.body.param.key}"`);
- apiErr.status = 422;
+ const apiErr = new Error(`Product category already exists (may be deleted) for key ${req.body.key}`);
+ apiErr.status = 400;
return Promise.reject(apiErr);
}
// Create
return models.ProductCategory.create(entity);
}).then((createdEntity) => {
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE,
+ RESOURCES.PRODUCT_CATEGORY,
+ createdEntity.toJSON());
// Omit deletedAt, deletedBy
- res.status(201).json(util.wrapResponse(
- req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201));
+ res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'));
})
.catch(next);
},
diff --git a/src/routes/productCategories/create.spec.js b/src/routes/productCategories/create.spec.js
index c345eda3..eeb4c75a 100644
--- a/src/routes/productCategories/create.spec.js
+++ b/src/routes/productCategories/create.spec.js
@@ -12,46 +12,47 @@ import models from '../../models';
const should = chai.should();
describe('CREATE product category', () => {
- beforeEach(() => testUtil.clearDb()
- .then(() => models.ProductCategory.create({
- key: 'key1',
- displayName: 'displayName 1',
- icon: 'http://example.com/icon1.ico',
- question: 'question 1',
- info: 'info 1',
- aliases: ['key-1', 'key_1'],
- disabled: false,
- hidden: false,
- createdBy: 1,
- updatedBy: 1,
- })).then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.ProductCategory.create({
+ key: 'key1',
+ displayName: 'displayName 1',
+ icon: 'http://example.com/icon1.ico',
+ question: 'question 1',
+ info: 'info 1',
+ aliases: ['key-1', 'key_1'],
+ disabled: false,
+ hidden: false,
+ createdBy: 1,
+ updatedBy: 1,
+ }).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('POST /projects/metadata/productCategories', () => {
const body = {
- param: {
- key: 'app_dev',
- displayName: 'Application Development',
- icon: 'prod-cat-app-icon',
- info: 'Application Development Info',
- question: 'What kind of devlopment you need?',
- aliases: ['key-1', 'key_1'],
- disabled: true,
- hidden: true,
- },
+ key: 'app_dev',
+ displayName: 'Application Development',
+ icon: 'prod-cat-app-icon',
+ info: 'Application Development Info',
+ question: 'What kind of devlopment you need?',
+ aliases: ['key-1', 'key_1'],
+ disabled: true,
+ hidden: true,
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post('/v4/projects/metadata/productCategories')
+ .post('/v5/projects/metadata/productCategories')
.send(body)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .post('/v4/projects/metadata/productCategories')
+ .post('/v5/projects/metadata/productCategories')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -61,7 +62,7 @@ describe('CREATE product category', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .post('/v4/projects/metadata/productCategories')
+ .post('/v5/projects/metadata/productCategories')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -71,7 +72,7 @@ describe('CREATE product category', () => {
it('should return 403 for manager', (done) => {
request(server)
- .post('/v4/projects/metadata/productCategories')
+ .post('/v5/projects/metadata/productCategories')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -79,93 +80,93 @@ describe('CREATE product category', () => {
.expect(403, done);
});
- it('should return 422 for missing key', (done) => {
+ it('should return 400 for missing key', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.key;
+ delete invalidBody.key;
request(server)
- .post('/v4/projects/metadata/productCategories')
+ .post('/v5/projects/metadata/productCategories')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for missing displayName', (done) => {
+ it('should return 400 for missing displayName', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.displayName;
+ delete invalidBody.displayName;
request(server)
- .post('/v4/projects/metadata/productCategories')
+ .post('/v5/projects/metadata/productCategories')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for missing icon', (done) => {
+ it('should return 400 for missing icon', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.icon;
+ delete invalidBody.icon;
request(server)
- .post('/v4/projects/metadata/productCategories')
+ .post('/v5/projects/metadata/productCategories')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for missing question', (done) => {
+ it('should return 400 for missing question', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.question;
+ delete invalidBody.question;
request(server)
- .post('/v4/projects/metadata/productCategories')
+ .post('/v5/projects/metadata/productCategories')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for missing info', (done) => {
+ it('should return 400 for missing info', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.info;
+ delete invalidBody.info;
request(server)
- .post('/v4/projects/metadata/productCategories')
+ .post('/v5/projects/metadata/productCategories')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for duplicated key', (done) => {
+ it('should return 400 for duplicated key', (done) => {
const invalidBody = _.cloneDeep(body);
- invalidBody.param.key = 'key1';
+ invalidBody.key = 'key1';
request(server)
- .post('/v4/projects/metadata/productCategories')
+ .post('/v5/projects/metadata/productCategories')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 201 for admin', (done) => {
request(server)
- .post('/v4/projects/metadata/productCategories')
+ .post('/v5/projects/metadata/productCategories')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -173,15 +174,15 @@ describe('CREATE product category', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
- resJson.key.should.be.eql(body.param.key);
- resJson.displayName.should.be.eql(body.param.displayName);
- resJson.icon.should.be.eql(body.param.icon);
- resJson.info.should.be.eql(body.param.info);
- resJson.question.should.be.eql(body.param.question);
- resJson.aliases.should.be.eql(body.param.aliases);
- resJson.disabled.should.be.eql(body.param.disabled);
- resJson.hidden.should.be.eql(body.param.hidden);
+ const resJson = res.body;
+ resJson.key.should.be.eql(body.key);
+ resJson.displayName.should.be.eql(body.displayName);
+ resJson.icon.should.be.eql(body.icon);
+ resJson.info.should.be.eql(body.info);
+ resJson.question.should.be.eql(body.question);
+ resJson.aliases.should.be.eql(body.aliases);
+ resJson.disabled.should.be.eql(body.disabled);
+ resJson.hidden.should.be.eql(body.hidden);
resJson.createdBy.should.be.eql(40051333); // admin
should.exist(resJson.createdAt);
@@ -196,7 +197,7 @@ describe('CREATE product category', () => {
it('should return 201 for connect admin', (done) => {
request(server)
- .post('/v4/projects/metadata/productCategories')
+ .post('/v5/projects/metadata/productCategories')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -204,15 +205,15 @@ describe('CREATE product category', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
- resJson.key.should.be.eql(body.param.key);
- resJson.displayName.should.be.eql(body.param.displayName);
- resJson.icon.should.be.eql(body.param.icon);
- resJson.info.should.be.eql(body.param.info);
- resJson.question.should.be.eql(body.param.question);
- resJson.aliases.should.be.eql(body.param.aliases);
- resJson.disabled.should.be.eql(body.param.disabled);
- resJson.hidden.should.be.eql(body.param.hidden);
+ const resJson = res.body;
+ resJson.key.should.be.eql(body.key);
+ resJson.displayName.should.be.eql(body.displayName);
+ resJson.icon.should.be.eql(body.icon);
+ resJson.info.should.be.eql(body.info);
+ resJson.question.should.be.eql(body.question);
+ resJson.aliases.should.be.eql(body.aliases);
+ resJson.disabled.should.be.eql(body.disabled);
+ resJson.hidden.should.be.eql(body.hidden);
resJson.createdBy.should.be.eql(40051336); // connect admin
resJson.updatedBy.should.be.eql(40051336); // connect admin
done();
diff --git a/src/routes/productCategories/delete.js b/src/routes/productCategories/delete.js
index 89f575bd..c5a9f084 100644
--- a/src/routes/productCategories/delete.js
+++ b/src/routes/productCategories/delete.js
@@ -2,8 +2,11 @@
* API to delete a product category
*/
import validate from 'express-validation';
+import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../constants';
+import util from '../../util';
import models from '../../models';
const permissions = tcMiddleware.permissions;
@@ -19,7 +22,7 @@ module.exports = [
permissions('productCategory.delete'),
(req, res, next) =>
models.sequelize.transaction(() =>
- models.ProductCategory.findById(req.params.key)
+ models.ProductCategory.findByPk(req.params.key)
.then((entity) => {
if (!entity) {
const apiErr = new Error(`Product category not found for key ${req.params.key}`);
@@ -30,7 +33,11 @@ module.exports = [
return entity.update({ deletedBy: req.authUser.userId });
})
.then(entity => entity.destroy()))
- .then(() => {
+ .then((entity) => {
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE,
+ RESOURCES.PRODUCT_CATEGORY,
+ _.pick(entity.toJSON(), 'key'));
res.status(204).end();
})
.catch(next),
diff --git a/src/routes/productCategories/delete.spec.js b/src/routes/productCategories/delete.spec.js
index 40f8baa5..d5ada904 100644
--- a/src/routes/productCategories/delete.spec.js
+++ b/src/routes/productCategories/delete.spec.js
@@ -26,7 +26,7 @@ const expectAfterDelete = (key, err, next) => {
chai.assert.isNotNull(res.deletedBy);
request(server)
- .get(`/v4/projects/metadata/productCategories/${key}`)
+ .get(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -38,30 +38,33 @@ const expectAfterDelete = (key, err, next) => {
describe('DELETE product category', () => {
const key = 'key1';
- beforeEach(() => testUtil.clearDb()
- .then(() => models.ProductCategory.create({
- key: 'key1',
- displayName: 'displayName 1',
- icon: 'http://example.com/icon1.ico',
- question: 'question 1',
- info: 'info 1',
- aliases: ['key-1', 'key_1'],
- createdBy: 1,
- updatedBy: 1,
- })).then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.ProductCategory.create({
+ key: 'key1',
+ displayName: 'displayName 1',
+ icon: 'http://example.com/icon1.ico',
+ question: 'question 1',
+ info: 'info 1',
+ aliases: ['key-1', 'key_1'],
+ createdBy: 1,
+ updatedBy: 1,
+ }).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('DELETE /projects/metadata/productCategories/{key}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .delete(`/v4/projects/metadata/productCategories/${key}`)
+ .delete(`/v5/projects/metadata/productCategories/${key}`)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .delete(`/v4/projects/metadata/productCategories/${key}`)
+ .delete(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -70,7 +73,7 @@ describe('DELETE product category', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .delete(`/v4/projects/metadata/productCategories/${key}`)
+ .delete(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -79,7 +82,7 @@ describe('DELETE product category', () => {
it('should return 403 for manager', (done) => {
request(server)
- .delete(`/v4/projects/metadata/productCategories/${key}`)
+ .delete(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -88,7 +91,7 @@ describe('DELETE product category', () => {
it('should return 404 for non-existed product category', (done) => {
request(server)
- .delete('/v4/projects/metadata/productCategories/not_existed')
+ .delete('/v5/projects/metadata/productCategories/not_existed')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -99,7 +102,7 @@ describe('DELETE product category', () => {
models.ProductCategory.destroy({ where: { key } })
.then(() => {
request(server)
- .delete(`/v4/projects/metadata/productCategories/${key}`)
+ .delete(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -109,7 +112,7 @@ describe('DELETE product category', () => {
it('should return 204, for admin, if the product category was successfully removed', (done) => {
request(server)
- .delete(`/v4/projects/metadata/productCategories/${key}`)
+ .delete(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -119,7 +122,7 @@ describe('DELETE product category', () => {
it('should return 204, for connect admin, if the product category was successfully removed', (done) => {
request(server)
- .delete(`/v4/projects/metadata/productCategories/${key}`)
+ .delete(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
diff --git a/src/routes/productCategories/get.js b/src/routes/productCategories/get.js
index f113859b..8279dbc9 100644
--- a/src/routes/productCategories/get.js
+++ b/src/routes/productCategories/get.js
@@ -4,8 +4,8 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../util';
import models from '../../models';
+import util from '../../util';
const permissions = tcMiddleware.permissions;
@@ -18,22 +18,44 @@ const schema = {
module.exports = [
validate(schema),
permissions('productCategory.view'),
- (req, res, next) => models.ProductCategory.findOne({
- where: {
- key: req.params.key,
- },
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- })
- .then((productCategory) => {
- // Not found
- if (!productCategory) {
- const apiErr = new Error(`Product category not found for key ${req.params.key}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
- }
+ (req, res, next) => {
+ util.fetchByIdFromES('productCategories', {
+ query: {
+ nested: {
+ path: 'productCategories',
+ query: {
+ match: { 'productCategories.key': req.params.key },
+ },
+ inner_hits: {},
+ },
+ },
+ }, 'metadata')
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No productCategory found in ES');
+ models.ProductCategory.findOne({
+ where: {
+ key: req.params.key,
+ },
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ })
+ .then((productCategory) => {
+ // Not found
+ if (!productCategory) {
+ const apiErr = new Error(`Product category not found for key ${req.params.key}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
- res.json(util.wrapResponse(req.id, productCategory));
- return Promise.resolve();
+ res.json(productCategory);
+ return Promise.resolve();
+ })
+ .catch(next);
+ } else {
+ req.log.debug('productCategories found in ES');
+ res.json(data[0].inner_hits.productCategories.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle
+ }
})
- .catch(next),
+ .catch(next);
+ },
];
diff --git a/src/routes/productCategories/get.spec.js b/src/routes/productCategories/get.spec.js
index 87cf71cb..0e7e82bc 100644
--- a/src/routes/productCategories/get.spec.js
+++ b/src/routes/productCategories/get.spec.js
@@ -26,16 +26,18 @@ describe('GET product category', () => {
const key = productCategory.key;
- beforeEach(() => testUtil.clearDb()
- .then(() => models.ProductCategory.create(productCategory))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.ProductCategory.create(productCategory).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/productCategories/{key}', () => {
it('should return 404 for non-existed product category', (done) => {
request(server)
- .get('/v4/projects/metadata/productCategories/1234')
+ .get('/v5/projects/metadata/productCategories/1234')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -46,7 +48,7 @@ describe('GET product category', () => {
models.ProductCategory.destroy({ where: { key } })
.then(() => {
request(server)
- .get(`/v4/projects/metadata/productCategories/${key}`)
+ .get(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -56,13 +58,13 @@ describe('GET product category', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get(`/v4/projects/metadata/productCategories/${key}`)
+ .get(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(productCategory.key);
resJson.displayName.should.be.eql(productCategory.displayName);
resJson.icon.should.be.eql(productCategory.icon);
@@ -84,13 +86,13 @@ describe('GET product category', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get(`/v4/projects/metadata/productCategories/${key}`)
+ .get(`/v5/projects/metadata/productCategories/${key}`)
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get(`/v4/projects/metadata/productCategories/${key}`)
+ .get(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -100,7 +102,7 @@ describe('GET product category', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get(`/v4/projects/metadata/productCategories/${key}`)
+ .get(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -110,7 +112,7 @@ describe('GET product category', () => {
it('should return 200 for member', (done) => {
request(server)
- .get(`/v4/projects/metadata/productCategories/${key}`)
+ .get(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -119,7 +121,7 @@ describe('GET product category', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get(`/v4/projects/metadata/productCategories/${key}`)
+ .get(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/productCategories/list.js b/src/routes/productCategories/list.js
index abc2a9e7..4a5553c9 100644
--- a/src/routes/productCategories/list.js
+++ b/src/routes/productCategories/list.js
@@ -2,19 +2,30 @@
* API to list all product categories
*/
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../util';
import models from '../../models';
+import util from '../../util';
const permissions = tcMiddleware.permissions;
module.exports = [
permissions('productCategory.view'),
- (req, res, next) => models.ProductCategory.findAll({
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- raw: true,
- })
- .then((productCategories) => {
- res.json(util.wrapResponse(req.id, productCategories));
- })
- .catch(next),
+ (req, res, next) => {
+ util.fetchFromES('productCategories')
+ .then((data) => {
+ if (data.productCategories.length === 0) {
+ req.log.debug('No productCategory found in ES');
+ models.ProductCategory.findAll({
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ raw: true,
+ })
+ .then((productCategories) => {
+ res.json(productCategories);
+ })
+ .catch(next);
+ } else {
+ req.log.debug('productCategories found in ES');
+ res.json(data.productCategories);
+ }
+ });
+ },
];
diff --git a/src/routes/productCategories/list.spec.js b/src/routes/productCategories/list.spec.js
index 94114813..9dcbc07b 100644
--- a/src/routes/productCategories/list.spec.js
+++ b/src/routes/productCategories/list.spec.js
@@ -37,18 +37,22 @@ describe('LIST product categories', () => {
updatedBy: 1,
},
];
-
- beforeEach(() => testUtil.clearDb()
- .then(() => models.ProductCategory.create(productCategories[0]))
- .then(() => models.ProductCategory.create(productCategories[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ before((done) => {
+ testUtil.clearES(done);
+ });
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.ProductCategory.create(productCategories[0]))
+ .then(() => models.ProductCategory.create(productCategories[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/productCategories', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/projects/metadata/productCategories')
+ .get('/v5/projects/metadata/productCategories')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -56,7 +60,7 @@ describe('LIST product categories', () => {
.end((err, res) => {
const type = productCategories[0];
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
resJson[0].key.should.be.eql(type.key);
resJson[0].displayName.should.be.eql(type.displayName);
@@ -79,13 +83,13 @@ describe('LIST product categories', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/projects/metadata/productCategories')
+ .get('/v5/projects/metadata/productCategories')
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/projects/metadata/productCategories')
+ .get('/v5/projects/metadata/productCategories')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -95,7 +99,7 @@ describe('LIST product categories', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/projects/metadata/productCategories')
+ .get('/v5/projects/metadata/productCategories')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -105,7 +109,7 @@ describe('LIST product categories', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/projects/metadata/productCategories')
+ .get('/v5/projects/metadata/productCategories')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -114,7 +118,7 @@ describe('LIST product categories', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/projects/metadata/productCategories')
+ .get('/v5/projects/metadata/productCategories')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/productCategories/update.js b/src/routes/productCategories/update.js
index 68958966..f4b7611e 100644
--- a/src/routes/productCategories/update.js
+++ b/src/routes/productCategories/update.js
@@ -5,6 +5,7 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../constants';
import util from '../../util';
import models from '../../models';
@@ -14,31 +15,31 @@ const schema = {
params: {
key: Joi.string().max(45).required(),
},
- body: {
- param: Joi.object().keys({
- key: Joi.any().strip(),
- displayName: Joi.string().max(255).optional(),
- icon: Joi.string().max(255).optional(),
- question: Joi.string().max(255).optional(),
- info: Joi.string().max(255).optional(),
- aliases: Joi.array().optional(),
- disabled: Joi.boolean().optional(),
- hidden: Joi.boolean().optional(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+
+ body: Joi.object().keys({
+ key: Joi.any().strip(),
+ displayName: Joi.string().max(255).optional(),
+ icon: Joi.string().max(255).optional(),
+ question: Joi.string().max(255).optional(),
+ info: Joi.string().max(255).optional(),
+ aliases: Joi.array().optional(),
+ disabled: Joi.boolean().optional(),
+ hidden: Joi.boolean().optional(),
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
+
};
module.exports = [
validate(schema),
permissions('productCategory.edit'),
(req, res, next) => {
- const entityToUpdate = _.assign(req.body.param, {
+ const entityToUpdate = _.assign(req.body, {
updatedBy: req.authUser.userId,
});
@@ -59,7 +60,12 @@ module.exports = [
return productCategory.update(entityToUpdate);
})
.then((productCategory) => {
- res.json(util.wrapResponse(req.id, productCategory));
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE,
+ RESOURCES.PRODUCT_CATEGORY,
+ productCategory.get({ json: true }),
+ );
+ res.json(productCategory);
return Promise.resolve();
})
.catch(next);
diff --git a/src/routes/productCategories/update.spec.js b/src/routes/productCategories/update.spec.js
index ad9a43b5..95a04ad9 100644
--- a/src/routes/productCategories/update.spec.js
+++ b/src/routes/productCategories/update.spec.js
@@ -26,35 +26,35 @@ describe('UPDATE product category', () => {
};
const key = productCategory.key;
- beforeEach(() => testUtil.clearDb()
- .then(() => models.ProductCategory.create(productCategory))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.ProductCategory.create(productCategory).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('PATCH /projects/metadata/productCategories/{key}', () => {
const body = {
- param: {
- displayName: 'displayName 1 - update',
- icon: 'http://example.com/icon1.ico - update',
- question: 'question 1 - update',
- info: 'info 1 - update',
- aliases: ['key-1-updated', 'key_1_updated'],
- disabled: true,
- hidden: true,
- },
+ displayName: 'displayName 1 - update',
+ icon: 'http://example.com/icon1.ico - update',
+ question: 'question 1 - update',
+ info: 'info 1 - update',
+ aliases: ['key-1-updated', 'key_1_updated'],
+ disabled: true,
+ hidden: true,
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .patch(`/v4/projects/metadata/productCategories/${key}`)
+ .patch(`/v5/projects/metadata/productCategories/${key}`)
.send(body)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .patch(`/v4/projects/metadata/productCategories/${key}`)
+ .patch(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -64,7 +64,7 @@ describe('UPDATE product category', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .patch(`/v4/projects/metadata/productCategories/${key}`)
+ .patch(`/v5/projects/metadata/productCategories/${key}`)
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
@@ -74,7 +74,7 @@ describe('UPDATE product category', () => {
it('should return 403 for manager', (done) => {
request(server)
- .patch(`/v4/projects/metadata/productCategories/${key}`)
+ .patch(`/v5/projects/metadata/productCategories/${key}`)
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
@@ -84,7 +84,7 @@ describe('UPDATE product category', () => {
it('should return 404 for non-existed product category', (done) => {
request(server)
- .patch('/v4/projects/metadata/productCategories/1234')
+ .patch('/v5/projects/metadata/productCategories/1234')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -96,7 +96,7 @@ describe('UPDATE product category', () => {
models.ProductCategory.destroy({ where: { key } })
.then(() => {
request(server)
- .patch(`/v4/projects/metadata/productCategories/${key}`)
+ .patch(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -107,23 +107,23 @@ describe('UPDATE product category', () => {
it('should return 200 for admin displayName updated', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.icon;
- delete partialBody.param.info;
- delete partialBody.param.question;
- delete partialBody.param.aliases;
- delete partialBody.param.disabled;
- delete partialBody.param.hidden;
+ delete partialBody.icon;
+ delete partialBody.info;
+ delete partialBody.question;
+ delete partialBody.aliases;
+ delete partialBody.disabled;
+ delete partialBody.hidden;
request(server)
- .patch(`/v4/projects/metadata/productCategories/${key}`)
+ .patch(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(partialBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(key);
- resJson.displayName.should.be.eql(partialBody.param.displayName);
+ resJson.displayName.should.be.eql(partialBody.displayName);
resJson.icon.should.be.eql(productCategory.icon);
resJson.info.should.be.eql(productCategory.info);
resJson.question.should.be.eql(productCategory.question);
@@ -143,24 +143,24 @@ describe('UPDATE product category', () => {
it('should return 200 for admin icon updated', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.info;
- delete partialBody.param.displayName;
- delete partialBody.param.question;
- delete partialBody.param.aliases;
- delete partialBody.param.disabled;
- delete partialBody.param.hidden;
+ delete partialBody.info;
+ delete partialBody.displayName;
+ delete partialBody.question;
+ delete partialBody.aliases;
+ delete partialBody.disabled;
+ delete partialBody.hidden;
request(server)
- .patch(`/v4/projects/metadata/productCategories/${key}`)
+ .patch(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(partialBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(key);
resJson.displayName.should.be.eql(productCategory.displayName);
- resJson.icon.should.be.eql(partialBody.param.icon);
+ resJson.icon.should.be.eql(partialBody.icon);
resJson.info.should.be.eql(productCategory.info);
resJson.question.should.be.eql(productCategory.question);
resJson.aliases.should.be.eql(productCategory.aliases);
@@ -179,25 +179,25 @@ describe('UPDATE product category', () => {
it('should return 200 for admin info updated', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.icon;
- delete partialBody.param.displayName;
- delete partialBody.param.question;
- delete partialBody.param.aliases;
- delete partialBody.param.disabled;
- delete partialBody.param.hidden;
+ delete partialBody.icon;
+ delete partialBody.displayName;
+ delete partialBody.question;
+ delete partialBody.aliases;
+ delete partialBody.disabled;
+ delete partialBody.hidden;
request(server)
- .patch(`/v4/projects/metadata/productCategories/${key}`)
+ .patch(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(partialBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(key);
resJson.displayName.should.be.eql(productCategory.displayName);
resJson.icon.should.be.eql(productCategory.icon);
- resJson.info.should.be.eql(partialBody.param.info);
+ resJson.info.should.be.eql(partialBody.info);
resJson.question.should.be.eql(productCategory.question);
resJson.aliases.should.be.eql(productCategory.aliases);
resJson.disabled.should.be.eql(productCategory.disabled);
@@ -215,26 +215,26 @@ describe('UPDATE product category', () => {
it('should return 200 for admin question updated', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.icon;
- delete partialBody.param.info;
- delete partialBody.param.displayName;
- delete partialBody.param.aliases;
- delete partialBody.param.disabled;
- delete partialBody.param.hidden;
+ delete partialBody.icon;
+ delete partialBody.info;
+ delete partialBody.displayName;
+ delete partialBody.aliases;
+ delete partialBody.disabled;
+ delete partialBody.hidden;
request(server)
- .patch(`/v4/projects/metadata/productCategories/${key}`)
+ .patch(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(partialBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(key);
resJson.displayName.should.be.eql(productCategory.displayName);
resJson.icon.should.be.eql(productCategory.icon);
resJson.info.should.be.eql(productCategory.info);
- resJson.question.should.be.eql(partialBody.param.question);
+ resJson.question.should.be.eql(partialBody.question);
resJson.aliases.should.be.eql(productCategory.aliases);
resJson.disabled.should.be.eql(productCategory.disabled);
resJson.hidden.should.be.eql(productCategory.hidden);
@@ -251,27 +251,27 @@ describe('UPDATE product category', () => {
it('should return 200 for admin aliases updated', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.icon;
- delete partialBody.param.info;
- delete partialBody.param.question;
- delete partialBody.param.displayName;
- delete partialBody.param.disabled;
- delete partialBody.param.hidden;
+ delete partialBody.icon;
+ delete partialBody.info;
+ delete partialBody.question;
+ delete partialBody.displayName;
+ delete partialBody.disabled;
+ delete partialBody.hidden;
request(server)
- .patch(`/v4/projects/metadata/productCategories/${key}`)
+ .patch(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(partialBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(key);
resJson.displayName.should.be.eql(productCategory.displayName);
resJson.icon.should.be.eql(productCategory.icon);
resJson.info.should.be.eql(productCategory.info);
resJson.question.should.be.eql(productCategory.question);
- resJson.aliases.should.be.eql(partialBody.param.aliases);
+ resJson.aliases.should.be.eql(partialBody.aliases);
resJson.disabled.should.be.eql(productCategory.disabled);
resJson.hidden.should.be.eql(productCategory.hidden);
resJson.createdBy.should.be.eql(productCategory.createdBy);
@@ -287,28 +287,28 @@ describe('UPDATE product category', () => {
it('should return 200 for admin disabled updated', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.icon;
- delete partialBody.param.info;
- delete partialBody.param.question;
- delete partialBody.param.displayName;
- delete partialBody.param.aliases;
- delete partialBody.param.hidden;
+ delete partialBody.icon;
+ delete partialBody.info;
+ delete partialBody.question;
+ delete partialBody.displayName;
+ delete partialBody.aliases;
+ delete partialBody.hidden;
request(server)
- .patch(`/v4/projects/metadata/productCategories/${key}`)
+ .patch(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(partialBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(key);
resJson.displayName.should.be.eql(productCategory.displayName);
resJson.icon.should.be.eql(productCategory.icon);
resJson.info.should.be.eql(productCategory.info);
resJson.question.should.be.eql(productCategory.question);
resJson.aliases.should.be.eql(productCategory.aliases);
- resJson.disabled.should.be.eql(partialBody.param.disabled);
+ resJson.disabled.should.be.eql(partialBody.disabled);
resJson.hidden.should.be.eql(productCategory.hidden);
resJson.createdBy.should.be.eql(productCategory.createdBy);
resJson.createdBy.should.be.eql(productCategory.createdBy); // should not update createdAt
@@ -323,21 +323,21 @@ describe('UPDATE product category', () => {
it('should return 200 for admin hidden updated', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.icon;
- delete partialBody.param.info;
- delete partialBody.param.question;
- delete partialBody.param.displayName;
- delete partialBody.param.disabled;
- delete partialBody.param.aliases;
+ delete partialBody.icon;
+ delete partialBody.info;
+ delete partialBody.question;
+ delete partialBody.displayName;
+ delete partialBody.disabled;
+ delete partialBody.aliases;
request(server)
- .patch(`/v4/projects/metadata/productCategories/${key}`)
+ .patch(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(partialBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(key);
resJson.displayName.should.be.eql(productCategory.displayName);
resJson.icon.should.be.eql(productCategory.icon);
@@ -345,7 +345,7 @@ describe('UPDATE product category', () => {
resJson.question.should.be.eql(productCategory.question);
resJson.aliases.should.be.eql(productCategory.aliases);
resJson.disabled.should.be.eql(productCategory.disabled);
- resJson.hidden.should.be.eql(partialBody.param.hidden);
+ resJson.hidden.should.be.eql(partialBody.hidden);
resJson.createdBy.should.be.eql(productCategory.createdBy); // should not update createdAt
resJson.updatedBy.should.be.eql(40051333); // admin
should.exist(resJson.updatedAt);
@@ -358,22 +358,22 @@ describe('UPDATE product category', () => {
it('should return 200 for admin all fields updated', (done) => {
request(server)
- .patch(`/v4/projects/metadata/productCategories/${key}`)
+ .patch(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(body)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(key);
- resJson.displayName.should.be.eql(body.param.displayName);
- resJson.icon.should.be.eql(body.param.icon);
- resJson.info.should.be.eql(body.param.info);
- resJson.question.should.be.eql(body.param.question);
- resJson.aliases.should.be.eql(body.param.aliases);
- resJson.disabled.should.be.eql(body.param.disabled);
- resJson.hidden.should.be.eql(body.param.hidden);
+ resJson.displayName.should.be.eql(body.displayName);
+ resJson.icon.should.be.eql(body.icon);
+ resJson.info.should.be.eql(body.info);
+ resJson.question.should.be.eql(body.question);
+ resJson.aliases.should.be.eql(body.aliases);
+ resJson.disabled.should.be.eql(body.disabled);
+ resJson.hidden.should.be.eql(body.hidden);
resJson.createdBy.should.be.eql(productCategory.createdBy); // should not update createdAt
resJson.updatedBy.should.be.eql(40051333); // admin
should.exist(resJson.updatedAt);
@@ -386,22 +386,22 @@ describe('UPDATE product category', () => {
it('should return 200 for connect admin', (done) => {
request(server)
- .patch(`/v4/projects/metadata/productCategories/${key}`)
+ .patch(`/v5/projects/metadata/productCategories/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
.send(body)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(key);
- resJson.displayName.should.be.eql(body.param.displayName);
- resJson.icon.should.be.eql(body.param.icon);
- resJson.info.should.be.eql(body.param.info);
- resJson.question.should.be.eql(body.param.question);
- resJson.aliases.should.be.eql(body.param.aliases);
- resJson.disabled.should.be.eql(body.param.disabled);
- resJson.hidden.should.be.eql(body.param.hidden);
+ resJson.displayName.should.be.eql(body.displayName);
+ resJson.icon.should.be.eql(body.icon);
+ resJson.info.should.be.eql(body.info);
+ resJson.question.should.be.eql(body.question);
+ resJson.aliases.should.be.eql(body.aliases);
+ resJson.disabled.should.be.eql(body.disabled);
+ resJson.hidden.should.be.eql(body.hidden);
resJson.createdBy.should.be.eql(productCategory.createdBy); // should not update createdAt
resJson.updatedBy.should.be.eql(40051336); // connect admin
done();
diff --git a/src/routes/productTemplates/create.js b/src/routes/productTemplates/create.js
index 8875aa55..0dfcc4d1 100644
--- a/src/routes/productTemplates/create.js
+++ b/src/routes/productTemplates/create.js
@@ -5,6 +5,7 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../constants';
import fieldLookupValidation from '../../middlewares/fieldLookupValidation';
import util from '../../util';
import models from '../../models';
@@ -12,43 +13,42 @@ import models from '../../models';
const permissions = tcMiddleware.permissions;
const schema = {
- body: {
- param: Joi.object().keys({
- id: Joi.any().strip(),
- category: Joi.string().max(45).required(),
- subCategory: Joi.string().max(45).required(),
- name: Joi.string().max(255).required(),
- productKey: Joi.string().max(45).required(),
- icon: Joi.string().max(255).required(),
- brief: Joi.string().max(45).required(),
- details: Joi.string().max(255).required(),
- aliases: Joi.array().required(),
- template: Joi.object().empty(null),
- form: Joi.object().keys({
- key: Joi.string().required(),
- version: Joi.number(),
- }).empty(null),
- disabled: Joi.boolean().optional(),
- hidden: Joi.boolean().optional(),
- isAddOn: Joi.boolean().optional(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- })
+ body: Joi.object().keys({
+ id: Joi.any().strip(),
+ category: Joi.string().max(45).required(),
+ subCategory: Joi.string().max(45).required(),
+ name: Joi.string().max(255).required(),
+ productKey: Joi.string().max(45).required(),
+ icon: Joi.string().max(255).required(),
+ brief: Joi.string().max(45).required(),
+ details: Joi.string().max(255).required(),
+ aliases: Joi.array().required(),
+ template: Joi.object().empty(null),
+ form: Joi.object().keys({
+ key: Joi.string().required(),
+ version: Joi.number(),
+ }).empty(null),
+ disabled: Joi.boolean().optional(),
+ hidden: Joi.boolean().optional(),
+ isAddOn: Joi.boolean().optional(),
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ })
.xor('form', 'template')
.required(),
- },
+
};
module.exports = [
validate(schema),
permissions('productTemplate.create'),
- fieldLookupValidation(models.ProductCategory, 'key', 'body.param.category', 'Category'),
+ fieldLookupValidation(models.ProductCategory, 'key', 'body.category', 'Category'),
(req, res, next) => {
- const param = req.body.param;
+ const param = req.body;
const { form } = param;
return util.checkModel(form, 'Form', models.Form, 'product template')
.then(() => {
@@ -59,9 +59,13 @@ module.exports = [
return models.ProductTemplate.create(entity)
.then((createdEntity) => {
+ // emit event
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE,
+ RESOURCES.PRODUCT_TEMPLATE,
+ createdEntity.toJSON());
// Omit deletedAt, deletedBy
- res.status(201).json(util.wrapResponse(
- req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201));
+ res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'));
})
.catch(next);
})
diff --git a/src/routes/productTemplates/create.spec.js b/src/routes/productTemplates/create.spec.js
index 14f3807d..b0a99948 100644
--- a/src/routes/productTemplates/create.spec.js
+++ b/src/routes/productTemplates/create.spec.js
@@ -48,75 +48,75 @@ describe('CREATE product template', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.ProductCategory.bulkCreate(productCategories))
- .then(() => models.Form.create(forms[0]))
- .then(() => models.Form.create(forms[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.ProductCategory.bulkCreate(productCategories))
+ .then(() => models.Form.create(forms[0]))
+ .then(() => models.Form.create(forms[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('POST /projects/metadata/productTemplates', () => {
const body = {
- param: {
- name: 'name 1',
- productKey: 'productKey 1',
- category: 'generic',
- subCategory: 'generic',
- icon: 'http://example.com/icon1.ico',
- brief: 'brief 1',
- details: 'details 1',
- aliases: ['product key 1', 'product_key_1'],
- disabled: true,
- hidden: true,
- isAddOn: true,
- template: {
- template1: {
- name: 'template 1',
- details: {
- anyDetails: 'any details 1',
- },
- others: ['others 11', 'others 12'],
+ name: 'name 1',
+ productKey: 'productKey 1',
+ category: 'generic',
+ subCategory: 'generic',
+ icon: 'http://example.com/icon1.ico',
+ brief: 'brief 1',
+ details: 'details 1',
+ aliases: ['product key 1', 'product_key_1'],
+ disabled: true,
+ hidden: true,
+ isAddOn: true,
+ template: {
+ template1: {
+ name: 'template 1',
+ details: {
+ anyDetails: 'any details 1',
},
- template2: {
- name: 'template 2',
- details: {
- anyDetails: 'any details 2',
- },
- others: ['others 21', 'others 22'],
+ others: ['others 11', 'others 12'],
+ },
+ template2: {
+ name: 'template 2',
+ details: {
+ anyDetails: 'any details 2',
},
+ others: ['others 21', 'others 22'],
},
},
};
const bodyDefinedFormTemplate = _.cloneDeep(body);
- bodyDefinedFormTemplate.param.form = {
+ bodyDefinedFormTemplate.form = {
version: 1,
key: 'dev',
};
const bodyWithForm = _.cloneDeep(bodyDefinedFormTemplate);
- delete bodyWithForm.param.template;
+ delete bodyWithForm.template;
const bodyMissingFormTemplate = _.cloneDeep(bodyWithForm);
- delete bodyMissingFormTemplate.param.form;
+ delete bodyMissingFormTemplate.form;
const bodyInvalidForm = _.cloneDeep(body);
- bodyInvalidForm.param.form = {
+ bodyInvalidForm.form = {
version: 1,
key: 'wrongKey',
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post('/v4/projects/metadata/productTemplates')
+ .post('/v5/projects/metadata/productTemplates')
.send(body)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .post('/v4/projects/metadata/productTemplates')
+ .post('/v5/projects/metadata/productTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -126,7 +126,7 @@ describe('CREATE product template', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .post('/v4/projects/metadata/productTemplates')
+ .post('/v5/projects/metadata/productTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -136,7 +136,7 @@ describe('CREATE product template', () => {
it('should return 403 for connect manager', (done) => {
request(server)
- .post('/v4/projects/metadata/productTemplates')
+ .post('/v5/projects/metadata/productTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -144,53 +144,51 @@ describe('CREATE product template', () => {
.expect(403, done);
});
- it('should return 422 if validations dont pass', (done) => {
+ it('should return 400 if validations dont pass', (done) => {
const invalidBody = {
- param: {
- aliases: 'a',
- template: 1,
- },
+ aliases: 'a',
+ template: 1,
};
request(server)
- .post('/v4/projects/metadata/productTemplates')
+ .post('/v5/projects/metadata/productTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if product category is missing', (done) => {
+ it('should return 400 if product category is missing', (done) => {
const invalidBody = _.cloneDeep(body);
- invalidBody.param.category = null;
+ invalidBody.category = null;
request(server)
- .post('/v4/projects/metadata/productTemplates')
+ .post('/v5/projects/metadata/productTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if product category does not exist', (done) => {
+ it('should return 400 if product category does not exist', (done) => {
const invalidBody = _.cloneDeep(body);
- invalidBody.param.category = 'not_exist';
+ invalidBody.category = 'not_exist';
request(server)
- .post('/v4/projects/metadata/productTemplates')
+ .post('/v5/projects/metadata/productTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 201 for admin', (done) => {
request(server)
- .post('/v4/projects/metadata/productTemplates')
+ .post('/v5/projects/metadata/productTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -198,16 +196,16 @@ describe('CREATE product template', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson.id);
- resJson.name.should.be.eql(body.param.name);
- resJson.productKey.should.be.eql(body.param.productKey);
- resJson.category.should.be.eql(body.param.category);
- resJson.icon.should.be.eql(body.param.icon);
- resJson.brief.should.be.eql(body.param.brief);
- resJson.details.should.be.eql(body.param.details);
- resJson.aliases.should.be.eql(body.param.aliases);
- resJson.template.should.be.eql(body.param.template);
+ resJson.name.should.be.eql(body.name);
+ resJson.productKey.should.be.eql(body.productKey);
+ resJson.category.should.be.eql(body.category);
+ resJson.icon.should.be.eql(body.icon);
+ resJson.brief.should.be.eql(body.brief);
+ resJson.details.should.be.eql(body.details);
+ resJson.aliases.should.be.eql(body.aliases);
+ resJson.template.should.be.eql(body.template);
resJson.disabled.should.be.eql(true);
resJson.hidden.should.be.eql(true);
@@ -224,7 +222,7 @@ describe('CREATE product template', () => {
it('should return 201 for connect admin', (done) => {
request(server)
- .post('/v4/projects/metadata/productTemplates')
+ .post('/v5/projects/metadata/productTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -232,7 +230,7 @@ describe('CREATE product template', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.createdBy.should.be.eql(40051336); // connect admin
resJson.updatedBy.should.be.eql(40051336); // connect admin
done();
@@ -241,7 +239,7 @@ describe('CREATE product template', () => {
it('should return 201 with form data', (done) => {
request(server)
- .post('/v4/projects/metadata/productTemplates')
+ .post('/v5/projects/metadata/productTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -249,16 +247,16 @@ describe('CREATE product template', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson.id);
- resJson.name.should.be.eql(bodyWithForm.param.name);
- resJson.productKey.should.be.eql(bodyWithForm.param.productKey);
- resJson.category.should.be.eql(bodyWithForm.param.category);
- resJson.icon.should.be.eql(bodyWithForm.param.icon);
- resJson.brief.should.be.eql(bodyWithForm.param.brief);
- resJson.details.should.be.eql(bodyWithForm.param.details);
- resJson.aliases.should.be.eql(bodyWithForm.param.aliases);
- resJson.form.should.be.eql(bodyWithForm.param.form);
+ resJson.name.should.be.eql(bodyWithForm.name);
+ resJson.productKey.should.be.eql(bodyWithForm.productKey);
+ resJson.category.should.be.eql(bodyWithForm.category);
+ resJson.icon.should.be.eql(bodyWithForm.icon);
+ resJson.brief.should.be.eql(bodyWithForm.brief);
+ resJson.details.should.be.eql(bodyWithForm.details);
+ resJson.aliases.should.be.eql(bodyWithForm.aliases);
+ resJson.form.should.be.eql(bodyWithForm.form);
resJson.disabled.should.be.eql(true);
resJson.hidden.should.be.eql(true);
@@ -273,36 +271,36 @@ describe('CREATE product template', () => {
});
});
- it('should return 422 when form is invalid', (done) => {
+ it('should return 400 when form is invalid', (done) => {
request(server)
- .post('/v4/projects/metadata/productTemplates')
+ .post('/v5/projects/metadata/productTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(bodyInvalidForm)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if both form or template field are defined', (done) => {
+ it('should return 400 if both form or template field are defined', (done) => {
request(server)
- .post('/v4/projects/metadata/productTemplates')
+ .post('/v5/projects/metadata/productTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(bodyDefinedFormTemplate)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if both form or template field are missing', (done) => {
+ it('should return 400 if both form or template field are missing', (done) => {
request(server)
- .post('/v4/projects/metadata/productTemplates')
+ .post('/v5/projects/metadata/productTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(bodyMissingFormTemplate)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
});
});
diff --git a/src/routes/productTemplates/delete.js b/src/routes/productTemplates/delete.js
index 03ce1d3b..f702a33b 100644
--- a/src/routes/productTemplates/delete.js
+++ b/src/routes/productTemplates/delete.js
@@ -2,8 +2,11 @@
* API to delete a product template
*/
import validate from 'express-validation';
+import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../constants';
+import util from '../../util';
import models from '../../models';
const permissions = tcMiddleware.permissions;
@@ -19,7 +22,7 @@ module.exports = [
permissions('productTemplate.delete'),
(req, res, next) =>
models.sequelize.transaction(() =>
- models.ProductTemplate.findById(req.params.templateId)
+ models.ProductTemplate.findByPk(req.params.templateId)
.then((entity) => {
if (!entity) {
const apiErr = new Error(`Product template not found for template id ${req.params.templateId}`);
@@ -30,7 +33,12 @@ module.exports = [
return entity.update({ deletedBy: req.authUser.userId });
})
.then(entity => entity.destroy()))
- .then(() => {
+ .then((entity) => {
+ // emit event
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE,
+ RESOURCES.PRODUCT_TEMPLATE,
+ _.pick(entity.toJSON(), 'id'));
res.status(204).end();
})
.catch(next),
diff --git a/src/routes/productTemplates/delete.spec.js b/src/routes/productTemplates/delete.spec.js
index 5c4d177c..893653eb 100644
--- a/src/routes/productTemplates/delete.spec.js
+++ b/src/routes/productTemplates/delete.spec.js
@@ -25,7 +25,7 @@ const expectAfterDelete = (id, err, next) => {
chai.assert.isNotNull(res.deletedBy);
request(server)
- .get(`/v4/projects/metadata/productTemplates/${id}`)
+ .get(`/v5/projects/metadata/productTemplates/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -38,51 +38,54 @@ const expectAfterDelete = (id, err, next) => {
describe('DELETE product template', () => {
let templateId;
- beforeEach(() => testUtil.clearDb()
- .then(() => models.ProductTemplate.create({
- name: 'name 1',
- productKey: 'productKey 1',
- category: 'generic',
- subCategory: 'generic',
- icon: 'http://example.com/icon1.ico',
- brief: 'brief 1',
- details: 'details 1',
- aliases: ['product key 1', 'product_key_1'],
- template: {
- template1: {
- name: 'template 1',
- details: {
- anyDetails: 'any details 1',
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.ProductTemplate.create({
+ name: 'name 1',
+ productKey: 'productKey 1',
+ category: 'generic',
+ subCategory: 'generic',
+ icon: 'http://example.com/icon1.ico',
+ brief: 'brief 1',
+ details: 'details 1',
+ aliases: ['product key 1', 'product_key_1'],
+ template: {
+ template1: {
+ name: 'template 1',
+ details: {
+ anyDetails: 'any details 1',
+ },
+ others: ['others 11', 'others 12'],
},
- others: ['others 11', 'others 12'],
- },
- template2: {
- name: 'template 2',
- details: {
- anyDetails: 'any details 2',
+ template2: {
+ name: 'template 2',
+ details: {
+ anyDetails: 'any details 2',
+ },
+ others: ['others 21', 'others 22'],
},
- others: ['others 21', 'others 22'],
},
- },
- createdBy: 1,
- updatedBy: 2,
- })).then((template) => {
- templateId = template.id;
- return Promise.resolve();
- }),
- );
- after(testUtil.clearDb);
+ createdBy: 1,
+ updatedBy: 2,
+ }).then((template) => {
+ templateId = template.id;
+ done();
+ }));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('DELETE /projects/metadata/productTemplates/{templateId}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .delete(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .delete(`/v5/projects/metadata/productTemplates/${templateId}`)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .delete(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .delete(`/v5/projects/metadata/productTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -91,7 +94,7 @@ describe('DELETE product template', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .delete(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .delete(`/v5/projects/metadata/productTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -100,7 +103,7 @@ describe('DELETE product template', () => {
it('should return 403 for connect manager', (done) => {
request(server)
- .delete(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .delete(`/v5/projects/metadata/productTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -109,7 +112,7 @@ describe('DELETE product template', () => {
it('should return 404 for non-existed template', (done) => {
request(server)
- .delete('/v4/projects/metadata/productTemplates/1234')
+ .delete('/v5/projects/metadata/productTemplates/1234')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -120,7 +123,7 @@ describe('DELETE product template', () => {
models.ProductTemplate.destroy({ where: { id: templateId } })
.then(() => {
request(server)
- .delete(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .delete(`/v5/projects/metadata/productTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -130,7 +133,7 @@ describe('DELETE product template', () => {
it('should return 204, for admin, if template was successfully removed', (done) => {
request(server)
- .delete(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .delete(`/v5/projects/metadata/productTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -140,7 +143,7 @@ describe('DELETE product template', () => {
it('should return 204, for connect admin, if template was successfully removed', (done) => {
request(server)
- .delete(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .delete(`/v5/projects/metadata/productTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
diff --git a/src/routes/productTemplates/get.js b/src/routes/productTemplates/get.js
index e660f979..96780c2b 100644
--- a/src/routes/productTemplates/get.js
+++ b/src/routes/productTemplates/get.js
@@ -4,8 +4,8 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../util';
import models from '../../models';
+import util from '../../util';
const permissions = tcMiddleware.permissions;
@@ -18,24 +18,46 @@ const schema = {
module.exports = [
validate(schema),
permissions('productTemplate.view'),
- (req, res, next) => models.ProductTemplate.findOne({
- where: {
- deletedAt: { $eq: null },
- id: req.params.templateId,
- },
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- raw: true,
- })
- .then((productTemplate) => {
- // Not found
- if (!productTemplate) {
- const apiErr = new Error(`Product template not found for product id ${req.params.templateId}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
- }
+ (req, res, next) => {
+ util.fetchByIdFromES('productTemplates', {
+ query: {
+ nested: {
+ path: 'productTemplates',
+ query: {
+ match: { 'productTemplates.id': req.params.templateId },
+ },
+ inner_hits: {},
+ },
+ },
+ }, 'metadata')
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No productTemplate found in ES');
+ models.ProductTemplate.findOne({
+ where: {
+ deletedAt: { $eq: null },
+ id: req.params.templateId,
+ },
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ raw: true,
+ })
+ .then((productTemplate) => {
+ // Not found
+ if (!productTemplate) {
+ const apiErr = new Error(`Product template not found for product id ${req.params.templateId}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
- res.json(util.wrapResponse(req.id, productTemplate));
- return Promise.resolve();
+ res.json(productTemplate);
+ return Promise.resolve();
+ })
+ .catch(next);
+ } else {
+ req.log.debug('productTemplates found in ES');
+ res.json(data[0].inner_hits.productTemplates.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle
+ }
})
- .catch(next),
+ .catch(next);
+ },
];
diff --git a/src/routes/productTemplates/get.spec.js b/src/routes/productTemplates/get.spec.js
index 0c380dc7..4c0933d0 100644
--- a/src/routes/productTemplates/get.spec.js
+++ b/src/routes/productTemplates/get.spec.js
@@ -48,19 +48,21 @@ describe('GET product template', () => {
let templateId;
- beforeEach(() => testUtil.clearDb()
- .then(() => models.ProductTemplate.create(template))
- .then((createdTemplate) => {
- templateId = createdTemplate.id;
- return Promise.resolve();
- }),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.ProductTemplate.create(template).then((createdTemplate) => {
+ templateId = createdTemplate.id;
+ done();
+ }));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/productTemplates/{templateId}', () => {
it('should return 404 for non-existed template', (done) => {
request(server)
- .get('/v4/projects/metadata/productTemplates/1234')
+ .get('/v5/projects/metadata/productTemplates/1234')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -71,7 +73,7 @@ describe('GET product template', () => {
models.ProductTemplate.destroy({ where: { id: templateId } })
.then(() => {
request(server)
- .get(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .get(`/v5/projects/metadata/productTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -81,13 +83,13 @@ describe('GET product template', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .get(`/v5/projects/metadata/productTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(templateId);
resJson.name.should.be.eql(template.name);
resJson.productKey.should.be.eql(template.productKey);
@@ -111,13 +113,13 @@ describe('GET product template', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .get(`/v5/projects/metadata/productTemplates/${templateId}`)
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .get(`/v5/projects/metadata/productTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -127,7 +129,7 @@ describe('GET product template', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .get(`/v5/projects/metadata/productTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -137,7 +139,7 @@ describe('GET product template', () => {
it('should return 200 for member', (done) => {
request(server)
- .get(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .get(`/v5/projects/metadata/productTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -146,7 +148,7 @@ describe('GET product template', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .get(`/v5/projects/metadata/productTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/productTemplates/list.js b/src/routes/productTemplates/list.js
index c72c50ca..f1816d86 100644
--- a/src/routes/productTemplates/list.js
+++ b/src/routes/productTemplates/list.js
@@ -10,22 +10,31 @@ const permissions = tcMiddleware.permissions;
module.exports = [
permissions('productTemplate.view'),
(req, res, next) => {
- const filters = util.parseQueryFilter(req.query.filter);
- if (!util.isValidFilter(filters, ['productKey'])) {
- return util.handleError('Invalid filters', null, req, next);
- }
- const where = { deletedAt: { $eq: null }, disabled: false };
- if (filters.productKey) {
- where.productKey = { $eq: filters.productKey };
- }
- return models.ProductTemplate.findAll({
- where,
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- raw: true,
- })
- .then((productTemplates) => {
- res.json(util.wrapResponse(req.id, productTemplates));
- })
- .catch(next);
+ util.fetchFromES('productTemplates')
+ .then((data) => {
+ const filters = req.query;
+ if (!util.isValidFilter(filters, ['productKey'])) {
+ util.handleError('Invalid filters', null, req, next);
+ }
+ const where = { deletedAt: { $eq: null }, disabled: false };
+ if (filters.productKey) {
+ where.productKey = { $eq: filters.productKey };
+ }
+ if (data.productTemplates.length === 0) {
+ req.log.debug('No productTemplate found in ES');
+ models.ProductTemplate.findAll({
+ where,
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ raw: true,
+ })
+ .then((productTemplates) => {
+ res.json(productTemplates);
+ })
+ .catch(next);
+ } else {
+ req.log.debug('productTemplates found in ES');
+ res.json(data.productTemplates);
+ }
+ });
},
];
diff --git a/src/routes/productTemplates/list.spec.js b/src/routes/productTemplates/list.spec.js
index 9bd95b26..09af4696 100644
--- a/src/routes/productTemplates/list.spec.js
+++ b/src/routes/productTemplates/list.spec.js
@@ -1,22 +1,23 @@
/**
* Tests for list.js
*/
-// import chai from 'chai';
+
import _ from 'lodash';
import request from 'supertest';
-
+import chai from 'chai';
import models from '../../models';
import server from '../../app';
import testUtil from '../../tests/util';
-// const should = chai.should();
+const should = chai.should(); // eslint-disable-line no-unused-vars
const validateProductTemplates = (count, resJson, expectedTemplates) => {
resJson.should.have.length(count);
+
resJson.forEach((pt, idx) => {
- pt.should.have.all.keys('id', 'name', 'productKey', 'category', 'subCategory', 'icon', 'brief', 'details',
+ pt.should.include.all.keys('id', 'name', 'productKey', 'category', 'subCategory', 'icon', 'brief', 'details',
'aliases', 'template', 'disabled', 'form', 'hidden', 'isAddOn', 'createdBy', 'createdAt', 'updatedBy', 'updatedAt');
- pt.should.not.have.all.keys('deletedAt', 'deletedBy');
+ pt.should.not.include.all.keys('deletedAt', 'deletedBy');
pt.name.should.be.eql(expectedTemplates[idx].name);
pt.productKey.should.be.eql(expectedTemplates[idx].productKey);
pt.category.should.be.eql(expectedTemplates[idx].category);
@@ -90,25 +91,28 @@ describe('LIST product templates', () => {
let templateId;
- beforeEach(() => testUtil.clearDb()
+ beforeEach((done) => {
+ testUtil.clearDb()
.then(() => models.ProductTemplate.create(templates[0]))
.then((createdTemplate) => {
templateId = createdTemplate.id;
- return models.ProductTemplate.create(templates[1]);
- }).then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ return models.ProductTemplate.create(templates[1]).then(() => done());
+ });
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/productTemplates', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/projects/metadata/productTemplates')
+ .get('/v5/projects/metadata/productTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
validateProductTemplates(2, resJson, templates);
resJson[0].id.should.be.eql(templateId);
done();
@@ -117,19 +121,19 @@ describe('LIST product templates', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/projects/metadata/productTemplates')
+ .get('/v5/projects/metadata/productTemplates')
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/projects/metadata/productTemplates')
+ .get('/v5/projects/metadata/productTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
validateProductTemplates(2, resJson, templates);
resJson[0].id.should.be.eql(templateId);
done();
@@ -138,13 +142,13 @@ describe('LIST product templates', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/projects/metadata/productTemplates')
+ .get('/v5/projects/metadata/productTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
validateProductTemplates(2, resJson, templates);
resJson[0].id.should.be.eql(templateId);
done();
@@ -153,12 +157,12 @@ describe('LIST product templates', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/projects/metadata/productTemplates')
+ .get('/v5/projects/metadata/productTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
validateProductTemplates(2, resJson, templates);
resJson[0].id.should.be.eql(templateId);
done();
@@ -167,12 +171,12 @@ describe('LIST product templates', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/projects/metadata/productTemplates')
+ .get('/v5/projects/metadata/productTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
validateProductTemplates(2, resJson, templates);
resJson[0].id.should.be.eql(templateId);
done();
@@ -181,13 +185,13 @@ describe('LIST product templates', () => {
it('should return filtered templates', (done) => {
request(server)
- .get('/v4/projects/metadata/productTemplates?filter=productKey%3DproductKey-2')
+ .get('/v5/projects/metadata/productTemplates?productKey=productKey-2')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
validateProductTemplates(1, resJson, [templates[1]]);
done();
});
diff --git a/src/routes/productTemplates/update.js b/src/routes/productTemplates/update.js
index d3d2fc2e..a96c4c31 100644
--- a/src/routes/productTemplates/update.js
+++ b/src/routes/productTemplates/update.js
@@ -5,6 +5,7 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../constants';
import fieldLookupValidation from '../../middlewares/fieldLookupValidation';
import util from '../../util';
import models from '../../models';
@@ -15,47 +16,45 @@ const schema = {
params: {
templateId: Joi.number().integer().positive().required(),
},
- body: {
- param: Joi.object().keys({
- id: Joi.any().strip(),
- name: Joi.string().max(255),
- productKey: Joi.string().max(45),
- category: Joi.string().max(45),
- subCategory: Joi.string().max(45),
- icon: Joi.string().max(255),
- brief: Joi.string().max(45),
- details: Joi.string().max(255),
- aliases: Joi.array(),
- template: Joi.object().empty(null),
- form: Joi.object().keys({
- key: Joi.string().required(),
- version: Joi.number(),
- }).empty(null),
- disabled: Joi.boolean().optional(),
- hidden: Joi.boolean().optional(),
- isAddOn: Joi.boolean().optional(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- })
+ body: Joi.object().keys({
+ id: Joi.any().strip(),
+ name: Joi.string().max(255),
+ productKey: Joi.string().max(45),
+ category: Joi.string().max(45),
+ subCategory: Joi.string().max(45),
+ icon: Joi.string().max(255),
+ brief: Joi.string().max(45),
+ details: Joi.string().max(255),
+ aliases: Joi.array(),
+ template: Joi.object().empty(null),
+ form: Joi.object().keys({
+ key: Joi.string().required(),
+ version: Joi.number(),
+ }).empty(null),
+ disabled: Joi.boolean().optional(),
+ hidden: Joi.boolean().optional(),
+ isAddOn: Joi.boolean().optional(),
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ })
.xor('form', 'template')
.required(),
- },
};
module.exports = [
validate(schema),
permissions('productTemplate.edit'),
- fieldLookupValidation(models.ProductCategory, 'key', 'body.param.category', 'Category'),
+ fieldLookupValidation(models.ProductCategory, 'key', 'body.category', 'Category'),
(req, res, next) => {
- const param = req.body.param;
+ const param = req.body;
const { form } = param;
return util.checkModel(form, 'Form', models.Form, 'product template')
.then(() => {
- const entityToUpdate = _.assign(req.body.param, {
+ const entityToUpdate = _.assign(req.body, {
updatedBy: req.authUser.userId,
});
@@ -82,7 +81,13 @@ module.exports = [
return productTemplate.update(entityToUpdate);
})
.then((productTemplate) => {
- res.json(util.wrapResponse(req.id, productTemplate));
+ // emit event
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE,
+ RESOURCES.PRODUCT_TEMPLATE,
+ productTemplate.get({ plain: true }),
+ );
+ res.json(productTemplate);
return Promise.resolve();
})
.catch(next);
diff --git a/src/routes/productTemplates/update.spec.js b/src/routes/productTemplates/update.spec.js
index 633ca3e0..3526b1cd 100644
--- a/src/routes/productTemplates/update.spec.js
+++ b/src/routes/productTemplates/update.spec.js
@@ -69,7 +69,8 @@ describe('UPDATE product template', () => {
let templateId;
- beforeEach(() => testUtil.clearDb()
+ beforeEach((done) => {
+ testUtil.clearDb()
.then(() => models.Form.create(forms[0]))
.then(() => models.Form.create(forms[1]))
.then(() => models.ProductCategory.bulkCreate([
@@ -94,73 +95,72 @@ describe('UPDATE product template', () => {
updatedBy: 1,
},
]))
- .then(() => models.ProductTemplate.create(template))
- .then((createdTemplate) => {
+ .then(() => models.ProductTemplate.create(template).then((createdTemplate) => {
templateId = createdTemplate.id;
- return Promise.resolve();
- }),
- );
- after(testUtil.clearDb);
+ done();
+ }));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('PATCH /projects/metadata/productTemplates/{templateId}', () => {
const body = {
- param: {
- name: 'template 1 - update',
- productKey: 'productKey 1 - update',
- category: 'concrete',
- subCategory: 'concrete',
- icon: 'http://example.com/icon1-update.ico',
- brief: 'brief 1 - update',
- details: 'details 1 - update',
- aliases: ['productTemplate-1-update', 'productTemplate_1-update'],
- template: {
- template1: {
- name: 'template 1 - update',
- details: {
- anyDetails: 'any details 1 - update',
- newDetails: 'new',
- },
- others: ['others new'],
+ name: 'template 1 - update',
+ productKey: 'productKey 1 - update',
+ category: 'concrete',
+ subCategory: 'concrete',
+ icon: 'http://example.com/icon1-update.ico',
+ brief: 'brief 1 - update',
+ details: 'details 1 - update',
+ aliases: ['productTemplate-1-update', 'productTemplate_1-update'],
+ template: {
+ template1: {
+ name: 'template 1 - update',
+ details: {
+ anyDetails: 'any details 1 - update',
+ newDetails: 'new',
},
- template3: {
- name: 'template 3',
- details: {
- anyDetails: 'any details 3',
- },
- others: ['others 31', 'others 32'],
+ others: ['others new'],
+ },
+ template3: {
+ name: 'template 3',
+ details: {
+ anyDetails: 'any details 3',
},
+ others: ['others 31', 'others 32'],
},
},
};
const bodyDefinedFormTemplate = _.cloneDeep(body);
- bodyDefinedFormTemplate.param.form = {
+ bodyDefinedFormTemplate.form = {
version: 1,
key: 'dev',
};
const bodyWithForm = _.cloneDeep(bodyDefinedFormTemplate);
- delete bodyWithForm.param.template;
+ delete bodyWithForm.template;
const bodyMissingFormTemplate = _.cloneDeep(bodyWithForm);
- delete bodyMissingFormTemplate.param.form;
+ delete bodyMissingFormTemplate.form;
const bodyInvalidForm = _.cloneDeep(body);
- bodyInvalidForm.param.form = {
+ bodyInvalidForm.form = {
version: 1,
key: 'wrongKey',
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .patch(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/productTemplates/${templateId}`)
.send(body)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .patch(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/productTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -170,7 +170,7 @@ describe('UPDATE product template', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .patch(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/productTemplates/${templateId}`)
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
@@ -180,7 +180,7 @@ describe('UPDATE product template', () => {
it('should return 403 for connect manager', (done) => {
request(server)
- .patch(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/productTemplates/${templateId}`)
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
@@ -188,26 +188,24 @@ describe('UPDATE product template', () => {
.expect(403, done);
});
- it('should return 422 for invalid request', (done) => {
+ it('should return 400 for invalid request', (done) => {
const invalidBody = {
- param: {
- aliases: 'a',
- template: 1,
- },
+ aliases: 'a',
+ template: 1,
};
request(server)
- .patch(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/productTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 404 for non-existed template', (done) => {
request(server)
- .patch('/v4/projects/metadata/productTemplates/1234')
+ .patch('/v5/projects/metadata/productTemplates/1234')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -219,7 +217,7 @@ describe('UPDATE product template', () => {
models.ProductTemplate.destroy({ where: { id: templateId } })
.then(() => {
request(server)
- .patch(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/productTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -230,24 +228,24 @@ describe('UPDATE product template', () => {
it('should return 200 for admin', (done) => {
request(server)
- .patch(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/productTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(body)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(templateId);
- resJson.name.should.be.eql(body.param.name);
- resJson.productKey.should.be.eql(body.param.productKey);
- resJson.category.should.be.eql(body.param.category);
- resJson.icon.should.be.eql(body.param.icon);
- resJson.brief.should.be.eql(body.param.brief);
- resJson.details.should.be.eql(body.param.details);
+ resJson.name.should.be.eql(body.name);
+ resJson.productKey.should.be.eql(body.productKey);
+ resJson.category.should.be.eql(body.category);
+ resJson.icon.should.be.eql(body.icon);
+ resJson.brief.should.be.eql(body.brief);
+ resJson.details.should.be.eql(body.details);
resJson.disabled.should.be.eql(true);
resJson.hidden.should.be.eql(true);
- resJson.aliases.should.be.eql(body.param.aliases);
+ resJson.aliases.should.be.eql(body.aliases);
resJson.template.should.be.eql({
template1: {
name: 'template 1 - update',
@@ -285,7 +283,7 @@ describe('UPDATE product template', () => {
it('should return 200 for connect admin', (done) => {
request(server)
- .patch(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/productTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -296,47 +294,47 @@ describe('UPDATE product template', () => {
it('should return 200 when update form', (done) => {
request(server)
- .patch(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/productTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(bodyWithForm)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
- resJson.form.should.be.eql(bodyWithForm.param.form);
+ const resJson = res.body;
+ resJson.form.should.be.eql(bodyWithForm.form);
done();
});
});
- it('should return 422 when form is invalid', (done) => {
+ it('should return 400 when form is invalid', (done) => {
request(server)
- .patch(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/productTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(bodyInvalidForm)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if both form or template field are defined', (done) => {
+ it('should return 400 if both form or template field are defined', (done) => {
request(server)
- .patch(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/productTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(bodyDefinedFormTemplate)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if both form or template field are missing', (done) => {
+ it('should return 400 if both form or template field are missing', (done) => {
request(server)
- .patch(`/v4/projects/metadata/productTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/productTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(bodyMissingFormTemplate)
- .expect(422, done);
+ .expect(400, done);
});
});
});
diff --git a/src/routes/productTemplates/upgrade.js b/src/routes/productTemplates/upgrade.js
index c78fe827..4eade980 100644
--- a/src/routes/productTemplates/upgrade.js
+++ b/src/routes/productTemplates/upgrade.js
@@ -5,20 +5,19 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../constants';
import util from '../../util';
import models from '../../models';
const permissions = tcMiddleware.permissions;
const schema = {
- body: {
- param: Joi.object().keys({
- form: Joi.object().keys({
- version: Joi.number().integer().positive().required(),
- key: Joi.string().required(),
- }).optional(),
+ body: Joi.object().keys({
+ form: Joi.object().keys({
+ version: Joi.number().integer().positive().required(),
+ key: Joi.string().required(),
}).optional(),
- },
+ }).optional(),
};
module.exports = [
@@ -32,19 +31,19 @@ module.exports = [
},
}).then(async (productTemplate) => {
if (_.isNil(productTemplate)) {
- const apiErr = new Error(`product template not found for id ${req.body.param.templateId}`);
+ const apiErr = new Error(`product template not found for id ${req.body.templateId}`);
apiErr.status = 404;
throw apiErr;
}
if (_.isNil(productTemplate.template)) {
const apiErr = new Error('Current product template\'s template is null');
- apiErr.status = 422;
+ apiErr.status = 400;
throw apiErr;
}
let newForm = {};
- if (_.isNil(req.body.param.form)) {
+ if (_.isNil(req.body.form)) {
const { productKey, template = {} } = productTemplate;
const { version } = await models.Form.createNewVersion(productKey, template, req.authUser.userId);
newForm = {
@@ -52,7 +51,7 @@ module.exports = [
key: productKey,
};
} else {
- newForm = req.body.param.form;
+ newForm = req.body.form;
await util.checkModel(newForm, 'Form', models.Form, 'product template');
}
// update product template with new form data
@@ -63,13 +62,14 @@ module.exports = [
};
const newProductTemplate = await productTemplate.update(updatePayload);
- const response = util.wrapResponse(
- req.id,
- _.omit(newProductTemplate.toJSON(), 'deletedAt', 'deletedBy'),
- 1,
- 201,
- );
- return res.status(201).json(response);
+
+ // emit event
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE,
+ RESOURCES.PRODUCT_TEMPLATE,
+ updatePayload);
+
+ return res.status(201).json(_.omit(newProductTemplate.toJSON(), 'deletedAt', 'deletedBy'));
}).catch(next));
},
];
diff --git a/src/routes/productTemplates/upgrade.spec.js b/src/routes/productTemplates/upgrade.spec.js
index 8fc41e5f..9d39c696 100644
--- a/src/routes/productTemplates/upgrade.spec.js
+++ b/src/routes/productTemplates/upgrade.spec.js
@@ -60,7 +60,8 @@ describe('UPGRADE product template', () => {
let templateId;
let missingTemplateId;
- beforeEach(() => testUtil.clearDb()
+ beforeEach((done) => {
+ testUtil.clearDb()
.then(() => models.ProductCategory.bulkCreate([
{
key: 'generic',
@@ -122,47 +123,45 @@ describe('UPGRADE product template', () => {
.then((createdTemplate) => {
templateId = createdTemplate.id;
})
- .then(() => models.ProductTemplate.create(productTemplateMissed))
- .then((createdTemplate) => {
+ .then(() => models.ProductTemplate.create(productTemplateMissed).then((createdTemplate) => {
missingTemplateId = createdTemplate.id;
- }),
- );
- after(testUtil.clearDb);
+ done();
+ }));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('POST /projects/metadata/productTemplates/{templateId}/upgrade', () => {
const body = {
- param: {
- form: {
- key: 'newKey',
- version: 1,
- },
+
+ form: {
+ key: 'newKey',
+ version: 1,
},
+
};
const bodyInvalidForm = {
- param: {
- form: {
- key: 'wrongKey',
- version: 1,
- },
+ form: {
+ key: 'wrongKey',
+ version: 1,
},
};
const emptyBody = {
- param: {
- },
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post(`/v4/projects/metadata/productTemplates/${templateId}/upgrade`)
+ .post(`/v5/projects/metadata/productTemplates/${templateId}/upgrade`)
.send(body)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .post(`/v4/projects/metadata/productTemplates/${templateId}/upgrade`)
+ .post(`/v5/projects/metadata/productTemplates/${templateId}/upgrade`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -172,7 +171,7 @@ describe('UPGRADE product template', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .post(`/v4/projects/metadata/productTemplates/${templateId}/upgrade`)
+ .post(`/v5/projects/metadata/productTemplates/${templateId}/upgrade`)
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
@@ -182,7 +181,7 @@ describe('UPGRADE product template', () => {
it('should return 403 for connect manager', (done) => {
request(server)
- .post(`/v4/projects/metadata/productTemplates/${templateId}/upgrade`)
+ .post(`/v5/projects/metadata/productTemplates/${templateId}/upgrade`)
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
@@ -190,28 +189,26 @@ describe('UPGRADE product template', () => {
.expect(403, done);
});
- it('should return 422 for invalid request', (done) => {
+ it('should return 400 for invalid request', (done) => {
const invalidBody = {
- param: {
- form: {
- key: 'notvalid',
- version: 1,
- },
+ form: {
+ key: 'notvalid',
+ version: 1,
},
};
request(server)
- .post(`/v4/projects/metadata/productTemplates/${templateId}/upgrade`)
+ .post(`/v5/projects/metadata/productTemplates/${templateId}/upgrade`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 404 for non-existed template', (done) => {
request(server)
- .post('/v4/projects/metadata/productTemplates/1234/upgrade')
+ .post('/v5/projects/metadata/productTemplates/1234/upgrade')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -223,7 +220,7 @@ describe('UPGRADE product template', () => {
models.ProductTemplate.destroy({ where: { id: templateId } })
.then(() => {
request(server)
- .post(`/v4/projects/metadata/productTemplates/${templateId}/upgrade`)
+ .post(`/v5/projects/metadata/productTemplates/${templateId}/upgrade`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -234,14 +231,14 @@ describe('UPGRADE product template', () => {
it('should return 200 for admin', (done) => {
request(server)
- .post(`/v4/projects/metadata/productTemplates/${templateId}/upgrade`)
+ .post(`/v5/projects/metadata/productTemplates/${templateId}/upgrade`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(body)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(templateId);
should.not.exist(resJson.template);
@@ -260,16 +257,16 @@ describe('UPGRADE product template', () => {
});
});
- it('should create new version of model if param not given model key and version', (done) => {
+ it('should create new version of model if body not given model key and version', (done) => {
request(server)
- .post(`/v4/projects/metadata/productTemplates/${templateId}/upgrade`)
+ .post(`/v5/projects/metadata/productTemplates/${templateId}/upgrade`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(emptyBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.not.exist(resJson.scope);
should.not.exist(resJson.phases);
@@ -290,24 +287,24 @@ describe('UPGRADE product template', () => {
});
});
- it('should return 422 when form is invalid', (done) => {
+ it('should return 400 when form is invalid', (done) => {
request(server)
- .post(`/v4/projects/metadata/productTemplates/${templateId}/upgrade`)
+ .post(`/v5/projects/metadata/productTemplates/${templateId}/upgrade`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(bodyInvalidForm)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 when template is missing', (done) => {
+ it('should return 400 when template is missing', (done) => {
request(server)
- .post(`/v4/projects/metadata/productTemplates/${missingTemplateId}/upgrade`)
+ .post(`/v5/projects/metadata/productTemplates/${missingTemplateId}/upgrade`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(body)
- .expect(422, done);
+ .expect(400, done);
});
});
});
diff --git a/src/routes/projectEstimationItems/list.js b/src/routes/projectEstimationItems/list.js
index 3b960c92..c958c756 100644
--- a/src/routes/projectEstimationItems/list.js
+++ b/src/routes/projectEstimationItems/list.js
@@ -4,7 +4,6 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../util';
import models from '../../models';
const permissions = tcMiddleware.permissions;
@@ -19,7 +18,7 @@ const schema = {
module.exports = [
validate(schema),
permissions('projectEstimation.item.list'),
- (req, res, next) => models.ProjectEstimation.find({
+ (req, res, next) => models.ProjectEstimation.findOne({
where: {
id: req.params.estimationId,
projectId: req.params.projectId,
@@ -43,7 +42,7 @@ module.exports = [
reqUser: req.authUser,
members: req.context.currentProjectMembers,
}).then((items) => {
- res.json(util.wrapResponse(req.id, items));
+ res.json(items);
return Promise.resolve();
});
}).catch(next),
diff --git a/src/routes/projectEstimationItems/list.spec.js b/src/routes/projectEstimationItems/list.spec.js
index 11f8a942..6e3c4443 100644
--- a/src/routes/projectEstimationItems/list.spec.js
+++ b/src/routes/projectEstimationItems/list.spec.js
@@ -99,15 +99,18 @@ describe('GET project estimation items', () => {
.then(() => models.ProjectEstimationItem.bulkCreate(projectEstimationItems))
.then(() => Promise.resolve()),
);
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
- const url = '/v4/projects/1/estimations/1/items';
+ const url = '/v5/projects/1/estimations/1/items';
describe(`GET ${url}`, () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
.get(url)
- .expect(403, done);
+ .expect(403)
+ .end(done);
});
it('should return 403 if user is not copilot or above', (done) => {
@@ -116,25 +119,28 @@ describe('GET project estimation items', () => {
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
- .expect(403, done);
+ .expect(403)
+ .end(done);
});
it('should return 404 if project not exists', (done) => {
request(server)
- .get('/v4/projects/999/estimations/1/items')
+ .get('/v5/projects/999/estimations/1/items')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .expect(404, done);
+ .expect(404)
+ .end(done);
});
it('should return 404 if project estimation not exists', (done) => {
request(server)
- .get('/v4/projects/1/estimations/999/items')
+ .get('/v5/projects/1/estimations/999/items')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .expect(404, done);
+ .expect(404)
+ .end(done);
});
it('should return all project estimation items for admin', (done) => {
@@ -148,7 +154,7 @@ describe('GET project estimation items', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.length.should.be.eql(3);
// convert items to map with type.
@@ -187,7 +193,7 @@ describe('GET project estimation items', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.length.should.be.eql(1);
// convert items to map with type.
@@ -215,7 +221,7 @@ describe('GET project estimation items', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.length.should.be.eql(0);
// convert items to map with type.
diff --git a/src/routes/projectMemberInvites/create.js b/src/routes/projectMemberInvites/create.js
index 0115ed21..bdd35484 100644
--- a/src/routes/projectMemberInvites/create.js
+++ b/src/routes/projectMemberInvites/create.js
@@ -1,4 +1,5 @@
+
import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
@@ -7,10 +8,10 @@ import { middleware as tcMiddleware } from 'tc-core-library-js';
import models from '../../models';
import util from '../../util';
import { PROJECT_MEMBER_ROLE, PROJECT_MEMBER_MANAGER_ROLES,
- MANAGER_ROLES, INVITE_STATUS, EVENT, BUS_API_EVENT, USER_ROLE, MAX_PARALLEL_REQUEST_QTY } from '../../constants';
+ MANAGER_ROLES, INVITE_STATUS, EVENT, RESOURCES, USER_ROLE,
+ MAX_PARALLEL_REQUEST_QTY, CONNECT_NOTIFICATION_EVENT } from '../../constants';
import { createEvent } from '../../services/busApi';
-
/**
* API to create member invite to project.
*
@@ -18,13 +19,11 @@ import { createEvent } from '../../services/busApi';
const permissions = tcMiddleware.permissions;
const addMemberValidations = {
- body: {
- param: Joi.object().keys({
- userIds: Joi.array().items(Joi.number()).optional().min(1),
- emails: Joi.array().items(Joi.string().email()).optional().min(1),
- role: Joi.any().valid(_.values(PROJECT_MEMBER_ROLE)).required(),
- }).required(),
- },
+ body: Joi.object().keys({
+ userIds: Joi.array().items(Joi.number()).optional().min(1),
+ emails: Joi.array().items(Joi.string().email()).optional().min(1),
+ role: Joi.any().valid(_.values(PROJECT_MEMBER_ROLE)).required(),
+ }).required(),
};
/**
@@ -142,7 +141,6 @@ const buildCreateInvitePromises = (req, invite, invites, data, failed, members)
invitePromises.push(models.ProjectMemberInvite.create(dataNew));
});
-
// remove invites for users that are invited already
_.remove(nonExistentUserEmails, email =>
_.some(invites, (i) => {
@@ -172,15 +170,14 @@ const buildCreateInvitePromises = (req, invite, invites, data, failed, members)
return invitePromises;
});
}
-
return invitePromises;
};
const sendInviteEmail = (req, projectId, invite) => {
req.log.debug(req.authUser);
- const emailEventType = BUS_API_EVENT.PROJECT_MEMBER_EMAIL_INVITE_CREATED;
+ const emailEventType = CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_EMAIL_INVITE_CREATED;
const promises = [
- models.Project.find({
+ models.Project.findOne({
where: { id: projectId },
raw: true,
}),
@@ -233,7 +230,7 @@ module.exports = [
permissions('projectMemberInvite.create'),
(req, res, next) => {
let failed = [];
- const invite = req.body.param;
+ const invite = req.body;
// let us request user fields during creating, probably this should be move to GET by ID endpoint instead
const fields = req.query.fields ? req.query.fields.split(',') : null;
@@ -321,16 +318,16 @@ module.exports = [
};
req.log.debug('Creating invites');
- return models.sequelize.Promise.all(buildCreateInvitePromises(req, invite, invites, data, failed, members))
+ return models.Sequelize.Promise.all(buildCreateInvitePromises(req, invite, invites, data, failed, members))
.then((values) => {
values.forEach((v) => {
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, {
+ // emit the event
+ util.sendResourceToKafkaBus(
req,
- userId: v.userId,
- email: v.email,
- status: v.status,
- role: v.role,
- });
+ EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED,
+ RESOURCES.PROJECT_MEMBER_INVITE,
+ v.toJSON());
+
req.app.services.pubsub.publish(
EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED,
v,
@@ -356,11 +353,9 @@ module.exports = [
.then((values) => {
const success = _.assign({}, { success: values });
if (failed.length) {
- res.status(403).json(util.wrapResponse(req.id,
- util.maskInviteEmails('$.email', _.assign({}, success, { failed }), req), null, 403));
+ res.status(403).json(_.assign({}, util.maskInviteEmails('$.success[?(@.email)]', success, req), { failed }));
} else {
- res.status(201).json(util.wrapResponse(req.id,
- util.maskInviteEmails('$.success[?(@.email)]', success, req), null, 201));
+ res.status(201).json(util.maskInviteEmails('$.success[?(@.email)]', success, req));
}
})
.catch(err => next(err));
diff --git a/src/routes/projectMemberInvites/create.spec.js b/src/routes/projectMemberInvites/create.spec.js
index 95148823..ac77d1cc 100644
--- a/src/routes/projectMemberInvites/create.spec.js
+++ b/src/routes/projectMemberInvites/create.spec.js
@@ -14,6 +14,8 @@ import {
PROJECT_MEMBER_ROLE,
INVITE_STATUS,
BUS_API_EVENT,
+ RESOURCES,
+ CONNECT_NOTIFICATION_EVENT,
} from '../../constants';
const should = chai.should();
@@ -175,54 +177,56 @@ describe('Project Member Invite create', () => {
});
it('should return 201 if userIds and emails are presented the same time',
- (done) => {
- request(server)
- .post(`/v4/projects/${project2.id}/members/invite`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.admin}`,
- })
- .send({
- param: {
- userIds: [40051331],
- emails: ['romit.choudhary@rivigo.com'],
- role: 'customer',
- },
- })
- .expect('Content-Type', /json/)
- .expect(201)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- res.body.result.status.should.equal(201);
- done();
- }
- });
- });
+ (done) => {
+ request(server)
+ .post(`/v5/projects/${project1.id}/members/invite`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.admin}`,
+ })
+ .send({
+ userIds: [40051331],
+ emails: ['hello@world.com'],
+ role: 'customer',
+ })
+ .expect('Content-Type', /json/)
+ .expect(201)
+ .end((err, res) => {
+ if (err) {
+ done(err);
+ } else {
+ const resJson = res.body.success[1];
+ should.exist(resJson);
+ resJson.role.should.equal('customer');
+ resJson.projectId.should.equal(project1.id);
+ resJson.email.should.equal('hello@world.com');
+ server.services.pubsub.publish.calledWith('project.member.invite.created').should.be.true;
+ done();
+ }
+ });
+ });
it('should return 400 if neither userIds or email is presented',
- (done) => {
- request(server)
- .post(`/v4/projects/${project1.id}/members/invite`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.admin}`,
- })
- .send({
- param: {
- role: 'customer',
- },
- })
- .expect('Content-Type', /json/)
- .expect(400)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- res.body.result.status.should.equal(400);
- done();
- }
- });
- });
+ (done) => {
+ request(server)
+ .post(`/v5/projects/${project1.id}/members/invite`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.admin}`,
+ })
+ .send({
+ role: 'customer',
+ })
+ .expect('Content-Type', /json/)
+ .expect(400)
+ .end((err, res) => {
+ if (err) {
+ done(err);
+ } else {
+ const errorMessage = _.get(res.body, 'message', '');
+ sinon.assert.match(errorMessage, /.*Either userIds or emails are required/);
+ done();
+ }
+ });
+ });
it('should return 403 if try to create copilot without MANAGER_ROLES', (done) => {
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
@@ -243,15 +247,13 @@ describe('Project Member Invite create', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .post(`/v4/projects/${project2.id}/members/invite`)
+ .post(`/v5/projects/${project2.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- userIds: [40152855],
- role: 'copilot',
- },
+ userIds: [40152855],
+ role: 'copilot',
})
.expect('Content-Type', /json/)
.expect(403)
@@ -259,9 +261,7 @@ describe('Project Member Invite create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
- res.body.result.status.should.equal(403);
- const errorMessage = _.get(resJson, 'message', '');
+ const errorMessage = _.get(res.body, 'message', '');
sinon.assert.match(errorMessage, /.*You are not allowed to invite user as/);
done();
}
@@ -287,15 +287,13 @@ describe('Project Member Invite create', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .post(`/v4/projects/${project2.id}/members/invite`)
+ .post(`/v5/projects/${project2.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- userIds: [40152855],
- role: 'copilot',
- },
+ userIds: [40152855],
+ role: 'copilot',
})
.expect('Content-Type', /json/)
.expect(403)
@@ -303,9 +301,7 @@ describe('Project Member Invite create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
- res.body.result.status.should.equal(403);
- const errorMessage = _.get(resJson, 'message', '');
+ const errorMessage = _.get(res.body, 'message', '');
sinon.assert.match(errorMessage, /.*You are not allowed to invite user as/);
done();
}
@@ -333,15 +329,13 @@ describe('Project Member Invite create', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .post(`/v4/projects/${project2.id}/members/invite`)
+ .post(`/v5/projects/${project2.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- emails: ['hello@world.com'],
- role: 'customer',
- },
+ emails: ['hello@world.com'],
+ role: 'customer',
})
.expect('Content-Type', /json/)
.expect(201)
@@ -349,7 +343,7 @@ describe('Project Member Invite create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content.success[0];
+ const resJson = res.body.success[0];
should.exist(resJson);
resJson.role.should.equal('customer');
resJson.projectId.should.equal(project2.id);
@@ -388,15 +382,13 @@ describe('Project Member Invite create', () => {
email: 'hello@world.com',
}]));
request(server)
- .post(`/v4/projects/${project2.id}/members/invite`)
+ .post(`/v5/projects/${project2.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- emails: ['hello@world.com'],
- role: 'customer',
- },
+ emails: ['hello@world.com'],
+ role: 'customer',
})
.expect('Content-Type', /json/)
.expect(201)
@@ -404,7 +396,7 @@ describe('Project Member Invite create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content.success[0];
+ const resJson = res.body.success[0];
should.exist(resJson);
resJson.role.should.equal('customer');
resJson.projectId.should.equal(project2.id);
@@ -439,15 +431,13 @@ describe('Project Member Invite create', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .post(`/v4/projects/${project2.id}/members/invite`)
+ .post(`/v5/projects/${project2.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- userIds: [40152855],
- role: 'customer',
- },
+ userIds: [40152855],
+ role: 'customer',
})
.expect('Content-Type', /json/)
.expect(201)
@@ -455,7 +445,7 @@ describe('Project Member Invite create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content.success[0];
+ const resJson = res.body.success[0];
should.exist(resJson);
resJson.role.should.equal('customer');
resJson.projectId.should.equal(project2.id);
@@ -471,31 +461,21 @@ describe('Project Member Invite create', () => {
get: () => Promise.resolve({
status: 200,
data: {
- id: 'requesterId',
- version: 'v3',
- result: {
- success: true,
- status: 200,
- content: {
- success: [{
- roleName: USER_ROLE.COPILOT,
- }],
- },
- },
+ success: [{
+ roleName: USER_ROLE.COPILOT,
+ }],
},
}),
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .post(`/v4/projects/${project1.id}/members/invite`)
+ .post(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- userIds: [40158431],
- role: 'customer',
- },
+ userIds: [40158431],
+ role: 'customer',
})
.expect('Content-Type', /json/)
.expect(403)
@@ -503,7 +483,7 @@ describe('Project Member Invite create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content.failed;
+ const resJson = res.body.failed;
should.exist(resJson);
resJson[0].userId.should.equal(40158431);
resJson[0].message.should.equal('User with such handle is already a member of the team.');
@@ -519,17 +499,9 @@ describe('Project Member Invite create', () => {
get: () => Promise.resolve({
status: 200,
data: {
- id: 'requesterId',
- version: 'v3',
- result: {
- success: true,
- status: 200,
- content: {
- success: [{
- roleName: USER_ROLE.COPILOT,
- }],
- },
- },
+ success: [{
+ roleName: USER_ROLE.COPILOT,
+ }],
},
}),
});
@@ -540,15 +512,13 @@ describe('Project Member Invite create', () => {
email: 'romit.choudhary@rivigo.com',
}]));
request(server)
- .post(`/v4/projects/${project1.id}/members/invite`)
+ .post(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- emails: ['romit.choudhary@rivigo.com'],
- role: 'customer',
- },
+ emails: ['romit.choudhary@rivigo.com'],
+ role: 'customer',
})
.expect('Content-Type', /json/)
.expect(403)
@@ -556,7 +526,7 @@ describe('Project Member Invite create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content.failed;
+ const resJson = res.body.failed;
should.exist(resJson);
resJson[0].email.should.equal('romit.choudhary@rivigo.com');
resJson[0].message.should.equal('User with such email is already a member of the team.');
@@ -588,15 +558,13 @@ describe('Project Member Invite create', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .post(`/v4/projects/${project1.id}/members/invite`)
+ .post(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- userIds: [40051335],
- role: 'customer',
- },
+ userIds: [40051335],
+ role: 'customer',
})
.expect('Content-Type', /json/)
.expect(403)
@@ -604,7 +572,7 @@ describe('Project Member Invite create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content.failed;
+ const resJson = res.body.failed;
should.exist(resJson);
resJson.length.should.equal(1);
resJson[0].userId.should.equal(40051335);
@@ -646,15 +614,13 @@ describe('Project Member Invite create', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .post(`/v4/projects/${project1.id}/members/invite`)
+ .post(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- userIds: [40152855],
- role: 'manager',
- },
+ userIds: [40152855],
+ role: 'manager',
})
.expect('Content-Type', /json/)
.expect(403)
@@ -662,9 +628,8 @@ describe('Project Member Invite create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
- res.body.result.status.should.equal(403);
const errorMessage = _.get(resJson, 'message', '');
sinon.assert.match(errorMessage, /.*not allowed to invite user as/);
done();
@@ -676,20 +641,18 @@ describe('Project Member Invite create', () => {
util.getUserRoles.restore();
sandbox.stub(util, 'getUserRoles', () => Promise.resolve([USER_ROLE.MANAGER]));
request(server)
- .post(`/v4/projects/${project1.id}/members/invite`)
+ .post(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.send({
- param: {
- userIds: [40152855],
- role: 'manager',
- },
+ userIds: [40152855],
+ role: 'manager',
})
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content.success[0];
+ const resJson = res.body.success[0];
should.exist(resJson);
resJson.role.should.equal('manager');
resJson.projectId.should.equal(project1.id);
@@ -703,20 +666,18 @@ describe('Project Member Invite create', () => {
util.getUserRoles.restore();
sandbox.stub(util, 'getUserRoles', () => Promise.resolve([USER_ROLE.MANAGER]));
request(server)
- .post(`/v4/projects/${project1.id}/members/invite`)
+ .post(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.send({
- param: {
- userIds: [40152855],
- role: 'account_manager',
- },
+ userIds: [40152855],
+ role: 'account_manager',
})
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content.success[0];
+ const resJson = res.body.success[0];
should.exist(resJson);
resJson.role.should.equal('account_manager');
resJson.projectId.should.equal(project1.id);
@@ -730,15 +691,13 @@ describe('Project Member Invite create', () => {
util.getUserRoles.restore();
sandbox.stub(util, 'getUserRoles', () => Promise.resolve(['Topcoder User']));
request(server)
- .post(`/v4/projects/${project1.id}/members/invite`)
+ .post(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.send({
- param: {
- userIds: [40152855],
- role: 'account_manager',
- },
+ userIds: [40152855],
+ role: 'account_manager',
})
.expect('Content-Type', /json/)
.expect(403)
@@ -746,9 +705,8 @@ describe('Project Member Invite create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content.failed[0];
+ const resJson = res.body.failed[0];
should.exist(resJson);
- res.body.result.status.should.equal(403);
const errorMessage = _.get(resJson, 'message', '');
sinon.assert.match(errorMessage, /.*cannot be added with a Manager role to the project/);
done();
@@ -777,15 +735,13 @@ describe('Project Member Invite create', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .post(`/v4/projects/${project1.id}/members/invite`)
+ .post(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.send({
- param: {
- userIds: [40051331],
- role: 'copilot',
- },
+ userIds: [40051331],
+ role: 'copilot',
})
.expect('Content-Type', /json/)
.expect(201)
@@ -793,7 +749,7 @@ describe('Project Member Invite create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content.success[0];
+ const resJson = res.body.success[0];
should.exist(resJson);
resJson.role.should.equal('copilot');
resJson.projectId.should.equal(project1.id);
@@ -806,15 +762,13 @@ describe('Project Member Invite create', () => {
it('should return 403 and failed list when trying add already invited member by lowercase email', (done) => {
request(server)
- .post(`/v4/projects/${project1.id}/members/invite`)
+ .post(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- emails: ['DUPLICATE_LOWERCASE@test.com'],
- role: 'customer',
- },
+ emails: ['DUPLICATE_LOWERCASE@test.com'],
+ role: 'customer',
})
.expect('Content-Type', /json/)
.expect(403)
@@ -822,7 +776,7 @@ describe('Project Member Invite create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content.failed;
+ const resJson = res.body.failed;
should.exist(resJson);
resJson[0].email.should.equal('duplicate_lowercase@test.com');
resJson[0].message.should.equal('User with such email is already invited to this project.');
@@ -834,15 +788,13 @@ describe('Project Member Invite create', () => {
it('should return 403 and failed list when trying add already invited member by uppercase email', (done) => {
request(server)
- .post(`/v4/projects/${project1.id}/members/invite`)
+ .post(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- emails: ['duplicate_uppercase@test.com'],
- role: 'customer',
- },
+ emails: ['duplicate_uppercase@test.com'],
+ role: 'customer',
})
.expect('Content-Type', /json/)
.expect(403)
@@ -850,7 +802,7 @@ describe('Project Member Invite create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content.failed;
+ const resJson = res.body.failed;
should.exist(resJson);
resJson[0].email.should.equal('DUPLICATE_UPPERCASE@test.com'); // email is masked
resJson[0].message.should.equal('User with such email is already invited to this project.');
@@ -863,15 +815,13 @@ describe('Project Member Invite create', () => {
xit('should return 403 and failed list when trying add already invited member by gmail email with dot',
(done) => {
request(server)
- .post(`/v4/projects/${project1.id}/members/invite`)
+ .post(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- emails: ['WITHdot@gmail.com'],
- role: 'customer',
- },
+ emails: ['WITHdot@gmail.com'],
+ role: 'customer',
})
.expect('Content-Type', /json/)
.expect(403)
@@ -879,7 +829,7 @@ describe('Project Member Invite create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content.failed;
+ const resJson = res.body.failed;
should.exist(resJson);
resJson[0].email.should.equal('WITHdot@gmail.com');
resJson.length.should.equal(1);
@@ -891,15 +841,13 @@ describe('Project Member Invite create', () => {
xit('should return 403 and failed list when trying add already invited member by gmail email without dot',
(done) => {
request(server)
- .post(`/v4/projects/${project1.id}/members/invite`)
+ .post(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- emails: ['WITHOUT.dot@gmail.com'],
- role: 'customer',
- },
+ emails: ['WITHOUT.dot@gmail.com'],
+ role: 'customer',
})
.expect('Content-Type', /json/)
.expect(403)
@@ -907,7 +855,7 @@ describe('Project Member Invite create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content.failed;
+ const resJson = res.body.failed;
should.exist(resJson);
resJson.length.should.equal(1);
resJson[0].email.should.equal('WITHOUT.dot@gmail.com');
@@ -928,100 +876,103 @@ describe('Project Member Invite create', () => {
createEventSpy = sandbox.spy(busApi, 'createEvent');
});
- it('sends single BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED message when userId invite added', (done) => {
+ it('should send correct BUS API messages when invite added by userId', (done) => {
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
get: () => Promise.resolve({
status: 200,
- data: {
- id: 'requesterId',
- version: 'v3',
- result: {
- success: true,
- status: 200,
- content: [{
- roleName: USER_ROLE.MANAGER,
- }],
- },
- },
+ data: [{
+ roleName: USER_ROLE.MANAGER,
+ }],
}),
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .post(`/v4/projects/${project1.id}/members/invite`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.manager}`,
- })
- .send({
- param: {
- userIds: [3],
- role: PROJECT_MEMBER_ROLE.CUSTOMER,
- },
- })
- .expect(201)
- .end((err) => {
- if (err) {
- done(err);
- } else {
- testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, sinon.match({
- projectId: project1.id,
- userId: 3,
- email: null,
- isSSO: false,
- })).should.be.true;
- done();
- });
- }
- });
+ .post(`/v5/projects/${project1.id}/members/invite`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.manager}`,
+ })
+ .send({
+ userIds: [3],
+ role: PROJECT_MEMBER_ROLE.CUSTOMER,
+ })
+ .expect(201)
+ .end((err) => {
+ if (err) {
+ done(err);
+ } else {
+ testUtil.wait(() => {
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, sinon.match({
+ resource: RESOURCES.PROJECT_MEMBER_INVITE,
+ projectId: project1.id,
+ userId: 3,
+ email: null,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_CREATED, sinon.match({
+ projectId: project1.id,
+ userId: 3,
+ email: null,
+ isSSO: false,
+ })).should.be.true;
+
+ done();
+ });
+ }
+ });
});
- it('sends single BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED message when email invite added', (done) => {
+ it('should send correct BUS API messages when invite added by email', (done) => {
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
get: () => Promise.resolve({
status: 200,
- data: {
- id: 'requesterId',
- version: 'v3',
- result: {
- success: true,
- status: 200,
- content: [{
- roleName: USER_ROLE.MANAGER,
- }],
- },
- },
+ data: [{
+ roleName: USER_ROLE.MANAGER,
+ }],
}),
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .post(`/v4/projects/${project1.id}/members/invite`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.manager}`,
- })
- .send({
- param: {
- emails: ['hello@world.com'],
- role: PROJECT_MEMBER_ROLE.CUSTOMER,
- },
- })
- .expect(201)
- .end((err) => {
- if (err) {
- done(err);
- } else {
- testUtil.wait(() => {
- createEventSpy.calledTwice.should.be.true;
- createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, sinon.match({
- projectId: project1.id,
- userId: null,
- email: 'hello@world.com',
- isSSO: false,
- })).should.be.true;
- done();
- });
- }
- });
+ .post(`/v5/projects/${project1.id}/members/invite`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.manager}`,
+ })
+ .send({
+ emails: ['hello@world.com'],
+ role: PROJECT_MEMBER_ROLE.CUSTOMER,
+ })
+ .expect(201)
+ .end((err) => {
+ if (err) {
+ done(err);
+ } else {
+ testUtil.wait(() => {
+ createEventSpy.callCount.should.be.eql(3);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, sinon.match({
+ resource: RESOURCES.PROJECT_MEMBER_INVITE,
+ projectId: project1.id,
+ userId: null,
+ email: 'hello@world.com',
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_CREATED, sinon.match({
+ projectId: project1.id,
+ userId: null,
+ email: 'hello@world.com',
+ isSSO: false,
+ })).should.be.true;
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_EMAIL_INVITE_CREATED, sinon.match({
+ recipients: ['hello@world.com'],
+ })).should.be.true;
+
+ done();
+ });
+ }
+ });
});
});
});
diff --git a/src/routes/projectMemberInvites/get.js b/src/routes/projectMemberInvites/get.js
index 59e337c4..2cc9fb1d 100644
--- a/src/routes/projectMemberInvites/get.js
+++ b/src/routes/projectMemberInvites/get.js
@@ -1,3 +1,5 @@
+
+
import _ from 'lodash';
import Joi from 'joi';
import validate from 'express-validation';
@@ -14,45 +16,75 @@ const schema = {
fields: Joi.string().optional(),
},
};
+
const permissions = tcMiddleware.permissions;
module.exports = [
validate(schema),
permissions('projectMemberInvite.get'),
- async (req, res, next) => {
- try {
- const projectId = _.parseInt(req.params.projectId);
- const currentUserId = req.authUser.userId;
- const invite = await models.ProjectMemberInvite.getPendingInviteByEmailOrUserId(
- projectId, req.authUser.email, currentUserId,
- );
- if (!invite) {
- // check there is an existing invite for the user with status PENDING
- // handle 404
- const err = new Error(
- 'invite not found for project id ' +
- `${projectId}, userId ${currentUserId}, email ${req.authUser.email}`,
- );
- err.status = 404;
- throw err;
- }
+ (req, res, next) => {
+ const projectId = _.parseInt(req.params.projectId);
+ const currentUserId = req.authUser.userId;
+ const email = req.authUser.email;
+ const fields = req.query.fields ? req.query.fields.split(',') : null;
- let fields = null;
- if (req.query.fields) {
- fields = req.query.fields.split(',');
- }
- let inviteWithDetails;
- try {
- [inviteWithDetails] = await util.getObjectsWithMemberDetails([invite], fields, req);
- } catch (err) {
- inviteWithDetails = invite;
- req.log.error('Cannot get user details for invite.');
- req.log.debug('Error during getting user details for invite.', err);
- }
+ util.fetchByIdFromES('invites', {
+ query: {
+ nested: {
+ path: 'invites',
+ query:
+ {
+ filtered: {
+ filter: {
+ bool: {
+ must: [
+ { term: { 'invites.projectId': projectId } },
+ {
+ bool: {
+ should: [
+ { term: { 'invites.email': email } },
+ { term: { 'invites.userId': currentUserId } },
+ ],
+ minimum_number_should_match: 1,
+ },
+ },
- return res.json(util.wrapResponse(req.id, inviteWithDetails));
- } catch (err) {
- return next(err);
- }
+ ],
+ },
+ },
+ },
+ },
+ inner_hits: {},
+ },
+ },
+ })
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No project member invite found in ES');
+ return models.ProjectMemberInvite.getPendingInviteByEmailOrUserId(projectId, email, currentUserId)
+ .then((invite) => {
+ if (!invite) {
+ // check there is an existing invite for the user with status PENDING
+ // handle 404
+ const err = new Error('invite not found for project id ' +
+ `${projectId}, userId ${currentUserId}, email ${email}`);
+ err.status = 404;
+ throw err;
+ }
+ return invite;
+ });
+ }
+ req.log.debug('project member found in ES');
+ return data[0].inner_hits.invites.hits.hits[0]._source; // eslint-disable-line no-underscore-dangle
+ }).then(invite => (
+ util.getObjectsWithMemberDetails([invite], fields, req)
+ .then(([inviteWithDetails]) => inviteWithDetails)
+ .catch((err) => {
+ req.log.error('Cannot get user details for invite.');
+ req.log.debug('Error during getting user details for invite.', err);
+ })
+ ))
+ .then(invite => res.json(util.maskInviteEmails('$[*].email', invite, req)))
+ .catch(next);
},
];
diff --git a/src/routes/projectMemberInvites/get.spec.js b/src/routes/projectMemberInvites/get.spec.js
index b5a1757d..5b04ae5b 100644
--- a/src/routes/projectMemberInvites/get.spec.js
+++ b/src/routes/projectMemberInvites/get.spec.js
@@ -76,13 +76,13 @@ describe('GET Project', () => {
describe('GET /projects/{id}/members/invite', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get(`/v4/projects/${project2.id}/members/invite`)
+ .get(`/v5/projects/${project2.id}/members/invite`)
.expect(403, done);
});
it('should return 404 if requested project doesn\'t exist', (done) => {
request(server)
- .get('/v4/projects/14343323/members/invite')
+ .get('/v5/projects/14343323/members/invite')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -91,7 +91,7 @@ describe('GET Project', () => {
it('should return the invite if user is invited to this project', (done) => {
request(server)
- .get(`/v4/projects/${project1.id}/members/invite`)
+ .get(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -101,7 +101,7 @@ describe('GET Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
should.exist(resJson.projectId);
resJson.userId.should.be.eql(40051331);
@@ -113,7 +113,7 @@ describe('GET Project', () => {
it('should return 404 if user is not invited to this project', (done) => {
request(server)
- .get(`/v4/projects/${project2.id}/members/invite`)
+ .get(`/v5/projects/${project2.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
diff --git a/src/routes/projectMemberInvites/list.js b/src/routes/projectMemberInvites/list.js
index 41e061ef..03edbe48 100644
--- a/src/routes/projectMemberInvites/list.js
+++ b/src/routes/projectMemberInvites/list.js
@@ -1,3 +1,5 @@
+
+
import _ from 'lodash';
import Joi from 'joi';
import validate from 'express-validation';
@@ -6,39 +8,66 @@ import models from '../../models';
import util from '../../util';
/**
- * API to list all project member invites.
+ * API to update invite member to project.
*
*/
-const permissions = tcMiddleware.permissions;
-
const schema = {
query: {
fields: Joi.string().optional(),
},
};
+const permissions = tcMiddleware.permissions;
+
module.exports = [
validate(schema),
permissions('projectMemberInvite.list'),
- async (req, res, next) => {
- try {
- let fields = null;
- if (req.query.fields) {
- fields = req.query.fields.split(',');
- }
- const projectId = _.parseInt(req.params.projectId);
- const invites = await models.ProjectMemberInvite.getPendingAndReguestedInvitesForProject(projectId);
- let invitesWithDetails;
- try {
- invitesWithDetails = await util.getObjectsWithMemberDetails(invites, fields, req);
- } catch (err) {
- invitesWithDetails = invites;
- req.log.error('Cannot get user details for invites.');
- req.log.debug('Error during getting user details for invites', err);
+ (req, res, next) => {
+ const projectId = _.parseInt(req.params.projectId);
+ const fields = req.query.fields ? req.query.fields.split(',') : null;
+
+ util.fetchByIdFromES('invites', {
+ query: {
+ nested: {
+ path: 'invites',
+ query:
+ {
+ filtered: {
+ filter: {
+ bool: {
+ must: [
+ { term: { 'invites.projectId': projectId } },
+ ],
+ },
+ },
+ },
+ },
+ inner_hits: {
+ // TODO: replace this temporary fix with a better solution
+ // we have to get all the members of the project,
+ // should we just get a project object instead of creating such a detailed request?
+ // I guess just retrieving project by id and after returning members from it
+ // should work much faster
+ size: 1000,
+ },
+ },
+ },
+ })
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No project member invites found in ES');
+ return models.ProjectMemberInvite.getPendingAndReguestedInvitesForProject(projectId);
}
- return res.json(util.wrapResponse(req.id, util.maskInviteEmails('$[*].email', invitesWithDetails, req)));
- } catch (err) {
- return next(err);
- }
+ req.log.debug('project member found in ES');
+ return data[0].inner_hits.invites.hits.hits.map(hit => hit._source); // eslint-disable-line no-underscore-dangle
+ }).then(invites => (
+ util.getObjectsWithMemberDetails(invites, fields, req)
+ .catch((err) => {
+ req.log.error('Cannot get user details for invites.');
+ req.log.debug('Error during getting user details for invites.', err);
+ })
+ ))
+ .then(invites => res.json(util.maskInviteEmails('$[*].email', invites, req)))
+ .catch(next);
},
];
diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js
index bed74cef..5b779e82 100644
--- a/src/routes/projectMemberInvites/update.js
+++ b/src/routes/projectMemberInvites/update.js
@@ -4,7 +4,7 @@ import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import models from '../../models';
import util from '../../util';
-import { PROJECT_MEMBER_ROLE, MANAGER_ROLES, INVITE_STATUS, EVENT, USER_ROLE } from '../../constants';
+import { PROJECT_MEMBER_ROLE, MANAGER_ROLES, INVITE_STATUS, EVENT, RESOURCES, USER_ROLE } from '../../constants';
/**
* API to update invite member to project.
@@ -13,8 +13,7 @@ import { PROJECT_MEMBER_ROLE, MANAGER_ROLES, INVITE_STATUS, EVENT, USER_ROLE } f
const permissions = tcMiddleware.permissions;
const updateMemberValidations = {
- body: {
- param: Joi.object()
+ body: Joi.object()
.keys({
userId: Joi.number().optional(),
email: Joi.string()
@@ -25,7 +24,6 @@ const updateMemberValidations = {
.required(),
})
.required(),
- },
};
module.exports = [
@@ -33,7 +31,7 @@ module.exports = [
validate(updateMemberValidations),
permissions('projectMemberInvite.put'),
(req, res, next) => {
- const putInvite = req.body.param;
+ const putInvite = req.body;
const projectId = _.parseInt(req.params.projectId);
// userId or email should be provided
@@ -93,14 +91,13 @@ module.exports = [
status: putInvite.status,
})
.then((updatedInvite) => {
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED, {
+ // emit the event
+ util.sendResourceToKafkaBus(
req,
- userId: updatedInvite.userId,
- email: updatedInvite.email,
- status: updatedInvite.status,
- role: updatedInvite.role,
- createdBy: updatedInvite.createdBy,
- });
+ EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED,
+ RESOURCES.PROJECT_MEMBER_INVITE,
+ updatedInvite.toJSON());
+
req.app.services.pubsub.publish(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED, updatedInvite, {
correlationId: req.id,
});
@@ -135,13 +132,11 @@ module.exports = [
};
return util
.addUserToProject(req, member)
- .then(() => res.json(util.wrapResponse(req.id,
- util.maskInviteEmails('$.email', updatedInvite, req))))
+ .then(() => res.json(util.maskInviteEmails('$.email', updatedInvite, req)))
.catch(err => next(err));
});
}
-
- return res.json(util.wrapResponse(req.id, util.maskInviteEmails('$.email', updatedInvite, req)));
+ return res.json(util.maskInviteEmails('$.email', updatedInvite, req));
});
});
},
diff --git a/src/routes/projectMemberInvites/update.spec.js b/src/routes/projectMemberInvites/update.spec.js
index 6256e5fd..49e53e3c 100644
--- a/src/routes/projectMemberInvites/update.spec.js
+++ b/src/routes/projectMemberInvites/update.spec.js
@@ -8,7 +8,14 @@ import server from '../../app';
import util from '../../util';
import testUtil from '../../tests/util';
import busApi from '../../services/busApi';
-import { BUS_API_EVENT, USER_ROLE, PROJECT_MEMBER_ROLE, INVITE_STATUS } from '../../constants';
+import {
+ BUS_API_EVENT,
+ RESOURCES,
+ USER_ROLE,
+ PROJECT_MEMBER_ROLE,
+ INVITE_STATUS,
+ CONNECT_NOTIFICATION_EVENT,
+} from '../../constants';
const should = chai.should();
@@ -103,9 +110,7 @@ describe('Project member invite update', () => {
describe('PUT /projects/{id}/members/invite', () => {
const body = {
- param: {
- status: 'accepted',
- },
+ status: 'accepted',
};
let sandbox;
@@ -118,22 +123,20 @@ describe('Project member invite update', () => {
it('should return 403 if user does not have permissions', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}/members/invite`)
+ .patch(`/v5/projects/${project1.id}/members/invite`)
.send(body)
.expect(403, done);
});
it('should return 404 if user has no invite', (done) => {
request(server)
- .put(`/v4/projects/${project1.id}/members/invite`)
+ .put(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- userId: 123,
- status: INVITE_STATUS.CANCELED,
- },
+ userId: 123,
+ status: INVITE_STATUS.CANCELED,
})
.expect('Content-Type', /json/)
.expect(404)
@@ -144,14 +147,12 @@ describe('Project member invite update', () => {
it('should return 400 no userId or email is presented', (done) => {
request(server)
- .put(`/v4/projects/${project1.id}/members/invite`)
+ .put(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- status: INVITE_STATUS.CANCELED,
- },
+ status: INVITE_STATUS.CANCELED,
})
.expect('Content-Type', /json/)
.expect(400)
@@ -159,9 +160,8 @@ describe('Project member invite update', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
- res.body.result.status.should.equal(400);
const errorMessage = _.get(resJson, 'message', '');
sinon.assert.match(errorMessage, /.*userId or email should be provided/);
done();
@@ -188,15 +188,13 @@ describe('Project member invite update', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .put(`/v4/projects/${project1.id}/members/invite`)
+ .put(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- userId: invite2.userId,
- status: INVITE_STATUS.CANCELED,
- },
+ userId: invite2.userId,
+ status: INVITE_STATUS.CANCELED,
})
.expect('Content-Type', /json/)
.expect(403)
@@ -204,9 +202,8 @@ describe('Project member invite update', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
- res.body.result.status.should.equal(403);
const errorMessage = _.get(resJson, 'message', '');
sinon.assert.match(errorMessage, /.*Project members can cancel invites only for customer/);
done();
@@ -233,15 +230,13 @@ describe('Project member invite update', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .put(`/v4/projects/${project1.id}/members/invite`)
+ .put(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
.send({
- param: {
- userId: invite2.userId,
- status: INVITE_STATUS.CANCELED,
- },
+ userId: invite2.userId,
+ status: INVITE_STATUS.CANCELED,
})
.expect('Content-Type', /json/)
.expect(403)
@@ -249,9 +244,8 @@ describe('Project member invite update', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
- res.body.result.status.should.equal(403);
const errorMessage = _.get(resJson, 'message', '');
sinon.assert.match(errorMessage, /.*Project members can cancel invites only for customer/);
done();
@@ -278,15 +272,13 @@ describe('Project member invite update', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .put(`/v4/projects/${project1.id}/members/invite`)
+ .put(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- userId: invite3.userId,
- status: INVITE_STATUS.ACCEPTED,
- },
+ userId: invite3.userId,
+ status: INVITE_STATUS.ACCEPTED,
})
.expect('Content-Type', /json/)
.expect(403)
@@ -294,9 +286,8 @@ describe('Project member invite update', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
- res.body.result.status.should.equal(403);
const errorMessage = _.get(resJson, 'message', '');
sinon.assert.match(errorMessage, 'Requested invites can only be updated by Copilot manager');
done();
@@ -317,34 +308,22 @@ describe('Project member invite update', () => {
createEventSpy = sandbox.spy(busApi, 'createEvent');
});
- it('Accept invite sends BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED ' +
- 'and BUS_API_EVENT.PROJECT_MEMBER_ADDED messages', (done) => {
+ it('should send correct BUS API messages when invite is accepted', (done) => {
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
get: () => Promise.resolve({
status: 200,
- data: {
- id: 'requesterId',
- version: 'v3',
- result: {
- success: true,
- status: 200,
- content: [{
- }],
- },
- },
+ data: {},
}),
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .put(`/v4/projects/${project1.id}/members/invite`)
+ .put(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
.send({
- param: {
- userId: invite1.userId,
- status: INVITE_STATUS.ACCEPTED,
- },
+ userId: invite1.userId,
+ status: INVITE_STATUS.ACCEPTED,
})
.expect('Content-Type', /json/)
.expect(200)
@@ -353,26 +332,51 @@ describe('Project member invite update', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledThrice.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, sinon.match({
+ createEventSpy.callCount.should.be.eql(5);
+
+ /*
+ Events for accepted invite
+ */
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, sinon.match({
+ resource: RESOURCES.PROJECT_MEMBER_INVITE,
+ projectId: project1.id,
+ userId: invite1.userId,
+ status: INVITE_STATUS.ACCEPTED,
+ email: null,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_UPDATED, sinon.match({
projectId: project1.id,
userId: invite1.userId,
status: INVITE_STATUS.ACCEPTED,
email: null,
isSSO: false,
})).should.be.true;
- createEventSpy.secondCall.calledWith(BUS_API_EVENT.MEMBER_JOINED, sinon.match({
+
+ /*
+ Events for created member (after invite acceptance)
+ */
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_ADDED, sinon.match({
+ resource: RESOURCES.PROJECT_MEMBER,
+ projectId: project1.id,
+ userId: invite1.userId,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MEMBER_JOINED, sinon.match({
projectId: project1.id,
projectName: project1.name,
userId: invite1.userId,
initiatorUserId: 40051331,
})).should.be.true;
- createEventSpy.thirdCall.calledWith(BUS_API_EVENT.PROJECT_TEAM_UPDATED, sinon.match({
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED, sinon.match({
projectId: project1.id,
projectName: project1.name,
userId: invite1.userId,
initiatorUserId: 40051331,
})).should.be.true;
+
done();
});
}
diff --git a/src/routes/projectMembers/create.js b/src/routes/projectMembers/create.js
index 1309593f..cb72ebc7 100644
--- a/src/routes/projectMembers/create.js
+++ b/src/routes/projectMembers/create.js
@@ -14,21 +14,18 @@ import models from '../../models';
const permissions = tcMiddleware.permissions;
const createProjectMemberValidations = {
- body: {
- param: Joi.object()
- .keys({
- role: Joi.any()
- .valid(
- PROJECT_MEMBER_ROLE.MANAGER,
- PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER,
- PROJECT_MEMBER_ROLE.COPILOT,
- PROJECT_MEMBER_ROLE.PROJECT_MANAGER,
- PROJECT_MEMBER_ROLE.PROGRAM_MANAGER,
- PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT,
- PROJECT_MEMBER_ROLE.ACCOUNT_EXECUTIVE,
- ),
- }),
- },
+ body: Joi.object().keys({
+ role: Joi.any()
+ .valid(
+ PROJECT_MEMBER_ROLE.MANAGER,
+ PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER,
+ PROJECT_MEMBER_ROLE.COPILOT,
+ PROJECT_MEMBER_ROLE.PROJECT_MANAGER,
+ PROJECT_MEMBER_ROLE.PROGRAM_MANAGER,
+ PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT,
+ PROJECT_MEMBER_ROLE.ACCOUNT_EXECUTIVE,
+ ),
+ }),
};
module.exports = [
@@ -37,8 +34,8 @@ module.exports = [
permissions('project.addMember'),
(req, res, next) => {
let targetRole;
- if (_.get(req, 'body.param.role')) {
- targetRole = _.get(req, 'body.param.role');
+ if (_.get(req, 'body.role')) {
+ targetRole = _.get(req, 'body.role');
if (PROJECT_MEMBER_ROLE.MANAGER === targetRole &&
!util.hasRoles(req, [USER_ROLE.MANAGER])) {
@@ -148,23 +145,19 @@ module.exports = [
return next(err);
}
- return util.addUserToProject(req, member)
- .then((newMember) => {
- let invite;
- return models.ProjectMemberInvite.getPendingInviteByEmailOrUserId(projectId, null, newMember.userId)
- .then((_invite) => {
- invite = _invite;
+ return util.addUserToProject(req, member) // Kafka event is emitted inside `addUserToProject`
+ .then(newMember =>
+ models.ProjectMemberInvite.getPendingInviteByEmailOrUserId(projectId, null, newMember.userId)
+ .then((invite) => {
if (!invite) {
- return res.status(201)
- .json(util.wrapResponse(req.id, newMember, 1, 201));
+ return res.status(201).json(newMember);
}
return invite.update({
status: INVITE_STATUS.ACCEPTED,
})
- .then(() => res.status(201)
- .json(util.wrapResponse(req.id, newMember, 1, 201)));
- });
- });
+ .then(() => res.status(201).json(newMember));
+ }),
+ );
})
.catch(err => next(err));
},
diff --git a/src/routes/projectMembers/create.spec.js b/src/routes/projectMembers/create.spec.js
index f6c85633..7e193990 100644
--- a/src/routes/projectMembers/create.spec.js
+++ b/src/routes/projectMembers/create.spec.js
@@ -9,7 +9,7 @@ import util from '../../util';
import server from '../../app';
import testUtil from '../../tests/util';
import busApi from '../../services/busApi';
-import { USER_ROLE, BUS_API_EVENT } from '../../constants';
+import { USER_ROLE, BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT, INVITE_STATUS } from '../../constants';
const should = chai.should();
@@ -52,7 +52,7 @@ describe('Project Members create', () => {
it('should return 403 if user does not have permissions', (done) => {
request(server)
- .post(`/v4/projects/${project1.id}/members/`)
+ .post(`/v5/projects/${project1.id}/members/`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -79,15 +79,13 @@ describe('Project Members create', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .post(`/v4/projects/${project1.id}/members/invite`)
+ .post(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- userIds: [40051332],
- role: 'copilot',
- },
+ userIds: [40051332],
+ role: 'copilot',
})
.expect('Content-Type', /json/)
.expect(201)
@@ -95,23 +93,20 @@ describe('Project Members create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content.success[0];
+ const resJson = res.body.success[0];
should.exist(resJson);
resJson.role.should.equal('copilot');
resJson.projectId.should.equal(project1.id);
resJson.userId.should.equal(40051332);
server.services.pubsub.publish.calledWith('project.member.invite.created').should.be.true;
-
request(server)
- .put(`/v4/projects/${project1.id}/members/invite`)
+ .put(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
.send({
- param: {
- userId: 40051332,
- status: 'accepted',
- },
+ userId: 40051332,
+ status: 'accepted',
})
.expect('Content-Type', /json/)
.expect(200)
@@ -119,7 +114,7 @@ describe('Project Members create', () => {
if (err2) {
done(err2);
} else {
- const resJson2 = res2.body.result.content;
+ const resJson2 = res2.body;
should.exist(resJson2);
resJson2.role.should.equal('copilot');
resJson2.projectId.should.equal(project1.id);
@@ -128,15 +123,13 @@ describe('Project Members create', () => {
server.services.pubsub.publish.calledWith('project.member.added').should.be.true;
request(server)
- .put(`/v4/projects/${project1.id}/members/invite`)
+ .put(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
.send({
- param: {
- userId: 40051332,
- status: 'accepted',
- },
+ userId: 40051332,
+ status: 'accepted',
})
.expect('Content-Type', /json/)
.expect(404)
@@ -144,7 +137,8 @@ describe('Project Members create', () => {
if (err3) {
done(err3);
} else {
- res3.body.result.status.should.equal(404);
+ const errorMessage = _.get(res3.body, 'message', '');
+ sinon.assert.match(errorMessage, /.*invite not found for project id 1, email undefined and userId/);
done();
}
});
@@ -185,7 +179,7 @@ describe('Project Members create', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .post(`/v4/projects/${project1.id}/members/`)
+ .post(`/v5/projects/${project1.id}/members/`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -195,7 +189,7 @@ describe('Project Members create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.role.should.equal('manager');
resJson.isPrimary.should.be.truthy;
@@ -219,7 +213,7 @@ describe('Project Members create', () => {
createEventSpy = sandbox.spy(busApi, 'createEvent');
});
- it('sends single BUS_API_EVENT.PROJECT_TEAM_UPDATED message when manager added', (done) => {
+ it('should send correct BUS API messages when a manager added', (done) => {
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
get: () => Promise.resolve({
status: 200,
@@ -250,7 +244,7 @@ describe('Project Members create', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .post(`/v4/projects/${project1.id}/members/`)
+ .post(`/v5/projects/${project1.id}/members/`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -260,32 +254,39 @@ describe('Project Members create', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledTwice.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.MEMBER_JOINED_MANAGER);
- createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_TEAM_UPDATED, sinon.match({
+ createEventSpy.callCount.should.be.eql(3);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_ADDED, sinon.match({
+ resource: RESOURCES.PROJECT_MEMBER,
+ projectId: project1.id,
+ userId: 40051334,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MEMBER_JOINED_MANAGER).should.be.true;
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED, sinon.match({
projectId: project1.id,
projectName: project1.name,
projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`,
userId: 40051334,
initiatorUserId: 40051334,
})).should.be.true;
+
done();
});
}
});
});
- it('sends single BUS_API_EVENT.PROJECT_TEAM_UPDATED message when copilot added', (done) => {
+ it('should send correct BUS API messages when copilot added', (done) => {
request(server)
- .post(`/v4/projects/${project1.id}/members/invite`)
+ .post(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- userIds: [40051332],
- role: 'copilot',
- },
+ userIds: [40051332],
+ role: 'copilot',
})
.expect(201)
.end((err) => {
@@ -293,15 +294,13 @@ describe('Project Members create', () => {
done(err);
} else {
request(server)
- .put(`/v4/projects/${project1.id}/members/invite`)
+ .put(`/v5/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
.send({
- param: {
- userId: 40051332,
- status: 'accepted',
- },
+ userId: 40051332,
+ status: 'accepted',
})
.expect('Content-Type', /json/)
.expect(200)
@@ -310,11 +309,42 @@ describe('Project Members create', () => {
done(err2);
} else {
testUtil.wait(() => {
- createEventSpy.callCount.should.equal(4);
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_REQUESTED);
- createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED);
- createEventSpy.thirdCall.calledWith(BUS_API_EVENT.MEMBER_JOINED_COPILOT);
- createEventSpy.lastCall.calledWith(BUS_API_EVENT.PROJECT_TEAM_UPDATED, sinon.match({
+ createEventSpy.callCount.should.equal(7);
+
+ /*
+ Copilot invitation requested
+ */
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, sinon.match({
+ resource: RESOURCES.PROJECT_MEMBER_INVITE,
+ projectId: project1.id,
+ userId: 40051332,
+ email: null,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_REQUESTED).should.be.true;
+
+ /*
+ Copilot invitation accepted
+ */
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, sinon.match({
+ resource: RESOURCES.PROJECT_MEMBER_INVITE,
+ projectId: project1.id,
+ userId: 40051332,
+ status: INVITE_STATUS.ACCEPTED,
+ email: null,
+ })).should.be.true;
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_ADDED, sinon.match({
+ resource: RESOURCES.PROJECT_MEMBER,
+ projectId: project1.id,
+ userId: 40051332,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_UPDATED).should.be.true;
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MEMBER_JOINED_COPILOT).should.be.true;
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED, sinon.match({
projectId: project1.id,
projectName: project1.name,
projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`,
diff --git a/src/routes/projectMembers/delete.js b/src/routes/projectMembers/delete.js
index b58aaa84..d13a8f71 100644
--- a/src/routes/projectMembers/delete.js
+++ b/src/routes/projectMembers/delete.js
@@ -3,7 +3,8 @@
import _ from 'lodash';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import models from '../../models';
-import { EVENT, PROJECT_MEMBER_ROLE } from '../../constants';
+import util from '../../util';
+import { EVENT, RESOURCES, PROJECT_MEMBER_ROLE } from '../../constants';
/**
* API to delete a project member.
@@ -76,7 +77,13 @@ module.exports = [
pmember,
{ correlationId: req.id },
);
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, { req, member: pmember });
+
+ // emit the event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED,
+ RESOURCES.PROJECT_MEMBER,
+ pmember);
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 9f539ef2..dd1556ae 100644
--- a/src/routes/projectMembers/delete.spec.js
+++ b/src/routes/projectMembers/delete.spec.js
@@ -9,7 +9,7 @@ import util from '../../util';
import server from '../../app';
import testUtil from '../../tests/util';
import busApi from '../../services/busApi';
-import { BUS_API_EVENT } from '../../constants';
+import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants';
const should = chai.should();
@@ -93,39 +93,35 @@ describe('Project members delete', () => {
it('should return 403 if user does not have permissions', (done) => {
request(server)
- .delete(`/v4/projects/${project1.id}/members/${member1.id}`)
+ .delete(`/v5/projects/${project1.id}/members/${member1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
.send({
- param: {
- userId: 1,
- projectId: project1.id,
- role: 'customer',
- },
+ userId: 1,
+ projectId: project1.id,
+ role: 'customer',
})
.expect(403, done);
});
it('should return 403 if user not found', (done) => {
request(server)
- .delete(`/v4/projects/${project1.id}/members/8888888`)
+ .delete(`/v5/projects/${project1.id}/members/8888888`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- userId: 1,
- projectId: project1.id,
- role: 'customer',
- },
+ userId: 1,
+ projectId: project1.id,
+ role: 'customer',
})
.expect(403, done);
});
it('should return 204 if copilot user has access to the project', (done) => {
request(server)
- .delete(`/v4/projects/${project1.id}/members/${member1.id}`)
+ .delete(`/v5/projects/${project1.id}/members/${member1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -184,7 +180,7 @@ describe('Project members delete', () => {
updatedAt: '2016-08-30 00:33:07+00',
}]).then(() => {
request(server)
- .delete(`/v4/projects/${project1.id}/members/${member1.id}`)
+ .delete(`/v5/projects/${project1.id}/members/${member1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -242,7 +238,7 @@ describe('Project members delete', () => {
const postSpy = sinon.spy(mockHttpClient, 'post');
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .delete(`/v4/projects/${project1.id}/members/${member2.id}`)
+ .delete(`/v5/projects/${project1.id}/members/${member2.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -289,7 +285,7 @@ describe('Project members delete', () => {
})
.then(() => {
request(server)
- .delete(`/v4/projects/${project1.id}/members/${member2.id}`)
+ .delete(`/v5/projects/${project1.id}/members/${member2.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -313,7 +309,7 @@ describe('Project members delete', () => {
it('should return 403 if copilot user is trying to remove a manager', (done) => {
request(server)
- .delete(`/v4/projects/${project1.id}/members/${member2.id}`)
+ .delete(`/v5/projects/${project1.id}/members/${member2.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -332,7 +328,7 @@ describe('Project members delete', () => {
createEventSpy = sandbox.spy(busApi, 'createEvent');
});
- it('sends single BUS_API_EVENT.PROJECT_TEAM_UPDATED message when manager removed', (done) => {
+ it('should send correct BUS API messages when manager left', (done) => {
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
post: () => Promise.resolve({
status: 200,
@@ -349,7 +345,7 @@ describe('Project members delete', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .delete(`/v4/projects/${project1.id}/members/${member2.id}`)
+ .delete(`/v5/projects/${project1.id}/members/${member2.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -359,24 +355,32 @@ describe('Project members delete', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledTwice.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.MEMBER_LEFT);
- createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_TEAM_UPDATED, sinon.match({
+ createEventSpy.callCount.should.equal(3);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_REMOVED, sinon.match({
+ resource: RESOURCES.PROJECT_MEMBER,
+ id: member2.id,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MEMBER_LEFT).should.be.true;
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED, sinon.match({
projectId: project1.id,
projectName: project1.name,
projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`,
userId: 40051334,
initiatorUserId: 40051334,
})).should.be.true;
+
done();
});
}
});
});
- it('sends single BUS_API_EVENT.PROJECT_TEAM_UPDATED message when copilot removed', (done) => {
+ it('should send correct BUS API messages when copilot removed', (done) => {
request(server)
- .delete(`/v4/projects/${project1.id}/members/${member1.id}`)
+ .delete(`/v5/projects/${project1.id}/members/${member1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -386,15 +390,23 @@ describe('Project members delete', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledTwice.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.MEMBER_REMOVED);
- createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_TEAM_UPDATED, sinon.match({
+ createEventSpy.callCount.should.equal(3);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_REMOVED, sinon.match({
+ resource: RESOURCES.PROJECT_MEMBER,
+ id: member1.id,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MEMBER_REMOVED).should.be.true;
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED, sinon.match({
projectId: project1.id,
projectName: project1.name,
projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`,
userId: 40051334,
initiatorUserId: 40051334,
})).should.be.true;
+
done();
});
}
diff --git a/src/routes/projectMembers/get.js b/src/routes/projectMembers/get.js
index 0659020c..d6768f86 100644
--- a/src/routes/projectMembers/get.js
+++ b/src/routes/projectMembers/get.js
@@ -4,51 +4,86 @@ import _ from 'lodash';
import Joi from 'joi';
import validate from 'express-validation';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import models from '../../models';
import util from '../../util';
/**
- * API to get a project member in a project.
+ * API to get project member.
+ *
*/
const permissions = tcMiddleware.permissions;
const schema = {
+ params: {
+ projectId: Joi.number().integer().positive().required(),
+ id: Joi.number().integer().positive().required(),
+ },
query: {
fields: Joi.string().optional(),
},
};
module.exports = [
+ // handles request validations
validate(schema),
- permissions('project.getMember'),
- async (req, res, next) => {
- try {
- const projectId = _.parseInt(req.params.projectId);
- let fields = null;
- if (req.query.fields) {
- fields = req.query.fields.split(',');
- }
- const memberId = _.parseInt(req.params.id);
- const member = _.find(req.context.currentProjectMembers, user => user.id === memberId);
- if (!member) {
- const err = new Error(
- `member not found for project id ${projectId}, userId ${memberId}`,
- );
- err.status = 404;
- throw err;
- }
+ permissions('project.viewMember'),
+ (req, res, next) => {
+ const projectId = _.parseInt(req.params.projectId);
+ const memberRecordId = _.parseInt(req.params.id);
+ const fields = req.query.fields ? req.query.fields.split(',') : null;
- let memberWithDetails;
- try {
- [memberWithDetails] = await util.getObjectsWithMemberDetails([member], fields, req);
- } catch (err) {
- memberWithDetails = member;
- req.log.error('Cannot get user details for the member.');
- req.log.debug('Error during getting user details for member.', err);
+ util.fetchByIdFromES('members', {
+ query: {
+ nested: {
+ path: 'members',
+ query:
+ {
+ filtered: {
+ filter: {
+ bool: {
+ must: [
+ { term: { 'members.projectId': projectId } },
+ { term: { 'members.id': memberRecordId } },
+ ],
+ },
+ },
+ },
+ },
+ inner_hits: {},
+ },
+ },
+ })
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No project member found in ES');
+ return models.ProjectMember.findOne({
+ where: {
+ id: memberRecordId,
+ projectId },
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ raw: true,
+ })
+ .then((member) => {
+ if (!member) {
+ // check there is an existing member
+ const err = new Error(`member not found for project id ${projectId}, id ${memberRecordId}`);
+ err.status = 404;
+ throw err;
+ }
+ return member;
+ });
}
-
- return res.json(util.wrapResponse(req.id, memberWithDetails));
- } catch (err) {
- return next(err);
- }
+ req.log.debug('project member found in ES');
+ return data[0].inner_hits.members.hits.hits[0]._source; // eslint-disable-line no-underscore-dangle
+ }).then(member => (
+ util.getObjectsWithMemberDetails([member], fields, req)
+ .then(([memberWithDetails]) => memberWithDetails)
+ .catch((err) => {
+ req.log.error('Cannot get user details for member.');
+ req.log.debug('Error during getting user details for member.', err);
+ })
+ ))
+ .then(member => res.json(member))
+ .catch(next);
},
];
diff --git a/src/routes/projectMembers/get.spec.js b/src/routes/projectMembers/get.spec.js
new file mode 100644
index 00000000..bde598a1
--- /dev/null
+++ b/src/routes/projectMembers/get.spec.js
@@ -0,0 +1,190 @@
+/**
+ * Tests for list.js
+ */
+import _ from 'lodash';
+import chai from 'chai';
+import request from 'supertest';
+
+import models from '../../models';
+import server from '../../app';
+import testUtil from '../../tests/util';
+
+const should = chai.should();
+
+describe('GET project member', () => {
+ let projectId;
+ let memberId;
+ let memberId2;
+
+ const memberUser = {
+ handle: testUtil.getDecodedToken(testUtil.jwts.member).handle,
+ userId: testUtil.getDecodedToken(testUtil.jwts.member).userId,
+ firstName: 'fname',
+ lastName: 'lName',
+ email: 'some@abc.com',
+ };
+ const copilotUser = {
+ handle: testUtil.getDecodedToken(testUtil.jwts.copilot).handle,
+ userId: testUtil.getDecodedToken(testUtil.jwts.copilot).userId,
+ firstName: 'fname',
+ lastName: 'lName',
+ email: 'some@abc.com',
+ };
+
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => testUtil.clearES())
+ .then(() => {
+ // Create projects
+ models.Project.create({
+ type: 'generic',
+ billingAccountId: 1,
+ name: 'test1',
+ description: 'test project1',
+ status: 'draft',
+ details: {},
+ createdBy: 1,
+ updatedBy: 1,
+ lastActivityAt: 1,
+ lastActivityUserId: '1',
+ })
+ .then((project) => {
+ projectId = project.id;
+ // create members
+ models.ProjectMember.create({
+ id: 1,
+ userId: copilotUser.userId,
+ projectId,
+ role: 'copilot',
+ isPrimary: false,
+ createdBy: 1,
+ updatedBy: 1,
+ }).then((_member) => {
+ memberId = _member.id;
+ models.ProjectMember.create({
+ id: 2,
+ userId: memberUser.userId,
+ projectId,
+ role: 'customer',
+ isPrimary: true,
+ createdBy: 1,
+ updatedBy: 1,
+ }).then((m) => {
+ memberId2 = m.id;
+ done();
+ });
+ });
+ });
+ });
+ });
+
+ after((done) => {
+ testUtil.clearDb(done);
+ });
+
+ describe('GET /projects/{projectId}/members/{memberId}', () => {
+ it('should return 403 for anonymous user', (done) => {
+ request(server)
+ .get(`/v5/projects/${projectId}/members/${memberId}`)
+ .expect(403, done);
+ });
+
+ it('should return 404 if requested project doesn\'t exist', (done) => {
+ request(server)
+ .get('/v5/projects/9999999/members/1')
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.admin}`,
+ })
+ .expect(404, done);
+ });
+
+ it('should return 404 if requested project member doesn\'t exist', (done) => {
+ request(server)
+ .get(`/v5/projects/${projectId}/members/9999`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.admin}`,
+ })
+ .expect(404, done);
+ });
+
+ it('should return 200 for connect admin', (done) => {
+ request(server)
+ .get(`/v5/projects/${projectId}/members/${memberId}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
+ })
+ .expect(200)
+ .end(done);
+ });
+
+ it('should return 200 for connect manager', (done) => {
+ request(server)
+ .get(`/v5/projects/${projectId}/members/${memberId}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.manager}`,
+ })
+ .expect(200)
+ .end(done);
+ });
+
+ it('should return 200 for member', (done) => {
+ request(server)
+ .get(`/v5/projects/${projectId}/members/${memberId}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.member}`,
+ })
+ .expect(200, done);
+ });
+
+ it('should return 200 for copilot', (done) => {
+ request(server)
+ .get(`/v5/projects/${projectId}/members/${memberId}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.copilot}`,
+ })
+ .expect(200, done);
+ });
+
+ it('should return 200 for admin when retrieve member with id=1', (done) => {
+ request(server)
+ .get(`/v5/projects/${projectId}/members/${memberId}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.admin}`,
+ })
+ .expect(200)
+ .end((err, res) => {
+ const resJson = res.body;
+ resJson.userId.should.be.eql(_.parseInt(copilotUser.userId));
+ resJson.role.should.be.eql('copilot');
+ resJson.projectId.should.be.eql(projectId);
+ should.exist(resJson.createdAt);
+ should.exist(resJson.updatedAt);
+ should.not.exist(resJson.deletedBy);
+ should.not.exist(resJson.deletedAt);
+
+ done();
+ });
+ });
+
+ it('should return 200 for admin when retrieve member with id=2', (done) => {
+ request(server)
+ .get(`/v5/projects/${projectId}/members/${memberId2}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.admin}`,
+ })
+ .expect(200)
+ .end((err, res) => {
+ const resJson = res.body;
+ resJson.userId.should.be.eql(_.parseInt(memberUser.userId));
+ resJson.role.should.be.eql('customer');
+ resJson.projectId.should.be.eql(projectId);
+ should.exist(resJson.createdAt);
+ should.exist(resJson.updatedAt);
+ should.not.exist(resJson.deletedBy);
+ should.not.exist(resJson.deletedAt);
+
+ done();
+ });
+ });
+ });
+});
diff --git a/src/routes/projectMembers/list.js b/src/routes/projectMembers/list.js
index 40f73000..92531c9d 100644
--- a/src/routes/projectMembers/list.js
+++ b/src/routes/projectMembers/list.js
@@ -1,42 +1,104 @@
-import Joi from 'joi';
+/**
+ * API to list all project members
+ */
+import _ from 'lodash';
import validate from 'express-validation';
+import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import models from '../../models';
import util from '../../util';
+import { PROJECT_MEMBER_ROLE } from '../../constants';
-/**
- * API to list all project members.
- *
- */
const permissions = tcMiddleware.permissions;
const schema = {
query: {
+ role: Joi.any()
+ .valid(PROJECT_MEMBER_ROLE.MANAGER,
+ PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER,
+ PROJECT_MEMBER_ROLE.COPILOT,
+ PROJECT_MEMBER_ROLE.CUSTOMER,
+ PROJECT_MEMBER_ROLE.OBSERVER),
fields: Joi.string().optional(),
},
+ params: {
+ projectId: Joi.number().integer().positive().required(),
+ },
};
module.exports = [
validate(schema),
- permissions('project.listMembers'),
- async (req, res, next) => {
- try {
- let fields = null;
- if (req.query.fields) {
- fields = req.query.fields.split(',');
- }
-
- let membersWithDetails;
- try {
- membersWithDetails = await util.getObjectsWithMemberDetails(req.context.currentProjectMembers, fields, req);
- } catch (err) {
- membersWithDetails = req.context.currentProjectMembers;
- req.log.error('Cannot get user details for the members.');
- req.log.debug('Error during getting user details for the members', err);
- }
+ permissions('project.viewMember'),
+ (req, res, next) => {
+ const projectId = _.parseInt(req.params.projectId);
+ const fields = req.query.fields ? req.query.fields.split(',') : null;
+ const must = [
+ { term: { 'members.projectId': projectId } },
+ ];
- return res.json(util.wrapResponse(req.id, membersWithDetails));
- } catch (err) {
- return next(err);
+ if (req.query.role) {
+ must.push({ term: { 'members.role': req.query.role } });
}
+
+ util.fetchByIdFromES('members', {
+ sort: [
+ { id: { order: 'asc' } },
+ ],
+ query: {
+ nested: {
+ path: 'members',
+ query:
+ {
+ filtered: {
+ filter: {
+ bool: {
+ must,
+ },
+ },
+ },
+ },
+ inner_hits: {
+ // TODO: replace this temporary fix with a better solution
+ // we have to get all the members of the project,
+ // should we just get a project object instead of creating such a detailed request?
+ // I guess just retrieving project by id and after returning members from it
+ // should work much faster
+ size: 1000,
+ },
+ },
+ },
+ })
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No project members found in ES');
+ // Get all project members
+ const where = {
+ projectId,
+ };
+ if (req.query.role) {
+ where.role = req.query.role;
+ }
+ return models.ProjectMember.findAll({
+ where,
+ // Add order
+ order: [
+ ['id', 'ASC'],
+ ],
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ raw: true,
+ });
+ }
+ req.log.debug('project members found in ES');
+ return data[0].inner_hits.members.hits.hits.map(hit => hit._source); // eslint-disable-line no-underscore-dangle
+ })
+ .then(members => (
+ util.getObjectsWithMemberDetails(members, fields, req)
+ .catch((err) => {
+ req.log.error('Cannot get user details for member.');
+ req.log.debug('Error during getting user details for member.', err);
+ })
+ ))
+ .then(members => res.json(members))
+ .catch(next);
},
];
diff --git a/src/routes/projectMembers/list.spec.js b/src/routes/projectMembers/list.spec.js
new file mode 100644
index 00000000..19228b50
--- /dev/null
+++ b/src/routes/projectMembers/list.spec.js
@@ -0,0 +1,176 @@
+/**
+ * Tests for list.js
+ */
+import _ from 'lodash';
+import chai from 'chai';
+import request from 'supertest';
+
+import models from '../../models';
+import server from '../../app';
+import testUtil from '../../tests/util';
+
+const should = chai.should();
+
+describe('LIST project members', () => {
+ let id;
+
+ const memberUser = {
+ handle: testUtil.getDecodedToken(testUtil.jwts.member).handle,
+ userId: testUtil.getDecodedToken(testUtil.jwts.member).userId,
+ firstName: 'fname',
+ lastName: 'lName',
+ email: 'some@abc.com',
+ };
+ const copilotUser = {
+ handle: testUtil.getDecodedToken(testUtil.jwts.copilot).handle,
+ userId: testUtil.getDecodedToken(testUtil.jwts.copilot).userId,
+ firstName: 'fname',
+ lastName: 'lName',
+ email: 'some@abc.com',
+ };
+
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => {
+ // Create projects
+ models.Project.create({
+ type: 'generic',
+ billingAccountId: 1,
+ name: 'test1',
+ description: 'test project1',
+ status: 'draft',
+ details: {},
+ createdBy: 1,
+ updatedBy: 1,
+ lastActivityAt: 1,
+ lastActivityUserId: '1',
+ })
+ .then((project) => {
+ id = project.id;
+ // create members
+ models.ProjectMember.create({
+ id: 1,
+ userId: copilotUser.userId,
+ projectId: id,
+ role: 'copilot',
+ isPrimary: false,
+ createdBy: 1,
+ updatedBy: 1,
+ }).then(() => {
+ models.ProjectMember.create({
+ id: 2,
+ userId: memberUser.userId,
+ projectId: id,
+ role: 'customer',
+ isPrimary: true,
+ createdBy: 1,
+ updatedBy: 1,
+ }).then(() => done());
+ });
+ });
+ });
+ });
+
+ after((done) => {
+ testUtil.clearDb(done);
+ });
+
+ describe('GET /projects/{id}/members', () => {
+ it('should return 403 for anonymous user', (done) => {
+ request(server)
+ .get(`/v5/projects/${id}/members`)
+ .expect(403, done);
+ });
+
+ it('should return 400 for invalid role', (done) => {
+ request(server)
+ .get(`/v5/projects/${id}/members?role=invalid`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.admin}`,
+ })
+ .expect(400, done);
+ });
+
+ it('should return 200 for connect admin', (done) => {
+ request(server)
+ .get(`/v5/projects/${id}/members`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
+ })
+ .expect(200)
+ .end(done);
+ });
+
+ it('should return 200 for connect manager', (done) => {
+ request(server)
+ .get(`/v5/projects/${id}/members`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.manager}`,
+ })
+ .expect(200)
+ .end(done);
+ });
+
+ it('should return 200 for member', (done) => {
+ request(server)
+ .get(`/v5/projects/${id}/members`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.member}`,
+ })
+ .expect(200, done);
+ });
+
+ it('should return 200 for copilot', (done) => {
+ request(server)
+ .get(`/v5/projects/${id}/members`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.copilot}`,
+ })
+ .expect(200, done);
+ });
+
+ it('should return 200 for admin', (done) => {
+ request(server)
+ .get(`/v5/projects/${id}/members`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.admin}`,
+ })
+ .expect(200)
+ .end((err, res) => {
+ const resJson = res.body;
+ resJson.should.have.length(2);
+ resJson[0].userId.should.be.eql(copilotUser.userId);
+ resJson[0].role.should.be.eql('copilot');
+ resJson[0].projectId.should.be.eql(id);
+ should.exist(resJson[0].createdAt);
+ should.exist(resJson[0].updatedAt);
+ should.not.exist(resJson[0].deletedBy);
+ should.not.exist(resJson[0].deletedAt);
+
+ done();
+ });
+ });
+
+ it('should return 200 for admin with filter', (done) => {
+ request(server)
+ .get(`/v5/projects/${id}/members?role=customer`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.admin}`,
+ })
+ .expect(200)
+ .end((err, res) => {
+ const resJson = res.body;
+ resJson.should.have.length(1);
+ resJson[0].userId.should.be.eql(_.parseInt(memberUser.userId));
+ resJson[0].role.should.be.eql('customer');
+ resJson[0].projectId.should.be.eql(id);
+ should.exist(resJson[0].createdAt);
+ should.exist(resJson[0].updatedAt);
+ should.not.exist(resJson[0].deletedBy);
+ should.not.exist(resJson[0].deletedAt);
+
+ done();
+ });
+ });
+ });
+});
diff --git a/src/routes/projectMembers/update.js b/src/routes/projectMembers/update.js
index 914dc233..97dbc7ed 100644
--- a/src/routes/projectMembers/update.js
+++ b/src/routes/projectMembers/update.js
@@ -5,7 +5,7 @@ import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import models from '../../models';
import util from '../../util';
-import { EVENT, PROJECT_MEMBER_ROLE, PROJECT_MEMBER_MANAGER_ROLES, MANAGER_ROLES } from '../../constants';
+import { EVENT, RESOURCES, PROJECT_MEMBER_ROLE, PROJECT_MEMBER_MANAGER_ROLES, MANAGER_ROLES } from '../../constants';
/**
* API to update a project member.
@@ -13,22 +13,20 @@ import { EVENT, PROJECT_MEMBER_ROLE, PROJECT_MEMBER_MANAGER_ROLES, MANAGER_ROLES
const permissions = tcMiddleware.permissions;
const updateProjectMemberValdiations = {
- body: {
- param: Joi.object().keys({
- isPrimary: Joi.boolean(),
- role: Joi.any().valid(
- PROJECT_MEMBER_ROLE.CUSTOMER,
- PROJECT_MEMBER_ROLE.MANAGER,
- PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER,
- PROJECT_MEMBER_ROLE.COPILOT,
- PROJECT_MEMBER_ROLE.OBSERVER,
- PROJECT_MEMBER_ROLE.PROGRAM_MANAGER,
- PROJECT_MEMBER_ROLE.ACCOUNT_EXECUTIVE,
- PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT,
- PROJECT_MEMBER_ROLE.PROJECT_MANAGER,
- ).required(),
- }),
- },
+ body: Joi.object().keys({
+ isPrimary: Joi.boolean(),
+ role: Joi.any().valid(
+ PROJECT_MEMBER_ROLE.CUSTOMER,
+ PROJECT_MEMBER_ROLE.MANAGER,
+ PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER,
+ PROJECT_MEMBER_ROLE.COPILOT,
+ PROJECT_MEMBER_ROLE.OBSERVER,
+ PROJECT_MEMBER_ROLE.PROGRAM_MANAGER,
+ PROJECT_MEMBER_ROLE.ACCOUNT_EXECUTIVE,
+ PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT,
+ PROJECT_MEMBER_ROLE.PROJECT_MANAGER,
+ ).required(),
+ }),
query: {
fields: Joi.string().optional(),
},
@@ -43,7 +41,7 @@ module.exports = [
*/
(req, res, next) => {
let projectMember;
- let updatedProps = req.body.param;
+ let updatedProps = req.body;
const projectId = _.parseInt(req.params.projectId);
const memberRecordId = _.parseInt(req.params.id);
updatedProps = _.pick(updatedProps, ['isPrimary', 'role']);
@@ -105,34 +103,6 @@ module.exports = [
return Promise.all(operations);
});
})
- // .then(() => {
- // // TODO move this to an event
- // // if copilot role is added or removed should invoke related direct project service
- // if(previousValue.role !== newValue.role && (previousValue.role === PROJECT_MEMBER_ROLE.COPILOT
- // || newValue.role === PROJECT_MEMBER_ROLE.COPILOT)) {
- // return models.Project.getDirectProjectId(projectId)
- // .then(directProjectId => {
- // if(directProjectId) {
- // if(previousValue.role === PROJECT_MEMBER_ROLE.COPILOT) {
- // // new role not copilot so remove direct project copilot
- // return directProject.deleteCopilot(req, directProjectId, {
- // copilotUserId: projectMember.userId
- // })
- // } else {
- // // new role is copilot so add direct project copilot
- // return directProject.addCopilot(req, directProjectId, {
- // copilotUserId: projectMember.userId
- // })
- // }
- // } else {
- // return Promise.resolve()
- // }
- // })
- //
- // } else {
- // return Promise.resolve()
- // }
- // })
.then(() => projectMember.reload(projectMember.id))
.then(() => {
projectMember = projectMember.get({ plain: true });
@@ -153,10 +123,14 @@ module.exports = [
{ original: previousValue, updated: projectMember },
{ correlationId: req.id },
);
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_MEMBER_UPDATED,
- { req, original: previousValue, updated: projectMember });
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_MEMBER_UPDATED,
+ RESOURCES.PROJECT_MEMBER,
+ projectMember,
+ previousValue);
req.log.debug('updated project member', projectMember);
- res.json(util.wrapResponse(req.id, memberWithDetails || projectMember));
+ res.json(memberWithDetails || projectMember);
})
.catch(err => next(err)));
},
diff --git a/src/routes/projectMembers/update.spec.js b/src/routes/projectMembers/update.spec.js
index 5b39f2dc..28c3d666 100644
--- a/src/routes/projectMembers/update.spec.js
+++ b/src/routes/projectMembers/update.spec.js
@@ -8,7 +8,7 @@ import server from '../../app';
import util from '../../util';
import testUtil from '../../tests/util';
import busApi from '../../services/busApi';
-import { BUS_API_EVENT } from '../../constants';
+import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants';
const should = chai.should();
@@ -88,10 +88,8 @@ describe('Project members update', () => {
describe('PUT /projects/{id}/members/{id}', () => {
const body = {
- param: {
- role: 'manager',
- isPrimary: false,
- },
+ role: 'manager',
+ isPrimary: false,
};
let sandbox;
@@ -104,7 +102,7 @@ describe('Project members update', () => {
it('should return 403 if user does not have permissions', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}/members/${member2.id}`)
+ .patch(`/v5/projects/${project1.id}/members/${member2.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -112,49 +110,43 @@ describe('Project members update', () => {
.expect(403, done);
});
- it('should return 422 if no role', (done) => {
+ it('should return 400 if no role', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}/members/${member2.id}`)
+ .patch(`/v5/projects/${project1.id}/members/${member2.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({
- param: {},
- })
- .expect(422, done);
+ .send({})
+ .expect(400, done);
});
- it('should return 422 if role is invalid', (done) => {
+ it('should return 400 if role is invalid', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}/members/${member2.id}`)
+ .patch(`/v5/projects/${project1.id}/members/${member2.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
.send({
- param: {
- role: 'wrong',
- },
+ role: 'wrong',
})
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if isPrimary is invalid', (done) => {
+ it('should return 400 if isPrimary is invalid', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}/members/${member2.id}`)
+ .patch(`/v5/projects/${project1.id}/members/${member2.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
.send({
- param: {
- isPrimary: 'wrong',
- },
+ isPrimary: 'wrong',
})
- .expect(422, done);
+ .expect(400, done);
});
it('should return 404 if not exist id', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}/members/999999`)
+ .patch(`/v5/projects/${project1.id}/members/999999`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -165,10 +157,7 @@ describe('Project members update', () => {
if (err) {
done(err);
} else {
- const result = res.body.result;
- result.success.should.be.false;
- result.status.should.equal(404);
- result.content.message.should.equal('project member not found for project id' +
+ res.body.message.should.equal('project member not found for project id' +
` ${project1.id} and member id 999999`);
done();
}
@@ -192,14 +181,12 @@ describe('Project members update', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .patch(`/v4/projects/${project1.id}/members/${member2.id}`)
+ .patch(`/v5/projects/${project1.id}/members/${member2.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- role: 'customer',
- },
+ role: 'customer',
})
.expect('Content-Type', /json/)
.expect(200)
@@ -207,7 +194,7 @@ describe('Project members update', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.role.should.equal('customer');
resJson.isPrimary.should.be.true;
@@ -243,7 +230,7 @@ describe('Project members update', () => {
})
.then(() => {
request(server)
- .patch(`/v4/projects/${project1.id}/members/${member2.id}`)
+ .patch(`/v5/projects/${project1.id}/members/${member2.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -254,9 +241,9 @@ describe('Project members update', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
- resJson.role.should.equal(body.param.role);
+ resJson.role.should.equal(body.role);
resJson.isPrimary.should.be.false;
resJson.updatedBy.should.equal(40051332);
server.services.pubsub.publish.calledWith('project.member.updated').should.be.true;
@@ -284,7 +271,7 @@ describe('Project members update', () => {
const deleteSpy = sinon.spy(mockHttpClient, 'delete');
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .patch(`/v4/projects/${project1.id}/members/${member2.id}`)
+ .patch(`/v5/projects/${project1.id}/members/${member2.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -295,9 +282,9 @@ describe('Project members update', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
- resJson.role.should.equal(body.param.role);
+ resJson.role.should.equal(body.role);
resJson.isPrimary.should.be.false;
resJson.updatedBy.should.equal(40051332);
deleteSpy.should.have.been.calledOnce;
@@ -314,7 +301,7 @@ describe('Project members update', () => {
const deleteSpy = sinon.spy(mockHttpClient, 'delete');
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .patch(`/v4/projects/${project1.id}/members/${member2.id}`)
+ .patch(`/v5/projects/${project1.id}/members/${member2.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -353,15 +340,13 @@ describe('Project members update', () => {
const postSpy = sinon.spy(mockHttpClient, 'post');
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .patch(`/v4/projects/${project1.id}/members/${member3.id}`)
+ .patch(`/v5/projects/${project1.id}/members/${member3.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.send({
- param: {
- role: 'manager',
- isPrimary: false,
- },
+ role: 'manager',
+ isPrimary: false,
})
.expect('Content-Type', /json/)
.expect(200)
@@ -369,7 +354,7 @@ describe('Project members update', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.role.should.equal('manager');
resJson.isPrimary.should.be.false;
@@ -407,15 +392,13 @@ describe('Project members update', () => {
const postSpy = sinon.spy(mockHttpClient, 'post');
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .patch(`/v4/projects/${project1.id}/members/${member3.id}`)
+ .patch(`/v5/projects/${project1.id}/members/${member3.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.send({
- param: {
- role: 'manager',
- isPrimary: false,
- },
+ role: 'manager',
+ isPrimary: false,
})
.expect('Content-Type', /json/)
.expect(200)
@@ -423,7 +406,7 @@ describe('Project members update', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.role.should.equal('manager');
resJson.isPrimary.should.be.false;
@@ -456,15 +439,13 @@ describe('Project members update', () => {
const postSpy = sinon.spy(mockHttpClient, 'post');
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .patch(`/v4/projects/${project1.id}/members/${member1.id}`)
+ .patch(`/v5/projects/${project1.id}/members/${member1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- role: 'copilot',
- isPrimary: true,
- },
+ role: 'copilot',
+ isPrimary: true,
})
.expect('Content-Type', /json/)
.expect(200)
@@ -472,7 +453,7 @@ describe('Project members update', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.role.should.equal('copilot');
resJson.isPrimary.should.be.true;
@@ -496,7 +477,7 @@ describe('Project members update', () => {
createEventSpy = sandbox.spy(busApi, 'createEvent');
});
- it('sends single BUS_API_EVENT.PROJECT_TEAM_UPDATED message when user role updated', (done) => {
+ it('should send correct BUS API messages when user role updated', (done) => {
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
get: () => Promise.resolve({
status: 200,
@@ -513,14 +494,12 @@ describe('Project members update', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .patch(`/v4/projects/${project1.id}/members/${member2.id}`)
+ .patch(`/v5/projects/${project1.id}/members/${member2.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- role: 'customer',
- },
+ role: 'customer',
})
.expect('Content-Type', /json/)
.expect(200)
@@ -529,14 +508,24 @@ describe('Project members update', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.calledWith(BUS_API_EVENT.PROJECT_TEAM_UPDATED, sinon.match({
+ createEventSpy.callCount.should.equal(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_UPDATED, sinon.match({
+ resource: RESOURCES.PROJECT_MEMBER,
+ id: member2.id,
+ role: 'customer',
+ userId: 40051332,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED, sinon.match({
projectId: project1.id,
projectName: project1.name,
projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`,
userId: 40051332,
initiatorUserId: 40051332,
})).should.be.true;
+
done();
});
}
diff --git a/src/routes/projectReports/getReport.js b/src/routes/projectReports/getReport.js
index b2179c23..fe842fd6 100644
--- a/src/routes/projectReports/getReport.js
+++ b/src/routes/projectReports/getReport.js
@@ -21,7 +21,7 @@ module.exports = [
req.log.info('using mock');
// using mock
return mock(projectId, reportName, req, res);
- // res.status(200).json(util.wrapResponse(req.id, project));
+ // res.status(200).json(project);
}
const lookApi = new LookApi(req.log);
@@ -47,7 +47,7 @@ module.exports = [
}
req.log.debug(result);
- return res.status(200).json(util.wrapResponse(req.id, result));
+ return res.status(200).json(result);
} catch (err) {
req.log.error(err);
return res.status(500).send(err.toString());
diff --git a/src/routes/projectReports/mock.js b/src/routes/projectReports/mock.js
index 33e6787d..9d52e5dc 100644
--- a/src/routes/projectReports/mock.js
+++ b/src/routes/projectReports/mock.js
@@ -1,5 +1,4 @@
import _ from 'lodash';
-import util from '../../util';
const summaryJson = require('./mockFiles/summary.json');
let projectBudgetJson = require('./mockFiles/projectBudget.json');
@@ -11,12 +10,12 @@ module.exports = (projectId, reportName, req, res) => {
switch (reportName) {
case 'summary':
- res.status(200).json(util.wrapResponse(req.id, summaryJson));
+ res.status(200).json(summaryJson);
break;
case 'projectBudget': {
const augmentProjectId = pb => _.assign(pb, { 'project_stream.tc_connect_project_id': projectId });
projectBudgetJson = _.map(projectBudgetJson, augmentProjectId);
- res.status(200).json(util.wrapResponse(req.id, projectBudgetJson));
+ res.status(200).json(projectBudgetJson);
break;
}
default:
diff --git a/src/routes/projectSettings/create.js b/src/routes/projectSettings/create.js
index f97fc115..6623a723 100644
--- a/src/routes/projectSettings/create.js
+++ b/src/routes/projectSettings/create.js
@@ -15,17 +15,15 @@ const schema = {
params: {
projectId: Joi.number().integer().positive().required(),
},
- body: {
- param: Joi.object().keys({
- key: Joi.string().max(255).required(),
- value: Joi.string().max(255).required(),
- valueType: Joi.string().valid(_.values(VALUE_TYPE)).required(),
- projectId: Joi.any().strip(),
- metadata: Joi.object().optional(),
- readPermission: Joi.object().required(),
- writePermission: Joi.object().required(),
- }).required(),
- },
+ body: Joi.object().keys({
+ key: Joi.string().max(255).required(),
+ value: Joi.string().max(255).required(),
+ valueType: Joi.string().valid(_.values(VALUE_TYPE)).required(),
+ projectId: Joi.any().strip(),
+ metadata: Joi.object().optional(),
+ readPermission: Joi.object().required(),
+ writePermission: Joi.object().required(),
+ }).required(),
};
module.exports = [
@@ -34,7 +32,7 @@ module.exports = [
(req, res, next) => {
let setting = null;
const projectId = req.params.projectId;
- const entity = _.assign(req.body.param, {
+ const entity = _.assign(req.body, {
createdBy: req.authUser.userId,
updatedBy: req.authUser.userId,
projectId,
@@ -55,7 +53,7 @@ module.exports = [
includeAllProjectSettingsForInternalUsage: true,
where: {
projectId,
- key: req.body.param.key,
+ key: req.body.key,
},
paranoid: false,
});
@@ -63,8 +61,8 @@ module.exports = [
.then((projectSetting) => {
if (projectSetting) {
const apiErr = new Error(`Project Setting already exists for project id ${projectId} ` +
- `and key ${req.body.param.key}`);
- apiErr.status = 422;
+ `and key ${req.body.key}`);
+ apiErr.status = 400;
return Promise.reject(apiErr);
}
@@ -86,8 +84,7 @@ module.exports = [
req.log.debug('new project setting created (id# %d, key: %s)',
setting.id, setting.key);
// Omit deletedAt, deletedBy
- res.status(201).json(util.wrapResponse(
- req.id, _.omit(setting.toJSON(), 'deletedAt', 'deletedBy'), 1, 201));
+ res.status(201).json(_.omit(setting.toJSON(), 'deletedAt', 'deletedBy'));
})
.catch(next);
},
diff --git a/src/routes/projectSettings/create.spec.js b/src/routes/projectSettings/create.spec.js
index 1e9a70de..152ee7fa 100644
--- a/src/routes/projectSettings/create.spec.js
+++ b/src/routes/projectSettings/create.spec.js
@@ -74,18 +74,16 @@ describe('CREATE Project Setting', () => {
let estimationId;
const body = {
- param: {
- key: 'markup_topcoder_service',
- value: '3500',
- valueType: 'double',
- readPermission: {
- projectRoles: ['customer'],
- topcoderRoles: ['administrator'],
- },
- writePermission: {
- allowRule: { topcoderRoles: ['administrator'] },
- denyRule: { projectRoles: ['copilot'] },
- },
+ key: 'markup_topcoder_service',
+ value: '3500',
+ valueType: 'double',
+ readPermission: {
+ projectRoles: ['customer'],
+ topcoderRoles: ['administrator'],
+ },
+ writePermission: {
+ allowRule: { topcoderRoles: ['administrator'] },
+ denyRule: { projectRoles: ['copilot'] },
},
};
@@ -131,19 +129,21 @@ describe('CREATE Project Setting', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('POST /projects/{projectId}/settings', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/settings`)
+ .post(`/v5/projects/${projectId}/settings`)
.send(body)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/settings`)
+ .post(`/v5/projects/${projectId}/settings`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -153,7 +153,7 @@ describe('CREATE Project Setting', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/settings`)
+ .post(`/v5/projects/${projectId}/settings`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -163,7 +163,7 @@ describe('CREATE Project Setting', () => {
it('should return 404 for non-existed project', (done) => {
request(server)
- .post('/v4/projects/9999/settings')
+ .post('/v5/projects/9999/settings')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -171,102 +171,102 @@ describe('CREATE Project Setting', () => {
.expect(404, done);
});
- it('should return 422 for missing key', (done) => {
+ it('should return 400 for missing key', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.key;
+ delete invalidBody.key;
request(server)
- .post(`/v4/projects/${projectId}/settings`)
+ .post(`/v5/projects/${projectId}/settings`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for missing value', (done) => {
+ it('should return 400 for missing value', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.value;
+ delete invalidBody.value;
request(server)
- .post(`/v4/projects/${projectId}/settings`)
+ .post(`/v5/projects/${projectId}/settings`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for missing valueType', (done) => {
+ it('should return 400 for missing valueType', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.valueType;
+ delete invalidBody.valueType;
request(server)
- .post(`/v4/projects/${projectId}/settings`)
+ .post(`/v5/projects/${projectId}/settings`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- xit('should return 422 for negative value when valueType = percentage', (done) => {
+ xit('should return 400 for negative value when valueType = percentage', (done) => {
const invalidBody = _.cloneDeep(body);
- invalidBody.param.value = '-10';
- invalidBody.param.valueType = VALUE_TYPE.PERCENTAGE;
+ invalidBody.value = '-10';
+ invalidBody.valueType = VALUE_TYPE.PERCENTAGE;
request(server)
- .post(`/v4/projects/${projectId}/settings`)
+ .post(`/v5/projects/${projectId}/settings`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- xit('should return 422 for value greater than 100 when valueType = percentage', (done) => {
+ xit('should return 400 for value greater than 100 when valueType = percentage', (done) => {
const invalidBody = _.cloneDeep(body);
- invalidBody.param.value = '150';
- invalidBody.param.valueType = VALUE_TYPE.PERCENTAGE;
+ invalidBody.value = '150';
+ invalidBody.valueType = VALUE_TYPE.PERCENTAGE;
request(server)
- .post(`/v4/projects/${projectId}/settings`)
+ .post(`/v5/projects/${projectId}/settings`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422, for admin, when create key with existing key', (done) => {
+ it('should return 400, for admin, when create key with existing key', (done) => {
const existing = _.cloneDeep(body);
- existing.param.projectId = projectId;
- existing.param.createdBy = 1;
- existing.param.updatedBy = 1;
+ existing.projectId = projectId;
+ existing.createdBy = 1;
+ existing.updatedBy = 1;
- models.ProjectSetting.create(existing.param).then(() => {
+ models.ProjectSetting.create(existing).then(() => {
request(server)
- .post(`/v4/projects/${projectId}/settings`)
+ .post(`/v5/projects/${projectId}/settings`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(body)
- .expect(422, done);
+ .expect(400, done);
}).catch(done);
});
it('should return 201 for manager with non-estimation type, not calculating project estimation items',
(done) => {
const createBody = _.cloneDeep(body);
- createBody.param.key = 'markup_no_estimation';
+ createBody.key = 'markup_no_estimation';
request(server)
- .post(`/v4/projects/${projectId}/settings`)
+ .post(`/v5/projects/${projectId}/settings`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -276,10 +276,10 @@ describe('CREATE Project Setting', () => {
.end((err, res) => {
if (err) done(err);
- const resJson = res.body.result.content;
- resJson.key.should.be.eql(createBody.param.key);
- resJson.value.should.be.eql(createBody.param.value);
- resJson.valueType.should.be.eql(createBody.param.valueType);
+ const resJson = res.body;
+ resJson.key.should.be.eql(createBody.key);
+ resJson.value.should.be.eql(createBody.value);
+ resJson.valueType.should.be.eql(createBody.valueType);
resJson.projectId.should.be.eql(projectId);
resJson.createdBy.should.be.eql(40051334);
should.exist(resJson.createdAt);
@@ -293,7 +293,7 @@ describe('CREATE Project Setting', () => {
it('should return 201 for manager, calculating project estimation items', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/settings`)
+ .post(`/v5/projects/${projectId}/settings`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -303,10 +303,10 @@ describe('CREATE Project Setting', () => {
.end((err, res) => {
if (err) done(err);
- const resJson = res.body.result.content;
- resJson.key.should.be.eql(body.param.key);
- resJson.value.should.be.eql(body.param.value);
- resJson.valueType.should.be.eql(body.param.valueType);
+ const resJson = res.body;
+ resJson.key.should.be.eql(body.key);
+ resJson.value.should.be.eql(body.value);
+ resJson.valueType.should.be.eql(body.valueType);
resJson.projectId.should.be.eql(projectId);
resJson.createdBy.should.be.eql(40051334);
should.exist(resJson.createdAt);
@@ -316,16 +316,16 @@ describe('CREATE Project Setting', () => {
should.not.exist(resJson.deletedAt);
expectAfterCreate(resJson.id, projectId, _.assign(estimation, {
id: estimationId,
- value: body.param.value,
- valueType: body.param.valueType,
- key: body.param.key,
+ value: body.value,
+ valueType: body.valueType,
+ key: body.key,
}), 1, 0, err, done);
});
});
it('should return 201 for admin', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/settings`)
+ .post(`/v5/projects/${projectId}/settings`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -335,10 +335,10 @@ describe('CREATE Project Setting', () => {
.end((err, res) => {
if (err) done(err);
- const resJson = res.body.result.content;
- resJson.key.should.be.eql(body.param.key);
- resJson.value.should.be.eql(body.param.value);
- resJson.valueType.should.be.eql(body.param.valueType);
+ const resJson = res.body;
+ resJson.key.should.be.eql(body.key);
+ resJson.value.should.be.eql(body.value);
+ resJson.valueType.should.be.eql(body.valueType);
resJson.projectId.should.be.eql(projectId);
resJson.createdBy.should.be.eql(40051333);
should.exist(resJson.createdAt);
@@ -352,7 +352,7 @@ describe('CREATE Project Setting', () => {
it('should return 201 for connect admin', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/settings`)
+ .post(`/v5/projects/${projectId}/settings`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -362,10 +362,10 @@ describe('CREATE Project Setting', () => {
.end((err, res) => {
if (err) done(err);
- const resJson = res.body.result.content;
- resJson.key.should.be.eql(body.param.key);
- resJson.value.should.be.eql(body.param.value);
- resJson.valueType.should.be.eql(body.param.valueType);
+ const resJson = res.body;
+ resJson.key.should.be.eql(body.key);
+ resJson.value.should.be.eql(body.value);
+ resJson.valueType.should.be.eql(body.valueType);
resJson.projectId.should.be.eql(projectId);
resJson.createdBy.should.be.eql(40051336);
resJson.updatedBy.should.be.eql(40051336);
diff --git a/src/routes/projectSettings/delete.spec.js b/src/routes/projectSettings/delete.spec.js
index 368f883d..78d1e514 100644
--- a/src/routes/projectSettings/delete.spec.js
+++ b/src/routes/projectSettings/delete.spec.js
@@ -144,18 +144,20 @@ describe('DELETE Project Setting', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('DELETE /projects/{projectId}/settings/{id}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/settings/${id}`)
+ .delete(`/v5/projects/${projectId}/settings/${id}`)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/settings/${id}`)
+ .delete(`/v5/projects/${projectId}/settings/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -164,7 +166,7 @@ describe('DELETE Project Setting', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/settings/${id}`)
+ .delete(`/v5/projects/${projectId}/settings/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -173,7 +175,7 @@ describe('DELETE Project Setting', () => {
it('should return 404 for non-existed project', (done) => {
request(server)
- .delete(`/v4/projects/9999/settings/${id}`)
+ .delete(`/v5/projects/9999/settings/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -182,7 +184,7 @@ describe('DELETE Project Setting', () => {
it('should return 404 for non-existed project setting', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/settings/1234`)
+ .delete(`/v5/projects/${projectId}/settings/1234`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -193,7 +195,7 @@ describe('DELETE Project Setting', () => {
models.ProjectSetting.destroy({ where: { id } })
.then(() => {
request(server)
- .delete(`/v4/projects/${projectId}/settings/${id}`)
+ .delete(`/v5/projects/${projectId}/settings/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -213,7 +215,7 @@ describe('DELETE Project Setting', () => {
})
.then(() => {
request(server)
- .delete(`/v4/projects/${projectId}/settings/${id}`)
+ .delete(`/v5/projects/${projectId}/settings/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -225,7 +227,7 @@ describe('DELETE Project Setting', () => {
it('should return 204, for admin, if project setting with non-estimation type was successfully removed',
(done) => {
request(server)
- .delete(`/v4/projects/${projectId}/settings/${id2}`)
+ .delete(`/v5/projects/${projectId}/settings/${id2}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -266,7 +268,7 @@ describe('DELETE Project Setting', () => {
updatedBy: 1,
}).then(() => {
request(server)
- .delete(`/v4/projects/${projectId}/settings/${id}`)
+ .delete(`/v5/projects/${projectId}/settings/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
diff --git a/src/routes/projectSettings/list.js b/src/routes/projectSettings/list.js
index 1668178f..480cd26c 100644
--- a/src/routes/projectSettings/list.js
+++ b/src/routes/projectSettings/list.js
@@ -6,7 +6,6 @@ import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import models from '../../models';
-import util from '../../util';
const permissions = tcMiddleware.permissions;
@@ -44,7 +43,7 @@ module.exports = [
return models.ProjectSetting.findAll(options);
})
.then((result) => {
- res.json(util.wrapResponse(req.id, _.filter(result, r => r)));
+ res.json(_.filter(result, r => r));
})
.catch(next);
},
diff --git a/src/routes/projectSettings/list.spec.js b/src/routes/projectSettings/list.spec.js
index 0ea1a14f..f5e0b597 100644
--- a/src/routes/projectSettings/list.spec.js
+++ b/src/routes/projectSettings/list.spec.js
@@ -113,13 +113,13 @@ describe('LIST Project Settings', () => {
describe('GET /projects/{projectId}/settings', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/settings`)
+ .get(`/v5/projects/${projectId}/settings`)
.expect(403, done);
});
it('should return 403 when user have no permission (non team member)', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/settings`)
+ .get(`/v5/projects/${projectId}/settings`)
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -130,7 +130,7 @@ describe('LIST Project Settings', () => {
models.Project.destroy({ where: { id: projectId } })
.then(() => {
request(server)
- .get(`/v4/projects/${projectId}/settings`)
+ .get(`/v5/projects/${projectId}/settings`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -140,7 +140,7 @@ describe('LIST Project Settings', () => {
it('should return 404 for non-existed project', (done) => {
request(server)
- .get('/v4/projects/99999/settings')
+ .get('/v5/projects/99999/settings')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -149,7 +149,7 @@ describe('LIST Project Settings', () => {
it('should return 0 setting when copilot has readPermission for both denyRule and allowRule', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/settings`)
+ .get(`/v5/projects/${projectId}/settings`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -159,7 +159,7 @@ describe('LIST Project Settings', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(0);
done();
@@ -169,7 +169,7 @@ describe('LIST Project Settings', () => {
it('should return 0 setting when connect admin has readPermission for denyRule', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/settings`)
+ .get(`/v5/projects/${projectId}/settings`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -179,7 +179,7 @@ describe('LIST Project Settings', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(0);
done();
@@ -189,7 +189,7 @@ describe('LIST Project Settings', () => {
it('should return 1 setting when user have readPermission (customer)', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/settings`)
+ .get(`/v5/projects/${projectId}/settings`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -198,7 +198,7 @@ describe('LIST Project Settings', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(1);
const setting = settings[0];
@@ -215,7 +215,7 @@ describe('LIST Project Settings', () => {
it('should return 2 settings when user have readPermission (administrator)', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/settings`)
+ .get(`/v5/projects/${projectId}/settings`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -225,7 +225,7 @@ describe('LIST Project Settings', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(2);
const setting = settings[0];
diff --git a/src/routes/projectSettings/update.js b/src/routes/projectSettings/update.js
index df9504c4..9f22b1d9 100644
--- a/src/routes/projectSettings/update.js
+++ b/src/routes/projectSettings/update.js
@@ -16,16 +16,14 @@ const schema = {
projectId: Joi.number().integer().positive().required(),
id: Joi.number().integer().positive().required(),
},
- body: {
- param: Joi.object().keys({
- value: Joi.string().max(255),
- valueType: Joi.string().valid(_.values(VALUE_TYPE)),
- projectId: Joi.any().strip(),
- metadata: Joi.object(),
- readPermission: Joi.object(),
- writePermission: Joi.object(),
- }).required(),
- },
+ body: Joi.object().keys({
+ value: Joi.string().max(255),
+ valueType: Joi.string().valid(_.values(VALUE_TYPE)),
+ projectId: Joi.any().strip(),
+ metadata: Joi.object(),
+ readPermission: Joi.object(),
+ writePermission: Joi.object(),
+ }).required(),
};
module.exports = [
@@ -36,7 +34,7 @@ module.exports = [
let updatedSetting = null;
const projectId = req.params.projectId;
const id = req.params.id;
- const entityToUpdate = _.assign(req.body.param, {
+ const entityToUpdate = _.assign(req.body, {
updatedBy: req.authUser.userId,
});
@@ -69,7 +67,7 @@ module.exports = [
}),
) // transaction end
.then(() => {
- res.json(util.wrapResponse(req.id, updatedSetting));
+ res.json(updatedSetting);
})
.catch(next);
},
diff --git a/src/routes/projectSettings/update.spec.js b/src/routes/projectSettings/update.spec.js
index 0c477e0d..916757f2 100644
--- a/src/routes/projectSettings/update.spec.js
+++ b/src/routes/projectSettings/update.spec.js
@@ -90,29 +90,27 @@ describe('UPDATE Project Setting', () => {
};
const body = {
- param: {
- value: '5599.96',
- valueType: 'double',
- readPermission: {
- projectRoles: ['customer'],
- topcoderRoles: ['administrator'],
+ value: '5599.96',
+ valueType: 'double',
+ readPermission: {
+ projectRoles: ['customer'],
+ topcoderRoles: ['administrator'],
+ },
+ writePermission: {
+ allowRule: {
+ projectRoles: ['customer', 'copilot'],
+ topcoderRoles: ['administrator', 'Connect Admin'],
},
- writePermission: {
- allowRule: {
- projectRoles: ['customer', 'copilot'],
- topcoderRoles: ['administrator', 'Connect Admin'],
- },
- denyRule: {
- projectRoles: ['copilot'],
- topcoderRoles: ['Connect Admin'],
- },
+ denyRule: {
+ projectRoles: ['copilot'],
+ topcoderRoles: ['Connect Admin'],
},
},
};
// we don't include these params into the body, we cannot update them
// but we use them for creating model directly and for checking returned values
- const bodyParamNonMutable = {
+ const bodyNonMutable = {
key: 'markup_topcoder_service',
createdBy: 1,
updatedBy: 1,
@@ -169,7 +167,7 @@ describe('UPDATE Project Setting', () => {
updatedBy: 1,
}])
.then(() => {
- models.ProjectSetting.create(_.assign({}, body.param, bodyParamNonMutable, {
+ models.ProjectSetting.create(_.assign({}, body, bodyNonMutable, {
projectId,
}))
.then((s) => {
@@ -186,19 +184,21 @@ describe('UPDATE Project Setting', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('PATCH /projects/{projectId}/settings/{id}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/settings/${id}`)
+ .patch(`/v5/projects/${projectId}/settings/${id}`)
.send(body)
.expect(403, done);
});
it('should return 403 when user have no permission (non team member)', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/settings/${id}`)
+ .patch(`/v5/projects/${projectId}/settings/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -208,7 +208,7 @@ describe('UPDATE Project Setting', () => {
it('should return 403 when copilot is in both denyRule and allowRule', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/settings/${id}`)
+ .patch(`/v5/projects/${projectId}/settings/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -218,7 +218,7 @@ describe('UPDATE Project Setting', () => {
it('should return 403 when connect admin is in both denyRule and allowRule', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/settings/${id}`)
+ .patch(`/v5/projects/${projectId}/settings/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -228,7 +228,7 @@ describe('UPDATE Project Setting', () => {
it('should return 404 for non-existed project', (done) => {
request(server)
- .patch(`/v4/projects/9999/settings/${id}`)
+ .patch(`/v5/projects/9999/settings/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -238,7 +238,7 @@ describe('UPDATE Project Setting', () => {
it('should return 404 for non-existed project setting', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/settings/1234`)
+ .patch(`/v5/projects/${projectId}/settings/1234`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -250,7 +250,7 @@ describe('UPDATE Project Setting', () => {
models.ProjectSetting.destroy({ where: { id } })
.then(() => {
request(server)
- .patch(`/v4/projects/${projectId}/settings/${id}`)
+ .patch(`/v5/projects/${projectId}/settings/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -259,24 +259,22 @@ describe('UPDATE Project Setting', () => {
});
});
- it('should return 422, when try to update key', (done) => {
+ it('should return 400, when try to update key', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/settings/${id}`)
+ .patch(`/v5/projects/${projectId}/settings/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- key: 'updated_key',
- },
+ key: 'updated_key',
})
- .expect(422, done);
+ .expect(400, done);
});
it('should return 200, for member with permission (team member), value updated but no project estimation present',
(done) => {
const notPresent = _.cloneDeep(body);
- notPresent.param.value = '4500';
+ notPresent.value = '4500';
models.ProjectEstimation.destroy({
where: {
@@ -290,36 +288,34 @@ describe('UPDATE Project Setting', () => {
},
}).then(() => {
request(server)
- .patch(`/v4/projects/${projectId}/settings/${id}`)
+ .patch(`/v5/projects/${projectId}/settings/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
.send({
- param: {
- value: notPresent.param.value,
- },
+ value: notPresent.value,
})
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
if (err) done(err);
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(id);
- resJson.key.should.be.eql(bodyParamNonMutable.key);
- resJson.value.should.be.eql(notPresent.param.value);
- resJson.valueType.should.be.eql(notPresent.param.valueType);
+ resJson.key.should.be.eql(bodyNonMutable.key);
+ resJson.value.should.be.eql(notPresent.value);
+ resJson.valueType.should.be.eql(notPresent.valueType);
resJson.projectId.should.be.eql(projectId);
- resJson.createdBy.should.be.eql(bodyParamNonMutable.createdBy);
+ resJson.createdBy.should.be.eql(bodyNonMutable.createdBy);
resJson.updatedBy.should.be.eql(40051331);
should.exist(resJson.updatedAt);
should.not.exist(resJson.deletedBy);
should.not.exist(resJson.deletedAt);
expectAfterUpdate(id, projectId, _.assign(estimation, {
id: estimationId,
- value: notPresent.param.value,
- valueType: notPresent.param.valueType,
- key: bodyParamNonMutable.key,
+ value: notPresent.value,
+ valueType: notPresent.valueType,
+ key: bodyNonMutable.key,
}), 0, 0, err, done);
});
});
@@ -327,7 +323,7 @@ describe('UPDATE Project Setting', () => {
});
it('should return 200 for admin when value updated, calculating project estimation items', (done) => {
- body.param.value = '4500';
+ body.value = '4500';
models.ProjectEstimationItem.create({
projectEstimationId: estimationId,
@@ -339,76 +335,72 @@ describe('UPDATE Project Setting', () => {
updatedBy: 1,
}).then(() => {
request(server)
- .patch(`/v4/projects/${projectId}/settings/${id}`)
+ .patch(`/v5/projects/${projectId}/settings/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- value: body.param.value,
- },
+ value: body.value,
})
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
if (err) done(err);
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(id);
- resJson.key.should.be.eql(bodyParamNonMutable.key);
- resJson.value.should.be.eql(body.param.value);
- resJson.valueType.should.be.eql(body.param.valueType);
+ resJson.key.should.be.eql(bodyNonMutable.key);
+ resJson.value.should.be.eql(body.value);
+ resJson.valueType.should.be.eql(body.valueType);
resJson.projectId.should.be.eql(projectId);
- resJson.createdBy.should.be.eql(bodyParamNonMutable.createdBy);
+ resJson.createdBy.should.be.eql(bodyNonMutable.createdBy);
resJson.updatedBy.should.be.eql(40051333); // admin
should.exist(resJson.updatedAt);
should.not.exist(resJson.deletedBy);
should.not.exist(resJson.deletedAt);
expectAfterUpdate(id, projectId, _.assign(estimation, {
id: estimationId,
- value: body.param.value,
- valueType: body.param.valueType,
- key: bodyParamNonMutable.key,
+ value: body.value,
+ valueType: body.valueType,
+ key: bodyNonMutable.key,
}), 1, 1, err, done);
});
}).catch(done);
});
it('should return 200, for admin, update valueType from double to percentage', (done) => {
- body.param.value = '10.76';
- body.param.valueType = VALUE_TYPE.PERCENTAGE;
+ body.value = '10.76';
+ body.valueType = VALUE_TYPE.PERCENTAGE;
request(server)
- .patch(`/v4/projects/${projectId}/settings/${id}`)
+ .patch(`/v5/projects/${projectId}/settings/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- value: body.param.value,
- valueType: VALUE_TYPE.PERCENTAGE,
- },
+ value: body.value,
+ valueType: VALUE_TYPE.PERCENTAGE,
})
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
if (err) done(err);
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(id);
- resJson.key.should.be.eql(bodyParamNonMutable.key);
- resJson.value.should.be.eql(body.param.value);
+ resJson.key.should.be.eql(bodyNonMutable.key);
+ resJson.value.should.be.eql(body.value);
resJson.valueType.should.be.eql(VALUE_TYPE.PERCENTAGE);
resJson.projectId.should.be.eql(projectId);
- resJson.createdBy.should.be.eql(bodyParamNonMutable.createdBy);
+ resJson.createdBy.should.be.eql(bodyNonMutable.createdBy);
resJson.updatedBy.should.be.eql(40051333); // admin
should.exist(resJson.updatedAt);
should.not.exist(resJson.deletedBy);
should.not.exist(resJson.deletedAt);
expectAfterUpdate(id, projectId, _.assign(estimation, {
id: estimationId,
- value: body.param.value,
- valueType: body.param.valueType,
- key: bodyParamNonMutable.key,
+ value: body.value,
+ valueType: body.valueType,
+ key: bodyNonMutable.key,
}), 1, 0, err, done);
});
});
diff --git a/src/routes/projectTemplates/create.js b/src/routes/projectTemplates/create.js
index 80cd520e..5cc7ae07 100644
--- a/src/routes/projectTemplates/create.js
+++ b/src/routes/projectTemplates/create.js
@@ -5,6 +5,7 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../constants';
import fieldLookupValidation from '../../middlewares/fieldLookupValidation';
import util from '../../util';
import models from '../../models';
@@ -12,52 +13,50 @@ import models from '../../models';
const permissions = tcMiddleware.permissions;
const schema = {
- body: {
- param: Joi.object().keys({
- id: Joi.any().strip(),
- name: Joi.string().max(255).required(),
- key: Joi.string().max(45).required(),
- category: Joi.string().max(45).required(),
- icon: Joi.string().max(255).required(),
- question: Joi.string().max(255).required(),
- info: Joi.string().max(255).required(),
- aliases: Joi.array().required(),
- scope: Joi.object().empty(null),
- phases: Joi.object().empty(null),
- form: Joi.object().keys({
- key: Joi.string().required(),
- version: Joi.number(),
- }).empty(null),
- planConfig: Joi.object().keys({
- key: Joi.string().required(),
- version: Joi.number(),
- }).empty(null),
- priceConfig: Joi.object().keys({
- key: Joi.string().required(),
- version: Joi.number(),
- }).empty(null),
- disabled: Joi.boolean().optional(),
- hidden: Joi.boolean().optional(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- })
+ body: Joi.object().keys({
+ id: Joi.any().strip(),
+ name: Joi.string().max(255).required(),
+ key: Joi.string().max(45).required(),
+ category: Joi.string().max(45).required(),
+ icon: Joi.string().max(255).required(),
+ question: Joi.string().max(255).required(),
+ info: Joi.string().max(255).required(),
+ aliases: Joi.array().required(),
+ scope: Joi.object().empty(null),
+ phases: Joi.object().empty(null),
+ form: Joi.object().keys({
+ key: Joi.string().required(),
+ version: Joi.number(),
+ }).empty(null),
+ planConfig: Joi.object().keys({
+ key: Joi.string().required(),
+ version: Joi.number(),
+ }).empty(null),
+ priceConfig: Joi.object().keys({
+ key: Joi.string().required(),
+ version: Joi.number(),
+ }).empty(null),
+ disabled: Joi.boolean().optional(),
+ hidden: Joi.boolean().optional(),
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ })
.xor('form', 'scope')
.xor('phases', 'planConfig')
.nand('priceConfig', 'scope')
.required(),
- },
};
module.exports = [
validate(schema),
permissions('projectTemplate.create'),
- fieldLookupValidation(models.ProjectType, 'key', 'body.param.category', 'Category'),
+ fieldLookupValidation(models.ProjectType, 'key', 'body.category', 'Category'),
(req, res, next) => {
- const param = req.body.param;
+ const param = req.body;
const { form, priceConfig, planConfig } = param;
return Promise.all([
@@ -66,16 +65,22 @@ module.exports = [
util.checkModel(planConfig, 'PlanConfig', models.PlanConfig, 'project template'),
])
.then(() => {
- const entity = _.assign(req.body.param, {
+ const entity = _.assign(req.body, {
createdBy: req.authUser.userId,
updatedBy: req.authUser.userId,
});
return models.ProjectTemplate.create(entity)
.then((createdEntity) => {
+ // emit event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE,
+ RESOURCES.PROJECT_TEMPLATE,
+ createdEntity.toJSON(),
+ );
// Omit deletedAt, deletedBy
- res.status(201).json(util.wrapResponse(
- req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201));
+ res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'));
});
}).catch(next);
},
diff --git a/src/routes/projectTemplates/create.spec.js b/src/routes/projectTemplates/create.spec.js
index 2355548a..54b8eca9 100644
--- a/src/routes/projectTemplates/create.spec.js
+++ b/src/routes/projectTemplates/create.spec.js
@@ -60,68 +60,65 @@ describe('CREATE project template', () => {
describe('POST /projects/metadata/projectTemplates', () => {
const body = {
- param: {
- name: 'template 1',
- key: 'key 1',
- category: 'generic',
- icon: 'http://example.com/icon1.ico',
- question: 'question 1',
- info: 'info 1',
- aliases: ['key-1', 'key_1'],
- disabled: true,
- hidden: true,
- scope: {
- scope1: {
- subScope1A: 1,
- subScope1B: 2,
- },
- scope2: [1, 2, 3],
+ name: 'template 1',
+ key: 'key 1',
+ category: 'generic',
+ icon: 'http://example.com/icon1.ico',
+ question: 'question 1',
+ info: 'info 1',
+ aliases: ['key-1', 'key_1'],
+ disabled: true,
+ hidden: true,
+ scope: {
+ scope1: {
+ subScope1A: 1,
+ subScope1B: 2,
},
- phases: {
- phase1: {
- name: 'phase 1',
- details: {
- anyDetails: 'any details 1',
- },
- others: ['others 11', 'others 12'],
+ scope2: [1, 2, 3],
+ },
+ phases: {
+ phase1: {
+ name: 'phase 1',
+ details: {
+ anyDetails: 'any details 1',
},
- phase2: {
- name: 'phase 2',
- details: {
- anyDetails: 'any details 2',
- },
- others: ['others 21', 'others 22'],
+ others: ['others 11', 'others 12'],
+ },
+ phase2: {
+ name: 'phase 2',
+ details: {
+ anyDetails: 'any details 2',
},
+ others: ['others 21', 'others 22'],
},
},
};
const newModelBody = {
- param: {
- name: 'template 1',
- key: 'key 1',
- category: 'generic',
- icon: 'http://example.com/icon1.ico',
- question: 'question 1',
- info: 'info 1',
- aliases: ['key-1', 'key_1'],
- disabled: true,
- hidden: true,
- form: {
- key: 'test',
- version: 1,
- },
- priceConfig: {
- key: 'test',
- },
- planConfig: {
- key: 'test',
- },
+ name: 'template 1',
+ key: 'key 1',
+ category: 'generic',
+ icon: 'http://example.com/icon1.ico',
+ question: 'question 1',
+ info: 'info 1',
+ aliases: ['key-1', 'key_1'],
+ disabled: true,
+ hidden: true,
+ form: {
+ key: 'test',
+ version: 1,
+ },
+ priceConfig: {
+ key: 'test',
},
+ planConfig: {
+ key: 'test',
+ },
+
};
const bodyDefinedFormScope = _.cloneDeep(body);
- bodyDefinedFormScope.param.form = {
+ bodyDefinedFormScope.form = {
scope1: {
subScope1A: 1,
subScope1B: 2,
@@ -129,18 +126,18 @@ describe('CREATE project template', () => {
scope2: [1, 2, 3],
};
const bodyMissingFormScope = _.cloneDeep(body);
- delete bodyMissingFormScope.param.scope;
+ delete bodyMissingFormScope.scope;
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post('/v4/projects/metadata/projectTemplates')
+ .post('/v5/projects/metadata/projectTemplates')
.send(body)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .post('/v4/projects/metadata/projectTemplates')
+ .post('/v5/projects/metadata/projectTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -150,7 +147,7 @@ describe('CREATE project template', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .post('/v4/projects/metadata/projectTemplates')
+ .post('/v5/projects/metadata/projectTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -160,7 +157,7 @@ describe('CREATE project template', () => {
it('should return 403 for connect manager', (done) => {
request(server)
- .post('/v4/projects/metadata/projectTemplates')
+ .post('/v5/projects/metadata/projectTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -168,53 +165,52 @@ describe('CREATE project template', () => {
.expect(403, done);
});
- it('should return 422 if validations dont pass', (done) => {
+ it('should return 400 if validations dont pass', (done) => {
const invalidBody = {
- param: {
- scope: 'a',
- phases: 1,
- },
+
+ scope: 'a',
+ phases: 1,
};
request(server)
- .post('/v4/projects/metadata/projectTemplates')
+ .post('/v5/projects/metadata/projectTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if project type is missing', (done) => {
+ it('should return 400 if project type is missing', (done) => {
const invalidBody = _.cloneDeep(body);
- invalidBody.param.type = null;
+ invalidBody.type = null;
request(server)
- .post('/v4/projects/metadata/projectTemplates')
+ .post('/v5/projects/metadata/projectTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if project type does not exist', (done) => {
+ it('should return 400 if project type does not exist', (done) => {
const invalidBody = _.cloneDeep(body);
- invalidBody.param.type = 'not_exist';
+ invalidBody.type = 'not_exist';
request(server)
- .post('/v4/projects/metadata/projectTemplates')
+ .post('/v5/projects/metadata/projectTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 201 for admin', (done) => {
request(server)
- .post('/v4/projects/metadata/projectTemplates')
+ .post('/v5/projects/metadata/projectTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -222,15 +218,15 @@ describe('CREATE project template', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson.id);
- resJson.name.should.be.eql(body.param.name);
- resJson.key.should.be.eql(body.param.key);
- resJson.category.should.be.eql(body.param.category);
+ resJson.name.should.be.eql(body.name);
+ resJson.key.should.be.eql(body.key);
+ resJson.category.should.be.eql(body.category);
resJson.disabled.should.be.eql(true);
resJson.hidden.should.be.eql(true);
- resJson.scope.should.be.eql(body.param.scope);
- resJson.phases.should.be.eql(body.param.phases);
+ resJson.scope.should.be.eql(body.scope);
+ resJson.phases.should.be.eql(body.phases);
resJson.createdBy.should.be.eql(40051333); // admin
should.exist(resJson.createdAt);
@@ -245,7 +241,7 @@ describe('CREATE project template', () => {
it('should return 201 with new model', (done) => {
request(server)
- .post('/v4/projects/metadata/projectTemplates')
+ .post('/v5/projects/metadata/projectTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -253,18 +249,18 @@ describe('CREATE project template', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson.id);
- resJson.name.should.be.eql(newModelBody.param.name);
- resJson.key.should.be.eql(newModelBody.param.key);
- resJson.category.should.be.eql(newModelBody.param.category);
+ resJson.name.should.be.eql(newModelBody.name);
+ resJson.key.should.be.eql(newModelBody.key);
+ resJson.category.should.be.eql(newModelBody.category);
resJson.disabled.should.be.eql(true);
resJson.hidden.should.be.eql(true);
should.not.exist(resJson.scope);
should.not.exist(resJson.phase);
- resJson.form.should.be.eql(newModelBody.param.form);
- resJson.planConfig.should.be.eql(newModelBody.param.planConfig);
- resJson.priceConfig.should.be.eql(newModelBody.param.priceConfig);
+ resJson.form.should.be.eql(newModelBody.form);
+ resJson.planConfig.should.be.eql(newModelBody.planConfig);
+ resJson.priceConfig.should.be.eql(newModelBody.priceConfig);
resJson.createdBy.should.be.eql(40051333); // admin
should.exist(resJson.createdAt);
@@ -279,7 +275,7 @@ describe('CREATE project template', () => {
it('should return 201 for connect admin', (done) => {
request(server)
- .post('/v4/projects/metadata/projectTemplates')
+ .post('/v5/projects/metadata/projectTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -287,33 +283,33 @@ describe('CREATE project template', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.createdBy.should.be.eql(40051336); // connect admin
resJson.updatedBy.should.be.eql(40051336); // connect admin
done();
});
});
- it('should return 422 if both scope and form are defined', (done) => {
+ it('should return 400 if both scope and form are defined', (done) => {
request(server)
- .post('/v4/projects/metadata/projectTemplates')
+ .post('/v5/projects/metadata/projectTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(bodyDefinedFormScope)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if both scope and form are missing', (done) => {
+ it('should return 400 if both scope and form are missing', (done) => {
request(server)
- .post('/v4/projects/metadata/projectTemplates')
+ .post('/v5/projects/metadata/projectTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(bodyMissingFormScope)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
});
});
diff --git a/src/routes/projectTemplates/delete.js b/src/routes/projectTemplates/delete.js
index b3f353b6..aaf8055d 100644
--- a/src/routes/projectTemplates/delete.js
+++ b/src/routes/projectTemplates/delete.js
@@ -2,8 +2,11 @@
* API to delete a project template
*/
import validate from 'express-validation';
+import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../constants';
+import util from '../../util';
import models from '../../models';
const permissions = tcMiddleware.permissions;
@@ -17,9 +20,9 @@ const schema = {
module.exports = [
validate(schema),
permissions('projectTemplate.delete'),
- (req, res, next) =>
+ (req, res, next) => {
models.sequelize.transaction(() =>
- models.ProjectTemplate.findById(req.params.templateId)
+ models.ProjectTemplate.findByPk(req.params.templateId)
.then((entity) => {
if (!entity) {
const apiErr = new Error(`Project template not found for template id ${req.params.templateId}`);
@@ -30,8 +33,17 @@ module.exports = [
return entity.update({ deletedBy: req.authUser.userId });
})
.then(entity => entity.destroy()))
- .then(() => {
+ .then((entity) => {
+ // emit event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE,
+ RESOURCES.PROJECT_TEMPLATE,
+ _.pick(entity.toJSON(), 'id'),
+ );
+
res.status(204).end();
})
- .catch(next),
+ .catch(next);
+ },
];
diff --git a/src/routes/projectTemplates/delete.spec.js b/src/routes/projectTemplates/delete.spec.js
index 8c3c268f..580f8504 100644
--- a/src/routes/projectTemplates/delete.spec.js
+++ b/src/routes/projectTemplates/delete.spec.js
@@ -25,7 +25,7 @@ const expectAfterDelete = (id, err, next) => {
chai.assert.isNotNull(res.deletedBy);
request(server)
- .get(`/v4/projects/metadata/projectTemplates/${id}`)
+ .get(`/v5/projects/metadata/projectTemplates/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -37,57 +37,60 @@ const expectAfterDelete = (id, err, next) => {
describe('DELETE project template', () => {
let templateId;
- beforeEach(() => testUtil.clearDb()
- .then(() => models.ProjectTemplate.create({
- name: 'template 1',
- key: 'key 1',
- category: 'category 1',
- icon: 'http://example.com/icon1.ico',
- question: 'question 1',
- info: 'info 1',
- aliases: ['key-1', 'key_1'],
- scope: {
- scope1: {
- subScope1A: 1,
- subScope1B: 2,
- },
- scope2: [1, 2, 3],
- },
- phases: {
- phase1: {
- name: 'phase 1',
- details: {
- anyDetails: 'any details 1',
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.ProjectTemplate.create({
+ name: 'template 1',
+ key: 'key 1',
+ category: 'category 1',
+ icon: 'http://example.com/icon1.ico',
+ question: 'question 1',
+ info: 'info 1',
+ aliases: ['key-1', 'key_1'],
+ scope: {
+ scope1: {
+ subScope1A: 1,
+ subScope1B: 2,
},
- others: ['others 11', 'others 12'],
+ scope2: [1, 2, 3],
},
- phase2: {
- name: 'phase 2',
- details: {
- anyDetails: 'any details 2',
+ phases: {
+ phase1: {
+ name: 'phase 1',
+ details: {
+ anyDetails: 'any details 1',
+ },
+ others: ['others 11', 'others 12'],
+ },
+ phase2: {
+ name: 'phase 2',
+ details: {
+ anyDetails: 'any details 2',
+ },
+ others: ['others 21', 'others 22'],
},
- others: ['others 21', 'others 22'],
},
- },
- createdBy: 1,
- updatedBy: 1,
- })).then((template) => {
- templateId = template.id;
- return Promise.resolve();
- }),
- );
- after(testUtil.clearDb);
+ createdBy: 1,
+ updatedBy: 1,
+ }).then((template) => {
+ templateId = template.id;
+ done();
+ }));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('DELETE /projects/metadata/projectTemplates/{templateId}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .delete(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .delete(`/v5/projects/metadata/projectTemplates/${templateId}`)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .delete(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .delete(`/v5/projects/metadata/projectTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -96,7 +99,7 @@ describe('DELETE project template', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .delete(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .delete(`/v5/projects/metadata/projectTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -105,7 +108,7 @@ describe('DELETE project template', () => {
it('should return 403 for connect manager', (done) => {
request(server)
- .delete(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .delete(`/v5/projects/metadata/projectTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -114,7 +117,7 @@ describe('DELETE project template', () => {
it('should return 404 for non-existed template', (done) => {
request(server)
- .delete('/v4/projects/metadata/projectTemplates/1234')
+ .delete('/v5/projects/metadata/projectTemplates/1234')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -125,7 +128,7 @@ describe('DELETE project template', () => {
models.ProjectTemplate.destroy({ where: { id: templateId } })
.then(() => {
request(server)
- .delete(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .delete(`/v5/projects/metadata/projectTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -135,7 +138,7 @@ describe('DELETE project template', () => {
it('should return 204, for admin, if template was successfully removed', (done) => {
request(server)
- .delete(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .delete(`/v5/projects/metadata/projectTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -144,7 +147,7 @@ describe('DELETE project template', () => {
it('should return 204, for connect admin, if template was successfully removed', (done) => {
request(server)
- .delete(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .delete(`/v5/projects/metadata/projectTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
diff --git a/src/routes/projectTemplates/get.js b/src/routes/projectTemplates/get.js
index e81c0939..db464bda 100644
--- a/src/routes/projectTemplates/get.js
+++ b/src/routes/projectTemplates/get.js
@@ -4,8 +4,8 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../util';
import models from '../../models';
+import util from '../../util';
const permissions = tcMiddleware.permissions;
@@ -18,24 +18,46 @@ const schema = {
module.exports = [
validate(schema),
permissions('projectTemplate.view'),
- (req, res, next) => models.ProjectTemplate.findOne({
- where: {
- deletedAt: { $eq: null },
- id: req.params.templateId,
- },
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- raw: true,
- })
- .then((projectTemplate) => {
- // Not found
- if (!projectTemplate) {
- const apiErr = new Error(`Project template not found for project id ${req.params.templateId}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
- }
+ (req, res, next) => {
+ util.fetchByIdFromES('projectTemplates', {
+ query: {
+ nested: {
+ path: 'projectTemplates',
+ query: {
+ match: { 'projectTemplates.id': req.params.templateId },
+ },
+ inner_hits: {},
+ },
+ },
+ }, 'metadata')
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No projectTemplate found in ES');
+ models.ProjectTemplate.findOne({
+ where: {
+ deletedAt: { $eq: null },
+ id: req.params.templateId,
+ },
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ raw: true,
+ })
+ .then((projectTemplate) => {
+ // Not found
+ if (!projectTemplate) {
+ const apiErr = new Error(`Project template not found for project id ${req.params.templateId}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
- res.json(util.wrapResponse(req.id, projectTemplate));
- return Promise.resolve();
+ res.json(projectTemplate);
+ return Promise.resolve();
+ })
+ .catch(next);
+ } else {
+ req.log.debug('projectTemplate found in ES');
+ res.json(data[0].inner_hits.projectTemplates.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle
+ }
})
- .catch(next),
+ .catch(next);
+ },
];
diff --git a/src/routes/projectTemplates/get.spec.js b/src/routes/projectTemplates/get.spec.js
index 61fc4da0..d4641ecb 100644
--- a/src/routes/projectTemplates/get.spec.js
+++ b/src/routes/projectTemplates/get.spec.js
@@ -48,19 +48,21 @@ describe('GET project template', () => {
let templateId;
- beforeEach(() => testUtil.clearDb()
- .then(() => models.ProjectTemplate.create(template))
- .then((createdTemplate) => {
- templateId = createdTemplate.id;
- return Promise.resolve();
- }),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.ProjectTemplate.create(template).then((createdTemplate) => {
+ templateId = createdTemplate.id;
+ done();
+ }));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/projectTemplates/{templateId}', () => {
it('should return 404 for non-existed template', (done) => {
request(server)
- .get('/v4/projects/metadata/projectTemplates/1234')
+ .get('/v5/projects/metadata/projectTemplates/1234')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -71,7 +73,7 @@ describe('GET project template', () => {
models.ProjectTemplate.destroy({ where: { id: templateId } })
.then(() => {
request(server)
- .get(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .get(`/v5/projects/metadata/projectTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -81,13 +83,13 @@ describe('GET project template', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .get(`/v5/projects/metadata/projectTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(templateId);
resJson.name.should.be.eql(template.name);
resJson.key.should.be.eql(template.key);
@@ -107,13 +109,13 @@ describe('GET project template', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .get(`/v5/projects/metadata/projectTemplates/${templateId}`)
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .get(`/v5/projects/metadata/projectTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -123,7 +125,7 @@ describe('GET project template', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .get(`/v5/projects/metadata/projectTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -133,7 +135,7 @@ describe('GET project template', () => {
it('should return 200 for member', (done) => {
request(server)
- .get(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .get(`/v5/projects/metadata/projectTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -142,7 +144,7 @@ describe('GET project template', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .get(`/v5/projects/metadata/projectTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/projectTemplates/list.js b/src/routes/projectTemplates/list.js
index 09a8f7bf..ce239861 100644
--- a/src/routes/projectTemplates/list.js
+++ b/src/routes/projectTemplates/list.js
@@ -2,23 +2,43 @@
* API to list all project templates
*/
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../util';
import models from '../../models';
+import util from '../../util';
const permissions = tcMiddleware.permissions;
module.exports = [
permissions('projectTemplate.view'),
- (req, res, next) => models.ProjectTemplate.findAll({
- where: {
- deletedAt: { $eq: null },
- disabled: false,
- },
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- raw: true,
- })
- .then((projectTemplates) => {
- res.json(util.wrapResponse(req.id, projectTemplates));
- })
- .catch(next),
+ (req, res, next) => {
+ util.fetchFromES('projectTemplates', {
+ query: {
+ nested: {
+ path: 'projectTemplates',
+ query: {
+ match: { 'projectTemplates.disabled': false },
+ },
+ inner_hits: {},
+ },
+ },
+ }, 'metadata')
+ .then((data) => {
+ if (data.projectTemplates.length === 0) {
+ req.log.debug('No projectTemplates found in ES');
+ models.ProjectTemplate.findAll({
+ where: {
+ deletedAt: { $eq: null },
+ disabled: false,
+ },
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ raw: true,
+ }).then((projectTemplates) => {
+ res.json(projectTemplates);
+ })
+ .catch(next);
+ } else {
+ req.log.debug('projectTemplates found in ES');
+ res.json(data.projectTemplates);
+ }
+ });
+ },
];
diff --git a/src/routes/projectTemplates/list.spec.js b/src/routes/projectTemplates/list.spec.js
index 3971aa3b..25eaae95 100644
--- a/src/routes/projectTemplates/list.spec.js
+++ b/src/routes/projectTemplates/list.spec.js
@@ -65,19 +65,22 @@ describe('LIST project templates', () => {
let templateId;
- beforeEach(() => testUtil.clearDb()
- .then(() => models.ProjectTemplate.create(templates[0]))
- .then((createdTemplate) => {
- templateId = createdTemplate.id;
- return models.ProjectTemplate.create(templates[1]);
- }).then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.ProjectTemplate.create(templates[0]))
+ .then((createdTemplate) => {
+ templateId = createdTemplate.id;
+ return models.ProjectTemplate.create(templates[1]).then(() => done());
+ });
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/projectTemplates', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/projects/metadata/projectTemplates')
+ .get('/v5/projects/metadata/projectTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -85,7 +88,7 @@ describe('LIST project templates', () => {
.end((err, res) => {
const template = templates[0];
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
resJson[0].id.should.be.eql(templateId);
resJson[0].name.should.be.eql(template.name);
@@ -106,13 +109,13 @@ describe('LIST project templates', () => {
it('should return 403 for anonymous user', (done) => {
request(server)
- .get('/v4/projects/metadata/projectTemplates')
+ .get('/v5/projects/metadata/projectTemplates')
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/projects/metadata/projectTemplates')
+ .get('/v5/projects/metadata/projectTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -122,7 +125,7 @@ describe('LIST project templates', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/projects/metadata/projectTemplates')
+ .get('/v5/projects/metadata/projectTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -132,7 +135,7 @@ describe('LIST project templates', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/projects/metadata/projectTemplates')
+ .get('/v5/projects/metadata/projectTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -141,7 +144,7 @@ describe('LIST project templates', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/projects/metadata/projectTemplates')
+ .get('/v5/projects/metadata/projectTemplates')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/projectTemplates/update.js b/src/routes/projectTemplates/update.js
index 00384a1c..016a20ff 100644
--- a/src/routes/projectTemplates/update.js
+++ b/src/routes/projectTemplates/update.js
@@ -5,6 +5,7 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../constants';
import fieldLookupValidation from '../../middlewares/fieldLookupValidation';
import util from '../../util';
import models from '../../models';
@@ -15,52 +16,50 @@ const schema = {
params: {
templateId: Joi.number().integer().positive().required(),
},
- body: {
- param: Joi.object().keys({
- id: Joi.any().strip(),
- name: Joi.string().max(255),
- key: Joi.string().max(45),
- category: Joi.string().max(45),
- icon: Joi.string().max(255),
- question: Joi.string().max(255),
- info: Joi.string().max(255),
- aliases: Joi.array(),
- scope: Joi.object().empty(null),
- phases: Joi.object().empty(null),
- form: Joi.object().keys({
- key: Joi.string().required(),
- version: Joi.number(),
- }).empty(null),
- planConfig: Joi.object().keys({
- key: Joi.string().required(),
- version: Joi.number(),
- }).empty(null),
- priceConfig: Joi.object().keys({
- key: Joi.string().required(),
- version: Joi.number(),
- }).empty(null),
- disabled: Joi.boolean().optional(),
- hidden: Joi.boolean().optional(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- })
+ body: Joi.object().keys({
+ id: Joi.any().strip(),
+ name: Joi.string().max(255),
+ key: Joi.string().max(45),
+ category: Joi.string().max(45),
+ icon: Joi.string().max(255),
+ question: Joi.string().max(255),
+ info: Joi.string().max(255),
+ aliases: Joi.array(),
+ scope: Joi.object().empty(null),
+ phases: Joi.object().empty(null),
+ form: Joi.object().keys({
+ key: Joi.string().required(),
+ version: Joi.number(),
+ }).empty(null),
+ planConfig: Joi.object().keys({
+ key: Joi.string().required(),
+ version: Joi.number(),
+ }).empty(null),
+ priceConfig: Joi.object().keys({
+ key: Joi.string().required(),
+ version: Joi.number(),
+ }).empty(null),
+ disabled: Joi.boolean().optional(),
+ hidden: Joi.boolean().optional(),
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ })
.xor('form', 'scope')
.xor('phases', 'planConfig')
.nand('priceConfig', 'scope')
.required(),
- },
};
module.exports = [
validate(schema),
permissions('projectTemplate.edit'),
- fieldLookupValidation(models.ProjectType, 'key', 'body.param.category', 'Category'),
+ fieldLookupValidation(models.ProjectType, 'key', 'body.category', 'Category'),
(req, res, next) => {
- const param = req.body.param;
+ const param = req.body;
const { form, priceConfig, planConfig } = param;
return Promise.all([
@@ -69,7 +68,7 @@ module.exports = [
util.checkModel(planConfig, 'PlanConfig', models.PlanConfig, 'project template'),
])
.then(() => {
- const entityToUpdate = _.assign(req.body.param, {
+ const entityToUpdate = _.assign(req.body, {
updatedBy: req.authUser.userId,
});
@@ -102,7 +101,14 @@ module.exports = [
return projectTemplate.update(entityToUpdate);
})
.then((projectTemplate) => {
- res.json(util.wrapResponse(req.id, projectTemplate));
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE,
+ RESOURCES.PROJECT_TEMPLATE,
+ projectTemplate.get({ plain: true }),
+ );
+
+ res.json(projectTemplate);
});
}).catch(next);
},
diff --git a/src/routes/projectTemplates/update.spec.js b/src/routes/projectTemplates/update.spec.js
index 22854cf4..85757148 100644
--- a/src/routes/projectTemplates/update.spec.js
+++ b/src/routes/projectTemplates/update.spec.js
@@ -52,7 +52,8 @@ describe('UPDATE project template', () => {
let templateId;
- beforeEach(() => testUtil.clearDb()
+ beforeEach((done) => {
+ testUtil.clearDb()
.then(() => models.ProjectType.bulkCreate([
{
key: 'generic',
@@ -77,11 +78,9 @@ describe('UPDATE project template', () => {
updatedBy: 1,
},
]))
- .then(() => models.ProjectTemplate.create(template))
- .then((createdTemplate) => {
+ .then(() => models.ProjectTemplate.create(template).then((createdTemplate) => {
templateId = createdTemplate.id;
- return Promise.resolve();
- })
+ }))
.then(() => models.Form.create({
key: 'test',
config: {
@@ -111,70 +110,69 @@ describe('UPDATE project template', () => {
revision: 1,
createdBy: 1,
updatedBy: 1,
- })),
- );
- after(testUtil.clearDb);
+ }).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('PATCH /projects/metadata/projectTemplates/{templateId}', () => {
const body = {
- param: {
- name: 'template 1 - update',
- key: 'key 1 - update',
- category: 'concrete',
- scope: {
- scope1: {
- subScope1A: 11,
- subScope1C: 'new',
- },
- scope2: [4],
- scope3: 'new',
+
+ name: 'template 1 - update',
+ key: 'key 1 - update',
+ category: 'concrete',
+ scope: {
+ scope1: {
+ subScope1A: 11,
+ subScope1C: 'new',
},
- phases: {
- phase1: {
- name: 'phase 1 - update',
- details: {
- anyDetails: 'any details 1 - update',
- newDetails: 'new',
- },
- others: ['others new'],
+ scope2: [4],
+ scope3: 'new',
+ },
+ phases: {
+ phase1: {
+ name: 'phase 1 - update',
+ details: {
+ anyDetails: 'any details 1 - update',
+ newDetails: 'new',
},
- phase3: {
- name: 'phase 3',
- details: {
- anyDetails: 'any details 3',
- },
- others: ['others 31', 'others 32'],
+ others: ['others new'],
+ },
+ phase3: {
+ name: 'phase 3',
+ details: {
+ anyDetails: 'any details 3',
},
+ others: ['others 31', 'others 32'],
},
},
};
const newModelBody = {
- param: {
- name: 'template 1',
- key: 'key 1',
- category: 'generic',
- icon: 'http://example.com/icon1.ico',
- question: 'question 1',
- info: 'info 1',
- aliases: ['key-1', 'key_1'],
- disabled: true,
- hidden: true,
- form: {
- key: 'test',
- version: 1,
- },
- priceConfig: {
- key: 'test',
- },
- planConfig: {
- key: 'test',
- },
+ name: 'template 1',
+ key: 'key 1',
+ category: 'generic',
+ icon: 'http://example.com/icon1.ico',
+ question: 'question 1',
+ info: 'info 1',
+ aliases: ['key-1', 'key_1'],
+ disabled: true,
+ hidden: true,
+ form: {
+ key: 'test',
+ version: 1,
+ },
+ priceConfig: {
+ key: 'test',
+ },
+ planConfig: {
+ key: 'test',
},
};
const bodyDefinedFormScope = _.cloneDeep(body);
- bodyDefinedFormScope.param.form = {
+ bodyDefinedFormScope.form = {
scope1: {
subScope1A: 1,
subScope1B: 2,
@@ -182,18 +180,18 @@ describe('UPDATE project template', () => {
scope2: [1, 2, 3],
};
const bodyMissingFormScope = _.cloneDeep(body);
- delete bodyMissingFormScope.param.scope;
+ delete bodyMissingFormScope.scope;
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .patch(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/projectTemplates/${templateId}`)
.send(body)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .patch(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/projectTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -203,7 +201,7 @@ describe('UPDATE project template', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .patch(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/projectTemplates/${templateId}`)
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
@@ -213,7 +211,7 @@ describe('UPDATE project template', () => {
it('should return 403 for connect manager', (done) => {
request(server)
- .patch(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/projectTemplates/${templateId}`)
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
@@ -221,26 +219,26 @@ describe('UPDATE project template', () => {
.expect(403, done);
});
- it('should return 422 for invalid request', (done) => {
+ it('should return 400 for invalid request', (done) => {
const invalidBody = {
- param: {
- scope: 'a',
- phases: 1,
- },
+
+ scope: 'a',
+ phases: 1,
+
};
request(server)
- .patch(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/projectTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 404 for non-existed template', (done) => {
request(server)
- .patch('/v4/projects/metadata/projectTemplates/1234')
+ .patch('/v5/projects/metadata/projectTemplates/1234')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -252,7 +250,7 @@ describe('UPDATE project template', () => {
models.ProjectTemplate.destroy({ where: { id: templateId } })
.then(() => {
request(server)
- .patch(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/projectTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -263,18 +261,18 @@ describe('UPDATE project template', () => {
it('should return 200 for admin', (done) => {
request(server)
- .patch(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/projectTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(body)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(templateId);
- resJson.name.should.be.eql(body.param.name);
- resJson.key.should.be.eql(body.param.key);
- resJson.category.should.be.eql(body.param.category);
+ resJson.name.should.be.eql(body.name);
+ resJson.key.should.be.eql(body.key);
+ resJson.category.should.be.eql(body.category);
resJson.scope.should.be.eql({
scope1: {
subScope1A: 11,
@@ -323,21 +321,21 @@ describe('UPDATE project template', () => {
it('should return 200 for new model', (done) => {
request(server)
- .patch(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/projectTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(newModelBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(templateId);
- resJson.name.should.be.eql(newModelBody.param.name);
- resJson.key.should.be.eql(newModelBody.param.key);
- resJson.category.should.be.eql(newModelBody.param.category);
- resJson.form.should.be.eql(newModelBody.param.form);
- resJson.priceConfig.should.be.eql(newModelBody.param.priceConfig);
- resJson.planConfig.should.be.eql(newModelBody.param.planConfig);
+ resJson.name.should.be.eql(newModelBody.name);
+ resJson.key.should.be.eql(newModelBody.key);
+ resJson.category.should.be.eql(newModelBody.category);
+ resJson.form.should.be.eql(newModelBody.form);
+ resJson.priceConfig.should.be.eql(newModelBody.priceConfig);
+ resJson.planConfig.should.be.eql(newModelBody.planConfig);
resJson.disabled.should.be.eql(true);
resJson.hidden.should.be.eql(true);
@@ -354,7 +352,7 @@ describe('UPDATE project template', () => {
it('should return 200 for connect admin', (done) => {
request(server)
- .patch(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/projectTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -363,24 +361,24 @@ describe('UPDATE project template', () => {
.end(done);
});
- it('should return 422 if both scope and form are defined', (done) => {
+ it('should return 400 if both scope and form are defined', (done) => {
request(server)
- .patch(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/projectTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(bodyDefinedFormScope)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if both scope and form are missing', (done) => {
+ it('should return 400 if both scope and form are missing', (done) => {
request(server)
- .patch(`/v4/projects/metadata/projectTemplates/${templateId}`)
+ .patch(`/v5/projects/metadata/projectTemplates/${templateId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(bodyMissingFormScope)
- .expect(422, done);
+ .expect(400, done);
});
});
});
diff --git a/src/routes/projectTemplates/upgrade.js b/src/routes/projectTemplates/upgrade.js
index c6706b80..4c064d26 100644
--- a/src/routes/projectTemplates/upgrade.js
+++ b/src/routes/projectTemplates/upgrade.js
@@ -5,29 +5,27 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../constants';
import util from '../../util';
import models from '../../models';
const permissions = tcMiddleware.permissions;
-
const schema = {
- body: {
- param: Joi.object().keys({
- form: Joi.object().keys({
- version: Joi.number().integer().positive().required(),
- key: Joi.string().required(),
- }).optional(),
- priceConfig: Joi.object().keys({
- version: Joi.number().integer().positive().required(),
- key: Joi.string().required(),
- }).optional(),
- planConfig: Joi.object().keys({
- version: Joi.number().integer().positive().required(),
- key: Joi.string().required(),
- }).optional(),
+ body: Joi.object().keys({
+ form: Joi.object().keys({
+ version: Joi.number().integer().positive().required(),
+ key: Joi.string().required(),
}).optional(),
- },
+ priceConfig: Joi.object().keys({
+ version: Joi.number().integer().positive().required(),
+ key: Joi.string().required(),
+ }).optional(),
+ planConfig: Joi.object().keys({
+ version: Joi.number().integer().positive().required(),
+ key: Joi.string().required(),
+ }).optional(),
+ }).optional(),
};
@@ -42,19 +40,19 @@ module.exports = [
// eslint-disable-next-line consistent-return
}).then(async (pt) => {
if (pt == null) {
- const apiErr = new Error(`project template not found for id ${req.body.param.templateId}`);
+ const apiErr = new Error(`project template not found for id ${req.body.templateId}`);
apiErr.status = 404;
throw apiErr;
}
if ((pt.scope == null) || (pt.phases == null)) {
const apiErr = new Error('Current project template\'s scope or phases is null');
- apiErr.status = 422;
+ apiErr.status = 400;
throw apiErr;
}
// get form field
let newForm = {};
- if (req.body.param.form == null) {
+ if (req.body.form == null) {
const scope = {
sections: pt.scope ? pt.scope.sections : null,
wizard: pt.scope ? pt.scope.wizard : null,
@@ -66,12 +64,12 @@ module.exports = [
key: pt.key,
};
} else {
- newForm = req.body.param.form;
+ newForm = req.body.form;
await util.checkModel(newForm, 'Form', models.Form, 'project template');
}
// get price config field
let newPriceConfig = {};
- if (req.body.param.priceConfig == null) {
+ if (req.body.priceConfig == null) {
const config = {};
if (pt.scope) {
Object.keys(pt.scope).filter(key => (key !== 'wizard') && (key !== 'sections')).forEach((key) => {
@@ -84,19 +82,19 @@ module.exports = [
key: pt.key,
};
} else {
- newPriceConfig = req.body.param.priceConfig;
+ newPriceConfig = req.body.priceConfig;
await util.checkModel(newPriceConfig, 'PriceConfig', models.PriceConfig, 'project template');
}
// get plan config field
let newPlanConfig = {};
- if (req.body.param.planConfig == null) {
+ if (req.body.planConfig == null) {
const planConfig = await models.PlanConfig.createNewVersion(pt.key, pt.phases, req.authUser.userId);
newPlanConfig = {
version: planConfig.version,
key: pt.key,
};
} else {
- newPlanConfig = req.body.param.planConfig;
+ newPlanConfig = req.body.planConfig;
await util.checkModel(newPlanConfig, 'PlanConfig', models.PlanConfig, 'project template');
}
@@ -111,8 +109,14 @@ module.exports = [
const newPt = await pt.update(updateInfo);
- res.status(201).json(util.wrapResponse(
- req.id, _.omit(newPt.toJSON(), 'deletedAt', 'deletedBy'), 1, 201));
+ // emit event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE,
+ RESOURCES.PROJECT_TEMPLATE,
+ updateInfo);
+
+ res.status(201).json(_.omit(newPt.toJSON(), 'deletedAt', 'deletedBy'));
})
.catch(next));
},
diff --git a/src/routes/projectTemplates/upgrade.spec.js b/src/routes/projectTemplates/upgrade.spec.js
index 71a34aca..28341e8b 100644
--- a/src/routes/projectTemplates/upgrade.spec.js
+++ b/src/routes/projectTemplates/upgrade.spec.js
@@ -50,109 +50,108 @@ describe('Upgrade project template', () => {
let templateId;
- beforeEach(() => testUtil.clearDb()
- .then(() => models.ProjectType.bulkCreate([
- {
- key: 'generic',
- displayName: 'Generic',
- icon: 'http://example.com/icon1.ico',
- question: 'question 1',
- info: 'info 1',
- aliases: ['key-1', 'key_1'],
- metadata: {},
- createdBy: 1,
- updatedBy: 1,
- },
- {
- key: 'concrete',
- displayName: 'Concrete',
- icon: 'http://example.com/icon1.ico',
- question: 'question 2',
- info: 'info 2',
- aliases: ['key-2', 'key_2'],
- metadata: {},
- createdBy: 1,
- updatedBy: 1,
- },
- ]))
- .then(() => {
- models.Form.bulkCreate([
- {
- key: 'dev',
- version: 1,
- revision: 1,
- config: ['key-1', 'key_1'],
- createdBy: 1,
- updatedBy: 1,
- },
- ]);
- })
- .then(() => {
- models.PriceConfig.bulkCreate([
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.ProjectType.bulkCreate([
{
- key: 'dev',
- version: 1,
- revision: 1,
- config: ['key-1', 'key_1'],
+ key: 'generic',
+ displayName: 'Generic',
+ icon: 'http://example.com/icon1.ico',
+ question: 'question 1',
+ info: 'info 1',
+ aliases: ['key-1', 'key_1'],
+ metadata: {},
createdBy: 1,
updatedBy: 1,
},
- ]);
- })
- .then(() => {
- models.PlanConfig.bulkCreate([
{
- key: 'dev',
- version: 1,
- revision: 1,
- config: ['key-1', 'key_1'],
+ key: 'concrete',
+ displayName: 'Concrete',
+ icon: 'http://example.com/icon1.ico',
+ question: 'question 2',
+ info: 'info 2',
+ aliases: ['key-2', 'key_2'],
+ metadata: {},
createdBy: 1,
updatedBy: 1,
},
- ]);
- })
- .then(() => models.ProjectTemplate.create(template))
- .then((createdTemplate) => {
- templateId = createdTemplate.id;
- return Promise.resolve();
- }),
- );
- after(testUtil.clearDb);
+ ]))
+ .then(() => {
+ models.Form.bulkCreate([
+ {
+ key: 'dev',
+ version: 1,
+ revision: 1,
+ config: ['key-1', 'key_1'],
+ createdBy: 1,
+ updatedBy: 1,
+ },
+ ]);
+ })
+ .then(() => {
+ models.PriceConfig.bulkCreate([
+ {
+ key: 'dev',
+ version: 1,
+ revision: 1,
+ config: ['key-1', 'key_1'],
+ createdBy: 1,
+ updatedBy: 1,
+ },
+ ]);
+ })
+ .then(() => {
+ models.PlanConfig.bulkCreate([
+ {
+ key: 'dev',
+ version: 1,
+ revision: 1,
+ config: ['key-1', 'key_1'],
+ createdBy: 1,
+ updatedBy: 1,
+ },
+ ]);
+ })
+ .then(() => models.ProjectTemplate.create(template))
+ .then((createdTemplate) => {
+ templateId = createdTemplate.id;
+ done();
+ });
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('POST /projects/metadata/projectTemplates/{templateId}/upgrade', () => {
const body = {
- param: {
- form: {
- key: 'dev',
- version: 1,
- },
- priceConfig: {
- key: 'dev',
- version: 1,
- },
- planConfig: {
- key: 'dev',
- version: 1,
- },
+ form: {
+ key: 'dev',
+ version: 1,
+ },
+ priceConfig: {
+ key: 'dev',
+ version: 1,
+ },
+ planConfig: {
+ key: 'dev',
+ version: 1,
},
};
const emptyBody = {
- param: {
- },
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post(`/v4/projects/metadata/projectTemplates/${templateId}/upgrade`)
+ .post(`/v5/projects/metadata/projectTemplates/${templateId}/upgrade`)
.send(body)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .post(`/v4/projects/metadata/projectTemplates/${templateId}/upgrade`)
+ .post(`/v5/projects/metadata/projectTemplates/${templateId}/upgrade`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -162,7 +161,7 @@ describe('Upgrade project template', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .post(`/v4/projects/metadata/projectTemplates/${templateId}/upgrade`)
+ .post(`/v5/projects/metadata/projectTemplates/${templateId}/upgrade`)
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
@@ -172,7 +171,7 @@ describe('Upgrade project template', () => {
it('should return 403 for connect manager', (done) => {
request(server)
- .post(`/v4/projects/metadata/projectTemplates/${templateId}/upgrade`)
+ .post(`/v5/projects/metadata/projectTemplates/${templateId}/upgrade`)
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
@@ -183,7 +182,7 @@ describe('Upgrade project template', () => {
it('should return 404 for non-existed template', (done) => {
request(server)
- .post('/v4/projects/metadata/projectTemplates/123/upgrade')
+ .post('/v5/projects/metadata/projectTemplates/123/upgrade')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -195,7 +194,7 @@ describe('Upgrade project template', () => {
models.ProjectTemplate.destroy({ where: { id: templateId } })
.then(() => {
request(server)
- .post(`/v4/projects/metadata/projectTemplates/${templateId}/upgrade`)
+ .post(`/v5/projects/metadata/projectTemplates/${templateId}/upgrade`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -206,14 +205,14 @@ describe('Upgrade project template', () => {
it('should return 200 for admin', (done) => {
request(server)
- .post(`/v4/projects/metadata/projectTemplates/${templateId}/upgrade`)
+ .post(`/v5/projects/metadata/projectTemplates/${templateId}/upgrade`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(body)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(templateId);
should.not.exist(resJson.scope);
@@ -244,16 +243,16 @@ describe('Upgrade project template', () => {
});
});
- it('should create new version of model if param not given model key and version', (done) => {
+ it('should create new version of model if body not given model key and version', (done) => {
request(server)
- .post(`/v4/projects/metadata/projectTemplates/${templateId}/upgrade`)
+ .post(`/v5/projects/metadata/projectTemplates/${templateId}/upgrade`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(emptyBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.not.exist(resJson.scope);
should.not.exist(resJson.phases);
diff --git a/src/routes/projectTypes/create.js b/src/routes/projectTypes/create.js
index 15cf3d49..88051a51 100644
--- a/src/routes/projectTypes/create.js
+++ b/src/routes/projectTypes/create.js
@@ -5,57 +5,61 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../constants';
import util from '../../util';
import models from '../../models';
const permissions = tcMiddleware.permissions;
const schema = {
- body: {
- param: Joi.object().keys({
- key: Joi.string().max(45).required(),
- displayName: Joi.string().max(255).required(),
- icon: Joi.string().max(255).required(),
- question: Joi.string().max(255).required(),
- info: Joi.string().max(255).required(),
- aliases: Joi.array().required(),
- disabled: Joi.boolean().optional(),
- hidden: Joi.boolean().optional(),
- metadata: Joi.object().required(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ body: Joi.object().keys({
+ key: Joi.string().max(45).required(),
+ displayName: Joi.string().max(255).required(),
+ icon: Joi.string().max(255).required(),
+ question: Joi.string().max(255).required(),
+ info: Joi.string().max(255).required(),
+ aliases: Joi.array().required(),
+ disabled: Joi.boolean().optional(),
+ hidden: Joi.boolean().optional(),
+ metadata: Joi.object().required(),
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
};
module.exports = [
validate(schema),
permissions('projectType.create'),
(req, res, next) => {
- const entity = _.assign(req.body.param, {
+ const entity = _.assign(req.body, {
createdBy: req.authUser.userId,
updatedBy: req.authUser.userId,
});
// Check if duplicated key
- return models.ProjectType.findById(req.body.param.key, { paranoid: false })
+ return models.ProjectType.findByPk(req.body.key, { paranoid: false })
.then((existing) => {
if (existing) {
- const apiErr = new Error(`Project type already exists (may be deleted) for key "${req.body.param.key}"`);
- apiErr.status = 422;
+ const apiErr = new Error(`Project type already exists (may be deleted) for key ${req.body.key}`);
+ apiErr.status = 400;
return Promise.reject(apiErr);
}
// Create
return models.ProjectType.create(entity);
}).then((createdEntity) => {
+ // emit event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE,
+ RESOURCES.PROJECT_TYPE,
+ createdEntity.toJSON());
// Omit deletedAt, deletedBy
- res.status(201).json(util.wrapResponse(
- req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201));
+ res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'));
})
.catch(next);
},
diff --git a/src/routes/projectTypes/create.spec.js b/src/routes/projectTypes/create.spec.js
index 7823e45b..003c1aef 100644
--- a/src/routes/projectTypes/create.spec.js
+++ b/src/routes/projectTypes/create.spec.js
@@ -12,48 +12,50 @@ import models from '../../models';
const should = chai.should();
describe('CREATE project type', () => {
- beforeEach(() => testUtil.clearDb()
- .then(() => models.ProjectType.create({
- key: 'key1',
- displayName: 'displayName 1',
- icon: 'http://example.com/icon1.ico',
- question: 'question 1',
- info: 'info 1',
- aliases: ['key-1', 'key_1'],
- disabled: false,
- hidden: false,
- metadata: { 'slack-notification-mappings': { color: '#96d957', label: 'Full App' } },
- createdBy: 1,
- updatedBy: 1,
- })).then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.ProjectType.create({
+ key: 'key1',
+ displayName: 'displayName 1',
+ icon: 'http://example.com/icon1.ico',
+ question: 'question 1',
+ info: 'info 1',
+ aliases: ['key-1', 'key_1'],
+ disabled: false,
+ hidden: false,
+ metadata: { 'slack-notification-mappings': { color: '#96d957', label: 'Full App' } },
+ createdBy: 1,
+ updatedBy: 1,
+ }).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('POST /projects/metadata/projectTypes', () => {
const body = {
- param: {
- key: 'app_dev',
- displayName: 'Application Development',
- icon: 'prod-cat-app-icon',
- info: 'Application Development Info',
- question: 'What kind of devlopment you need?',
- aliases: ['key-1', 'key_1'],
- disabled: true,
- hidden: true,
- metadata: { 'slack-notification-mappings': { color: '#96d957', label: 'Full App' } },
- },
+ key: 'app_dev',
+ displayName: 'Application Development',
+ icon: 'prod-cat-app-icon',
+ info: 'Application Development Info',
+ question: 'What kind of devlopment you need?',
+ aliases: ['key-1', 'key_1'],
+ disabled: true,
+ hidden: true,
+ metadata: { 'slack-notification-mappings': { color: '#96d957', label: 'Full App' } },
+
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post('/v4/projects/metadata/projectTypes')
+ .post('/v5/projects/metadata/projectTypes')
.send(body)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .post('/v4/projects/metadata/projectTypes')
+ .post('/v5/projects/metadata/projectTypes')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -63,7 +65,7 @@ describe('CREATE project type', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .post('/v4/projects/metadata/projectTypes')
+ .post('/v5/projects/metadata/projectTypes')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -73,7 +75,7 @@ describe('CREATE project type', () => {
it('should return 403 for manager', (done) => {
request(server)
- .post('/v4/projects/metadata/projectTypes')
+ .post('/v5/projects/metadata/projectTypes')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -81,107 +83,107 @@ describe('CREATE project type', () => {
.expect(403, done);
});
- it('should return 422 for missing key', (done) => {
+ it('should return 400 for missing key', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.key;
+ delete invalidBody.key;
request(server)
- .post('/v4/projects/metadata/projectTypes')
+ .post('/v5/projects/metadata/projectTypes')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for missing displayName', (done) => {
+ it('should return 400 for missing displayName', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.displayName;
+ delete invalidBody.displayName;
request(server)
- .post('/v4/projects/metadata/projectTypes')
+ .post('/v5/projects/metadata/projectTypes')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for missing icon', (done) => {
+ it('should return 400 for missing icon', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.icon;
+ delete invalidBody.icon;
request(server)
- .post('/v4/projects/metadata/projectTypes')
+ .post('/v5/projects/metadata/projectTypes')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for missing question', (done) => {
+ it('should return 400 for missing question', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.question;
+ delete invalidBody.question;
request(server)
- .post('/v4/projects/metadata/projectTypes')
+ .post('/v5/projects/metadata/projectTypes')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for missing info', (done) => {
+ it('should return 400 for missing info', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.info;
+ delete invalidBody.info;
request(server)
- .post('/v4/projects/metadata/projectTypes')
+ .post('/v5/projects/metadata/projectTypes')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for missing metadata', (done) => {
+ it('should return 400 for missing metadata', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.metadata;
+ delete invalidBody.metadata;
request(server)
- .post('/v4/projects/metadata/projectTypes')
+ .post('/v5/projects/metadata/projectTypes')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for duplicated key', (done) => {
+ it('should return 400 for duplicated key', (done) => {
const invalidBody = _.cloneDeep(body);
- invalidBody.param.key = 'key1';
+ invalidBody.key = 'key1';
request(server)
- .post('/v4/projects/metadata/projectTypes')
+ .post('/v5/projects/metadata/projectTypes')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 201 for admin', (done) => {
request(server)
- .post('/v4/projects/metadata/projectTypes')
+ .post('/v5/projects/metadata/projectTypes')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -189,16 +191,16 @@ describe('CREATE project type', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
- resJson.key.should.be.eql(body.param.key);
- resJson.displayName.should.be.eql(body.param.displayName);
- resJson.icon.should.be.eql(body.param.icon);
- resJson.info.should.be.eql(body.param.info);
- resJson.question.should.be.eql(body.param.question);
- resJson.aliases.should.be.eql(body.param.aliases);
- resJson.disabled.should.be.eql(body.param.disabled);
- resJson.hidden.should.be.eql(body.param.hidden);
- resJson.metadata.should.be.eql(body.param.metadata);
+ const resJson = res.body;
+ resJson.key.should.be.eql(body.key);
+ resJson.displayName.should.be.eql(body.displayName);
+ resJson.icon.should.be.eql(body.icon);
+ resJson.info.should.be.eql(body.info);
+ resJson.question.should.be.eql(body.question);
+ resJson.aliases.should.be.eql(body.aliases);
+ resJson.disabled.should.be.eql(body.disabled);
+ resJson.hidden.should.be.eql(body.hidden);
+ resJson.metadata.should.be.eql(body.metadata);
resJson.createdBy.should.be.eql(40051333); // admin
should.exist(resJson.createdAt);
@@ -213,7 +215,7 @@ describe('CREATE project type', () => {
it('should return 201 for connect admin', (done) => {
request(server)
- .post('/v4/projects/metadata/projectTypes')
+ .post('/v5/projects/metadata/projectTypes')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -221,16 +223,16 @@ describe('CREATE project type', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
- resJson.key.should.be.eql(body.param.key);
- resJson.displayName.should.be.eql(body.param.displayName);
- resJson.icon.should.be.eql(body.param.icon);
- resJson.info.should.be.eql(body.param.info);
- resJson.question.should.be.eql(body.param.question);
- resJson.aliases.should.be.eql(body.param.aliases);
- resJson.disabled.should.be.eql(body.param.disabled);
- resJson.hidden.should.be.eql(body.param.hidden);
- resJson.metadata.should.be.eql(body.param.metadata);
+ const resJson = res.body;
+ resJson.key.should.be.eql(body.key);
+ resJson.displayName.should.be.eql(body.displayName);
+ resJson.icon.should.be.eql(body.icon);
+ resJson.info.should.be.eql(body.info);
+ resJson.question.should.be.eql(body.question);
+ resJson.aliases.should.be.eql(body.aliases);
+ resJson.disabled.should.be.eql(body.disabled);
+ resJson.hidden.should.be.eql(body.hidden);
+ resJson.metadata.should.be.eql(body.metadata);
resJson.createdBy.should.be.eql(40051336); // connect admin
resJson.updatedBy.should.be.eql(40051336); // connect admin
done();
diff --git a/src/routes/projectTypes/delete.js b/src/routes/projectTypes/delete.js
index 2f5e2f07..35aeb9fd 100644
--- a/src/routes/projectTypes/delete.js
+++ b/src/routes/projectTypes/delete.js
@@ -2,8 +2,11 @@
* API to delete a project type
*/
import validate from 'express-validation';
+import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../constants';
+import util from '../../util';
import models from '../../models';
const permissions = tcMiddleware.permissions;
@@ -19,7 +22,7 @@ module.exports = [
permissions('projectType.delete'),
(req, res, next) =>
models.sequelize.transaction(() =>
- models.ProjectType.findById(req.params.key)
+ models.ProjectType.findByPk(req.params.key)
.then((entity) => {
if (!entity) {
const apiErr = new Error(`Project type not found for key ${req.params.key}`);
@@ -30,7 +33,12 @@ module.exports = [
return entity.update({ deletedBy: req.authUser.userId });
})
.then(entity => entity.destroy()))
- .then(() => {
+ .then((entity) => {
+ // emit event
+ util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE,
+ RESOURCES.PROJECT_TYPE,
+ _.pick(entity.toJSON(), 'key'));
res.status(204).end();
})
.catch(next),
diff --git a/src/routes/projectTypes/delete.spec.js b/src/routes/projectTypes/delete.spec.js
index 5b9cee54..749dc139 100644
--- a/src/routes/projectTypes/delete.spec.js
+++ b/src/routes/projectTypes/delete.spec.js
@@ -24,7 +24,7 @@ const expectAfterDelete = (key, err, next) => {
chai.assert.isNotNull(res.deletedBy);
request(server)
- .get(`/v4/projects/metadata/projectTypes/${key}`)
+ .get(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -36,31 +36,34 @@ const expectAfterDelete = (key, err, next) => {
describe('DELETE project type', () => {
const key = 'key1';
- beforeEach(() => testUtil.clearDb()
- .then(() => models.ProjectType.create({
- key: 'key1',
- displayName: 'displayName 1',
- icon: 'http://example.com/icon1.ico',
- question: 'question 1',
- info: 'info 1',
- aliases: ['key-1', 'key_1'],
- metadata: { 'slack-notification-mappings': { color: '#96d957', label: 'Full App' } },
- createdBy: 1,
- updatedBy: 1,
- })).then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.ProjectType.create({
+ key: 'key1',
+ displayName: 'displayName 1',
+ icon: 'http://example.com/icon1.ico',
+ question: 'question 1',
+ info: 'info 1',
+ aliases: ['key-1', 'key_1'],
+ metadata: { 'slack-notification-mappings': { color: '#96d957', label: 'Full App' } },
+ createdBy: 1,
+ updatedBy: 1,
+ }).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('DELETE /projects/metadata/projectTypes/{key}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .delete(`/v4/projects/metadata/projectTypes/${key}`)
+ .delete(`/v5/projects/metadata/projectTypes/${key}`)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .delete(`/v4/projects/metadata/projectTypes/${key}`)
+ .delete(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -69,7 +72,7 @@ describe('DELETE project type', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .delete(`/v4/projects/metadata/projectTypes/${key}`)
+ .delete(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -78,7 +81,7 @@ describe('DELETE project type', () => {
it('should return 403 for manager', (done) => {
request(server)
- .delete(`/v4/projects/metadata/projectTypes/${key}`)
+ .delete(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -87,7 +90,7 @@ describe('DELETE project type', () => {
it('should return 404 for non-existed type', (done) => {
request(server)
- .delete('/v4/projects/metadata/projectTypes/not_existed')
+ .delete('/v5/projects/metadata/projectTypes/not_existed')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -98,7 +101,7 @@ describe('DELETE project type', () => {
models.ProjectType.destroy({ where: { key } })
.then(() => {
request(server)
- .delete(`/v4/projects/metadata/projectTypes/${key}`)
+ .delete(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -108,7 +111,7 @@ describe('DELETE project type', () => {
it('should return 204, for admin, if type was successfully removed', (done) => {
request(server)
- .delete(`/v4/projects/metadata/projectTypes/${key}`)
+ .delete(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -118,7 +121,7 @@ describe('DELETE project type', () => {
it('should return 204, for connect admin, if type was successfully removed', (done) => {
request(server)
- .delete(`/v4/projects/metadata/projectTypes/${key}`)
+ .delete(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
diff --git a/src/routes/projectTypes/get.js b/src/routes/projectTypes/get.js
index f7eb0b95..c203d8f4 100644
--- a/src/routes/projectTypes/get.js
+++ b/src/routes/projectTypes/get.js
@@ -4,8 +4,8 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../util';
import models from '../../models';
+import util from '../../util';
const permissions = tcMiddleware.permissions;
@@ -18,22 +18,45 @@ const schema = {
module.exports = [
validate(schema),
permissions('projectType.view'),
- (req, res, next) => models.ProjectType.findOne({
- where: {
- key: req.params.key,
- },
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- })
- .then((projectType) => {
- // Not found
- if (!projectType) {
- const apiErr = new Error(`Project type not found for key ${req.params.key}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
- }
+ (req, res, next) => {
+ util.fetchByIdFromES('projectTypes', {
+ query: {
+ nested: {
+ path: 'projectTypes',
+ query: {
+ match: { 'projectTypes.key': req.params.key },
+ },
+ inner_hits: {},
+ },
+ },
+ }, 'metadata')
+ .then((data) => {
+ if (data.length === 0) {
+ req.log.debug('No projectType found in ES');
+ models.ProjectType.findOne({
+ where: {
+ key: req.params.key,
+ },
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ })
+ .then((projectType) => {
+ // Not found
+ if (!projectType) {
+ const apiErr = new Error(`Project type not found for key ${req.params.key}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
- res.json(util.wrapResponse(req.id, projectType));
- return Promise.resolve();
+ res.json(projectType);
+ return Promise.resolve();
+ })
+ .catch(next);
+ } else {
+ req.log.debug('projectTypes found in ES');
+ res.json(data[0].inner_hits.projectTypes.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle
+ }
})
- .catch(next),
+ .catch(next);
+ },
+
];
diff --git a/src/routes/projectTypes/get.spec.js b/src/routes/projectTypes/get.spec.js
index 621b30d6..a0fb9e0a 100644
--- a/src/routes/projectTypes/get.spec.js
+++ b/src/routes/projectTypes/get.spec.js
@@ -27,16 +27,18 @@ describe('GET project type', () => {
const key = type.key;
- beforeEach(() => testUtil.clearDb()
- .then(() => models.ProjectType.create(type))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.ProjectType.create(type).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/projectTypes/{key}', () => {
it('should return 404 for non-existed type', (done) => {
request(server)
- .get('/v4/projects/metadata/projectTypes/1234')
+ .get('/v5/projects/metadata/projectTypes/1234')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -47,7 +49,7 @@ describe('GET project type', () => {
models.ProjectType.destroy({ where: { key } })
.then(() => {
request(server)
- .get(`/v4/projects/metadata/projectTypes/${key}`)
+ .get(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -57,13 +59,13 @@ describe('GET project type', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get(`/v4/projects/metadata/projectTypes/${key}`)
+ .get(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(type.key);
resJson.displayName.should.be.eql(type.displayName);
resJson.icon.should.be.eql(type.icon);
@@ -86,13 +88,13 @@ describe('GET project type', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get(`/v4/projects/metadata/projectTypes/${key}`)
+ .get(`/v5/projects/metadata/projectTypes/${key}`)
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get(`/v4/projects/metadata/projectTypes/${key}`)
+ .get(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -102,7 +104,7 @@ describe('GET project type', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get(`/v4/projects/metadata/projectTypes/${key}`)
+ .get(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -112,7 +114,7 @@ describe('GET project type', () => {
it('should return 200 for member', (done) => {
request(server)
- .get(`/v4/projects/metadata/projectTypes/${key}`)
+ .get(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -121,7 +123,7 @@ describe('GET project type', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get(`/v4/projects/metadata/projectTypes/${key}`)
+ .get(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/projectTypes/list.js b/src/routes/projectTypes/list.js
index 56bc2059..f71f11bd 100644
--- a/src/routes/projectTypes/list.js
+++ b/src/routes/projectTypes/list.js
@@ -2,19 +2,31 @@
* API to list all project types
*/
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../util';
import models from '../../models';
+import util from '../../util';
const permissions = tcMiddleware.permissions;
module.exports = [
permissions('projectType.view'),
- (req, res, next) => models.ProjectType.findAll({
- attributes: { exclude: ['deletedAt', 'deletedBy'] },
- raw: true,
- })
- .then((projectTypes) => {
- res.json(util.wrapResponse(req.id, projectTypes));
- })
- .catch(next),
+ (req, res, next) => {
+ util.fetchFromES('projectTypes')
+ .then((data) => {
+ if (data.projectTypes.length === 0) {
+ req.log.debug('No projectType found in ES');
+ models.ProjectType.findAll({
+ attributes: { exclude: ['deletedAt', 'deletedBy'] },
+ raw: true,
+ })
+ .then((projectTypes) => {
+ res.json(projectTypes);
+ })
+ .catch(next);
+ } else {
+ req.log.debug('projectTypes found in ES');
+ res.json(data.projectTypes);
+ }
+ });
+ },
+
];
diff --git a/src/routes/projectTypes/list.spec.js b/src/routes/projectTypes/list.spec.js
index fa2a1c35..7074ae84 100644
--- a/src/routes/projectTypes/list.spec.js
+++ b/src/routes/projectTypes/list.spec.js
@@ -40,17 +40,22 @@ describe('LIST project types', () => {
},
];
- beforeEach(() => testUtil.clearDb()
- .then(() => models.ProjectType.create(types[0]))
- .then(() => models.ProjectType.create(types[1]))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ before((done) => {
+ testUtil.clearES(done);
+ });
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.ProjectType.create(types[0]))
+ .then(() => models.ProjectType.create(types[1]).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/projectTypes', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/projects/metadata/projectTypes')
+ .get('/v5/projects/metadata/projectTypes')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -58,7 +63,7 @@ describe('LIST project types', () => {
.end((err, res) => {
const type = types[0];
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
resJson[0].key.should.be.eql(type.key);
resJson[0].displayName.should.be.eql(type.displayName);
@@ -82,13 +87,13 @@ describe('LIST project types', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/projects/metadata/projectTypes')
+ .get('/v5/projects/metadata/projectTypes')
.expect(403, done);
});
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/projects/metadata/projectTypes')
+ .get('/v5/projects/metadata/projectTypes')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -98,7 +103,7 @@ describe('LIST project types', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/projects/metadata/projectTypes')
+ .get('/v5/projects/metadata/projectTypes')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -108,7 +113,7 @@ describe('LIST project types', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/projects/metadata/projectTypes')
+ .get('/v5/projects/metadata/projectTypes')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -117,7 +122,7 @@ describe('LIST project types', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/projects/metadata/projectTypes')
+ .get('/v5/projects/metadata/projectTypes')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
diff --git a/src/routes/projectTypes/update.js b/src/routes/projectTypes/update.js
index 9975f478..9cb09002 100644
--- a/src/routes/projectTypes/update.js
+++ b/src/routes/projectTypes/update.js
@@ -5,6 +5,7 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import { EVENT, RESOURCES } from '../../constants';
import util from '../../util';
import models from '../../models';
@@ -14,32 +15,30 @@ const schema = {
params: {
key: Joi.string().max(45).required(),
},
- body: {
- param: Joi.object().keys({
- key: Joi.any().strip(),
- displayName: Joi.string().max(255).optional(),
- icon: Joi.string().max(255).optional(),
- question: Joi.string().max(255).optional(),
- info: Joi.string().max(255).optional(),
- aliases: Joi.array().optional(),
- disabled: Joi.boolean().optional(),
- hidden: Joi.boolean().optional(),
- metadata: Joi.object().optional(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ body: Joi.object().keys({
+ key: Joi.any().strip(),
+ displayName: Joi.string().max(255).optional(),
+ icon: Joi.string().max(255).optional(),
+ question: Joi.string().max(255).optional(),
+ info: Joi.string().max(255).optional(),
+ aliases: Joi.array().optional(),
+ disabled: Joi.boolean().optional(),
+ hidden: Joi.boolean().optional(),
+ metadata: Joi.object().optional(),
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
};
module.exports = [
validate(schema),
permissions('projectType.edit'),
(req, res, next) => {
- const entityToUpdate = _.assign(req.body.param, {
+ const entityToUpdate = _.assign(req.body, {
updatedBy: req.authUser.userId,
});
@@ -60,7 +59,14 @@ module.exports = [
return projectType.update(entityToUpdate);
})
.then((projectType) => {
- res.json(util.wrapResponse(req.id, projectType));
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE,
+ RESOURCES.PROJECT_TYPE,
+ projectType.get({ plain: true }),
+ );
+
+ res.json(projectType);
return Promise.resolve();
})
.catch(next);
diff --git a/src/routes/projectTypes/update.spec.js b/src/routes/projectTypes/update.spec.js
index 229f544a..2738e7c2 100644
--- a/src/routes/projectTypes/update.spec.js
+++ b/src/routes/projectTypes/update.spec.js
@@ -27,36 +27,36 @@ describe('UPDATE project type', () => {
};
const key = type.key;
- beforeEach(() => testUtil.clearDb()
- .then(() => models.ProjectType.create(type))
- .then(() => Promise.resolve()),
- );
- after(testUtil.clearDb);
+ beforeEach((done) => {
+ testUtil.clearDb()
+ .then(() => models.ProjectType.create(type).then(() => done()));
+ });
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('PATCH /projects/metadata/projectTypes/{key}', () => {
const body = {
- param: {
- displayName: 'displayName 1 - update',
- icon: 'http://example.com/icon1.ico - update',
- question: 'question 1 - update',
- info: 'info 1 - update',
- aliases: ['key-1-updated', 'key_1_updated'],
- disabled: true,
- hidden: true,
- metadata: { 'slack-notification-mappings': { color: '#b47dd6', label: 'Full App 2' } },
- },
+ displayName: 'displayName 1 - update',
+ icon: 'http://example.com/icon1.ico - update',
+ question: 'question 1 - update',
+ info: 'info 1 - update',
+ aliases: ['key-1-updated', 'key_1_updated'],
+ disabled: true,
+ hidden: true,
+ metadata: { 'slack-notification-mappings': { color: '#b47dd6', label: 'Full App 2' } },
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .patch(`/v4/projects/metadata/projectTypes/${key}`)
+ .patch(`/v5/projects/metadata/projectTypes/${key}`)
.send(body)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .patch(`/v4/projects/metadata/projectTypes/${key}`)
+ .patch(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -66,7 +66,7 @@ describe('UPDATE project type', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .patch(`/v4/projects/metadata/projectTypes/${key}`)
+ .patch(`/v5/projects/metadata/projectTypes/${key}`)
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
@@ -76,7 +76,7 @@ describe('UPDATE project type', () => {
it('should return 403 for manager', (done) => {
request(server)
- .patch(`/v4/projects/metadata/projectTypes/${key}`)
+ .patch(`/v5/projects/metadata/projectTypes/${key}`)
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
@@ -86,7 +86,7 @@ describe('UPDATE project type', () => {
it('should return 404 for non-existed type', (done) => {
request(server)
- .patch('/v4/projects/metadata/projectTypes/1234')
+ .patch('/v5/projects/metadata/projectTypes/1234')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -98,7 +98,7 @@ describe('UPDATE project type', () => {
models.ProjectType.destroy({ where: { key } })
.then(() => {
request(server)
- .patch(`/v4/projects/metadata/projectTypes/${key}`)
+ .patch(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -109,24 +109,24 @@ describe('UPDATE project type', () => {
it('should return 200 for admin displayName updated', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.icon;
- delete partialBody.param.info;
- delete partialBody.param.question;
- delete partialBody.param.aliases;
- delete partialBody.param.disabled;
- delete partialBody.param.hidden;
- delete partialBody.param.metadata;
+ delete partialBody.icon;
+ delete partialBody.info;
+ delete partialBody.question;
+ delete partialBody.aliases;
+ delete partialBody.disabled;
+ delete partialBody.hidden;
+ delete partialBody.metadata;
request(server)
- .patch(`/v4/projects/metadata/projectTypes/${key}`)
+ .patch(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(partialBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(key);
- resJson.displayName.should.be.eql(partialBody.param.displayName);
+ resJson.displayName.should.be.eql(partialBody.displayName);
resJson.icon.should.be.eql(type.icon);
resJson.info.should.be.eql(type.info);
resJson.question.should.be.eql(type.question);
@@ -147,25 +147,25 @@ describe('UPDATE project type', () => {
it('should return 200 for admin icon updated', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.info;
- delete partialBody.param.displayName;
- delete partialBody.param.question;
- delete partialBody.param.aliases;
- delete partialBody.param.disabled;
- delete partialBody.param.hidden;
- delete partialBody.param.metadata;
+ delete partialBody.info;
+ delete partialBody.displayName;
+ delete partialBody.question;
+ delete partialBody.aliases;
+ delete partialBody.disabled;
+ delete partialBody.hidden;
+ delete partialBody.metadata;
request(server)
- .patch(`/v4/projects/metadata/projectTypes/${key}`)
+ .patch(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(partialBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(key);
resJson.displayName.should.be.eql(type.displayName);
- resJson.icon.should.be.eql(partialBody.param.icon);
+ resJson.icon.should.be.eql(partialBody.icon);
resJson.info.should.be.eql(type.info);
resJson.question.should.be.eql(type.question);
resJson.aliases.should.be.eql(type.aliases);
@@ -185,26 +185,26 @@ describe('UPDATE project type', () => {
it('should return 200 for admin info updated', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.icon;
- delete partialBody.param.displayName;
- delete partialBody.param.question;
- delete partialBody.param.aliases;
- delete partialBody.param.disabled;
- delete partialBody.param.hidden;
- delete partialBody.param.metadata;
+ delete partialBody.icon;
+ delete partialBody.displayName;
+ delete partialBody.question;
+ delete partialBody.aliases;
+ delete partialBody.disabled;
+ delete partialBody.hidden;
+ delete partialBody.metadata;
request(server)
- .patch(`/v4/projects/metadata/projectTypes/${key}`)
+ .patch(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(partialBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(key);
resJson.displayName.should.be.eql(type.displayName);
resJson.icon.should.be.eql(type.icon);
- resJson.info.should.be.eql(partialBody.param.info);
+ resJson.info.should.be.eql(partialBody.info);
resJson.question.should.be.eql(type.question);
resJson.aliases.should.be.eql(type.aliases);
resJson.disabled.should.be.eql(type.disabled);
@@ -223,27 +223,27 @@ describe('UPDATE project type', () => {
it('should return 200 for admin question updated', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.icon;
- delete partialBody.param.info;
- delete partialBody.param.displayName;
- delete partialBody.param.aliases;
- delete partialBody.param.disabled;
- delete partialBody.param.hidden;
- delete partialBody.param.metadata;
+ delete partialBody.icon;
+ delete partialBody.info;
+ delete partialBody.displayName;
+ delete partialBody.aliases;
+ delete partialBody.disabled;
+ delete partialBody.hidden;
+ delete partialBody.metadata;
request(server)
- .patch(`/v4/projects/metadata/projectTypes/${key}`)
+ .patch(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(partialBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(key);
resJson.displayName.should.be.eql(type.displayName);
resJson.icon.should.be.eql(type.icon);
resJson.info.should.be.eql(type.info);
- resJson.question.should.be.eql(partialBody.param.question);
+ resJson.question.should.be.eql(partialBody.question);
resJson.aliases.should.be.eql(type.aliases);
resJson.disabled.should.be.eql(type.disabled);
resJson.hidden.should.be.eql(type.hidden);
@@ -261,28 +261,28 @@ describe('UPDATE project type', () => {
it('should return 200 for admin aliases updated', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.icon;
- delete partialBody.param.info;
- delete partialBody.param.question;
- delete partialBody.param.displayName;
- delete partialBody.param.disabled;
- delete partialBody.param.hidden;
- delete partialBody.param.metadata;
+ delete partialBody.icon;
+ delete partialBody.info;
+ delete partialBody.question;
+ delete partialBody.displayName;
+ delete partialBody.disabled;
+ delete partialBody.hidden;
+ delete partialBody.metadata;
request(server)
- .patch(`/v4/projects/metadata/projectTypes/${key}`)
+ .patch(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(partialBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(key);
resJson.displayName.should.be.eql(type.displayName);
resJson.icon.should.be.eql(type.icon);
resJson.info.should.be.eql(type.info);
resJson.question.should.be.eql(type.question);
- resJson.aliases.should.be.eql(partialBody.param.aliases);
+ resJson.aliases.should.be.eql(partialBody.aliases);
resJson.disabled.should.be.eql(type.disabled);
resJson.hidden.should.be.eql(type.hidden);
resJson.metadata.should.be.eql(type.metadata);
@@ -299,29 +299,29 @@ describe('UPDATE project type', () => {
it('should return 200 for admin disabled updated', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.icon;
- delete partialBody.param.info;
- delete partialBody.param.question;
- delete partialBody.param.displayName;
- delete partialBody.param.aliases;
- delete partialBody.param.hidden;
- delete partialBody.param.metadata;
+ delete partialBody.icon;
+ delete partialBody.info;
+ delete partialBody.question;
+ delete partialBody.displayName;
+ delete partialBody.aliases;
+ delete partialBody.hidden;
+ delete partialBody.metadata;
request(server)
- .patch(`/v4/projects/metadata/projectTypes/${key}`)
+ .patch(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(partialBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(key);
resJson.displayName.should.be.eql(type.displayName);
resJson.icon.should.be.eql(type.icon);
resJson.info.should.be.eql(type.info);
resJson.question.should.be.eql(type.question);
resJson.aliases.should.be.eql(type.aliases);
- resJson.disabled.should.be.eql(partialBody.param.disabled);
+ resJson.disabled.should.be.eql(partialBody.disabled);
resJson.hidden.should.be.eql(type.hidden);
resJson.metadata.should.be.eql(type.metadata);
resJson.createdBy.should.be.eql(type.createdBy);
@@ -337,22 +337,22 @@ describe('UPDATE project type', () => {
it('should return 200 for admin hidden updated', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.icon;
- delete partialBody.param.info;
- delete partialBody.param.question;
- delete partialBody.param.displayName;
- delete partialBody.param.disabled;
- delete partialBody.param.aliases;
- delete partialBody.param.metadata;
+ delete partialBody.icon;
+ delete partialBody.info;
+ delete partialBody.question;
+ delete partialBody.displayName;
+ delete partialBody.disabled;
+ delete partialBody.aliases;
+ delete partialBody.metadata;
request(server)
- .patch(`/v4/projects/metadata/projectTypes/${key}`)
+ .patch(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(partialBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(key);
resJson.displayName.should.be.eql(type.displayName);
resJson.icon.should.be.eql(type.icon);
@@ -360,7 +360,7 @@ describe('UPDATE project type', () => {
resJson.question.should.be.eql(type.question);
resJson.aliases.should.be.eql(type.aliases);
resJson.disabled.should.be.eql(type.disabled);
- resJson.hidden.should.be.eql(partialBody.param.hidden);
+ resJson.hidden.should.be.eql(partialBody.hidden);
resJson.metadata.should.be.eql(type.metadata);
resJson.createdBy.should.be.eql(type.createdBy); // should not update createdAt
resJson.updatedBy.should.be.eql(40051333); // admin
@@ -374,22 +374,22 @@ describe('UPDATE project type', () => {
it('should return 200 for admin metadata updated', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.icon;
- delete partialBody.param.info;
- delete partialBody.param.question;
- delete partialBody.param.displayName;
- delete partialBody.param.disabled;
- delete partialBody.param.aliases;
- delete partialBody.param.hidden;
+ delete partialBody.icon;
+ delete partialBody.info;
+ delete partialBody.question;
+ delete partialBody.displayName;
+ delete partialBody.disabled;
+ delete partialBody.aliases;
+ delete partialBody.hidden;
request(server)
- .patch(`/v4/projects/metadata/projectTypes/${key}`)
+ .patch(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(partialBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(key);
resJson.displayName.should.be.eql(type.displayName);
resJson.icon.should.be.eql(type.icon);
@@ -398,7 +398,7 @@ describe('UPDATE project type', () => {
resJson.aliases.should.be.eql(type.aliases);
resJson.disabled.should.be.eql(type.disabled);
resJson.hidden.should.be.eql(type.hidden);
- resJson.metadata.should.be.eql(partialBody.param.metadata);
+ resJson.metadata.should.be.eql(partialBody.metadata);
resJson.createdBy.should.be.eql(type.createdBy); // should not update createdAt
resJson.updatedBy.should.be.eql(40051333); // admin
should.exist(resJson.updatedAt);
@@ -411,23 +411,23 @@ describe('UPDATE project type', () => {
it('should return 200 for admin all fields updated', (done) => {
request(server)
- .patch(`/v4/projects/metadata/projectTypes/${key}`)
+ .patch(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(body)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(key);
- resJson.displayName.should.be.eql(body.param.displayName);
- resJson.icon.should.be.eql(body.param.icon);
- resJson.info.should.be.eql(body.param.info);
- resJson.question.should.be.eql(body.param.question);
- resJson.aliases.should.be.eql(body.param.aliases);
- resJson.disabled.should.be.eql(body.param.disabled);
- resJson.hidden.should.be.eql(body.param.hidden);
- resJson.metadata.should.be.eql(body.param.metadata);
+ resJson.displayName.should.be.eql(body.displayName);
+ resJson.icon.should.be.eql(body.icon);
+ resJson.info.should.be.eql(body.info);
+ resJson.question.should.be.eql(body.question);
+ resJson.aliases.should.be.eql(body.aliases);
+ resJson.disabled.should.be.eql(body.disabled);
+ resJson.hidden.should.be.eql(body.hidden);
+ resJson.metadata.should.be.eql(body.metadata);
resJson.createdBy.should.be.eql(type.createdBy); // should not update createdAt
resJson.updatedBy.should.be.eql(40051333); // admin
should.exist(resJson.updatedAt);
@@ -440,23 +440,23 @@ describe('UPDATE project type', () => {
it('should return 200 for connect admin', (done) => {
request(server)
- .patch(`/v4/projects/metadata/projectTypes/${key}`)
+ .patch(`/v5/projects/metadata/projectTypes/${key}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
.send(body)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.key.should.be.eql(key);
- resJson.displayName.should.be.eql(body.param.displayName);
- resJson.icon.should.be.eql(body.param.icon);
- resJson.info.should.be.eql(body.param.info);
- resJson.question.should.be.eql(body.param.question);
- resJson.aliases.should.be.eql(body.param.aliases);
- resJson.disabled.should.be.eql(body.param.disabled);
- resJson.hidden.should.be.eql(body.param.hidden);
- resJson.metadata.should.be.eql(body.param.metadata);
+ resJson.displayName.should.be.eql(body.displayName);
+ resJson.icon.should.be.eql(body.icon);
+ resJson.info.should.be.eql(body.info);
+ resJson.question.should.be.eql(body.question);
+ resJson.aliases.should.be.eql(body.aliases);
+ resJson.disabled.should.be.eql(body.disabled);
+ resJson.hidden.should.be.eql(body.hidden);
+ resJson.metadata.should.be.eql(body.metadata);
resJson.createdBy.should.be.eql(type.createdBy); // should not update createdAt
resJson.updatedBy.should.be.eql(40051336); // connect admin
done();
diff --git a/src/routes/projectUpgrade/create.js b/src/routes/projectUpgrade/create.js
index 9dbf318e..1037f00d 100644
--- a/src/routes/projectUpgrade/create.js
+++ b/src/routes/projectUpgrade/create.js
@@ -13,6 +13,7 @@ import util from '../../util';
import {
PROJECT_STATUS,
EVENT,
+ RESOURCES,
} from '../../constants';
const permissions = tcMiddleware.permissions;
@@ -25,7 +26,7 @@ const permissions = tcMiddleware.permissions;
* @returns {Promise} the latest completed status' creation date, or undefined if not found
*/
async function findCompletedProjectEndDate(projectId, transaction) {
- const projectHistoryRecord = await models.ProjectHistory.find({
+ const projectHistoryRecord = await models.ProjectHistory.findOne({
where: { projectId, status: PROJECT_STATUS.COMPLETED },
order: [['createdAt', 'DESC']],
attributes: ['createdAt'],
@@ -92,7 +93,7 @@ async function migrateFromV2ToV3(req, project, defaultProductTemplateId, phaseNa
await models.sequelize.transaction(async (transaction) => {
const products = project.details.products;
- const projectTemplate = await models.ProjectTemplate.find({
+ const projectTemplate = await models.ProjectTemplate.findOne({
where: { key: products[0] },
attributes: ['id', 'phases'],
raw: true,
@@ -146,7 +147,7 @@ async function migrateFromV2ToV3(req, project, defaultProductTemplateId, phaseNa
} else {
query = { productKey: phaseProduct.productKey };
}
- const productTemplate = await models.ProductTemplate.find({
+ const productTemplate = await models.ProductTemplate.findOne({
where: query,
attributes: ['id', 'name', 'productKey', 'template'],
raw: true,
@@ -204,10 +205,11 @@ async function migrateFromV2ToV3(req, project, defaultProductTemplateId, phaseNa
correlationId: req.id,
},
);
+
req.app.emit(EVENT.ROUTING_KEY.PROJECT_UPDATED, {
req,
original: previousValue,
- updated: project,
+ updated: _.assign({ resource: RESOURCES.PROJECT }, project.toJSON()),
});
}
@@ -218,13 +220,11 @@ const allowedMigrations = {
};
const schema = {
- body: {
- param: Joi.object().keys({
- targetVersion: Joi.string().valid(Object.keys(allowedMigrations)).required(),
- defaultProductTemplateId: Joi.number().integer().positive().required(),
- phaseName: Joi.string(),
- }).required(),
- },
+ body: Joi.object().keys({
+ targetVersion: Joi.string().valid(Object.keys(allowedMigrations)).required(),
+ defaultProductTemplateId: Joi.number().integer().positive().required(),
+ phaseName: Joi.string(),
+ }).required(),
options: {
status: 400,
},
@@ -236,9 +236,9 @@ module.exports = [
async (req, res, next) => {
try {
const projectId = Number(req.params.projectId);
- const targetVersion = req.body.param.targetVersion;
+ const targetVersion = req.body.targetVersion;
const targetVersionMigrationData = allowedMigrations[targetVersion];
- const project = await models.Project.find({ where: { id: projectId } });
+ const project = await models.Project.findOne({ where: { id: projectId } });
if (!project) {
// returning 404
throw util.buildApiError(`project not found for id ${projectId}`, 404);
@@ -250,8 +250,8 @@ module.exports = [
targetVersion}`, 400);
}
// we have a valid project to be migrated
- await handler(req, project, req.body.param.defaultProductTemplateId, req.body.param.phaseName);
- res.status(200).json(util.wrapResponse(req.id, { message: 'Project successfully migrated' }));
+ await handler(req, project, req.body.defaultProductTemplateId, req.body.phaseName);
+ res.status(200).json({ message: 'Project successfully migrated' });
} catch (err) {
next(err);
}
diff --git a/src/routes/projectUpgrade/create.spec.js b/src/routes/projectUpgrade/create.spec.js
index 84ba5e5d..c0a1b680 100644
--- a/src/routes/projectUpgrade/create.spec.js
+++ b/src/routes/projectUpgrade/create.spec.js
@@ -130,10 +130,8 @@ describe('Project upgrade', () => {
updatedBy: 2,
}, specific))));
validBody = {
- param: {
- targetVersion: 'v3',
- defaultProductTemplateId: defaultProductTemplate.id,
- },
+ targetVersion: 'v3',
+ defaultProductTemplateId: defaultProductTemplate.id,
};
// restoring the stubs in beforeEach instead of afterEach because these methods are already stubbed
server.services.pubsub.init.restore();
@@ -151,14 +149,14 @@ describe('Project upgrade', () => {
it('should return 403 if user is not authenticated', async () => {
await request(server)
- .post(`/v4/projects/${project.id}/upgrade`)
+ .post(`/v5/projects/${project.id}/upgrade`)
.send(validBody)
.expect(403);
});
it('should return 403 for non admin', async () => {
await request(server)
- .post(`/v4/projects/${project.id}/upgrade`)
+ .post(`/v5/projects/${project.id}/upgrade`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -170,7 +168,7 @@ describe('Project upgrade', () => {
// since the product id is extracted from 'details.products', clearing that should trigger this error
await project.update({ details: {} });
await request(server)
- .post(`/v4/projects/${project.id}/upgrade`)
+ .post(`/v5/projects/${project.id}/upgrade`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -182,7 +180,7 @@ describe('Project upgrade', () => {
// by changing this we cause no matching product template to be found
await matchingProductTemplate.update({ productKey: 'non matching product key' });
await request(server)
- .post(`/v4/projects/${project.id}/upgrade`)
+ .post(`/v5/projects/${project.id}/upgrade`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -194,9 +192,9 @@ describe('Project upgrade', () => {
// by changing this the default product template id will be used
await projectTemplate.update({ phases: { nonMatchingPhase1: { products: ['non existing product'] } } });
// and we simulate a non existing one
- validBody.param.defaultProductTemplateId += 1000;
+ validBody.defaultProductTemplateId += 1000;
await request(server)
- .post(`/v4/projects/${project.id}/upgrade`)
+ .post(`/v5/projects/${project.id}/upgrade`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -208,7 +206,7 @@ describe('Project upgrade', () => {
// simulate an already migrated project
await project.update({ version: 'v3' });
await request(server)
- .post(`/v4/projects/${project.id}/upgrade`)
+ .post(`/v5/projects/${project.id}/upgrade`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -217,9 +215,9 @@ describe('Project upgrade', () => {
});
it('should return 400 if there\'s no migration handler for the sent target version', async () => {
- validBody.param.targetVersion = 'v4';
+ validBody.targetVersion = 'v4';
await request(server)
- .post(`/v4/projects/${project.id}/upgrade`)
+ .post(`/v5/projects/${project.id}/upgrade`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -231,7 +229,7 @@ describe('Project upgrade', () => {
// simulate an already migrated project
await project.update({ version: 'v3' });
await request(server)
- .post(`/v4/projects/${project.id + 1}/upgrade`)
+ .post(`/v5/projects/${project.id + 1}/upgrade`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -259,7 +257,7 @@ describe('Project upgrade', () => {
});
const commonTest = async (testCompleted, completedOnDate, additionalPhaseName) => {
- const migratedProject = await models.Project.find({ id: project.id });
+ const migratedProject = await models.Project.findOne({ id: project.id });
expect(migratedProject.version).to.equal('v3');
expect(migratedProject.templateId).to.equal(projectTemplate.id);
const newProjectPhases = await models.ProjectPhase.findAll({
@@ -311,7 +309,7 @@ describe('Project upgrade', () => {
it('should migrate a non completed project to the expected state', async () => {
await request(server)
- .post(`/v4/projects/${project.id}/upgrade`)
+ .post(`/v5/projects/${project.id}/upgrade`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -342,7 +340,7 @@ describe('Project upgrade', () => {
createdAt: yesterday,
});
await request(server)
- .post(`/v4/projects/${project.id}/upgrade`)
+ .post(`/v5/projects/${project.id}/upgrade`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -352,9 +350,9 @@ describe('Project upgrade', () => {
});
it('should migrate a project and assign the phase name passed in the parameters', async () => {
- validBody.param.phaseName = 'A custom phase name';
+ validBody.phaseName = 'A custom phase name';
await request(server)
- .post(`/v4/projects/${project.id}/upgrade`)
+ .post(`/v5/projects/${project.id}/upgrade`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
diff --git a/src/routes/projects/create.js b/src/routes/projects/create.js
index 893035f4..b0bb815f 100644
--- a/src/routes/projects/create.js
+++ b/src/routes/projects/create.js
@@ -8,10 +8,9 @@ import moment from 'moment';
import models from '../../models';
import { PROJECT_MEMBER_ROLE, MANAGER_ROLES, PROJECT_STATUS, PROJECT_PHASE_STATUS,
- EVENT, REGEX, WORKSTREAM_STATUS } from '../../constants';
+ EVENT, RESOURCES, REGEX, WORKSTREAM_STATUS } from '../../constants';
import fieldLookupValidation from '../../middlewares/fieldLookupValidation';
import util from '../../util';
-import directProject from '../../services/directProject';
const traverse = require('traverse');
@@ -26,60 +25,58 @@ const traverse = require('traverse');
const permissions = require('tc-core-library-js').middleware.permissions;
const createProjectValidations = {
- body: {
- param: Joi.object().keys({
- name: Joi.string().required(),
- description: Joi.string().allow(null).allow('').optional(),
- billingAccountId: Joi.number().positive(),
- utm: Joi.object().keys({
- source: Joi.string().allow(null),
- medium: Joi.string().allow(null),
- campaign: Joi.string().allow(null),
- }).allow(null),
- bookmarks: Joi.array().items(Joi.object().keys({
- title: Joi.string(),
- address: Joi.string().regex(REGEX.URL),
- createdAt: Joi.date(),
- createdBy: Joi.number().integer().positive(),
- updatedAt: Joi.date(),
- updatedBy: Joi.number().integer().positive(),
- })).optional().allow(null),
- estimatedPrice: Joi.number().precision(2).positive().optional()
+ body: Joi.object().keys({
+ name: Joi.string().required(),
+ description: Joi.string().allow(null).allow('').optional(),
+ billingAccountId: Joi.number().positive(),
+ utm: Joi.object().keys({
+ source: Joi.string().allow(null),
+ medium: Joi.string().allow(null),
+ campaign: Joi.string().allow(null),
+ }).allow(null),
+ bookmarks: Joi.array().items(Joi.object().keys({
+ title: Joi.string(),
+ address: Joi.string().regex(REGEX.URL),
+ createdAt: Joi.date(),
+ createdBy: Joi.number().integer().positive(),
+ updatedAt: Joi.date(),
+ updatedBy: Joi.number().integer().positive(),
+ })).optional().allow(null),
+ estimatedPrice: Joi.number().precision(2).positive().optional()
.allow(null),
- terms: Joi.array().items(Joi.number().positive()).optional(),
- external: Joi.object().keys({
- id: Joi.string(),
- type: Joi.any().valid('github', 'jira', 'asana', 'other'),
- data: Joi.string().max(300), // TODO - restrict length
- }).allow(null),
- type: Joi.string().max(45).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),
- templateId: Joi.number().integer().positive(),
- version: Joi.string(),
- estimation: Joi.array().items(Joi.object().keys({
- conditions: Joi.string().required(),
- price: Joi.number().required(),
- quantity: Joi.number().optional(),
- minTime: Joi.number().integer().required(),
- maxTime: Joi.number().integer().required(),
- buildingBlockKey: Joi.string().required(),
- metadata: Joi.object().optional(),
- })).optional(),
- attachments: Joi.array().items(Joi.object().keys({
- category: Joi.string().required(),
- contentType: Joi.string().required(),
- description: Joi.string().allow(null).allow('').optional(),
- filePath: Joi.string().required(),
- size: Joi.number().required(),
- title: Joi.string().required(),
- })).optional(),
- }).required(),
- },
+ terms: Joi.array().items(Joi.number().positive()).optional(),
+ external: Joi.object().keys({
+ id: Joi.string(),
+ type: Joi.any().valid('github', 'jira', 'asana', 'other'),
+ data: Joi.string().max(300), // TODO - restrict length
+ }).allow(null),
+ type: Joi.string().max(45).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),
+ templateId: Joi.number().integer().positive(),
+ version: Joi.string(),
+ estimation: Joi.array().items(Joi.object().keys({
+ conditions: Joi.string().required(),
+ price: Joi.number().required(),
+ quantity: Joi.number().optional(),
+ minTime: Joi.number().integer().required(),
+ maxTime: Joi.number().integer().required(),
+ buildingBlockKey: Joi.string().required(),
+ metadata: Joi.object().optional(),
+ })).optional(),
+ attachments: Joi.array().items(Joi.object().keys({
+ category: Joi.string().required(),
+ contentType: Joi.string().required(),
+ description: Joi.string().allow(null).allow('').optional(),
+ filePath: Joi.string().required(),
+ size: Joi.number().required(),
+ title: Joi.string().required(),
+ })).optional(),
+ }).required(),
};
/**
@@ -302,12 +299,12 @@ function validateAndFetchTemplates(templateId) {
// backward compatibility for releasing the service before releasing the front end
// we ignore missing template id field and create a project without phase/products
if (!templateId) return Promise.resolve({});
- return models.ProjectTemplate.findById(templateId, { raw: true })
+ return models.ProjectTemplate.findByPk(templateId, { raw: true })
.then((existingProjectTemplate) => {
if (!existingProjectTemplate) {
// Not found
const apiErr = new Error(`Project template not found for id ${templateId}`);
- apiErr.status = 422;
+ apiErr.status = 400;
return Promise.reject(apiErr);
}
return Promise.resolve(existingProjectTemplate);
@@ -327,7 +324,7 @@ function validateAndFetchTemplates(templateId) {
return models.PlanConfig.findOneWithLatestRevision(projectTemplate.planConfig).then((planConfig) => {
if (!planConfig) {
const apiErr = new Error(`Cannot find planConfig ${JSON.stringify(projectTemplate.planConfig)}`);
- apiErr.status = 422;
+ apiErr.status = 400;
throw apiErr;
}
@@ -349,16 +346,16 @@ function validateAndFetchTemplates(templateId) {
const productCount = _.isArray(phase.products) ? phase.products.length : 0;
if (productCount > config.maxPhaseProductCount) {
const apiErr = new Error(`Number of products per phase cannot exceed ${config.maxPhaseProductCount}`);
- apiErr.status = 422;
+ apiErr.status = 400;
throw apiErr;
}
_.map(phase.products, (product) => {
- productPromises.push(models.ProductTemplate.findById(product.id)
+ productPromises.push(models.ProductTemplate.findByPk(product.id)
.then((productTemplate) => {
if (!productTemplate) {
// Not found
const apiErr = new Error(`Product template not found for id ${product.id}`);
- apiErr.status = 422;
+ apiErr.status = 400;
return Promise.reject(apiErr);
}
return Promise.resolve(productTemplate);
@@ -380,13 +377,13 @@ module.exports = [
// handles request validations
validate(createProjectValidations),
permissions('project.create'),
- fieldLookupValidation(models.ProjectType, 'key', 'body.param.type', 'Project type'),
+ fieldLookupValidation(models.ProjectType, 'key', 'body.type', 'Project type'),
/**
* POST projects/
* Create a project if the user has access
*/
(req, res, next) => {
- const project = req.body.param;
+ const project = req.body;
// by default connect admin and managers joins projects as manager
const userRole = util.hasRoles(req, MANAGER_ROLES)
? PROJECT_MEMBER_ROLE.MANAGER
@@ -473,26 +470,14 @@ module.exports = [
updatedBy: req.authUser.userId,
}).then(() => req.log.debug('project history created for project %d', newProject.id))
.catch(() => req.log.error('project history failed for project %d', newProject.id));
- req.log.debug('creating direct project for project %d', newProject.id);
- return directProject.createDirectProject(req, body)
- .then((resp) => {
- newProject.directProjectId = resp.data.result.content.projectId;
- return newProject.save();
- })
- .then(() => newProject.reload(newProject.id))
- .catch((err) => {
- // log the error and continue
- req.log.error('Error creating direct project: %s', _.get(err, 'data.result.content', err));
- return Promise.resolve();
- });
- // return Promise.resolve();
+ return Promise.resolve();
});
})
.then(() => {
newProject = newProject.get({ plain: true });
// remove utm details & deletedAt field
newProject = _.omit(newProject, ['deletedAt', 'utm']);
- // adds the project attachments, if any
+ // add the project attachments, if any
newProject.attachments = projectAttachments;
// set phases array
newProject.phases = newPhases;
@@ -508,8 +493,10 @@ module.exports = [
);
req.log.debug('Sending event to Kafka bus for project %d', newProject.id);
// emit event
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_DRAFT_CREATED, { req, project: newProject });
- res.status(201).json(util.wrapResponse(req.id, newProject, 1, 201));
+ req.app.emit(EVENT.ROUTING_KEY.PROJECT_DRAFT_CREATED,
+ { req, project: _.assign({ resource: RESOURCES.PROJECT }, newProject),
+ });
+ res.status(201).json(newProject);
})
.catch((err) => {
req.log.error(err.message);
diff --git a/src/routes/projects/create.spec.js b/src/routes/projects/create.spec.js
index 0c5c9775..35abffea 100644
--- a/src/routes/projects/create.spec.js
+++ b/src/routes/projects/create.spec.js
@@ -19,6 +19,7 @@ describe('Project create', () => {
sinon.stub(RabbitMQService.prototype, 'init', () => {});
sinon.stub(RabbitMQService.prototype, 'publish', () => {});
testUtil.clearDb()
+ .then(() => testUtil.clearES())
.then(() => models.ProjectType.bulkCreate([
{
key: 'generic',
@@ -259,17 +260,15 @@ describe('Project create', () => {
describe('POST /projects', () => {
const body = {
- param: {
- type: 'generic',
- description: 'test project',
- details: {},
- billingAccountId: 1,
- name: 'test project1',
- bookmarks: [{
- title: 'title1',
- address: 'http://www.address.com',
- }],
- },
+ type: 'generic',
+ description: 'test project',
+ details: {},
+ billingAccountId: 1,
+ name: 'test project1',
+ bookmarks: [{
+ title: 'title1',
+ address: 'http://www.address.com',
+ }],
};
let sandbox;
@@ -282,124 +281,96 @@ describe('Project create', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post('/v4/projects')
+ .post('/v5/projects')
.send(body)
.expect(403, done);
});
- it('should return 422 if validations dont pass', (done) => {
+ it('should return 400 if validations dont pass', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.name;
+ delete invalidBody.name;
request(server)
- .post('/v4/projects')
+ .post('/v5/projects')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if project type is missing', (done) => {
+ it('should return 400 if project type is missing', (done) => {
const invalidBody = _.cloneDeep(body);
- invalidBody.param.type = null;
+ invalidBody.type = null;
request(server)
- .post('/v4/projects')
+ .post('/v5/projects')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if project type does not exist', (done) => {
+ it('should return 400 if project type does not exist', (done) => {
const invalidBody = _.cloneDeep(body);
- invalidBody.param.type = 'not_exist';
+ invalidBody.type = 'not_exist';
request(server)
- .post('/v4/projects')
+ .post('/v5/projects')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if templateId does not exist', (done) => {
+ it('should return 400 if templateId does not exist', (done) => {
const invalidBody = _.cloneDeep(body);
- invalidBody.param.templateId = 3000;
+ invalidBody.templateId = 3000;
request(server)
- .post('/v4/projects')
+ .post('/v5/projects')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if phaseProduct count exceeds max value', (done) => {
+ it('should return 400 if phaseProduct count exceeds max value', (done) => {
const invalidBody = _.cloneDeep(body);
- invalidBody.param.templateId = 1;
+ invalidBody.templateId = 1;
request(server)
- .post('/v4/projects')
+ .post('/v5/projects')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 with wrong format estimation field', (done) => {
+ it('should return 400 with wrong format estimation field', (done) => {
const invalidBody = _.cloneDeep(body);
- invalidBody.param.estimation = [
+ invalidBody.estimation = [
{
},
];
request(server)
- .post('/v4/projects')
+ .post('/v5/projects')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
- });
-
- it('should return 201 if error to create direct project', (done) => {
- const validBody = _.cloneDeep(body);
- validBody.param.templateId = 3;
- const mockHttpClient = _.merge(testUtil.mockHttpClient, {
- post: () => Promise.reject(new Error('error message')),
- });
- sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
- request(server)
- .post('/v4/projects')
- .set({
- Authorization: `Bearer ${testUtil.jwts.member}`,
- })
- .send(validBody)
- .expect('Content-Type', /json/)
- .expect(201)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const result = res.body.result;
- result.success.should.be.truthy;
- result.status.should.equal(201);
- server.services.pubsub.publish.calledWith('project.draft-created').should.be.true;
- done();
- }
- });
+ .expect(400, done);
});
it('should return 201 if valid user and data', (done) => {
const validBody = _.cloneDeep(body);
- validBody.param.templateId = 3;
+ validBody.templateId = 3;
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
post: () => Promise.resolve({
status: 200,
@@ -418,7 +389,7 @@ describe('Project create', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .post('/v4/projects')
+ .post('/v5/projects')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -429,13 +400,12 @@ describe('Project create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
should.exist(resJson.billingAccountId);
should.exist(resJson.name);
- resJson.directProjectId.should.be.eql(128);
resJson.status.should.be.eql('in_review');
- resJson.type.should.be.eql(body.param.type);
+ resJson.type.should.be.eql(body.type);
resJson.version.should.be.eql('v3');
resJson.members.should.have.lengthOf(1);
resJson.members[0].role.should.be.eql('customer');
@@ -474,7 +444,7 @@ describe('Project create', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .post('/v4/projects')
+ .post('/v5/projects')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -485,13 +455,12 @@ describe('Project create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
should.exist(resJson.billingAccountId);
should.exist(resJson.name);
- resJson.directProjectId.should.be.eql(128);
resJson.status.should.be.eql('in_review');
- resJson.type.should.be.eql(body.param.type);
+ resJson.type.should.be.eql(body.type);
resJson.version.should.be.eql('v2');
resJson.members.should.have.lengthOf(1);
resJson.members[0].role.should.be.eql('customer');
@@ -528,24 +497,23 @@ describe('Project create', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .post('/v4/projects')
+ .post('/v5/projects')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
- .send(_.merge({ param: { templateId: 3 } }, body))
+ .send(_.merge({ templateId: 3 }, body))
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
should.exist(resJson.billingAccountId);
should.exist(resJson.name);
- resJson.directProjectId.should.be.eql(128);
resJson.status.should.be.eql('in_review');
- resJson.type.should.be.eql(body.param.type);
+ resJson.type.should.be.eql(body.type);
resJson.members.should.have.lengthOf(1);
resJson.members[0].role.should.be.eql('customer');
resJson.members[0].userId.should.be.eql(40051331);
@@ -595,17 +563,15 @@ describe('Project create', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .post('/v4/projects')
+ .post('/v5/projects')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
.send(_.merge({
- param: {
- templateId: 4,
- details: {
- appDefinition: {
- deliverables: ['dev-qa', 'design'],
- },
+ templateId: 4,
+ details: {
+ appDefinition: {
+ deliverables: ['dev-qa', 'design'],
},
},
}, body))
@@ -615,13 +581,12 @@ describe('Project create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
should.exist(resJson.billingAccountId);
should.exist(resJson.name);
- resJson.directProjectId.should.be.eql(128);
resJson.status.should.be.eql('in_review');
- resJson.type.should.be.eql(body.param.type);
+ resJson.type.should.be.eql(body.type);
resJson.members.should.have.lengthOf(1);
resJson.members[0].role.should.be.eql('customer');
resJson.members[0].userId.should.be.eql(40051331);
@@ -655,7 +620,7 @@ describe('Project create', () => {
it('should return 201 if valid user and data (with estimation)', (done) => {
const validBody = _.cloneDeep(body);
- validBody.param.estimation = [
+ validBody.estimation = [
{
conditions: '( HAS_DESIGN_DELIVERABLE && HAS_ZEPLIN_APP_ADDON && CA_NEEDED)',
price: 6,
@@ -719,7 +684,7 @@ describe('Project create', () => {
buildingBlockKey: 'HAS_UNIT_TESTING_ADDON_CA',
},
];
- validBody.param.templateId = 3;
+ validBody.templateId = 3;
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
post: () => Promise.resolve({
status: 200,
@@ -738,7 +703,7 @@ describe('Project create', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .post('/v4/projects')
+ .post('/v5/projects')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -749,13 +714,12 @@ describe('Project create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
should.exist(resJson.billingAccountId);
should.exist(resJson.name);
- resJson.directProjectId.should.be.eql(128);
resJson.status.should.be.eql('in_review');
- resJson.type.should.be.eql(body.param.type);
+ resJson.type.should.be.eql(body.type);
resJson.version.should.be.eql('v3');
resJson.members.should.have.lengthOf(1);
resJson.members[0].role.should.be.eql('customer');
@@ -826,11 +790,11 @@ describe('Project create', () => {
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .post('/v4/projects')
+ .post('/v5/projects')
.set({
Authorization: 'Bearer userId_1800075',
})
- .send(_.merge({ param: { templateId: 3 } }, body))
+ .send(_.merge({ templateId: 3 }, body))
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
@@ -838,13 +802,12 @@ describe('Project create', () => {
server.log.error(err);
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
should.exist(resJson.billingAccountId);
should.exist(resJson.name);
- resJson.directProjectId.should.be.eql(128);
resJson.status.should.be.eql('in_review');
- resJson.type.should.be.eql(body.param.type);
+ resJson.type.should.be.eql(body.type);
resJson.members.should.have.lengthOf(1);
resJson.members[0].role.should.be.eql('customer');
resJson.members[0].userId.should.be.eql(1800075);
@@ -877,7 +840,7 @@ describe('Project create', () => {
it('should create correct estimation items with estimation', (done) => {
const validBody = _.cloneDeep(body);
- validBody.param.estimation = [
+ validBody.estimation = [
{
conditions: '( HAS_DEV_DELIVERABLE && (ONLY_ONE_OS_MOBILE) )',
price: 1000,
@@ -903,26 +866,18 @@ describe('Project create', () => {
buildingBlockKey: 'BLOCK_KEY3',
},
];
- validBody.param.templateId = 3;
+ validBody.templateId = 3;
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
post: () => Promise.resolve({
status: 200,
data: {
- id: 'requesterId',
- version: 'v3',
- result: {
- success: true,
- status: 200,
- content: {
- projectId: 128,
- },
- },
+ projectId: 128,
},
}),
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .post('/v4/projects')
+ .post('/v5/projects')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -933,7 +888,7 @@ describe('Project create', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
should.exist(resJson.name);
should.exist(resJson.estimations);
diff --git a/src/routes/projects/delete.js b/src/routes/projects/delete.js
index 915d91d9..bdbf7b03 100644
--- a/src/routes/projects/delete.js
+++ b/src/routes/projects/delete.js
@@ -1,7 +1,7 @@
import _ from 'lodash';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import { EVENT } from '../../constants';
+import { EVENT, RESOURCES } from '../../constants';
import models from '../../models';
/**
@@ -17,10 +17,10 @@ module.exports = [
const projectId = _.parseInt(req.params.projectId);
models.sequelize.transaction(() =>
- models.Project.findById(req.params.projectId)
+ models.Project.findByPk(req.params.projectId)
.then((entity) => {
if (!entity) {
- const apiErr = new Error(`Project template not found for template id ${projectId}`);
+ const apiErr = new Error(`Project not found for id ${projectId}`);
apiErr.status = 404;
return Promise.reject(apiErr);
}
@@ -28,14 +28,16 @@ module.exports = [
return entity.update({ deletedBy: req.authUser.userId });
})
.then(project => project.destroy({ cascade: true })))
- .then(() => {
+ .then((project) => {
req.app.services.pubsub.publish(
EVENT.ROUTING_KEY.PROJECT_DELETED,
{ id: projectId },
{ correlationId: req.id },
);
// emit event
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_DELETED, { req, id: projectId });
+ req.app.emit(EVENT.ROUTING_KEY.PROJECT_DELETED,
+ { req, project: _.assign({ resource: RESOURCES.PROJECT }, _.pick(project.toJSON(), 'id')),
+ });
res.status(204).json({});
})
.catch(err => next(err));
diff --git a/src/routes/projects/delete.spec.js b/src/routes/projects/delete.spec.js
index 0ade73b7..8047a21d 100644
--- a/src/routes/projects/delete.spec.js
+++ b/src/routes/projects/delete.spec.js
@@ -19,17 +19,16 @@ const expectAfterDelete = (id, err, next) => {
if (!res) {
throw new Error('Should found the entity');
} else {
- server.services.pubsub.publish.calledWith('project.deleted').should.be.true;
-
chai.assert.isNotNull(res.deletedAt);
chai.assert.isNotNull(res.deletedBy);
request(server)
- .get(`/v4/projects/${id}`)
+ .get(`/v5/projects/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .expect(404, next);
+ .expect(404)
+ .end(next);
}
}), 500);
};
@@ -37,6 +36,7 @@ describe('Project delete test', () => {
let project1;
beforeEach((done) => {
testUtil.clearDb()
+ .then(() => testUtil.clearES())
.then(() => {
models.Project.create({
type: 'generic',
@@ -106,16 +106,17 @@ describe('Project delete test', () => {
describe('DELETE /projects/{id}/', () => {
it('should return 403 if copilot tries to delete the project', (done) => {
request(server)
- .delete(`/v4/projects/${project1.id}`)
+ .delete(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .expect(403, done);
+ .expect(403)
+ .end(done);
});
it('should return 204 if project was successfully removed', (done) => {
request(server)
- .delete(`/v4/projects/${project1.id}`)
+ .delete(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -127,7 +128,7 @@ describe('Project delete test', () => {
it('should return 204, for connect admin, if project was successfully removed', (done) => {
request(server)
- .delete(`/v4/projects/${project1.id}`)
+ .delete(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -139,7 +140,7 @@ describe('Project delete test', () => {
it('should return 204, for connect admin, if project was successfully removed', (done) => {
request(server)
- .delete(`/v4/projects/${project1.id}`)
+ .delete(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
diff --git a/src/routes/projects/get.js b/src/routes/projects/get.js
index 7dc856b4..c76b433a 100644
--- a/src/routes/projects/get.js
+++ b/src/routes/projects/get.js
@@ -2,10 +2,14 @@
/* globals Promise */
import _ from 'lodash';
+import config from 'config';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import models from '../../models';
import util from '../../util';
+const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName');
+const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType');
+
/**
/**
* API to handle retrieving a single project by id
@@ -19,46 +23,113 @@ import util from '../../util';
const permissions = tcMiddleware.permissions;
const PROJECT_ATTRIBUTES = _.without(_.keys(models.Project.rawAttributes), 'utm', 'deletedAt');
const PROJECT_MEMBER_ATTRIBUTES = _.without(_.keys(models.ProjectMember.rawAttributes), 'deletedAt');
+const PROJECT_MEMBER_INVITE_ATTRIBUTES = _.without(_.keys(models.ProjectMemberInvite.rawAttributes), 'deletedAt');
+const PROJECT_ATTACHMENT_ATTRIBUTES = _.without(_.keys(models.ProjectAttachment.rawAttributes), 'deletedAt');
-module.exports = [
- permissions('project.view'),
- /**
- * GET projects/{projectId}
- * Get a project by id
- */
- (req, res, next) => {
- const projectId = Number(req.params.projectId);
- let fields = req.query.fields;
- fields = fields ? fields.split(',') : [];
- // parse the fields string to determine what fields are to be returned
- fields = util.parseFields(fields, {
- projects: PROJECT_ATTRIBUTES,
- project_members: PROJECT_MEMBER_ATTRIBUTES,
- });
- let project;
- return models.Project
- .find({
- where: { id: projectId },
- attributes: _.get(fields, 'projects', null),
- raw: true,
- })
- .then((_project) => {
- project = _project;
- if (!project) {
+/**
+ * Parse the ES search criteria and prepare search request body
+ *
+ * @param {projctId} projectId the projectId from url
+ * @param {fields} fields the fields from url
+ * @return {Object} search request body that can be passed to .search api call
+ */
+const parseElasticSearchCriteria = (projectId, fields) => {
+ const searchCriteria = {
+ index: ES_PROJECT_INDEX,
+ type: ES_PROJECT_TYPE,
+ };
+
+ let sourceInclude;
+ if (_.get(fields, 'projects', null)) {
+ sourceInclude = _.get(fields, 'projects');
+ }
+ if (_.get(fields, 'project_members', null)) {
+ const memberFields = _.get(fields, 'project_members');
+ sourceInclude = sourceInclude.concat(_.map(memberFields, single => `members.${single}`));
+ }
+ if (_.get(fields, 'project_member_invites', null)) {
+ const memberFields = _.get(fields, 'project_member_invites');
+ sourceInclude = sourceInclude.concat(_.map(memberFields, single => `invites.${single}`));
+ }
+
+ if (_.get(fields, 'attachments', null)) {
+ const attachmentFields = _.get(fields, 'attachments');
+ sourceInclude = sourceInclude.concat(_.map(attachmentFields, single => `attachments.${single}`));
+ }
+
+ if (sourceInclude) {
+ searchCriteria._sourceInclude = sourceInclude; // eslint-disable-line no-underscore-dangle
+ }
+
+
+ const body = {
+ query: {
+ bool: {
+ filter: [
+ {
+ term: {
+ id: projectId,
+ },
+ },
+ ],
+ },
+ },
+ };
+ searchCriteria.body = body;
+ return searchCriteria;
+};
+
+const retrieveProjectFromES = (projectId, req) => {
+ // parse the fields string to determine what fields are to be returned
+ let fields = req.query.fields;
+ fields = fields ? fields.split(',') : [];
+ fields = util.parseFields(fields, {
+ projects: PROJECT_ATTRIBUTES,
+ project_members: PROJECT_MEMBER_ATTRIBUTES,
+ project_member_invites: PROJECT_MEMBER_INVITE_ATTRIBUTES,
+ attachments: PROJECT_ATTACHMENT_ATTRIBUTES,
+ });
+
+ const searchCriteria = parseElasticSearchCriteria(projectId, fields) || {};
+ return new Promise((accept, reject) => {
+ const es = util.getElasticSearchClient();
+ es.search(searchCriteria).then((docs) => {
+ const rows = _.map(docs.hits.hits, single => single._source); // eslint-disable-line no-underscore-dangle
+ accept(rows[0]);
+ }).catch(reject);
+ });
+};
+
+const retrieveProjectFromDB = (projectId, req) => {
+ let project;
+ let fields = req.query.fields;
+ fields = fields ? fields.split(',') : [];
+ fields = util.parseFields(fields, {
+ projects: PROJECT_ATTRIBUTES,
+ project_members: PROJECT_MEMBER_ATTRIBUTES,
+ });
+ return models.Project
+ .findOne({
+ where: { id: projectId },
+ attributes: _.get(fields, 'projects', null),
+ raw: true,
+ }).then((_project) => {
+ project = _project;
+ if (!project) {
// returning 404
- const apiErr = new Error(`project not found for id ${projectId}`);
- apiErr.status = 404;
- return Promise.reject(apiErr);
- }
+ const apiErr = new Error(`project not found for id ${projectId}`);
+ apiErr.status = 404;
+ return Promise.reject(apiErr);
+ }
// check context for project members
- project.members = _.map(req.context.currentProjectMembers, m => _.pick(m, fields.project_members));
+ project.members = _.map(req.context.currentProjectMembers, m => _.pick(m, fields.project_members));
// check if attachments field was requested
- if (!req.query.fields || _.indexOf(req.query.fields, 'attachments') > -1) {
- return util.getProjectAttachments(req, project.id);
- }
+ if (!req.query.fields || _.indexOf(req.query.fields, 'attachments') > -1) {
+ return util.getProjectAttachments(req, project.id);
+ }
// return null if attachments were not requested.
- return Promise.resolve(null);
- })
+ return Promise.resolve(null);
+ })
.then((attachments) => {
// if attachments were requested
if (attachments) {
@@ -68,12 +139,31 @@ module.exports = [
})
.then((invites) => {
project.invites = invites;
- return models.ScopeChangeRequest.getProjectScopeChangeRequests(projectId);
- })
- .then((scopeChangeRequests) => {
- project.scopeChangeRequests = scopeChangeRequests;
- res.status(200).json(util.wrapResponse(req.id, util.maskInviteEmails('$.invites[?(@.email)]', project, req)));
- })
+ return project;
+ });
+};
+
+
+module.exports = [
+ permissions('project.view'),
+ /**
+ * GET projects/{projectId}
+ * Get a project by id
+ */
+ (req, res, next) => {
+ const projectId = Number(req.params.projectId);
+ // parse the fields string to determine what fields are to be returned
+
+ return retrieveProjectFromES(projectId, req).then((result) => {
+ if (result === undefined) {
+ req.log.debug('No project found in ES');
+ return retrieveProjectFromDB(projectId, req);
+ }
+ req.log.debug('Project found in ES');
+ return result;
+ }).then((project) => {
+ res.status(200).json(util.maskInviteEmails('$.invites[?(@.email)]', project, req));
+ })
.catch(err => next(err));
},
];
diff --git a/src/routes/projects/get.spec.js b/src/routes/projects/get.spec.js
index af1b4e30..94e6e225 100644
--- a/src/routes/projects/get.spec.js
+++ b/src/routes/projects/get.spec.js
@@ -1,20 +1,83 @@
/* eslint-disable no-unused-expressions */
+/* eslint-disable max-len */
import chai from 'chai';
import sinon from 'sinon';
import request from 'supertest';
+import config from 'config';
import models from '../../models';
import util from '../../util';
import server from '../../app';
import testUtil from '../../tests/util';
+
+const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName');
+const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType');
+
const should = chai.should();
+const data = [
+ {
+ id: 5,
+ type: 'generic',
+ billingAccountId: 1,
+ name: 'test1',
+ description: 'es_project',
+ status: 'active',
+ details: {
+ utm: {
+ code: 'code1',
+ },
+ },
+ createdBy: 1,
+ updatedBy: 1,
+ lastActivityAt: 1,
+ lastActivityUserId: '1',
+ members: [
+ {
+ id: 1,
+ userId: 40051331,
+ projectId: 1,
+ role: 'customer',
+ firstName: 'es_member_1_firstName',
+ lastName: 'Lastname',
+ handle: 'test_tourist_handle',
+ isPrimary: true,
+ createdBy: 1,
+ updatedBy: 1,
+ },
+ {
+ id: 2,
+ userId: 40051332,
+ projectId: 1,
+ role: 'copilot',
+ isPrimary: true,
+ createdBy: 1,
+ updatedBy: 1,
+ },
+ ],
+ attachments: [
+ {
+ id: 1,
+ title: 'Spec',
+ projectId: 1,
+ description: 'specification',
+ filePath: 'projects/1/spec.pdf',
+ contentType: 'application/pdf',
+ createdBy: 1,
+ updatedBy: 1,
+ },
+ ],
+ },
+];
+
describe('GET Project', () => {
+ // only add project1 to es
let project1;
let project2;
before((done) => {
testUtil.clearDb()
+ .then(() => testUtil.clearES())
.then(() => {
const p1 = models.Project.create({
type: 'generic',
@@ -53,7 +116,8 @@ describe('GET Project', () => {
type: 'visual_design',
billingAccountId: 1,
name: 'test2',
- description: 'test project2',
+ description: 'db_project',
+ id: 2,
status: 'draft',
details: {},
createdBy: 1,
@@ -62,44 +126,68 @@ describe('GET Project', () => {
lastActivityUserId: '1',
}).then((p) => {
project2 = p;
+ return models.ProjectMember.create({
+ userId: 40051335,
+ projectId: project2.id,
+ role: 'manager',
+ handle: 'manager_handle',
+ isPrimary: true,
+ createdBy: 1,
+ updatedBy: 1,
+ });
});
return Promise.all([p1, p2])
- .then(() => done());
+ .then(() => server.services.es.index({
+ index: ES_PROJECT_INDEX,
+ type: ES_PROJECT_TYPE,
+ id: data[0].id,
+ body: data[0],
+ })).then(() => {
+ // sleep for some time, let elasticsearch indices be settled
+ // sleep.sleep(5);
+ testUtil.wait(done);
+ // done();
+ });
});
});
after((done) => {
- testUtil.clearDb(done);
+ testUtil.clearDb()
+ .then(() => testUtil.clearES())
+ .then(done);
});
describe('GET /projects/{id}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get(`/v4/projects/${project2.id}`)
- .expect(403, done);
+ .get(`/v5/projects/${project2.id}`)
+ .expect(403)
+ .end(done);
});
it('should return 404 if requested project doesn\'t exist', (done) => {
request(server)
- .get('/v4/projects/14343323')
+ .get('/v5/projects/14343323')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .expect(404, done);
+ .expect(404)
+ .end(done);
});
- it('should return 404 if user does not have access to the project', (done) => {
+ it('should return 403 if user does not have access to the project', (done) => {
request(server)
- .get(`/v4/projects/${project2.id}`)
+ .get(`/v5/projects/${project2.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
- .expect(403, done);
+ .expect(403)
+ .end(done);
});
it('should return the project when registerd member attempts to access the project', (done) => {
request(server)
- .get(`/v4/projects/${project1.id}/?fields=id%2Cname%2Cstatus%2Cmembers.role%2Cmembers.id%2Cmembers.userId`)
+ .get(`/v5/projects/${project1.id}/?fields=id%2Cname%2Cstatus%2Cmembers.role%2Cmembers.id%2Cmembers.userId`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -109,7 +197,7 @@ describe('GET Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
should.not.exist(resJson.deletedAt);
should.not.exist(resJson.billingAccountId);
@@ -121,9 +209,53 @@ describe('GET Project', () => {
});
});
+ it('should return project with "members", "invites", and "attachments" by default when data comes from ES', (done) => {
+ request(server)
+ .get(`/v5/projects/${data[0].id}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.admin}`,
+ })
+ .expect('Content-Type', /json/)
+ .expect(200)
+ .end((err, res) => {
+ if (err) {
+ done(err);
+ } else {
+ const resJson = res.body;
+ should.exist(resJson);
+ resJson.description.should.be.eql('es_project');
+ resJson.members.should.have.lengthOf(2);
+ resJson.members[0].firstName.should.equal('es_member_1_firstName');
+ done();
+ }
+ });
+ });
+
+ it('should return project with "members", "invites", and "attachments" by default when data comes from DB', (done) => {
+ request(server)
+ .get(`/v5/projects/${project2.id}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.admin}`,
+ })
+ .expect('Content-Type', /json/)
+ .expect(200)
+ .end((err, res) => {
+ if (err) {
+ done(err);
+ } else {
+ const resJson = res.body;
+ should.exist(resJson);
+ resJson.description.should.be.eql('db_project');
+ resJson.members.should.have.lengthOf(1);
+ resJson.members[0].role.should.equal('manager');
+ done();
+ }
+ });
+ });
+
it('should return the project for administrator ', (done) => {
request(server)
- .get(`/v4/projects/${project1.id}`)
+ .get(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -133,7 +265,7 @@ describe('GET Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
done();
}
@@ -142,7 +274,7 @@ describe('GET Project', () => {
it('should return the project for connect admin ', (done) => {
request(server)
- .get(`/v4/projects/${project1.id}`)
+ .get(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -152,7 +284,7 @@ describe('GET Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
done();
}
@@ -188,7 +320,7 @@ describe('GET Project', () => {
const stub = sinon.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .get(`/v4/projects/${project1.id}`)
+ .get(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -199,7 +331,7 @@ describe('GET Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
spy.should.have.been.calledOnce;
resJson.attachments.should.have.lengthOf(1);
diff --git a/src/routes/projects/list-db.js b/src/routes/projects/list-db.js
deleted file mode 100644
index 29636da3..00000000
--- a/src/routes/projects/list-db.js
+++ /dev/null
@@ -1,144 +0,0 @@
-import _ from 'lodash';
-import Promise from 'bluebird';
-import models from '../../models';
-import { MANAGER_ROLES } from '../../constants';
-import util from '../../util';
-
-/**
- * API to handle retrieving projects
- *
- * Permissions:
- * Only users that have access to the project can retrieve it.
- *
- */
-const PROJECT_ATTRIBUTES = _.without(_.keys(models.Project.rawAttributes),
- 'utm',
- 'deletedAt',
-);
-const PROJECT_MEMBER_ATTRIBUTES = _.without(
- _.keys(models.ProjectMember.rawAttributes),
- 'deletedAt',
-);
-const PROJECT_ATTACHMENT_ATTRIBUTES = _.without(
- _.keys(models.ProjectAttachment.rawAttributes),
- 'deletedAt',
-
-);
-const retrieveProjects = (req, criteria, sort, ffields) => {
- // order by
- const order = sort ? [sort.split(' ')] : [['createdAt', 'asc']];
- let fields = ffields ? ffields.split(',') : [];
- // parse the fields string to determine what fields are to be returned
- fields = util.parseFields(fields, {
- projects: PROJECT_ATTRIBUTES,
- project_members: PROJECT_MEMBER_ATTRIBUTES,
- });
- // make sure project.id is part of fields
- if (_.indexOf(fields.projects, 'id') < 0) fields.projects.push('id');
- const retrieveAttachments = !req.query.fields || req.query.fields.indexOf('attachments') > -1;
- const retrieveMembers = !req.query.fields || !!fields.project_members.length;
-
- return models.Project.searchText({
- filters: criteria.filters,
- order,
- limit: criteria.limit,
- offset: criteria.offset,
- attributes: _.get(fields, 'projects', null),
- }, req.log)
- .then(({ rows, count }) => {
- const projectIds = _.map(rows, 'id');
- const promises = [];
- // retrieve members
- if (projectIds.length && retrieveMembers) {
- promises.push(
- models.ProjectMember.findAll({
- attributes: _.get(fields, 'ProjectMembers'),
- where: { projectId: { in: projectIds } },
- raw: true,
- }),
- );
- }
- if (projectIds.length && retrieveAttachments) {
- promises.push(
- models.ProjectAttachment.findAll({
- attributes: PROJECT_ATTACHMENT_ATTRIBUTES,
- where: { projectId: { in: projectIds } },
- raw: true,
- }),
- );
- }
- // return results after promise(s) have resolved
- return Promise.all(promises)
- .then((values) => {
- const allMembers = retrieveMembers ? values.shift() : [];
- const allAttachments = retrieveAttachments ? values.shift() : [];
- _.forEach(rows, (fp) => {
- const p = fp;
- // if values length is 1 it could be either attachments or members
- if (retrieveMembers) {
- p.members = _.filter(allMembers, m => m.projectId === p.id);
- }
- if (retrieveAttachments) {
- p.attachments = _.filter(allAttachments, a => a.projectId === p.id);
- }
- });
- return { rows, count };
- });
- });
-};
-
-module.exports = [
- /**
- * GET projects/
- * Return a list of projects that match the criteria
- */
- (req, res, next) => {
- // handle filters
- let filters = util.parseQueryFilter(req.query.filter);
- let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'createdAt';
- if (sort && sort.indexOf(' ') === -1) {
- sort += ' asc';
- }
- const sortableProps = [
- 'createdAt', 'createdAt asc', 'createdAt desc',
- 'updatedAt', 'updatedAt asc', 'updatedAt desc',
- 'lastActivityAt', 'lastActivityAt asc', 'lastActivityAt desc',
- 'id', 'id asc', 'id desc',
- 'status', 'status asc', 'status desc',
- 'name', 'name asc', 'name desc',
- 'type', 'type asc', 'type desc',
- ];
- if (!util.isValidFilter(filters, ['id', 'status', 'type', 'memberOnly', 'keyword']) ||
- (sort && _.indexOf(sortableProps, sort) < 0)) {
- 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);
- filters = _.omit(filters, 'memberOnly');
-
- const criteria = {
- filters,
- limit: Math.min(req.query.limit || 20, 20),
- offset: req.query.offset || 0,
- };
- req.log.debug(criteria);
-
- if (!memberOnly
- && (util.hasAdminRole(req)
- || util.hasRoles(req, MANAGER_ROLES))) {
- // admins & topcoder managers can see all projects
- return retrieveProjects(req, criteria, sort, req.query.fields)
- .then(result => res.json(util.wrapResponse(req.id,
- util.maskInviteEmails('$[*].invites[?(@.email)]', result.rows, req), result.count)))
- .catch(err => next(err));
- }
-
- // regular users can only see projects they are members of (or invited, handled bellow)
- criteria.filters.userId = req.authUser.userId;
- criteria.filters.email = req.authUser.email;
- return retrieveProjects(req, criteria, sort, req.query.fields)
- .then(result => res.json(util.wrapResponse(req.id,
- util.maskInviteEmails('$[*].invites[?(@.email)]', result.rows, req), result.count)))
- .catch(err => next(err));
- },
-];
diff --git a/src/routes/projects/list-db.spec.js b/src/routes/projects/list-db.spec.js
deleted file mode 100644
index f7022e51..00000000
--- a/src/routes/projects/list-db.spec.js
+++ /dev/null
@@ -1,480 +0,0 @@
-/* eslint-disable no-unused-expressions */
-/* eslint-disable max-len */
-import chai from 'chai';
-import request from 'supertest';
-
-import models from '../../models';
-import server from '../../app';
-import testUtil from '../../tests/util';
-
-const should = chai.should();
-
-/**
- * Add full text index for projects.
- * @return {Promise} returns the promise
- */
-function addFullTextIndex() {
- if (models.sequelize.options.dialect !== 'postgres') {
- return null;
- }
-
- return models.sequelize
- .query('ALTER TABLE projects ADD COLUMN "projectFullText" text;')
- .then(() => models.sequelize
- .query('UPDATE projects SET "projectFullText" = lower(' +
- 'name || \' \' || coalesce(description, \'\') || \' \' || coalesce(details#>>\'{utm, code}\', \'\'));'))
- .then(() => models.sequelize
- .query('CREATE EXTENSION IF NOT EXISTS pg_trgm;')).then(() => models.sequelize
- .query('CREATE INDEX project_text_search_idx ON projects USING GIN("projectFullText" gin_trgm_ops);'))
- .then(() => models.sequelize
- .query('CREATE OR REPLACE FUNCTION project_text_update_trigger() RETURNS trigger AS $$ ' +
- 'begin ' +
- 'new."projectFullText" := ' +
- 'lower(new.name || \' \' || coalesce(new.description, \'\') || \' \' || ' +
- ' coalesce(new.details#>>\'{utm, code}\', \'\')); ' +
- 'return new; ' +
- 'end ' +
- '$$ LANGUAGE plpgsql;'))
- .then(() => models.sequelize
- .query('DROP TRIGGER IF EXISTS project_text_update ON projects;'))
- .then(() => models.sequelize
- .query('CREATE TRIGGER project_text_update BEFORE INSERT OR UPDATE ON projects' +
- ' FOR EACH ROW EXECUTE PROCEDURE project_text_update_trigger();'));
-}
-
-describe('LIST Project db', () => {
- let project1;
- let project2;
- before((done) => {
- testUtil.clearDb()
- .then(() => addFullTextIndex())
- .then(() => {
- const p1 = models.Project.create({
- type: 'generic',
- billingAccountId: 1,
- name: 'test1',
- description: 'test project1',
- status: 'active',
- details: {
- utm: {
- code: 'code1',
- },
- },
- createdBy: 1,
- updatedBy: 1,
- lastActivityAt: 1,
- lastActivityUserId: '1',
- }).then((p) => {
- project1 = p;
- // create members
- const pm1 = models.ProjectMember.create({
- userId: 40051331,
- projectId: project1.id,
- role: 'customer',
- isPrimary: true,
- createdBy: 1,
- updatedBy: 1,
- });
- const pm2 = models.ProjectMember.create({
- userId: 40051332,
- projectId: project1.id,
- role: 'copilot',
- isPrimary: true,
- createdBy: 1,
- updatedBy: 1,
- });
- const pa1 = models.ProjectAttachment.create({
- title: 'Spec',
- projectId: project1.id,
- description: 'specification',
- filePath: 'projects/1/spec.pdf',
- contentType: 'application/pdf',
- createdBy: 1,
- updatedBy: 1,
- });
- return Promise.all([pm1, pm2, pa1]);
- });
-
- const p2 = models.Project.create({
- type: 'visual_design',
- billingAccountId: 1,
- name: 'test2',
- description: 'test project2',
- status: 'draft',
- details: {},
- createdBy: 1,
- updatedBy: 1,
- lastActivityAt: 2,
- lastActivityUserId: '1',
- }).then((p) => {
- project2 = p;
- return models.ProjectMember.create({
- userId: 40051332,
- projectId: project2.id,
- role: 'copilot',
- isPrimary: true,
- createdBy: 1,
- updatedBy: 1,
- });
- });
- const p3 = models.Project.create({
- type: 'visual_design',
- billingAccountId: 1,
- name: 'test3',
- description: 'test project3',
- status: 'reviewed',
- details: {},
- createdBy: 1,
- updatedBy: 1,
- lastActivityAt: 3,
- lastActivityUserId: '1',
- });
- return Promise.all([p1, p2, p3])
- .then(() => done());
- });
- });
-
- after((done) => {
- testUtil.clearDb(done);
- });
-
- describe('GET All /projects/', () => {
- it('should return 403 if user is not authenticated', (done) => {
- request(server)
- .get('/v4/projects/db/')
- .expect(403, done);
- });
-
- it('should return 200 and no projects if user does not have access', (done) => {
- request(server)
- .get(`/v4/projects/db/?filter=id%3Din%28${project2.id}%29`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.member}`,
- })
- .expect(200)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- res.body.result.content.should.have.lengthOf(0);
- done();
- }
- });
- });
-
- it('should return the project when registerd member attempts to access the project', (done) => {
- request(server)
- .get('/v4/projects/db/?filter=status%3Ddraft')
- .set({
- Authorization: `Bearer ${testUtil.jwts.copilot}`,
- })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const resJson = res.body.result.content;
- res.body.result.metadata.totalCount.should.equal(1);
- should.exist(resJson);
- resJson.should.have.lengthOf(1);
- resJson[0].id.should.equal(project2.id);
- done();
- }
- });
- });
-
- it('should return the project when project that is in reviewed state in which the copilot is its member or has been invited', (done) => {
- request(server)
- .get('/v4/projects/db/')
- .set({
- Authorization: `Bearer ${testUtil.jwts.copilot}`,
- })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const resJson = res.body.result.content;
- res.body.result.metadata.totalCount.should.equal(2);
- should.exist(resJson);
- resJson.should.have.lengthOf(2);
- done();
- }
- });
- });
-
- it('should return the project for administrator ', (done) => {
- request(server)
- .get('/v4/projects/db/?fields=id%2Cmembers.id')
- .set({
- Authorization: `Bearer ${testUtil.jwts.admin}`,
- })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const resJson = res.body.result.content;
- should.exist(resJson);
- resJson.should.have.lengthOf(3);
- done();
- }
- });
- });
-
- it('should return all projects that match when filtering by name', (done) => {
- request(server)
- .get('/v4/projects/db/?filter=keyword%3Dtest')
- .set({
- Authorization: `Bearer ${testUtil.jwts.admin}`,
- })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const resJson = res.body.result.content;
- should.exist(resJson);
- resJson.should.have.lengthOf(3);
- done();
- }
- });
- });
-
- it('should return the project when filtering by keyword, which matches the name', (done) => {
- request(server)
- .get('/v4/projects/db/?filter=keyword%3D1')
- .set({
- Authorization: `Bearer ${testUtil.jwts.admin}`,
- })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const resJson = res.body.result.content;
- should.exist(resJson);
- resJson.should.have.lengthOf(1);
- resJson[0].name.should.equal('test1');
- done();
- }
- });
- });
-
- it('should return the project when filtering by keyword, which matches the description', (done) => {
- request(server)
- .get('/v4/projects/db/?filter=keyword%3Dproject')
- .set({
- Authorization: `Bearer ${testUtil.jwts.admin}`,
- })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const resJson = res.body.result.content;
- should.exist(resJson);
- resJson.should.have.lengthOf(3);
- done();
- }
- });
- });
-
- it('should return the project when filtering by keyword, which matches the details', (done) => {
- request(server)
- .get('/v4/projects/db/?filter=keyword%3Dcode')
- .set({
- Authorization: `Bearer ${testUtil.jwts.admin}`,
- })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const resJson = res.body.result.content;
- should.exist(resJson);
- resJson.should.have.lengthOf(1);
- resJson[0].name.should.equal('test1');
- done();
- }
- });
- });
-
- describe('for connect admin ', () => {
- it('should return the project ', (done) => {
- request(server)
- .get('/v4/projects/db/?fields=id%2Cmembers.id')
- .set({
- Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
- })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const resJson = res.body.result.content;
- should.exist(resJson);
- resJson.should.have.lengthOf(3);
- done();
- }
- });
- });
-
- it('should return all projects that match when filtering by name', (done) => {
- request(server)
- .get('/v4/projects/db/?filter=keyword%3Dtest')
- .set({
- Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
- })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const resJson = res.body.result.content;
- should.exist(resJson);
- resJson.should.have.lengthOf(3);
- done();
- }
- });
- });
-
- it('should return the project when filtering by keyword, which matches the name', (done) => {
- request(server)
- .get('/v4/projects/db/?filter=keyword%3D1')
- .set({
- Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
- })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const resJson = res.body.result.content;
- should.exist(resJson);
- resJson.should.have.lengthOf(1);
- resJson[0].name.should.equal('test1');
- done();
- }
- });
- });
-
- it('should return the project when filtering by keyword, which matches the description', (done) => {
- request(server)
- .get('/v4/projects/db/?filter=keyword%3Dproject')
- .set({
- Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
- })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const resJson = res.body.result.content;
- should.exist(resJson);
- resJson.should.have.lengthOf(3);
- done();
- }
- });
- });
-
- it('should return the project when filtering by keyword, which matches the details', (done) => {
- request(server)
- .get('/v4/projects/db/?filter=keyword%3Dcode')
- .set({
- Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
- })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const resJson = res.body.result.content;
- should.exist(resJson);
- resJson.should.have.lengthOf(1);
- resJson[0].name.should.equal('test1');
- done();
- }
- });
- });
-
- it('should return list of projects ordered ascending by lastActivityAt when sort column is "lastActivityAt"', (done) => {
- request(server)
- .get('/v4/projects/db/?sort=lastActivityAt')
- .set({
- Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
- })
- .expect(200)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const resJson = res.body.result.content;
- should.exist(resJson);
- resJson.should.have.lengthOf(3);
- resJson[0].name.should.equal('test1');
- resJson[1].name.should.equal('test2');
- resJson[2].name.should.equal('test3');
- done();
- }
- });
- });
-
- it('should return list of projects ordered descending by lastActivityAt when sort column is "lastActivityAt desc"', (done) => {
- request(server)
- .get('/v4/projects/db/?sort=lastActivityAt desc')
- .set({
- Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
- })
- .expect(200)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const resJson = res.body.result.content;
- should.exist(resJson);
- resJson.should.have.lengthOf(3);
- resJson[0].name.should.equal('test3');
- resJson[1].name.should.equal('test2');
- resJson[2].name.should.equal('test1');
- done();
- }
- });
- });
-
- it('should return list of projects ordered ascending by lastActivityAt when sort column is "lastActivityAt asc"', (done) => {
- request(server)
- .get('/v4/projects/db/?sort=lastActivityAt asc')
- .set({
- Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
- })
- .expect(200)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const resJson = res.body.result.content;
- should.exist(resJson);
- resJson.should.have.lengthOf(3);
- resJson[0].name.should.equal('test1');
- resJson[1].name.should.equal('test2');
- resJson[2].name.should.equal('test3');
- done();
- }
- });
- });
- });
- });
-});
diff --git a/src/routes/projects/list.js b/src/routes/projects/list.js
index 4e347019..7ec1cbb6 100755
--- a/src/routes/projects/list.js
+++ b/src/routes/projects/list.js
@@ -1,3 +1,4 @@
+
/* globals Promise */
import _ from 'lodash';
@@ -285,10 +286,10 @@ const parseElasticSearchCriteria = (criteria, fields, order) => {
let mustQuery = [];
let shouldQuery = [];
let fullTextQuery;
- if (_.has(criteria, 'filters.id.$in')) {
+ if (_.has(criteria, 'filters.id') && _.isArray(criteria.filters.id)) {
boolQuery.push({
ids: {
- values: criteria.filters.id.$in,
+ values: criteria.filters.id,
},
});
} else if (_.has(criteria, 'filters.id')) {
@@ -430,6 +431,68 @@ const parseElasticSearchCriteria = (criteria, fields, order) => {
return searchCriteria;
};
+const retrieveProjectsFromDB = (req, criteria, sort, ffields) => {
+ // order by
+ const order = sort ? [sort.split(' ')] : [['createdAt', 'asc']];
+ let fields = ffields ? ffields.split(',') : [];
+ // parse the fields string to determine what fields are to be returned
+ fields = util.parseFields(fields, {
+ projects: PROJECT_ATTRIBUTES,
+ project_members: PROJECT_MEMBER_ATTRIBUTES,
+ });
+ // make sure project.id is part of fields
+ if (_.indexOf(fields.projects, 'id') < 0) fields.projects.push('id');
+ const retrieveAttachments = !req.query.fields || req.query.fields.indexOf('attachments') > -1;
+ const retrieveMembers = !req.query.fields || !!fields.project_members.length;
+
+ return models.Project.searchText({
+ filters: criteria.filters,
+ order,
+ limit: criteria.limit,
+ offset: criteria.offset,
+ attributes: _.get(fields, 'projects', null),
+ }, req.log)
+ .then(({ rows, count }) => {
+ const projectIds = _.map(rows, 'id');
+ const promises = [];
+ // retrieve members
+ if (projectIds.length && retrieveMembers) {
+ promises.push(
+ models.ProjectMember.findAll({
+ attributes: _.get(fields, 'ProjectMembers'),
+ where: { projectId: { $in: projectIds } },
+ raw: true,
+ }),
+ );
+ }
+ if (projectIds.length && retrieveAttachments) {
+ promises.push(
+ models.ProjectAttachment.findAll({
+ attributes: PROJECT_ATTACHMENT_ATTRIBUTES,
+ where: { projectId: { $in: projectIds } },
+ raw: true,
+ }),
+ );
+ }
+ // return results after promise(s) have resolved
+ return Promise.all(promises)
+ .then((values) => {
+ const allMembers = retrieveMembers ? values.shift() : [];
+ const allAttachments = retrieveAttachments ? values.shift() : [];
+ _.forEach(rows, (fp) => {
+ const p = fp;
+ // if values length is 1 it could be either attachments or members
+ if (retrieveMembers) {
+ p.members = _.filter(allMembers, m => m.projectId === p.id);
+ }
+ if (retrieveAttachments) {
+ p.attachments = _.filter(allAttachments, a => a.projectId === p.id);
+ }
+ });
+ return { rows, count, pageSize: criteria.limit, page: criteria.page };
+ });
+ });
+};
const retrieveProjects = (req, criteria, sort, ffields) => {
// order by
@@ -454,7 +517,7 @@ const retrieveProjects = (req, criteria, sort, ffields) => {
const es = util.getElasticSearchClient();
es.search(searchCriteria).then((docs) => {
const rows = _.map(docs.hits.hits, single => single._source); // eslint-disable-line no-underscore-dangle
- accept({ rows, count: docs.hits.total });
+ accept({ rows, count: docs.hits.total, pageSize: criteria.limit, page: criteria.page });
}).catch(reject);
});
};
@@ -466,7 +529,8 @@ module.exports = [
*/
(req, res, next) => {
// handle filters
- let filters = util.parseQueryFilter(req.query.filter);
+ let filters = _.omit(req.query, 'sort', 'perPage', 'page', 'fields');
+
let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'createdAt';
if (sort && sort.indexOf(' ') === -1) {
sort += ' asc';
@@ -490,10 +554,12 @@ module.exports = [
const memberOnly = _.get(filters, 'memberOnly', false);
filters = _.omit(filters, 'memberOnly');
+ const limit = Math.min(req.query.perPage || config.pageSize, config.pageSize);
const criteria = {
filters,
- limit: Math.min(req.query.limit || 20, 20),
- offset: req.query.offset || 0,
+ limit,
+ offset: ((req.query.page - 1) * limit) || 0,
+ page: req.query.page || 1,
};
req.log.info(criteria);
if (!memberOnly
@@ -501,8 +567,16 @@ module.exports = [
|| util.hasRoles(req, MANAGER_ROLES))) {
// admins & topcoder managers can see all projects
return retrieveProjects(req, criteria, sort, req.query.fields)
- .then(result => res.json(util.wrapResponse(req.id,
- util.maskInviteEmails('$[*].invites[?(@.email)]', result.rows, req), result.count)))
+ .then((result) => {
+ if (result.rows.length === 0) {
+ req.log.debug('No projects found in ES');
+ return retrieveProjectsFromDB(req, criteria, sort, req.query.fields)
+ .then(r => util.setPaginationHeaders(req, res, util.maskInviteEmails('$[*].invites[?(@.email)]', r, req)));
+ }
+ req.log.debug('Projects found in ES');
+ // set header
+ return util.setPaginationHeaders(req, res, util.maskInviteEmails('$[*].invites[?(@.email)]', result, req));
+ })
.catch(err => next(err));
}
@@ -510,8 +584,15 @@ module.exports = [
criteria.filters.email = req.authUser.email;
criteria.filters.userId = req.authUser.userId;
return retrieveProjects(req, criteria, sort, req.query.fields)
- .then(result => res.json(util.wrapResponse(req.id,
- util.maskInviteEmails('$[*].invites[?(@.email)]', result.rows, req), result.count)))
+ .then((result) => {
+ if (result.rows.length === 0) {
+ req.log.debug('No projects found in ES');
+ return retrieveProjectsFromDB(req, criteria, sort, req.query.fields)
+ .then(r => util.setPaginationHeaders(req, res, util.maskInviteEmails('$[*].invites[?(@.email)]', r, req)));
+ }
+ req.log.debug('Projects found in ES');
+ return util.setPaginationHeaders(req, res, util.maskInviteEmails('$[*].invites[?(@.email)]', result, req));
+ })
.catch(err => next(err));
},
];
diff --git a/src/routes/projects/list.spec.js b/src/routes/projects/list.spec.js
index 3edc9f4c..7b1f00e3 100644
--- a/src/routes/projects/list.spec.js
+++ b/src/routes/projects/list.spec.js
@@ -2,7 +2,7 @@
/* eslint-disable max-len */
import chai from 'chai';
import request from 'supertest';
-import sleep from 'sleep';
+// import sleep from 'sleep';
import config from 'config';
import models from '../../models';
import server from '../../app';
@@ -126,6 +126,7 @@ describe('LIST Project', () => {
before(function inner(done) {
this.timeout(10000);
testUtil.clearDb()
+ .then(() => testUtil.clearES())
.then(() => {
const p1 = models.Project.create({
type: 'generic',
@@ -246,8 +247,9 @@ describe('LIST Project', () => {
return Promise.all([esp1, esp2, esp3]);
}).then(() => {
// sleep for some time, let elasticsearch indices be settled
- sleep.sleep(5);
- done();
+ // sleep.sleep(5);
+ testUtil.wait(done);
+ // done();
});
});
});
@@ -259,13 +261,14 @@ describe('LIST Project', () => {
describe('GET All /projects/', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/projects/')
+ .get('/v5/projects/')
.expect(403, done);
});
it('should return 200 and no projects if user does not have access', (done) => {
request(server)
- .get(`/v4/projects/?filter=id%3Din%28${project2.id}%29`)
+ // .get(`/v5/projects/?id=in%28${project2.id}%29`)
+ .get(`/v5/projects/?id=${project2.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -274,7 +277,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- res.body.result.content.should.have.lengthOf(0);
+ res.body.should.have.lengthOf(0);
done();
}
});
@@ -282,7 +285,7 @@ describe('LIST Project', () => {
it('should return the project when registerd member attempts to access the project', (done) => {
request(server)
- .get('/v4/projects/?filter=status%3Ddraft')
+ .get('/v5/projects/?status=draft')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -292,8 +295,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
- res.body.result.metadata.totalCount.should.equal(1);
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(1);
// since project 2 is indexed with id 2
@@ -305,7 +307,7 @@ describe('LIST Project', () => {
it('should return the project when project that is in reviewed state in which the copilot is its member or has been invited', (done) => {
request(server)
- .get('/v4/projects')
+ .get('/v5/projects')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -315,8 +317,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
- res.body.result.metadata.totalCount.should.equal(2);
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(2);
done();
@@ -326,7 +327,7 @@ describe('LIST Project', () => {
it('should return the project for administrator ', (done) => {
request(server)
- .get('/v4/projects/?fields=id%2Cmembers.id')
+ .get('/v5/projects/?fields=id,members.id')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -336,7 +337,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(3);
done();
@@ -347,7 +348,7 @@ describe('LIST Project', () => {
it('should return the project for administrator with field description, billingAccountId and attachments',
(done) => {
request(server)
- .get('/v4/projects/?fields=description%2CbillingAccountId%2Cattachments&sort=id%20asc')
+ .get('/v5/projects/?fields=description,billingAccountId,attachments&sort=id asc')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -357,7 +358,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(3);
resJson[0].should.have.property('attachments');
@@ -379,7 +380,7 @@ describe('LIST Project', () => {
it('should return the project for administrator with field description and billingAccountId', (done) => {
request(server)
- .get('/v4/projects/?fields=description%2CbillingAccountId&sort=id%20asc')
+ .get('/v5/projects/?fields=description,billingAccountId&sort=id asc')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -389,10 +390,10 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(3);
- resJson[0].should.not.have.property('attachments');
+ resJson[0].should.have.property('attachments');
resJson[0].should.have.property('description');
resJson[0].should.have.property('billingAccountId');
done();
@@ -402,7 +403,7 @@ describe('LIST Project', () => {
it('should return the project for administrator with all field', (done) => {
request(server)
- .get('/v4/projects/?sort=id%20asc')
+ .get('/v5/projects/?sort=id asc')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -412,7 +413,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(3);
resJson[0].should.have.property('id');
@@ -432,7 +433,7 @@ describe('LIST Project', () => {
it('should return all projects that match when filtering by name', (done) => {
request(server)
- .get('/v4/projects/?filter=keyword%3Dtest')
+ .get('/v5/projects/?keyword=test')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -442,7 +443,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(3);
done();
@@ -452,7 +453,7 @@ describe('LIST Project', () => {
it('should return the project when filtering by keyword, which matches the name', (done) => {
request(server)
- .get('/v4/projects/?filter=keyword%3D1')
+ .get('/v5/projects/?keyword=1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -462,7 +463,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(1);
resJson[0].name.should.equal('test1');
@@ -473,7 +474,7 @@ describe('LIST Project', () => {
it('should return the project when filtering by keyword, which matches the description', (done) => {
request(server)
- .get('/v4/projects/?filter=keyword%3Dproject')
+ .get('/v5/projects/?keyword=project')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -483,7 +484,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(3);
done();
@@ -493,7 +494,7 @@ describe('LIST Project', () => {
it('should return the project when filtering by keyword, which matches the member handle', (done) => {
request(server)
- .get('/v4/projects/?filter=keyword%3Dtourist')
+ .get('/v5/projects/?keyword=tourist')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -503,7 +504,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(1);
resJson[0].name.should.equal('test1');
@@ -514,7 +515,7 @@ describe('LIST Project', () => {
it('should return project that match when filtering by id (exact)', (done) => {
request(server)
- .get('/v4/projects/?filter=id%3D1')
+ .get('/v5/projects/?id=1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -524,7 +525,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(1);
resJson[0].id.should.equal(1);
@@ -536,7 +537,7 @@ describe('LIST Project', () => {
it('should return project that match when filtering by name', (done) => {
request(server)
- .get('/v4/projects/?filter=name%3Dtest1')
+ .get('/v5/projects/?name=test1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -546,7 +547,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(1);
resJson[0].name.should.equal('test1');
@@ -557,7 +558,7 @@ describe('LIST Project', () => {
it('should return project that match when filtering by name\'s substring', (done) => {
request(server)
- .get('/v4/projects/?filter=name%3D*st1')
+ .get('/v5/projects/?name=*st1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -567,7 +568,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(1);
resJson[0].name.should.equal('test1');
@@ -578,7 +579,7 @@ describe('LIST Project', () => {
it('should return all projects that match when filtering by details code', (done) => {
request(server)
- .get('/v4/projects/?filter=code%3Dcode1')
+ .get('/v5/projects/?code=code1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -588,7 +589,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(1);
resJson[0].name.should.equal('test1');
@@ -600,7 +601,7 @@ describe('LIST Project', () => {
it('should return all projects that match when filtering by details code\'s substring', (done) => {
request(server)
- .get('/v4/projects/?filter=code%3D*de1')
+ .get('/v5/projects/?code=*de1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -610,7 +611,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(1);
resJson[0].name.should.equal('test1');
@@ -622,7 +623,7 @@ describe('LIST Project', () => {
it('should return all projects that match when filtering by customer', (done) => {
request(server)
- .get('/v4/projects/?filter=customer%3Dfirst*')
+ .get('/v5/projects/?customer=first*')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -632,7 +633,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(1);
resJson[0].name.should.equal('test1');
@@ -645,7 +646,7 @@ describe('LIST Project', () => {
it('should return all projects that match when filtering by manager', (done) => {
request(server)
- .get('/v4/projects/?filter=manager%3D*ast')
+ .get('/v5/projects/?manager=*ast')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -655,7 +656,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(1);
resJson[0].name.should.equal('test3');
@@ -668,7 +669,7 @@ describe('LIST Project', () => {
it('should return list of projects ordered ascending by lastActivityAt when sort column is "lastActivityAt"', (done) => {
request(server)
- .get('/v4/projects/?sort=lastActivityAt')
+ .get('/v5/projects/?sort=lastActivityAt')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -677,7 +678,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(3);
resJson[0].name.should.equal('test1');
@@ -690,7 +691,7 @@ describe('LIST Project', () => {
it('should return list of projects ordered descending by lastActivityAt when sort column is "lastActivityAt desc"', (done) => {
request(server)
- .get('/v4/projects/?sort=lastActivityAt desc')
+ .get('/v5/projects/?sort=lastActivityAt desc')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -699,7 +700,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(3);
resJson[0].name.should.equal('test3');
@@ -712,7 +713,7 @@ describe('LIST Project', () => {
it('should return list of projects ordered ascending by lastActivityAt when sort column is "lastActivityAt asc"', (done) => {
request(server)
- .get('/v4/projects/?sort=lastActivityAt asc')
+ .get('/v5/projects/?sort=lastActivityAt asc')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -721,7 +722,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(3);
resJson[0].name.should.equal('test1');
@@ -735,7 +736,7 @@ describe('LIST Project', () => {
describe('GET All /projects/ for Connect Admin, ', () => {
it('should return the project ', (done) => {
request(server)
- .get('/v4/projects/?fields=id%2Cmembers.id')
+ .get('/v5/projects/?fields=id,members.id')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -745,7 +746,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(3);
done();
@@ -755,7 +756,7 @@ describe('LIST Project', () => {
it('should return all projects, that match when filtering by name', (done) => {
request(server)
- .get('/v4/projects/?filter=keyword%3Dtest')
+ .get('/v5/projects/?keyword=test')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -765,7 +766,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(3);
done();
@@ -775,7 +776,7 @@ describe('LIST Project', () => {
it('should return the project, when filtering by keyword, which matches the name', (done) => {
request(server)
- .get('/v4/projects/?filter=keyword%3D1')
+ .get('/v5/projects/?keyword=1')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -785,7 +786,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(1);
resJson[0].name.should.equal('test1');
@@ -796,7 +797,7 @@ describe('LIST Project', () => {
it('should return the project, when filtering by keyword, which matches the description', (done) => {
request(server)
- .get('/v4/projects/?filter=keyword%3Dproject')
+ .get('/v5/projects/?keyword=project')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -806,7 +807,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(3);
done();
@@ -816,7 +817,7 @@ describe('LIST Project', () => {
it('should return the project, when filtering by keyword, which matches the member handle', (done) => {
request(server)
- .get('/v4/projects/?filter=keyword%3Dtourist')
+ .get('/v5/projects/?keyword=tourist')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -826,7 +827,7 @@ describe('LIST Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(1);
resJson[0].name.should.equal('test1');
diff --git a/src/routes/projects/update.js b/src/routes/projects/update.js
index 4f0ebee5..e714d064 100644
--- a/src/routes/projects/update.js
+++ b/src/routes/projects/update.js
@@ -9,15 +9,14 @@ import {
PROJECT_STATUS,
PROJECT_MEMBER_ROLE,
EVENT,
+ RESOURCES,
USER_ROLE,
REGEX,
} from '../../constants';
import util from '../../util';
-import directProject from '../../services/directProject';
const traverse = require('traverse');
-
/**
* API to handle updating a project.
*/
@@ -39,51 +38,49 @@ const mergeCustomizer = (objValue, srcValue) => {
};
const updateProjectValdiations = {
- body: {
- param: Joi.object().keys({
- id: Joi.number().valid(Joi.ref('$params.id')),
- name: Joi.string(),
- description: Joi.string().allow(null).allow('').optional(),
- billingAccountId: Joi.number().positive(),
- directProjectId: Joi.number().positive().allow(null),
- status: Joi.any().valid(_.values(PROJECT_STATUS)),
- estimatedPrice: Joi.number().precision(2).positive().allow(null),
- actualPrice: Joi.number().precision(2).positive(),
- terms: Joi.array().items(Joi.number().positive()),
- external: Joi.object().keys({
- id: Joi.string(),
- type: Joi.any().valid('github', 'jira', 'asana', 'other'),
- data: Joi.string().max(300), // TODO - restrict length
- }).allow(null),
- bookmarks: Joi.array().items(Joi.object().keys({
- title: Joi.string(),
- address: Joi.string().regex(REGEX.URL),
- createdAt: Joi.date(),
- createdBy: Joi.number().integer().positive(),
- updatedAt: Joi.date(),
- updatedBy: Joi.number().integer().positive(),
- })).optional().allow(null),
- type: Joi.string().max(45),
- details: Joi.any(),
- memers: Joi.any(),
- templateId: Joi.any().strip(), // ignore the template id
- createdBy: Joi.any(),
- createdAt: Joi.any(),
- updatedBy: Joi.any(),
- updatedAt: 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),
+ body: Joi.object().keys({
+ id: Joi.number().valid(Joi.ref('$params.id')),
+ name: Joi.string(),
+ description: Joi.string().allow(null).allow('').optional(),
+ billingAccountId: Joi.number().positive(),
+ directProjectId: Joi.number().positive().allow(null),
+ status: Joi.any().valid(_.values(PROJECT_STATUS)),
+ estimatedPrice: Joi.number().precision(2).positive().allow(null),
+ actualPrice: Joi.number().precision(2).positive(),
+ terms: Joi.array().items(Joi.number().positive()),
+ external: Joi.object().keys({
+ id: Joi.string(),
+ type: Joi.any().valid('github', 'jira', 'asana', 'other'),
+ data: Joi.string().max(300), // TODO - restrict length
+ }).allow(null),
+ bookmarks: Joi.array().items(Joi.object().keys({
+ title: Joi.string(),
+ address: Joi.string().regex(REGEX.URL),
+ createdAt: Joi.date(),
+ createdBy: Joi.number().integer().positive(),
+ updatedAt: Joi.date(),
+ updatedBy: Joi.number().integer().positive(),
+ })).optional().allow(null),
+ type: Joi.string().max(45),
+ details: Joi.any(),
+ memers: Joi.any(),
+ templateId: Joi.any().strip(), // ignore the template id
+ createdBy: Joi.any(),
+ createdAt: Joi.any(),
+ updatedBy: Joi.any(),
+ updatedAt: 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),
// 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(),
- }),
+ cancelReason: Joi.when('status', {
+ is: PROJECT_STATUS.CANCELLED,
+ then: Joi.string().required(),
+ otherwise: Joi.string().optional(),
}),
- },
+ }),
};
/**
@@ -163,14 +160,14 @@ module.exports = [
* Validate project type to be existed.
*/
(req, res, next) => {
- if (req.body.param.type) {
- models.ProjectType.findOne({ where: { key: req.body.param.type } })
+ if (req.body.type) {
+ models.ProjectType.findOne({ where: { key: req.body.type } })
.then((projectType) => {
if (projectType) {
next();
} else {
- const err = new Error(`Project type not found for key ${req.body.param.type}`);
- err.status = 422;
+ const err = new Error(`Project type not found for key ${req.body.type}`);
+ err.status = 400;
next(err);
}
});
@@ -184,7 +181,7 @@ module.exports = [
*/
(req, res, next) => {
let project;
- let updatedProps = req.body.param;
+ let updatedProps = req.body;
const projectId = _.parseInt(req.params.projectId);
// prune any fields that cannot be updated directly
updatedProps = _.omit(updatedProps, ['createdBy', 'createdAt', 'updatedBy', 'updatedAt', 'id']);
@@ -254,20 +251,6 @@ module.exports = [
project.set(newValues);
return project.save();
})
- .then(() => {
- if (updatedProps.billingAccountId &&
- (previousValue.billingAccountId !== updatedProps.billingAccountId)) {
- if (!previousValue.directProjectId) {
- return Promise.resolve();
- }
- // if billing account is updated and exist direct projectId we
- // should invoke direct project service
- return directProject.addBillingAccount(req, previousValue.directProjectId, {
- billingAccountId: updatedProps.billingAccountId,
- });
- }
- return Promise.resolve();
- })
.then(() => project.reload(project.id))
// update project history
.then(() => new Promise((accept, reject) => {
@@ -300,8 +283,9 @@ module.exports = [
req.app.emit(EVENT.ROUTING_KEY.PROJECT_UPDATED, {
req,
original: previousValue,
- updated: project,
+ updated: _.assign({ resource: RESOURCES.PROJECT }, project),
});
+
// check context for project members
project.members = req.context.currentProjectMembers;
// get attachments
@@ -310,7 +294,7 @@ module.exports = [
.then((attachments) => {
// make sure we only send response after transaction is committed
project.attachments = attachments;
- res.json(util.wrapResponse(req.id, project));
+ res.json(project);
})
.catch(err => next(err));
},
diff --git a/src/routes/projects/update.spec.js b/src/routes/projects/update.spec.js
index f1cb51ef..e198659e 100644
--- a/src/routes/projects/update.spec.js
+++ b/src/routes/projects/update.spec.js
@@ -1,5 +1,4 @@
/* eslint-disable no-unused-expressions */
-import _ from 'lodash';
import chai from 'chai';
import sinon from 'sinon';
import request from 'supertest';
@@ -7,13 +6,13 @@ import request from 'supertest';
import models from '../../models';
import server from '../../app';
import testUtil from '../../tests/util';
-import util from '../../util';
import busApi from '../../services/busApi';
import {
PROJECT_STATUS,
BUS_API_EVENT,
+ CONNECT_NOTIFICATION_EVENT,
} from '../../constants';
const should = chai.should();
@@ -24,6 +23,7 @@ describe('Project', () => {
let project3;
beforeEach((done) => {
testUtil.clearDb()
+ .then(() => testUtil.clearES())
.then(() => models.ProjectType.bulkCreate([
{
key: 'generic',
@@ -45,10 +45,8 @@ describe('Project', () => {
});
describe('PATCH /projects', () => {
const body = {
- param: {
- name: 'updatedProject name',
- type: 'generic',
- },
+ name: 'updatedProject name',
+ type: 'generic',
};
let sandbox;
afterEach(() => {
@@ -125,14 +123,14 @@ describe('Project', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.send(body)
.expect(403, done);
});
it('should return 400 if update completed project', (done) => {
request(server)
- .patch(`/v4/projects/${project2.id}`)
+ .patch(`/v5/projects/${project2.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -143,10 +141,7 @@ describe('Project', () => {
if (err) {
done(err);
} else {
- const result = res.body.result;
- result.success.should.be.false;
- result.status.should.equal(400);
- result.content.message.should.equal('Unable to update project');
+ res.body.message.should.equal('Unable to update project');
done();
}
});
@@ -154,14 +149,12 @@ describe('Project', () => {
it('should return 403 if invalid user will update a project', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- status: 'active',
- },
+ status: 'active',
})
.expect('Content-Type', /json/)
.expect(403)
@@ -169,10 +162,7 @@ describe('Project', () => {
if (err) {
done(err);
} else {
- const result = res.body.result;
- result.success.should.be.false;
- result.status.should.equal(403);
- result.content.message.should.equal('Only assigned topcoder-managers or topcoder admins' +
+ res.body.message.should.equal('Only assigned topcoder-managers or topcoder admins' +
' should be allowed to launch a project');
done();
}
@@ -181,25 +171,19 @@ describe('Project', () => {
it('should return 200 if topcoder manager user will update a project', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.send({
- param: {
- status: 'active',
- },
+ status: 'active',
})
.expect('Content-Type', /json/)
.expect(200)
- .end((err, res) => {
+ .end((err) => {
if (err) {
done(err);
} else {
- const result = res.body.result;
- result.success.should.be.true;
- result.status.should.equal(200);
- result.content.status.should.equal('active');
server.services.pubsub.publish.calledWith('project.updated').should.be.true;
done();
}
@@ -208,7 +192,7 @@ describe('Project', () => {
it('should return 200 if valid user and data', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -219,7 +203,7 @@ describe('Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.name.should.equal('updatedProject name');
resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00');
@@ -232,13 +216,12 @@ describe('Project', () => {
it('should return 200 and project history should be updated (status is not set)', (done) => {
const mbody = {
- param: {
- name: 'updatedProject name',
- status: PROJECT_STATUS.IN_REVIEW,
- },
+
+ name: 'updatedProject name',
+ status: PROJECT_STATUS.IN_REVIEW,
};
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -249,7 +232,7 @@ describe('Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.name.should.equal('updatedProject name');
resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00');
@@ -280,13 +263,11 @@ describe('Project', () => {
it('should return 200 and project history should not be updated (status is not updated)', (done) => {
const mbody = {
- param: {
- name: 'updatedProject name',
- status: PROJECT_STATUS.DRAFT,
- },
+ name: 'updatedProject name',
+ status: PROJECT_STATUS.DRAFT,
};
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -297,7 +278,7 @@ describe('Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.name.should.equal('updatedProject name');
resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00');
@@ -317,59 +298,51 @@ describe('Project', () => {
});
});
- it('should return 422 as cancel reason is mandatory if project status is cancelled', (done) => {
+ it('should return 400 as cancel reason is mandatory if project status is cancelled', (done) => {
const mbody = {
- param: {
- name: 'updatedProject name',
- status: PROJECT_STATUS.CANCELLED,
- },
+ name: 'updatedProject name',
+ status: PROJECT_STATUS.CANCELLED,
};
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send(mbody)
.expect('Content-Type', /json/)
- .expect(422)
+ .expect(400)
.end((err, res) => {
if (err) {
done(err);
} else {
- const result = res.body.result;
- result.success.should.be.false;
- result.status.should.equal(422);
+ res.body.message.should.equal('validation error: "cancelReason" is required');
done();
}
});
});
- it('should return 422 if project type does not exist', (done) => {
+ it('should return 400 if project type does not exist', (done) => {
const mbody = {
- param: {
- type: 'not_exist',
- },
+ type: 'not_exist',
};
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send(mbody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 200 and project history should be updated for cancelled project', (done) => {
const mbody = {
- param: {
- name: 'updatedProject name',
- status: PROJECT_STATUS.CANCELLED,
- cancelReason: 'price/cost',
- },
+ name: 'updatedProject name',
+ status: PROJECT_STATUS.CANCELLED,
+ cancelReason: 'price/cost',
};
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -380,7 +353,7 @@ describe('Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.name.should.equal('updatedProject name');
resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00');
@@ -416,13 +389,11 @@ describe('Project', () => {
})
.then(() => {
const mbody = {
- param: {
- name: 'updatedProject name',
- status: PROJECT_STATUS.ACTIVE,
- },
+ name: 'updatedProject name',
+ status: PROJECT_STATUS.ACTIVE,
};
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -433,7 +404,7 @@ describe('Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.name.should.equal('updatedProject name');
resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00');
@@ -469,13 +440,12 @@ describe('Project', () => {
})
.then(() => {
const mbody = {
- param: {
- name: 'updatedProject name',
- status: PROJECT_STATUS.ACTIVE,
- },
+ name: 'updatedProject name',
+ status: PROJECT_STATUS.ACTIVE,
+
};
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -486,7 +456,7 @@ describe('Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.name.should.equal('updatedProject name');
resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00');
@@ -522,13 +492,12 @@ describe('Project', () => {
})
.then(() => {
const mbody = {
- param: {
- name: 'updatedProject name',
- status: PROJECT_STATUS.ACTIVE,
- },
+ name: 'updatedProject name',
+ status: PROJECT_STATUS.ACTIVE,
+
};
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -539,9 +508,11 @@ describe('Project', () => {
if (err) {
done(err);
} else {
- const result = res.body.result;
- result.success.should.be.false;
- result.status.should.equal(403);
+ res
+ .body
+ .message
+ .should
+ .equal('Only assigned topcoder-managers or topcoder admins should be allowed to launch a project');
done();
}
});
@@ -550,7 +521,7 @@ describe('Project', () => {
it('should return 200 and project history should not be updated', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -561,7 +532,7 @@ describe('Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.name.should.equal('updatedProject name');
resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00');
@@ -581,64 +552,15 @@ describe('Project', () => {
});
});
- it('should return 500 if error to sync billing account id', (done) => {
- const mockHttpClient = _.merge(testUtil.mockHttpClient, {
- post: () => Promise.reject(new Error('error message')),
- });
- sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
- request(server)
- .patch(`/v4/projects/${project1.id}`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.copilot}`,
- })
- .send({
- param: {
- billingAccountId: 123,
- },
- })
- .expect('Content-Type', /json/)
- .expect(500)
- .end((err, res) => {
- if (err) {
- done(err);
- } else {
- const result = res.body.result;
- result.success.should.be.false;
- result.status.should.equal(500);
- result.content.message.should.equal('error message');
- done();
- }
- });
- });
-
it('should return 200 and sync new billing account id', (done) => {
- const mockHttpClient = _.merge(testUtil.mockHttpClient, {
- post: () => Promise.resolve({
- status: 200,
- data: {
- id: 'requesterId',
- version: 'v3',
- result: {
- success: true,
- status: 200,
- content: {
- billingAccountName: '2',
- },
- },
- },
- }),
- });
- const postSpy = sinon.spy(mockHttpClient, 'post');
- sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- billingAccountId: 123,
- },
+ billingAccountId: 123,
+
})
.expect('Content-Type', /json/)
.expect(200)
@@ -646,12 +568,11 @@ describe('Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.billingAccountId.should.equal(123);
resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00');
resJson.updatedBy.should.equal(40051332);
- postSpy.should.have.been.calledOnce;
server.services.pubsub.publish.calledWith('project.updated').should.be.true;
done();
}
@@ -660,14 +581,13 @@ describe('Project', () => {
it('should return 200 and not sync same billing account id', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- billingAccountId: 1,
- },
+ billingAccountId: 1,
+
})
.expect('Content-Type', /json/)
.expect(200)
@@ -675,7 +595,7 @@ describe('Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.billingAccountId.should.equal(1);
resJson.billingAccountId.should.equal(1);
@@ -687,14 +607,12 @@ describe('Project', () => {
it('should return 200 and not sync same billing account id for project without direct project id', (done) => {
request(server)
- .patch(`/v4/projects/${project3.id}`)
+ .patch(`/v5/projects/${project3.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- billingAccountId: 1,
- },
+ billingAccountId: 1,
})
.expect('Content-Type', /json/)
.expect(200)
@@ -702,7 +620,7 @@ describe('Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.billingAccountId.should.equal(1);
resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00');
@@ -715,17 +633,15 @@ describe('Project', () => {
it.skip('should return 200 and update bookmarks', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- bookmarks: [{
- title: 'title1',
- address: 'address1',
- }],
- },
+ bookmarks: [{
+ title: 'title1',
+ address: 'address1',
+ }],
})
.expect('Content-Type', /json/)
.expect(200)
@@ -733,20 +649,19 @@ describe('Project', () => {
if (err) {
done(err);
} else {
- let resJson = res.body.result.content;
+ let resJson = res.body;
should.exist(resJson);
resJson.bookmarks.should.have.lengthOf(1);
resJson.bookmarks[0].title.should.be.eql('title1');
resJson.bookmarks[0].address.should.be.eql('address1');
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
- param: {
- bookmarks: null,
- },
+ bookmarks: null,
+
})
.expect('Content-Type', /json/)
.expect(200)
@@ -754,7 +669,7 @@ describe('Project', () => {
if (error) {
done(error);
} else {
- resJson = resp.body.result.content;
+ resJson = resp.body;
should.exist(resJson);
should.not.exist(resJson.bookmarks);
server.services.pubsub.publish.calledWith('project.updated').should.be.true;
@@ -776,13 +691,13 @@ describe('Project', () => {
})
.then(() => {
const mbody = {
- param: {
- name: 'updatedProject name',
- status: PROJECT_STATUS.ACTIVE,
- },
+
+ name: 'updatedProject name',
+ status: PROJECT_STATUS.ACTIVE,
+
};
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -793,7 +708,7 @@ describe('Project', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.name.should.equal('updatedProject name');
resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00');
@@ -832,16 +747,14 @@ describe('Project', () => {
createEventSpy = sandbox.spy(busApi, 'createEvent');
});
- it('sends single BUS_API_EVENT.PROJECT_UPDATED message on project status update', (done) => {
+ it('should send correct BUS API messages when project status updated', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- status: PROJECT_STATUS.COMPLETED,
- },
+ status: PROJECT_STATUS.COMPLETED,
})
.expect(200)
.end((err) => {
@@ -849,32 +762,40 @@ describe('Project', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledTwice.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_COMPLETED);
- createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({
+ createEventSpy.callCount.should.equal(3);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({
+ resource: 'project',
+ id: project1.id,
+ status: PROJECT_STATUS.COMPLETED,
+ updatedBy: testUtil.userIds.admin,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_COMPLETED).should.be.true;
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_UPDATED, sinon.match({
projectId: project1.id,
projectName: project1.name,
projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`,
userId: 40051333,
initiatorUserId: 40051333,
})).should.be.true;
+
done();
});
}
});
});
- it('sends single BUS_API_EVENT.PROJECT_UPDATED message on project details update', (done) => {
+ it('should send correct BUS API messages when project details updated', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- details: {
- info: 'something',
- },
+ details: {
+ info: 'something',
},
})
.expect(200)
@@ -883,31 +804,39 @@ describe('Project', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledTwice.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_SPECIFICATION_MODIFIED);
- createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({
+ createEventSpy.callCount.should.equal(3);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({
+ resource: 'project',
+ id: project1.id,
+ details: { info: 'something' },
+ updatedBy: testUtil.userIds.admin,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_SPECIFICATION_MODIFIED).should.be.true;
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_UPDATED, sinon.match({
projectId: project1.id,
projectName: project1.name,
projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`,
userId: 40051333,
initiatorUserId: 40051333,
})).should.be.true;
+
done();
});
}
});
});
- it('sends single BUS_API_EVENT.PROJECT_UPDATED message on project name update', (done) => {
+ it('should send correct BUS API messages when project name updated', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- name: 'New project name',
- },
+ name: 'New project name',
})
.expect(200)
.end((err) => {
@@ -915,31 +844,39 @@ describe('Project', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledTwice.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_SPECIFICATION_MODIFIED);
- createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({
+ createEventSpy.callCount.should.equal(3);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({
+ resource: 'project',
+ id: project1.id,
+ name: 'New project name',
+ updatedBy: testUtil.userIds.admin,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_SPECIFICATION_MODIFIED).should.be.true;
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_UPDATED, sinon.match({
projectId: project1.id,
projectName: 'New project name',
projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`,
userId: 40051333,
initiatorUserId: 40051333,
})).should.be.true;
+
done();
});
}
});
});
- it('sends single BUS_API_EVENT.PROJECT_UPDATED message on project description update', (done) => {
+ it('should send correct BUS API messages when project description updated', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- description: 'Updated description',
- },
+ description: 'Updated description',
})
.expect(200)
.end((err) => {
@@ -947,33 +884,42 @@ describe('Project', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledTwice.should.be.true;
- createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({
+ createEventSpy.callCount.should.equal(3);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({
+ resource: 'project',
+ id: project1.id,
+ description: 'Updated description',
+ updatedBy: testUtil.userIds.admin,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_SPECIFICATION_MODIFIED).should.be.true;
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_UPDATED, sinon.match({
projectId: project1.id,
projectName: project1.name,
projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`,
userId: 40051333,
initiatorUserId: 40051333,
})).should.be.true;
+
done();
});
}
});
});
- it('sends single BUS_API_EVENT.PROJECT_UPDATED message on project bookmarks update', (done) => {
+ it('should send correct BUS API messages when project bookmarks updated', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- bookmarks: [{
- title: 'title1',
- address: 'http://someurl.com',
- }],
- },
+ bookmarks: [{
+ title: 'title1',
+ address: 'http://someurl.com',
+ }],
})
.expect(200)
.end((err) => {
@@ -981,31 +927,39 @@ describe('Project', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledTwice.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_SPECIFICATION_MODIFIED);
- createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({
+ createEventSpy.callCount.should.equal(3);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({
+ resource: 'project',
+ id: project1.id,
+ bookmarks: [{ title: 'title1', address: 'http://someurl.com' }],
+ updatedBy: testUtil.userIds.admin,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_LINK_CREATED).should.be.true;
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_UPDATED, sinon.match({
projectId: project1.id,
projectName: project1.name,
projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`,
userId: 40051333,
initiatorUserId: 40051333,
})).should.be.true;
+
done();
});
}
});
});
- it('should not send BUS_API_EVENT.PROJECT_UPDATED message when project estimatedPrice is updated', (done) => {
+ it('should send correct BUS API messages when project estimatedPrice updated', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- estimatedPrice: 123,
- },
+ estimatedPrice: 123,
})
.expect(200)
.end((err) => {
@@ -1013,23 +967,30 @@ describe('Project', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.notCalled.should.be.true;
+ createEventSpy.callCount.should.equal(1);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({
+ resource: 'project',
+ id: project1.id,
+ // FIXME https://github.com/sequelize/sequelize/issues/8019
+ // estimatedPrice: 123,
+ updatedBy: testUtil.userIds.admin,
+ })).should.be.true;
+
done();
});
}
});
});
- it('should not send BUS_API_EVENT.PROJECT_UPDATED message when project actualPrice is updated', (done) => {
+ it('should send correct BUS API messages when project actualPrice updated', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- actualPrice: 123,
- },
+ actualPrice: 123,
})
.expect(200)
.end((err) => {
@@ -1037,23 +998,30 @@ describe('Project', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.notCalled.should.be.true;
+ createEventSpy.callCount.should.equal(1);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({
+ resource: 'project',
+ id: project1.id,
+ // FIXME https://github.com/sequelize/sequelize/issues/8019
+ // actualPrice: 123,
+ updatedBy: testUtil.userIds.admin,
+ })).should.be.true;
+
done();
});
}
});
});
- it('should not send BUS_API_EVENT.PROJECT_UPDATED message when project terms are updated', (done) => {
+ it('should send correct BUS API messages when project terms are updated', (done) => {
request(server)
- .patch(`/v4/projects/${project1.id}`)
+ .patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- terms: [1, 2, 3],
- },
+ terms: [1, 2, 3],
})
.expect(200)
.end((err) => {
@@ -1061,7 +1029,15 @@ describe('Project', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.notCalled.should.be.true;
+ createEventSpy.callCount.should.equal(1);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({
+ resource: 'project',
+ id: project1.id,
+ terms: [1, 2, 3],
+ updatedBy: testUtil.userIds.admin,
+ })).should.be.true;
+
done();
});
}
diff --git a/src/routes/scopeChangeRequests/create.js b/src/routes/scopeChangeRequests/create.js
index 6d01bcf3..e63caae9 100644
--- a/src/routes/scopeChangeRequests/create.js
+++ b/src/routes/scopeChangeRequests/create.js
@@ -2,7 +2,6 @@ import _ from 'lodash';
import Joi from 'joi';
import validate from 'express-validation';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../util';
import { SCOPE_CHANGE_REQ_STATUS, PROJECT_MEMBER_ROLE, PROJECT_STATUS } from '../../constants';
import models from '../../models';
@@ -13,11 +12,8 @@ const permissions = tcMiddleware.permissions;
const createScopeChangeRequestValidations = {
body: {
- param: Joi.object()
- .keys({
- oldScope: Joi.object(),
- newScope: Joi.object(),
- }),
+ oldScope: Joi.object(),
+ newScope: Joi.object(),
},
};
@@ -27,8 +23,8 @@ module.exports = [
permissions('project.edit'),
(req, res, next) => {
const projectId = _.parseInt(req.params.projectId);
- const oldScope = _.get(req, 'body.param.oldScope');
- const newScope = _.get(req, 'body.param.newScope');
+ const oldScope = _.get(req, 'body.oldScope');
+ const newScope = _.get(req, 'body.newScope');
const members = req.context.currentProjectMembers;
const isCustomer = !_.isUndefined(_.find(members,
m => m.userId === req.authUser.userId && m.role === PROJECT_MEMBER_ROLE.CUSTOMER));
@@ -80,7 +76,7 @@ module.exports = [
.then((_newScopeChange) => {
req.log.debug('Created scope change request');
- res.json(util.wrapResponse(req.id, _newScopeChange));
+ res.json(_newScopeChange);
return Promise.resolve();
})
diff --git a/src/routes/scopeChangeRequests/create.spec.js b/src/routes/scopeChangeRequests/create.spec.js
index c27fb5b7..ca15f656 100644
--- a/src/routes/scopeChangeRequests/create.spec.js
+++ b/src/routes/scopeChangeRequests/create.spec.js
@@ -50,16 +50,14 @@ function createProject(status) {
*/
function newScopeChangeRequest() {
return {
- param: {
- newScope: {
- appDefinition: {
- numberScreens: '5-8',
- },
+ newScope: {
+ appDefinition: {
+ numberScreens: '5-8',
},
- oldScope: {
- appDefinition: {
- numberScreens: '2-4',
- },
+ },
+ oldScope: {
+ appDefinition: {
+ numberScreens: '2-4',
},
},
};
@@ -73,7 +71,7 @@ function newScopeChangeRequest() {
* @returns {undefined} - throws error if assertion failed
*/
function assertStatus(response, expectedStatus) {
- const status = _.get(response, 'body.result.content.status');
+ const status = _.get(response, 'body.status');
sinon.assert.match(status, expectedStatus);
}
@@ -102,7 +100,7 @@ describe('Create Scope Change Rquest', () => {
PROJECT_STATUS.ACTIVE,
];
- return Promise.all(projectStatuses.map(status => createProject(status)))
+ Promise.all(projectStatuses.map(status => createProject(status)))
.then(_projects => _projects.map((project, i) => [projectStatuses[i], project]))
.then((_projectStatusPairs) => {
projects = _.fromPairs(_projectStatusPairs);
@@ -119,12 +117,12 @@ describe('Create Scope Change Rquest', () => {
const project = projects[PROJECT_STATUS.REVIEWED];
request(server)
- .post(`/v4/projects/${project.id}/scopeChangeRequests`)
- .send(newScopeChangeRequest())
- .expect(200)
+ .post(`/v5/projects/${project.id}/scopeChangeRequests`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
+ .send(newScopeChangeRequest())
+ .expect(200)
.end((err, res) => {
if (err) {
done(err);
@@ -141,12 +139,12 @@ describe('Create Scope Change Rquest', () => {
const project = projects[PROJECT_STATUS.ACTIVE];
request(server)
- .post(`/v4/projects/${project.id}/scopeChangeRequests`)
- .send(newScopeChangeRequest())
- .expect(200)
+ .post(`/v5/projects/${project.id}/scopeChangeRequests`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
+ .send(newScopeChangeRequest())
+ .expect(200)
.end((err, res) => {
if (err) {
done(err);
@@ -163,12 +161,12 @@ describe('Create Scope Change Rquest', () => {
const project = projects[PROJECT_STATUS.DRAFT];
request(server)
- .post(`/v4/projects/${project.id}/scopeChangeRequests`)
- .send(newScopeChangeRequest())
- .expect(403)
+ .post(`/v5/projects/${project.id}/scopeChangeRequests`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
+ .send(newScopeChangeRequest())
+ .expect(403)
.end(err => done(err));
});
@@ -176,58 +174,58 @@ describe('Create Scope Change Rquest', () => {
const project = projects[PROJECT_STATUS.IN_REVIEW];
request(server)
- .post(`/v4/projects/${project.id}/scopeChangeRequests`)
- .send(newScopeChangeRequest())
- .expect(403)
+ .post(`/v5/projects/${project.id}/scopeChangeRequests`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
+ .send(newScopeChangeRequest())
+ .expect(403)
.end(err => done(err));
});
it('Should return error with status 404 if project not present', (done) => {
const nonExistentProjectId = 341212;
request(server)
- .post(`/v4/projects/${nonExistentProjectId}/scopeChangeRequests`)
- .send(newScopeChangeRequest())
- .expect(404)
+ .post(`/v5/projects/${nonExistentProjectId}/scopeChangeRequests`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
+ .send(newScopeChangeRequest())
+ .expect(404)
.end(err => done(err));
});
it('Should return error with status 403 if there is a request in pending status', (done) => {
request(server)
- .post(`/v4/projects/${projectWithPendingChange.id}/scopeChangeRequests`)
- .send(newScopeChangeRequest())
- .expect(403)
+ .post(`/v5/projects/${projectWithPendingChange.id}/scopeChangeRequests`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
+ .send(newScopeChangeRequest())
+ .expect(403)
.end(err => done(err));
});
it('Should return error with status 403 if there is a request in approved status', (done) => {
request(server)
- .post(`/v4/projects/${projectWithApprovedChange.id}/scopeChangeRequests`)
- .send(newScopeChangeRequest())
- .expect(403)
+ .post(`/v5/projects/${projectWithApprovedChange.id}/scopeChangeRequests`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
+ .send(newScopeChangeRequest())
+ .expect(403)
.end(err => done(err));
});
it('Should create scope change request if there is a request in canceled status', (done) => {
updateScopeChangeStatuses(projectWithApprovedChange, SCOPE_CHANGE_REQ_STATUS.CANCELED).then(() => {
request(server)
- .post(`/v4/projects/${projectWithApprovedChange.id}/scopeChangeRequests`)
- .send(newScopeChangeRequest())
- .expect(200)
+ .post(`/v5/projects/${projectWithApprovedChange.id}/scopeChangeRequests`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
+ .send(newScopeChangeRequest())
+ .expect(200)
.end((err, res) => {
if (err) {
done(err);
@@ -242,12 +240,12 @@ describe('Create Scope Change Rquest', () => {
it('Should create scope change request if there is a request in rejected status', (done) => {
updateScopeChangeStatuses(projectWithApprovedChange, SCOPE_CHANGE_REQ_STATUS.REJECTED).then(() => {
request(server)
- .post(`/v4/projects/${projectWithApprovedChange.id}/scopeChangeRequests`)
- .send(newScopeChangeRequest())
- .expect(200)
+ .post(`/v5/projects/${projectWithApprovedChange.id}/scopeChangeRequests`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
+ .send(newScopeChangeRequest())
+ .expect(200)
.end((err, res) => {
if (err) {
done(err);
@@ -262,12 +260,12 @@ describe('Create Scope Change Rquest', () => {
it('Should create scope change request if there is a request in activated status', (done) => {
updateScopeChangeStatuses(projectWithApprovedChange, SCOPE_CHANGE_REQ_STATUS.ACTIVATED).then(() => {
request(server)
- .post(`/v4/projects/${projectWithApprovedChange.id}/scopeChangeRequests`)
- .send(newScopeChangeRequest())
- .expect(200)
+ .post(`/v5/projects/${projectWithApprovedChange.id}/scopeChangeRequests`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
+ .send(newScopeChangeRequest())
+ .expect(200)
.end((err, res) => {
if (err) {
done(err);
diff --git a/src/routes/scopeChangeRequests/update.js b/src/routes/scopeChangeRequests/update.js
index aade0f3f..1f1e8ea5 100644
--- a/src/routes/scopeChangeRequests/update.js
+++ b/src/routes/scopeChangeRequests/update.js
@@ -19,10 +19,7 @@ const permissions = tcMiddleware.permissions;
const updateScopeChangeRequestValidations = {
body: {
- param: Joi.object()
- .keys({
- status: Joi.string().valid(_.values(SCOPE_CHANGE_REQ_STATUS)),
- }),
+ status: Joi.string().valid(_.values(SCOPE_CHANGE_REQ_STATUS)),
},
};
@@ -35,7 +32,7 @@ const updateScopeChangeRequestValidations = {
* @returns {Promise} The promise to update the project with merged data
*/
function updateProjectDetails(req, newScope, projectId) {
- return models.Project.findById(projectId).then((project) => {
+ return models.Project.findByPk(projectId).then((project) => {
const previousValue = _.clone(project.get({ plain: true }));
if (!project) {
@@ -77,7 +74,7 @@ module.exports = [
(req, res, next) => {
const projectId = _.parseInt(req.params.projectId);
const requestId = _.parseInt(req.params.requestId);
- const updatedProps = req.body.param;
+ const updatedProps = req.body;
const members = req.context.currentProjectMembers;
const member = _.find(members, m => m.userId === req.authUser.userId);
const isCustomer = member && member.role === PROJECT_MEMBER_ROLE.CUSTOMER;
@@ -121,7 +118,7 @@ module.exports = [
)
.then(() => scopeChangeReq.update(updatedProps))
.then((_updatedReq) => {
- res.json(util.wrapResponse(req.id, _updatedReq));
+ res.json(_updatedReq);
return Promise.resolve();
});
})
diff --git a/src/routes/scopeChangeRequests/update.spec.js b/src/routes/scopeChangeRequests/update.spec.js
index ee487736..cbceca57 100644
--- a/src/routes/scopeChangeRequests/update.spec.js
+++ b/src/routes/scopeChangeRequests/update.spec.js
@@ -89,7 +89,7 @@ function createScopeChangeRequest(project) {
* @returns {Promise} A promise to update details json in the project
*/
function updateProjectDetails(projectId, detailsChange) {
- return models.Project.findById(projectId).then((project) => {
+ return models.Project.findByPk(projectId).then((project) => {
const updatedDetails = _.merge({}, project.details, detailsChange);
return project.update({ details: updatedDetails });
});
@@ -99,7 +99,7 @@ describe('Update Scope Change Rquest', () => {
let project;
let scopeChangeRequest;
- before(done =>
+ before((done) => {
testUtil
.clearDb()
.then(() => createProject(PROJECT_STATUS.REVIEWED))
@@ -110,9 +110,10 @@ describe('Update Scope Change Rquest', () => {
.then(_project => createScopeChangeRequest(_project))
.then((_scopeChangeRequest) => {
scopeChangeRequest = _scopeChangeRequest;
+ return scopeChangeRequest;
})
- .then(() => done()),
- );
+ .then(() => done());
+ });
after((done) => {
testUtil.clearDb(done);
@@ -121,14 +122,12 @@ describe('Update Scope Change Rquest', () => {
describe('PATCH projects/{projectId}/scopeChangeRequests/{requestId}', () => {
it('Should approve change request with customer login', (done) => {
request(server)
- .patch(`/v4/projects/${project.id}/scopeChangeRequests/${scopeChangeRequest.id}`)
+ .patch(`/v5/projects/${project.id}/scopeChangeRequests/${scopeChangeRequest.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
.send({
- param: {
- status: SCOPE_CHANGE_REQ_STATUS.APPROVED,
- },
+ status: SCOPE_CHANGE_REQ_STATUS.APPROVED,
})
.expect(200)
.end((err) => {
@@ -147,14 +146,12 @@ describe('Update Scope Change Rquest', () => {
// Updating project details before activation. This is used in a later test case
updateProjectDetails(project.id, { apiDefinition: { notes: 'Please include swagger docs' } }).then(() => {
request(server)
- .patch(`/v4/projects/${project.id}/scopeChangeRequests/${scopeChangeRequest.id}`)
+ .patch(`/v5/projects/${project.id}/scopeChangeRequests/${scopeChangeRequest.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.send({
- param: {
- status: SCOPE_CHANGE_REQ_STATUS.ACTIVATED,
- },
+ status: SCOPE_CHANGE_REQ_STATUS.ACTIVATED,
})
.expect(200)
.end((err) => {
@@ -189,31 +186,27 @@ describe('Update Scope Change Rquest', () => {
it('Should not allow updating oldScope', (done) => {
request(server)
- .patch(`/v4/projects/${project.id}/scopeChangeRequests/${scopeChangeRequest.id}`)
+ .patch(`/v5/projects/${project.id}/scopeChangeRequests/${scopeChangeRequest.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.send({
- param: {
- oldScope: {},
- },
+ oldScope: {},
})
- .expect(422)
+ .expect(400)
.end(err => done(err));
});
it('Should not allow updating newScope', (done) => {
request(server)
- .patch(`/v4/projects/${project.id}/scopeChangeRequests/${scopeChangeRequest.id}`)
+ .patch(`/v5/projects/${project.id}/scopeChangeRequests/${scopeChangeRequest.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.send({
- param: {
- newScope: {},
- },
+ newScope: {},
})
- .expect(422)
+ .expect(400)
.end(err => done(err));
});
});
diff --git a/src/routes/timelines/create.js b/src/routes/timelines/create.js
index b001f2f4..cb2e09d0 100644
--- a/src/routes/timelines/create.js
+++ b/src/routes/timelines/create.js
@@ -9,30 +9,28 @@ import { middleware as tcMiddleware } from 'tc-core-library-js';
import util from '../../util';
import validateTimeline from '../../middlewares/validateTimeline';
import models from '../../models';
-import { EVENT, TIMELINE_REFERENCES, MILESTONE_STATUS, MILESTONE_TEMPLATE_REFERENCES }
+import { EVENT, RESOURCES, TIMELINE_REFERENCES, MILESTONE_STATUS, MILESTONE_TEMPLATE_REFERENCES }
from '../../constants';
const permissions = tcMiddleware.permissions;
const schema = {
- body: {
- param: Joi.object().keys({
- id: Joi.any().strip(),
- name: Joi.string().max(255).required(),
- description: Joi.string().max(255),
- startDate: Joi.date().required(),
- endDate: Joi.date().min(Joi.ref('startDate')).allow(null),
- reference: Joi.string().valid(_.values(TIMELINE_REFERENCES)).required(),
- referenceId: Joi.number().integer().positive().required(),
- templateId: Joi.number().integer().min(1).optional(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ body: Joi.object().keys({
+ id: Joi.any().strip(),
+ name: Joi.string().max(255).required(),
+ description: Joi.string().max(255),
+ startDate: Joi.date().required(),
+ endDate: Joi.date().min(Joi.ref('startDate')).allow(null),
+ reference: Joi.string().valid(_.values(TIMELINE_REFERENCES)).required(),
+ referenceId: Joi.number().integer().positive().required(),
+ templateId: Joi.number().integer().min(1).optional(),
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
};
module.exports = [
@@ -42,8 +40,8 @@ module.exports = [
validateTimeline.validateTimelineRequestBody,
permissions('timeline.create'),
(req, res, next) => {
- const templateId = req.body.param.templateId;
- const entity = _.assign({}, req.body.param, {
+ const templateId = req.body.templateId;
+ const entity = _.assign({}, req.body, {
createdBy: req.authUser.userId,
updatedBy: req.authUser.userId,
});
@@ -51,7 +49,7 @@ module.exports = [
let result;
// Save to DB
- return models.sequelize.transaction(() => {
+ models.sequelize.transaction(() => {
req.log.debug('Started transaction');
return models.Timeline.create(entity)
.then((createdEntity) => {
@@ -109,7 +107,8 @@ module.exports = [
});
}
return Promise.resolve();
- });
+ })
+ .catch(next);
})
.then(() => {
// Send event to bus
@@ -118,8 +117,22 @@ module.exports = [
_.assign({ projectId: req.params.projectId }, result),
{ correlationId: req.id },
);
+
+ // emit the event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.TIMELINE_ADDED,
+ RESOURCES.TIMELINE,
+ result);
+
+ // emit the event for milestones
+ _.map(result.milestones, milestone => util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.MILESTONE_ADDED,
+ RESOURCES.MILESTONE,
+ milestone));
+
// Write to the response
- res.status(201).json(util.wrapResponse(req.id, result, 1, 201));
+ res.status(201).json(result);
return Promise.resolve();
})
.catch(next);
diff --git a/src/routes/timelines/create.spec.js b/src/routes/timelines/create.spec.js
index c6ab1bc8..8e94687e 100644
--- a/src/routes/timelines/create.spec.js
+++ b/src/routes/timelines/create.spec.js
@@ -185,30 +185,30 @@ describe('CREATE timeline', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('POST /timelines', () => {
const body = {
- param: {
- name: 'new name',
- description: 'new description',
- startDate: '2018-05-29T00:00:00.000Z',
- endDate: '2018-05-30T00:00:00.000Z',
- reference: 'project',
- referenceId: 1,
- },
+ name: 'new name',
+ description: 'new description',
+ startDate: '2018-05-29T00:00:00.000Z',
+ endDate: '2018-05-30T00:00:00.000Z',
+ reference: 'project',
+ referenceId: 1,
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post('/v4/timelines')
+ .post('/v5/timelines')
.send(body)
.expect(403, done);
});
it('should return 403 for member who is not in the project', (done) => {
request(server)
- .post('/v4/timelines')
+ .post('/v5/timelines')
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -217,15 +217,13 @@ describe('CREATE timeline', () => {
});
it('should return 403 for member who is not in the project (timeline refers to a phase)', (done) => {
- const bodyWithPhase = {
- param: _.assign({}, body.param, {
- reference: 'phase',
- referenceId: 1,
- }),
- };
+ const bodyWithPhase = _.assign({}, body, {
+ reference: 'phase',
+ referenceId: 1,
+ });
request(server)
- .post('/v4/timelines')
+ .post('/v5/timelines')
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -233,199 +231,177 @@ describe('CREATE timeline', () => {
.expect(403, done);
});
- it('should return 422 if missing name', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- name: undefined,
- }),
- };
+ it('should return 400 if missing name', (done) => {
+ const invalidBody = _.assign({}, body, {
+ name: undefined,
+ });
request(server)
- .post('/v4/timelines')
+ .post('/v5/timelines')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if missing startDate', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- startDate: undefined,
- }),
- };
+ it('should return 400 if missing startDate', (done) => {
+ const invalidBody = _.assign({}, body, {
+ startDate: undefined,
+ });
request(server)
- .post('/v4/timelines')
+ .post('/v5/timelines')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if startDate is after endDate', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- startDate: '2018-05-29T00:00:00.000Z',
- endDate: '2018-05-28T00:00:00.000Z',
- }),
- };
+ it('should return 400 if startDate is after endDate', (done) => {
+ const invalidBody = _.assign({}, body, {
+ startDate: '2018-05-29T00:00:00.000Z',
+ endDate: '2018-05-28T00:00:00.000Z',
+ });
request(server)
- .post('/v4/timelines')
+ .post('/v5/timelines')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if missing reference', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- reference: undefined,
- }),
- };
+ it('should return 400 if missing reference', (done) => {
+ const invalidBody = _.assign({}, body, {
+ reference: undefined,
+ });
request(server)
- .post('/v4/timelines')
+ .post('/v5/timelines')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if missing referenceId', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- referenceId: undefined,
- }),
- };
+ it('should return 400 if missing referenceId', (done) => {
+ const invalidBody = _.assign({}, body, {
+ referenceId: undefined,
+ });
request(server)
- .post('/v4/timelines')
+ .post('/v5/timelines')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if invalid reference', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- reference: 'invalid',
- }),
- };
+ it('should return 400 if invalid reference', (done) => {
+ const invalidBody = _.assign({}, body, {
+ reference: 'invalid',
+ });
request(server)
- .post('/v4/timelines')
+ .post('/v5/timelines')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if invalid referenceId', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- referenceId: 0,
- }),
- };
+ it('should return 400 if invalid referenceId', (done) => {
+ const invalidBody = _.assign({}, body, {
+ referenceId: 0,
+ });
request(server)
- .post('/v4/timelines')
+ .post('/v5/timelines')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if project does not exist', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- referenceId: 1110,
- }),
- };
+ it('should return 400 if project does not exist', (done) => {
+ const invalidBody = _.assign({}, body, {
+ referenceId: 1110,
+ });
request(server)
- .post('/v4/timelines')
+ .post('/v5/timelines')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if project was deleted', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- referenceId: 2,
- }),
- };
+ it('should return 400 if project was deleted', (done) => {
+ const invalidBody = _.assign({}, body, {
+ referenceId: 2,
+ });
request(server)
- .post('/v4/timelines')
+ .post('/v5/timelines')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if phase does not exist', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- reference: 'phase',
- referenceId: 2222,
- }),
- };
+ it('should return 400 if phase does not exist', (done) => {
+ const invalidBody = _.assign({}, body, {
+ reference: 'phase',
+ referenceId: 2222,
+ });
request(server)
- .post('/v4/timelines')
+ .post('/v5/timelines')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if phase was deleted', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- reference: 'phase',
- referenceId: 2,
- }),
- };
+ it('should return 400 if phase was deleted', (done) => {
+ const invalidBody = _.assign({}, body, {
+ reference: 'phase',
+ referenceId: 2,
+ });
request(server)
- .post('/v4/timelines')
+ .post('/v5/timelines')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 201 for admin', (done) => {
request(server)
- .post('/v4/timelines')
+ .post('/v5/timelines')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -433,14 +409,14 @@ describe('CREATE timeline', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson.id);
- resJson.name.should.be.eql(body.param.name);
- resJson.description.should.be.eql(body.param.description);
- resJson.startDate.should.be.eql(body.param.startDate);
- resJson.endDate.should.be.eql(body.param.endDate);
- resJson.reference.should.be.eql(body.param.reference);
- resJson.referenceId.should.be.eql(body.param.referenceId);
+ resJson.name.should.be.eql(body.name);
+ resJson.description.should.be.eql(body.description);
+ resJson.startDate.should.be.eql(body.startDate);
+ resJson.endDate.should.be.eql(body.endDate);
+ resJson.reference.should.be.eql(body.reference);
+ resJson.referenceId.should.be.eql(body.referenceId);
resJson.createdBy.should.be.eql(40051333); // admin
should.exist(resJson.createdAt);
@@ -458,9 +434,9 @@ describe('CREATE timeline', () => {
it('should return 201 for admin (with milestones)', (done) => {
const withMilestones = _.cloneDeep(body);
- withMilestones.param.templateId = 1;
+ withMilestones.templateId = 1;
request(server)
- .post('/v4/timelines')
+ .post('/v5/timelines')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -468,14 +444,14 @@ describe('CREATE timeline', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson.id);
- resJson.name.should.be.eql(body.param.name);
- resJson.description.should.be.eql(body.param.description);
- resJson.startDate.should.be.eql(body.param.startDate);
- resJson.endDate.should.be.eql(body.param.endDate);
- resJson.reference.should.be.eql(body.param.reference);
- resJson.referenceId.should.be.eql(body.param.referenceId);
+ resJson.name.should.be.eql(body.name);
+ resJson.description.should.be.eql(body.description);
+ resJson.startDate.should.be.eql(body.startDate);
+ resJson.endDate.should.be.eql(body.endDate);
+ resJson.reference.should.be.eql(body.reference);
+ resJson.referenceId.should.be.eql(body.referenceId);
resJson.createdBy.should.be.eql(40051333); // admin
should.exist(resJson.createdAt);
@@ -549,7 +525,7 @@ describe('CREATE timeline', () => {
it('should return 201 for connect manager', (done) => {
request(server)
- .post('/v4/timelines')
+ .post('/v5/timelines')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -557,7 +533,7 @@ describe('CREATE timeline', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.createdBy.should.be.eql(40051334); // manager
resJson.updatedBy.should.be.eql(40051334); // manager
done();
@@ -566,7 +542,7 @@ describe('CREATE timeline', () => {
it('should return 201 for connect admin', (done) => {
request(server)
- .post('/v4/timelines')
+ .post('/v5/timelines')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -574,7 +550,7 @@ describe('CREATE timeline', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.createdBy.should.be.eql(40051336); // connect admin
resJson.updatedBy.should.be.eql(40051336); // connect admin
done();
@@ -583,7 +559,7 @@ describe('CREATE timeline', () => {
it('should return 201 for copilot', (done) => {
request(server)
- .post('/v4/timelines')
+ .post('/v5/timelines')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -591,7 +567,7 @@ describe('CREATE timeline', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.createdBy.should.be.eql(40051332); // copilot
resJson.updatedBy.should.be.eql(40051332); // copilot
done();
@@ -600,7 +576,7 @@ describe('CREATE timeline', () => {
it('should return 201 for member', (done) => {
request(server)
- .post('/v4/timelines')
+ .post('/v5/timelines')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -608,7 +584,7 @@ describe('CREATE timeline', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.createdBy.should.be.eql(40051331); // member
resJson.updatedBy.should.be.eql(40051331); // member
done();
@@ -617,13 +593,11 @@ describe('CREATE timeline', () => {
it('should return 201 for member (timeline refers to a phase)', (done) => {
const bodyWithPhase = _.merge({}, body, {
- param: {
- reference: 'phase',
- referenceId: 1,
- },
+ reference: 'phase',
+ referenceId: 1,
});
request(server)
- .post('/v4/timelines')
+ .post('/v5/timelines')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -631,7 +605,7 @@ describe('CREATE timeline', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.createdBy.should.be.eql(40051331); // member
resJson.updatedBy.should.be.eql(40051331); // member
done();
diff --git a/src/routes/timelines/delete.js b/src/routes/timelines/delete.js
index 911291ec..03e5e7d7 100644
--- a/src/routes/timelines/delete.js
+++ b/src/routes/timelines/delete.js
@@ -6,7 +6,8 @@ import Joi from 'joi';
import _ from 'lodash';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import models from '../../models';
-import { EVENT } from '../../constants';
+import util from '../../util';
+import { EVENT, RESOURCES } from '../../constants';
import validateTimeline from '../../middlewares/validateTimeline';
const permissions = tcMiddleware.permissions;
@@ -33,9 +34,18 @@ module.exports = [
.then(() => timeline.destroy())
// Cascade delete the milestones
.then(() => models.Milestone.update({ deletedBy: req.authUser.userId }, { where: { timelineId: timeline.id } }))
- .then(() => models.Milestone.destroy({ where: { timelineId: timeline.id } })),
+ .then(() => models.Milestone.destroy({ where: { timelineId: timeline.id } }))
+ .then(itemsDeleted => models.Milestone.findAll({
+ where: {
+ timelineId: timeline.id,
+ },
+ attributes: ['id'],
+ paranoid: false,
+ order: [['deletedAt', 'DESC']],
+ limit: itemsDeleted,
+ })),
)
- .then(() => {
+ .then((milestones) => {
// Send event to bus
req.log.debug('Sending event to RabbitMQ bus for timeline %d', deleted.id);
req.app.services.pubsub.publish(EVENT.ROUTING_KEY.TIMELINE_REMOVED,
@@ -43,6 +53,19 @@ module.exports = [
{ correlationId: req.id },
);
+ // emit the event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.TIMELINE_REMOVED,
+ RESOURCES.TIMELINE,
+ { id: req.params.timelineId });
+
+ // emit the event for milestones
+ _.map(milestones, milestone => util.sendResourceToKafkaBus(req,
+ EVENT.ROUTING_KEY.MILESTONE_REMOVED,
+ RESOURCES.MILESTONE,
+ milestone.toJSON()));
+
// Write to response
res.status(204).end();
return Promise.resolve();
diff --git a/src/routes/timelines/delete.spec.js b/src/routes/timelines/delete.spec.js
index f609397e..e082578c 100644
--- a/src/routes/timelines/delete.spec.js
+++ b/src/routes/timelines/delete.spec.js
@@ -28,7 +28,7 @@ const expectAfterDelete = (id, err, next) => {
chai.assert.isNotNull(res.deletedBy);
request(server)
- .get(`/v4/timelines/${id}`)
+ .get(`/v5/timelines/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -210,19 +210,21 @@ describe('DELETE timeline', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('DELETE /timelines/{timelineId}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .delete('/v4/timelines/1')
+ .delete('/v5/timelines/1')
.expect(403, done);
});
it('should return 403 for member who is not in the project', (done) => {
request(server)
- .delete('/v4/timelines/1')
+ .delete('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -231,7 +233,7 @@ describe('DELETE timeline', () => {
it('should return 403 for member who is not in the project (timeline refers to a phase)', (done) => {
request(server)
- .delete('/v4/timelines/2')
+ .delete('/v5/timelines/2')
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -240,7 +242,7 @@ describe('DELETE timeline', () => {
it('should return 404 for non-existed timeline', (done) => {
request(server)
- .delete('/v4/timelines/1234')
+ .delete('/v5/timelines/1234')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -249,20 +251,20 @@ describe('DELETE timeline', () => {
it('should return 404 for deleted timeline', (done) => {
request(server)
- .delete('/v4/timelines/3')
+ .delete('/v5/timelines/3')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(404, done);
});
- it('should return 422 for invalid param', (done) => {
+ it('should return 400 for invalid param', (done) => {
request(server)
- .delete('/v4/timelines/0')
+ .delete('/v5/timelines/0')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .expect(422, done);
+ .expect(400, done);
});
// eslint-disable-next-line func-names
@@ -274,7 +276,7 @@ describe('DELETE timeline', () => {
results.should.have.length(2);
request(server)
- .delete('/v4/timelines/1')
+ .delete('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -300,7 +302,7 @@ describe('DELETE timeline', () => {
it('should return 204, for connect admin, if timeline was successfully removed', (done) => {
request(server)
- .delete('/v4/timelines/1')
+ .delete('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -310,7 +312,7 @@ describe('DELETE timeline', () => {
it('should return 204, for connect manager, if timeline was successfully removed', (done) => {
request(server)
- .delete('/v4/timelines/1')
+ .delete('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -320,7 +322,7 @@ describe('DELETE timeline', () => {
it('should return 204, for copilot, if timeline was successfully removed', (done) => {
request(server)
- .delete('/v4/timelines/1')
+ .delete('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -330,7 +332,7 @@ describe('DELETE timeline', () => {
it('should return 204, for member, if timeline was successfully removed', (done) => {
request(server)
- .delete('/v4/timelines/1')
+ .delete('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
diff --git a/src/routes/timelines/get.js b/src/routes/timelines/get.js
index c02ff4be..f5145ffa 100644
--- a/src/routes/timelines/get.js
+++ b/src/routes/timelines/get.js
@@ -3,6 +3,7 @@
*/
import validate from 'express-validation';
import Joi from 'joi';
+import config from 'config';
import _ from 'lodash';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import util from '../../util';
@@ -10,28 +11,58 @@ import validateTimeline from '../../middlewares/validateTimeline';
const permissions = tcMiddleware.permissions;
+const ES_TIMELINE_INDEX = config.get('elasticsearchConfig.timelineIndexName');
+const ES_TIMELINE_TYPE = config.get('elasticsearchConfig.timelineDocType');
+
+const eClient = util.getElasticSearchClient();
+
const schema = {
params: {
timelineId: Joi.number().integer().positive().required(),
},
+ query: {
+ db: Joi.boolean().optional(),
+ },
};
+// Load the milestones
+const loadMilestones = timeline =>
+ timeline.getMilestones()
+ .then((milestones) => {
+ const loadedTimeline = _.omit(timeline.toJSON(), ['deletedAt', 'deletedBy']);
+ loadedTimeline.milestones =
+ _.map(milestones, milestone => _.omit(milestone.toJSON(), ['deletedAt', 'deletedBy']));
+
+ return Promise.resolve(loadedTimeline);
+ });
+
module.exports = [
validate(schema),
// Validate and get projectId from the timelineId param, and set to request params for
// checking by the permissions middleware
validateTimeline.validateTimelineIdParam,
permissions('timeline.view'),
- (req, res) => {
- // Load the milestones
- req.timeline.getMilestones()
- .then((milestones) => {
- const timeline = _.omit(req.timeline.toJSON(), ['deletedAt', 'deletedBy']);
- timeline.milestones =
- _.map(milestones, milestone => _.omit(milestone.toJSON(), ['deletedAt', 'deletedBy']));
-
- // Write to response
- res.json(util.wrapResponse(req.id, timeline));
- });
+ (req, res, next) => {
+ // when user query with db, bypass the elasticsearch
+ // and get the data directly from database
+ if (req.query.db) {
+ req.log.debug('bypass ES, gets timeline directly from database');
+ return loadMilestones(req.timeline).then(timeline => res.json(timeline));
+ }
+ return eClient.get({ index: ES_TIMELINE_INDEX,
+ type: ES_TIMELINE_TYPE,
+ id: req.params.timelineId,
+ })
+ .then((doc) => {
+ req.log.debug('timeline found in ES');
+ return res.json(doc._source); // eslint-disable-line no-underscore-dangle
+ })
+ .catch((err) => {
+ if (err.status === 404) {
+ req.log.debug('No timeline found in ES');
+ return loadMilestones(req.timeline).then(timeline => res.json(timeline));
+ }
+ return next(err);
+ });
},
];
diff --git a/src/routes/timelines/get.spec.js b/src/routes/timelines/get.spec.js
index da22d117..82ac3b50 100644
--- a/src/routes/timelines/get.spec.js
+++ b/src/routes/timelines/get.spec.js
@@ -3,6 +3,8 @@
*/
import chai from 'chai';
import request from 'supertest';
+import config from 'config';
+import _ from 'lodash';
import models from '../../models';
import server from '../../app';
@@ -10,6 +12,42 @@ import testUtil from '../../tests/util';
const should = chai.should();
+const ES_TIMELINE_INDEX = config.get('elasticsearchConfig.timelineIndexName');
+const ES_TIMELINE_TYPE = config.get('elasticsearchConfig.timelineDocType');
+
+const timelines = [
+ {
+ name: 'name 1',
+ description: 'description 1',
+ startDate: '2018-05-11T00:00:00.000Z',
+ endDate: '2018-05-12T00:00:00.000Z',
+ reference: 'project',
+ referenceId: 1,
+ createdBy: 1,
+ updatedBy: 1,
+ },
+ {
+ name: 'name 2',
+ description: 'description 2',
+ startDate: '2018-05-12T00:00:00.000Z',
+ endDate: '2018-05-13T00:00:00.000Z',
+ reference: 'phase',
+ referenceId: 1,
+ createdBy: 1,
+ updatedBy: 1,
+ },
+ {
+ name: 'name 3',
+ description: 'description 3',
+ startDate: '2018-05-13T00:00:00.000Z',
+ endDate: '2018-05-14T00:00:00.000Z',
+ reference: 'phase',
+ referenceId: 1,
+ createdBy: 1,
+ updatedBy: 1,
+ deletedAt: '2018-05-14T00:00:00.000Z',
+ },
+];
const milestones = [
{
id: 1,
@@ -143,57 +181,55 @@ describe('GET timeline', () => {
]))
.then(() =>
// Create timelines
- models.Timeline.bulkCreate([
- {
- name: 'name 1',
- description: 'description 1',
- startDate: '2018-05-11T00:00:00.000Z',
- endDate: '2018-05-12T00:00:00.000Z',
- reference: 'project',
- referenceId: 1,
- createdBy: 1,
- updatedBy: 1,
- },
- {
- name: 'name 2',
- description: 'description 2',
- startDate: '2018-05-12T00:00:00.000Z',
- endDate: '2018-05-13T00:00:00.000Z',
- reference: 'phase',
- referenceId: 1,
- createdBy: 1,
- updatedBy: 1,
- },
- {
- name: 'name 3',
- description: 'description 3',
- startDate: '2018-05-13T00:00:00.000Z',
- endDate: '2018-05-14T00:00:00.000Z',
- reference: 'phase',
- referenceId: 1,
- createdBy: 1,
- updatedBy: 1,
- deletedAt: '2018-05-14T00:00:00.000Z',
- },
- ]))
- .then(() => models.Milestone.bulkCreate(milestones))
- .then(() => done());
+ // Create timelines
+ models.Timeline.bulkCreate(timelines, { returning: true })
+ .then(createdTimelines => (
+ // create milestones after timelines
+ models.Milestone.bulkCreate(milestones))
+ .then(createdMilestones => [createdTimelines, createdMilestones]),
+ ),
+ ).then(([createdTimelines, createdMilestones]) =>
+ // Index to ES
+ Promise.all(_.map(createdTimelines, async (createdTimeline) => {
+ const timelineJson = _.omit(createdTimeline.toJSON(), 'deletedAt', 'deletedBy');
+ timelineJson.projectId = createdTimeline.id !== 3 ? 1 : 2;
+ if (timelineJson.id === 1) {
+ timelineJson.milestones = _.map(
+ createdMilestones,
+ cm => _.omit(cm.toJSON(), 'deletedAt', 'deletedBy'),
+ );
+ } else if (timelineJson.id === 2) {
+ timelineJson.description = 'from ES';
+ }
+
+ await server.services.es.index({
+ index: ES_TIMELINE_INDEX,
+ type: ES_TIMELINE_TYPE,
+ id: timelineJson.id,
+ body: timelineJson,
+ });
+ }))
+ .then(() => {
+ done();
+ }));
});
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /timelines/{timelineId}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/timelines/1')
+ .get('/v5/timelines/1')
.expect(403, done);
});
it('should return 403 for member who is not in the project', (done) => {
request(server)
- .get('/v4/timelines/1')
+ .get('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -202,7 +238,7 @@ describe('GET timeline', () => {
it('should return 403 for member who is not in the project (timeline refers to a phase)', (done) => {
request(server)
- .get('/v4/timelines/2')
+ .get('/v5/timelines/2')
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -211,7 +247,7 @@ describe('GET timeline', () => {
it('should return 404 for non-existed timeline', (done) => {
request(server)
- .get('/v4/timelines/1234')
+ .get('/v5/timelines/1234')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -220,31 +256,31 @@ describe('GET timeline', () => {
it('should return 404 for deleted timeline', (done) => {
request(server)
- .get('/v4/timelines/3')
+ .get('/v5/timelines/3')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(404, done);
});
- it('should return 422 for invalid param', (done) => {
+ it('should return 400 for invalid param', (done) => {
request(server)
- .get('/v4/timelines/0')
+ .get('/v5/timelines/0')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .expect(422, done);
+ .expect(400, done);
});
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/timelines/1')
+ .get('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(1);
resJson.name.should.be.eql('name 1');
resJson.description.should.be.eql('description 1');
@@ -279,7 +315,7 @@ describe('GET timeline', () => {
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/timelines/1')
+ .get('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -289,7 +325,7 @@ describe('GET timeline', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/timelines/1')
+ .get('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -299,7 +335,7 @@ describe('GET timeline', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/timelines/1')
+ .get('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -308,11 +344,59 @@ describe('GET timeline', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/timelines/1')
+ .get('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.expect(200, done);
});
+
+ it('should return data from ES when db param is not set', (done) => {
+ request(server)
+ .get('/v5/timelines/2')
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.admin}`,
+ })
+ .expect(200)
+ .end((err, res) => {
+ const resJson = res.body;
+ resJson.id.should.be.eql(2);
+ resJson.name.should.be.eql('name 2');
+ resJson.description.should.be.eql('from ES');
+
+ resJson.startDate.should.be.eql('2018-05-12T00:00:00.000Z');
+ resJson.endDate.should.be.eql('2018-05-13T00:00:00.000Z');
+ resJson.reference.should.be.eql('phase');
+ resJson.referenceId.should.be.eql(1);
+
+ resJson.createdBy.should.be.eql(1);
+ should.exist(resJson.createdAt);
+ resJson.updatedBy.should.be.eql(1);
+ should.exist(resJson.updatedAt);
+ should.not.exist(resJson.deletedBy);
+ should.not.exist(resJson.deletedAt);
+
+ should.not.exist(resJson.milestones);
+
+ done();
+ });
+ });
+
+ it('should return data from DB without calling ES when db param is set', (done) => {
+ request(server)
+ .get('/v5/timelines/2?db=true')
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.admin}`,
+ })
+ .expect(200)
+ .end((err, res) => {
+ const resJson = res.body;
+ resJson.id.should.be.eql(2);
+ resJson.name.should.be.eql('name 2');
+ resJson.description.should.be.eql('description 2');
+
+ done();
+ });
+ });
});
});
diff --git a/src/routes/timelines/list.js b/src/routes/timelines/list.js
index cf7777e8..d9ce06f4 100644
--- a/src/routes/timelines/list.js
+++ b/src/routes/timelines/list.js
@@ -4,12 +4,18 @@
import config from 'config';
import _ from 'lodash';
import { middleware as tcMiddleware } from 'tc-core-library-js';
+import models from '../../models';
import util from '../../util';
import validateTimeline from '../../middlewares/validateTimeline';
const ES_TIMELINE_INDEX = config.get('elasticsearchConfig.timelineIndexName');
const ES_TIMELINE_TYPE = config.get('elasticsearchConfig.timelineDocType');
+const MILESTONE_ATTRIBUTES = _.without(
+ _.keys(models.Milestone.rawAttributes),
+ 'deletedAt',
+);
+
/**
* Retrieve timelines from elastic search.
* @param {Array} esTerms the elastic search terms
@@ -31,15 +37,41 @@ function retrieveTimelines(esTerms) {
});
}
+/**
+ * Retrieve timelines from database.
+ * @param {Object} req the req object
+ * @param {Object} filters the filter object
+ * @returns {Array} the timelines
+ */
+function retrieveTimelinesFromDB(req, filters) {
+ return models.Timeline.search(filters, req.log)
+ .then((timelines) => {
+ const timelineIds = _.map(timelines, 'id');
+
+ // retrieve milestones
+ return models.Milestone.findAll({
+ attributes: MILESTONE_ATTRIBUTES,
+ where: { timelineId: { $in: timelineIds } },
+ raw: true,
+ })
+ .then((values) => {
+ _.forEach(timelines, (t) => {
+ t.milestones = _.filter(values, m => m.timelineId === t.id); // eslint-disable-line no-param-reassign
+ });
+ return timelines;
+ });
+ });
+}
+
const permissions = tcMiddleware.permissions;
module.exports = [
- // Validate and get projectId from the reference/referenceId pair, and set to request params for
+ // Validate and get projectId from the reference/referenceId pair, and set to request query for
// checking by the permissions middleware
validateTimeline.validateTimelineQueryFilter,
permissions('timeline.view'),
(req, res, next) => {
- const filter = req.params.filter;
+ const filter = req.query;
// Build the elastic search query
const esTerms = [{
@@ -50,7 +82,15 @@ module.exports = [
// Retrieve timelines, as we know the user has access for the provided reference/referenceId part
return retrieveTimelines(esTerms)
- .then(result => res.json(util.wrapResponse(req.id, result.rows, result.count)))
+ .then((result) => {
+ if (result.rows.length === 0) {
+ req.log.debug('Fetch timeline from db');
+ return retrieveTimelinesFromDB(req, filter)
+ .then(timelines => res.json(timelines));
+ }
+ req.log.debug('timeline found from ES');
+ return res.json(result.rows);
+ })
.catch(err => next(err));
},
];
diff --git a/src/routes/timelines/list.spec.js b/src/routes/timelines/list.spec.js
index 9da02b3b..3a3e9996 100644
--- a/src/routes/timelines/list.spec.js
+++ b/src/routes/timelines/list.spec.js
@@ -3,7 +3,7 @@
*/
import chai from 'chai';
import request from 'supertest';
-import sleep from 'sleep';
+// import sleep from 'sleep';
import config from 'config';
import _ from 'lodash';
@@ -209,55 +209,57 @@ describe('LIST timelines', () => {
}))
.then(() => {
// sleep for some time, let elasticsearch indices be settled
- sleep.sleep(5);
+ // sleep.sleep(5);
done();
}));
});
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /timelines', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/timelines')
+ .get('/v5/timelines')
.expect(403, done);
});
- it('should return 422 for invalid filter key', (done) => {
+ it('should return 400 for invalid filter key', (done) => {
request(server)
- .get('/v4/timelines?filter=invalid%3Dproject')
+ .get('/v5/timelines?invalid=project')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
- .expect(422)
+ .expect(400)
.end(done);
});
- it('should return 422 for invalid reference filter', (done) => {
+ it('should return 400 for invalid reference filter', (done) => {
request(server)
- .get('/v4/timelines?filter=reference%3Dinvalid%26referenceId%3D1')
+ .get('/v5/timelines?reference=invalid&referenceId=1')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
- .expect(422)
+ .expect(400)
.end(done);
});
- it('should return 422 for invalid referenceId filter', (done) => {
+ it('should return 400 for invalid referenceId filter', (done) => {
request(server)
- .get('/v4/timelines?filter=reference%3Dinvalid%26referenceId%3D0')
+ .get('/v5/timelines?reference=invalid&referenceId=0')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
- .expect(422)
+ .expect(400)
.end(done);
});
it('should return 200 for admin', (done) => {
request(server)
- .get('/v4/timelines?filter=reference%3Dproject%26referenceId%3D1')
+ .get('/v5/timelines?reference=project&referenceId=1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -265,7 +267,7 @@ describe('LIST timelines', () => {
.end((err, res) => {
const timeline = timelines[0];
- let resJson = res.body.result.content;
+ let resJson = res.body;
resJson.should.have.length(1);
resJson = _.sortBy(resJson, o => o.id);
resJson[0].id.should.be.eql(1);
@@ -302,13 +304,13 @@ describe('LIST timelines', () => {
it('should return 200 for connect admin', (done) => {
request(server)
- .get('/v4/timelines?filter=reference%3Dproject%26referenceId%3D1')
+ .get('/v5/timelines?reference=project&referenceId=1')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(1);
done();
@@ -317,13 +319,13 @@ describe('LIST timelines', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get('/v4/timelines?filter=reference%3Dproject%26referenceId%3D1')
+ .get('/v5/timelines?reference=project&referenceId=1')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(1);
done();
@@ -332,12 +334,12 @@ describe('LIST timelines', () => {
it('should return 200 for member', (done) => {
request(server)
- .get('/v4/timelines?filter=reference%3Dproject%26referenceId%3D1')
+ .get('/v5/timelines?reference=project&referenceId=1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(1);
done();
@@ -346,12 +348,12 @@ describe('LIST timelines', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .get('/v4/timelines?filter=reference%3Dproject%26referenceId%3D1')
+ .get('/v5/timelines?reference=project&referenceId=1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(1);
done();
@@ -360,7 +362,7 @@ describe('LIST timelines', () => {
it('should return 403 for member with not accessible project', (done) => {
request(server)
- .get('/v4/timelines?filter=reference%3Dproject%26referenceId%3D1')
+ .get('/v5/timelines?reference=project&referenceId=1')
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -369,13 +371,13 @@ describe('LIST timelines', () => {
it('should return 200 with reference and referenceId filter', (done) => {
request(server)
- .get('/v4/timelines?filter=reference%3Dproject%26referenceId%3D1')
+ .get('/v5/timelines?reference=project&referenceId=1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(1);
done();
diff --git a/src/routes/timelines/update.js b/src/routes/timelines/update.js
index bf403148..885d7db4 100644
--- a/src/routes/timelines/update.js
+++ b/src/routes/timelines/update.js
@@ -8,7 +8,7 @@ import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import util from '../../util';
import validateTimeline from '../../middlewares/validateTimeline';
-import { EVENT, TIMELINE_REFERENCES } from '../../constants';
+import { EVENT, RESOURCES, TIMELINE_REFERENCES } from '../../constants';
const permissions = tcMiddleware.permissions;
@@ -16,23 +16,21 @@ const schema = {
params: {
timelineId: Joi.number().integer().positive().required(),
},
- body: {
- param: Joi.object().keys({
- id: Joi.any().strip(),
- name: Joi.string().max(255).required(),
- description: Joi.string().max(255),
- startDate: Joi.date().required(),
- endDate: Joi.date().min(Joi.ref('startDate')).allow(null),
- reference: Joi.string().valid(_.values(TIMELINE_REFERENCES)).required(),
- referenceId: Joi.number().integer().positive().required(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ body: Joi.object().keys({
+ id: Joi.any().strip(),
+ name: Joi.string().max(255).required(),
+ description: Joi.string().max(255),
+ startDate: Joi.date().required(),
+ endDate: Joi.date().min(Joi.ref('startDate')).allow(null),
+ reference: Joi.string().valid(_.values(TIMELINE_REFERENCES)).required(),
+ referenceId: Joi.number().integer().positive().required(),
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
};
module.exports = [
@@ -43,7 +41,7 @@ module.exports = [
validateTimeline.validateTimelineRequestBody,
permissions('timeline.edit'),
(req, res, next) => {
- const entityToUpdate = _.assign(req.body.param, {
+ const entityToUpdate = _.assign(req.body, {
updatedBy: req.authUser.userId,
});
@@ -104,11 +102,16 @@ module.exports = [
{ original, updated },
{ correlationId: req.id },
);
- req.app.emit(EVENT.ROUTING_KEY.TIMELINE_UPDATED,
- { req, original, updated });
+ // emit the event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.TIMELINE_UPDATED,
+ RESOURCES.TIMELINE,
+ updated,
+ original);
// Write to response
- res.json(util.wrapResponse(req.id, updated));
+ res.json(updated);
return Promise.resolve();
})
.catch(next);
diff --git a/src/routes/timelines/update.spec.js b/src/routes/timelines/update.spec.js
index d0726cf1..73ae6267 100644
--- a/src/routes/timelines/update.spec.js
+++ b/src/routes/timelines/update.spec.js
@@ -9,7 +9,7 @@ import _ from 'lodash';
import models from '../../models';
import server from '../../app';
import testUtil from '../../tests/util';
-import { EVENT, BUS_API_EVENT } from '../../constants';
+import { EVENT, BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants';
import busApi from '../../services/busApi';
const should = chai.should();
@@ -186,30 +186,30 @@ describe('UPDATE timeline', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('PATCH /timelines/{timelineId}', () => {
const body = {
- param: {
- name: 'new name 1',
- description: 'new description 1',
- startDate: '2018-06-01T00:00:00.000Z',
- endDate: '2018-06-02T00:00:00.000Z',
- reference: 'project',
- referenceId: 1,
- },
+ name: 'new name 1',
+ description: 'new description 1',
+ startDate: '2018-06-01T00:00:00.000Z',
+ endDate: '2018-06-02T00:00:00.000Z',
+ reference: 'project',
+ referenceId: 1,
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.send(body)
.expect(403, done);
});
it('should return 403 for member who is not in the project', (done) => {
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -219,7 +219,7 @@ describe('UPDATE timeline', () => {
it('should return 403 for member who is not in the project (timeline refers to a phase)', (done) => {
request(server)
- .patch('/v4/timelines/2')
+ .patch('/v5/timelines/2')
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
@@ -229,7 +229,7 @@ describe('UPDATE timeline', () => {
it('should return 404 for non-existed timeline', (done) => {
request(server)
- .patch('/v4/timelines/1234')
+ .patch('/v5/timelines/1234')
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
@@ -239,7 +239,7 @@ describe('UPDATE timeline', () => {
it('should return 404 for deleted timeline', (done) => {
request(server)
- .patch('/v4/timelines/3')
+ .patch('/v5/timelines/3')
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
@@ -247,19 +247,19 @@ describe('UPDATE timeline', () => {
.expect(404, done);
});
- it('should return 422 for invalid param', (done) => {
+ it('should return 400 for invalid param', (done) => {
request(server)
- .patch('/v4/timelines/0')
+ .patch('/v5/timelines/0')
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .expect(422, done);
+ .expect(400, done);
});
it('should return 404 for non-existed template', (done) => {
request(server)
- .patch('/v4/timelines/1234')
+ .patch('/v5/timelines/1234')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -269,7 +269,7 @@ describe('UPDATE timeline', () => {
it('should return 404 for deleted template', (done) => {
request(server)
- .patch('/v4/timelines/3')
+ .patch('/v5/timelines/3')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -277,213 +277,191 @@ describe('UPDATE timeline', () => {
.expect(404, done);
});
- it('should return 422 if missing name', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- name: undefined,
- }),
- };
+ it('should return 400 if missing name', (done) => {
+ const invalidBody = _.assign({}, body, {
+ name: undefined,
+ });
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if missing startDate', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- startDate: undefined,
- }),
- };
+ it('should return 400 if missing startDate', (done) => {
+ const invalidBody = _.assign({}, body, {
+ startDate: undefined,
+ });
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if startDate is after endDate', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- startDate: '2018-05-29T00:00:00.000Z',
- endDate: '2018-05-28T00:00:00.000Z',
- }),
- };
+ it('should return 400 if startDate is after endDate', (done) => {
+ const invalidBody = _.assign({}, body, {
+ startDate: '2018-05-29T00:00:00.000Z',
+ endDate: '2018-05-28T00:00:00.000Z',
+ });
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if missing reference', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- reference: undefined,
- }),
- };
+ it('should return 400 if missing reference', (done) => {
+ const invalidBody = _.assign({}, body, {
+ reference: undefined,
+ });
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if missing referenceId', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- referenceId: undefined,
- }),
- };
+ it('should return 400 if missing referenceId', (done) => {
+ const invalidBody = _.assign({}, body, {
+ referenceId: undefined,
+ });
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if invalid reference', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- reference: 'invalid',
- }),
- };
+ it('should return 400 if invalid reference', (done) => {
+ const invalidBody = _.assign({}, body, {
+ reference: 'invalid',
+ });
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if invalid referenceId', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- referenceId: 0,
- }),
- };
+ it('should return 400 if invalid referenceId', (done) => {
+ const invalidBody = _.assign({}, body, {
+ referenceId: 0,
+ });
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if project does not exist', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- referenceId: 1110,
- }),
- };
+ it('should return 400 if project does not exist', (done) => {
+ const invalidBody = _.assign({}, body, {
+ referenceId: 1110,
+ });
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if project was deleted', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- referenceId: 2,
- }),
- };
+ it('should return 400 if project was deleted', (done) => {
+ const invalidBody = _.assign({}, body, {
+ referenceId: 2,
+ });
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if phase does not exist', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- reference: 'phase',
- referenceId: 2222,
- }),
- };
+ it('should return 400 if phase does not exist', (done) => {
+ const invalidBody = _.assign({}, body, {
+ reference: 'phase',
+ referenceId: 2222,
+ });
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 if phase was deleted', (done) => {
- const invalidBody = {
- param: _.assign({}, body.param, {
- reference: 'phase',
- referenceId: 2,
- }),
- };
+ it('should return 400 if phase was deleted', (done) => {
+ const invalidBody = _.assign({}, body, {
+ reference: 'phase',
+ referenceId: 2,
+ });
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 200 for admin', (done) => {
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(body)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson.id);
- resJson.name.should.be.eql(body.param.name);
- resJson.description.should.be.eql(body.param.description);
- resJson.startDate.should.be.eql(body.param.startDate);
- resJson.endDate.should.be.eql(body.param.endDate);
- resJson.reference.should.be.eql(body.param.reference);
- resJson.referenceId.should.be.eql(body.param.referenceId);
+ resJson.name.should.be.eql(body.name);
+ resJson.description.should.be.eql(body.description);
+ resJson.startDate.should.be.eql(body.startDate);
+ resJson.endDate.should.be.eql(body.endDate);
+ resJson.reference.should.be.eql(body.reference);
+ resJson.referenceId.should.be.eql(body.referenceId);
resJson.createdBy.should.be.eql(1);
should.exist(resJson.createdAt);
@@ -517,25 +495,23 @@ describe('UPDATE timeline', () => {
this.timeout(10000);
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({
- param: _.assign({}, body.param, {
- startDate: '2018-05-15T00:00:00.000Z',
- endDate: '2018-05-17T00:00:00.000Z', // no affect to milestones
- }),
- })
+ .send(_.assign({}, body, {
+ startDate: '2018-05-15T00:00:00.000Z',
+ endDate: '2018-05-17T00:00:00.000Z', // no affect to milestones
+ }))
.expect(200)
.end(() => {
setTimeout(() => {
- models.Milestone.findById(1)
+ models.Milestone.findByPk(1)
.then((milestone) => {
milestone.startDate.should.be.eql(new Date('2018-05-15T00:00:00.000Z'));
milestone.endDate.should.be.eql(new Date('2018-05-16T00:00:00.000Z'));
})
- .then(() => models.Milestone.findById(2))
+ .then(() => models.Milestone.findByPk(2))
.then((milestone) => {
milestone.startDate.should.be.eql(new Date('2018-05-17T00:00:00.000Z'));
milestone.endDate.should.be.eql(new Date('2018-05-19T00:00:00.000Z'));
@@ -551,25 +527,23 @@ describe('UPDATE timeline', () => {
this.timeout(10000);
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({
- param: _.assign({}, body.param, {
- startDate: '2018-05-12T00:00:00.000Z', // no affect to milestones
- endDate: '2018-05-15T00:00:00.000Z',
- }),
- })
+ .send(_.assign({}, body, {
+ startDate: '2018-05-12T00:00:00.000Z', // no affect to milestones
+ endDate: '2018-05-15T00:00:00.000Z',
+ }))
.expect(200)
.end(() => {
setTimeout(() => {
- models.Milestone.findById(1)
+ models.Milestone.findByPk(1)
.then((milestone) => {
milestone.startDate.should.be.eql(new Date('2018-05-12T00:00:00.000Z'));
milestone.endDate.should.be.eql(new Date('2018-05-13T00:00:00.000Z'));
})
- .then(() => models.Milestone.findById(2))
+ .then(() => models.Milestone.findByPk(2))
.then((milestone) => {
milestone.startDate.should.be.eql(new Date('2018-05-14T00:00:00.000Z'));
milestone.endDate.should.be.eql(new Date('2018-05-16T00:00:00.000Z'));
@@ -583,7 +557,7 @@ describe('UPDATE timeline', () => {
it('should return 200 for connect admin', (done) => {
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -594,7 +568,7 @@ describe('UPDATE timeline', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -605,7 +579,7 @@ describe('UPDATE timeline', () => {
it('should return 200 for copilot', (done) => {
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -616,7 +590,7 @@ describe('UPDATE timeline', () => {
it('should return 200 for member', (done) => {
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -626,15 +600,13 @@ describe('UPDATE timeline', () => {
});
it('should return 200 if changing reference and referenceId', (done) => {
- const newBody = {
- param: _.assign({}, body.param, {
- reference: 'phase',
- referenceId: 1,
- }),
- };
+ const newBody = _.assign({}, body, {
+ reference: 'phase',
+ referenceId: 1,
+ });
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -662,9 +634,9 @@ describe('UPDATE timeline', () => {
// not testing fields separately as startDate is required parameter,
// thus TIMELINE_ADJUSTED will be always sent
- it('should send message BUS_API_EVENT.TIMELINE_ADJUSTED when timeline updated', (done) => {
+ it('should send correct BUS API messages when timeline updated', (done) => {
request(server)
- .patch('/v4/timelines/1')
+ .patch('/v5/timelines/1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -675,14 +647,22 @@ describe('UPDATE timeline', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.calledWith(BUS_API_EVENT.TIMELINE_ADJUSTED, sinon.match({
+ createEventSpy.callCount.should.equal(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.TIMELINE_UPDATED, sinon.match({
+ resource: RESOURCES.TIMELINE,
+ name: body.name,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.TIMELINE_ADJUSTED, sinon.match({
projectId: 1,
projectName: 'test1',
projectUrl: 'https://local.topcoder-dev.com/projects/1',
userId: 40051332,
initiatorUserId: 40051332,
})).should.be.true;
+
done();
});
}
diff --git a/src/routes/workItems/create.js b/src/routes/workItems/create.js
index 06629560..f53ef1b4 100644
--- a/src/routes/workItems/create.js
+++ b/src/routes/workItems/create.js
@@ -7,7 +7,7 @@ import Joi from 'joi';
import models from '../../models';
import util from '../../util';
-import { EVENT } from '../../constants';
+import { EVENT, RESOURCES } from '../../constants';
const permissions = require('tc-core-library-js').middleware.permissions;
@@ -18,16 +18,14 @@ const schema = {
workId: Joi.number().integer().positive().required(),
},
body: {
- param: Joi.object().keys({
- name: Joi.string().required(),
- type: Joi.string().required(),
- templateId: Joi.number().positive().optional(),
- directProjectId: Joi.number().positive().optional(),
- billingAccountId: Joi.number().positive().optional(),
- estimatedPrice: Joi.number().positive().optional(),
- actualPrice: Joi.number().positive().optional(),
- details: Joi.any().optional(),
- }).required(),
+ name: Joi.string().required(),
+ type: Joi.string().required(),
+ templateId: Joi.number().positive().optional(),
+ directProjectId: Joi.number().positive().optional(),
+ billingAccountId: Joi.number().positive().optional(),
+ estimatedPrice: Joi.number().positive().optional(),
+ actualPrice: Joi.number().positive().optional(),
+ details: Joi.any().optional(),
},
};
@@ -42,7 +40,7 @@ module.exports = [
const workStreamId = _.parseInt(req.params.workStreamId);
const phaseId = _.parseInt(req.params.workId);
- const data = req.body.param;
+ const data = req.body;
// default values
_.assign(data, {
projectId,
@@ -127,9 +125,14 @@ module.exports = [
{ correlationId: req.id },
);
req.log.debug('Sending event to Kafka bus for phase product %d', newPhaseProduct.id);
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_ADDED, { req, created: newPhaseProduct });
+ // emit the event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_ADDED,
+ RESOURCES.PHASE_PRODUCT,
+ newPhaseProduct);
- res.status(201).json(util.wrapResponse(req.id, newPhaseProduct, 1, 201));
+ res.status(201).json(newPhaseProduct);
})
.catch((err) => { next(err); });
},
diff --git a/src/routes/workItems/create.spec.js b/src/routes/workItems/create.spec.js
index a339d1b7..0e7e3482 100644
--- a/src/routes/workItems/create.spec.js
+++ b/src/routes/workItems/create.spec.js
@@ -12,6 +12,8 @@ import models from '../../models';
import testUtil from '../../tests/util';
import busApi from '../../services/busApi';
+import { BUS_API_EVENT, RESOURCES } from '../../constants';
+
const should = chai.should();
const body = {
@@ -156,109 +158,109 @@ describe('CREATE Work Item', () => {
describe('POST /projects/{projectId}/workstreams/{workStreamId}/works/{workId}/workitems', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
- .send({ param: body })
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
+ .send(body)
.expect(403, done);
});
it('should return 403 for copilot', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: body })
+ .send(body)
.expect(403, done);
});
it('should return 404 when no work stream with specific workStreamId', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/workstreams/999/works/${workId}/workitems`)
+ .post(`/v5/projects/${projectId}/workstreams/999/works/${workId}/workitems`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(404, done);
});
it('should return 404 when no work with specific workId', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/999/workitems`)
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/999/workitems`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(404, done);
});
- it('should return 422 when name not provided', (done) => {
+ it('should return 400 when name not provided', (done) => {
const reqBody = _.cloneDeep(body);
delete reqBody.name;
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.send({ param: reqBody })
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 when type not provided', (done) => {
+ it('should return 400 when type not provided', (done) => {
const reqBody = _.cloneDeep(body);
delete reqBody.type;
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.send({ param: reqBody })
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 when estimatedPrice is negative', (done) => {
+ it('should return 400 when estimatedPrice is negative', (done) => {
const reqBody = _.cloneDeep(body);
reqBody.estimatedPrice = -20;
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.send({ param: reqBody })
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 when actualPrice is negative', (done) => {
+ it('should return 400 when actualPrice is negative', (done) => {
const reqBody = _.cloneDeep(body);
reqBody.actualPrice = -20;
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.send({ param: reqBody })
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 200 for member', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.name.should.be.eql(body.name);
resJson.type.should.be.eql(body.type);
@@ -272,18 +274,18 @@ describe('CREATE Work Item', () => {
it('should return 201 if payload is valid', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.name.should.be.eql(body.name);
resJson.type.should.be.eql(body.type);
@@ -312,13 +314,13 @@ describe('CREATE Work Item', () => {
sandbox.restore();
});
- it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when work item created', (done) => {
+ it('should send correct BUS API messages when work item created', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(201)
.end((err) => {
@@ -326,7 +328,12 @@ describe('CREATE Work Item', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.notCalled.should.be.true;
+ createEventSpy.callCount.should.be.eql(1);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, sinon.match({
+ resource: RESOURCES.PHASE_PRODUCT,
+ })).should.be.true;
+
done();
});
}
diff --git a/src/routes/workItems/delete.js b/src/routes/workItems/delete.js
index f8a596e5..bab03805 100644
--- a/src/routes/workItems/delete.js
+++ b/src/routes/workItems/delete.js
@@ -6,7 +6,8 @@ import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import models from '../../models';
-import { EVENT } from '../../constants';
+import util from '../../util';
+import { EVENT, RESOURCES } from '../../constants';
const permissions = tcMiddleware.permissions;
@@ -83,7 +84,12 @@ module.exports = [
deleted,
{ correlationId: req.id },
);
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_REMOVED, { req, deleted });
+ // emit the event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_REMOVED,
+ RESOURCES.PHASE_PRODUCT,
+ _.pick(deleted.toJSON(), 'id'));
res.status(204).json({});
})
diff --git a/src/routes/workItems/delete.spec.js b/src/routes/workItems/delete.spec.js
index acd509f8..f9e98731 100644
--- a/src/routes/workItems/delete.spec.js
+++ b/src/routes/workItems/delete.spec.js
@@ -12,6 +12,8 @@ import models from '../../models';
import testUtil from '../../tests/util';
import busApi from '../../services/busApi';
+import { BUS_API_EVENT, RESOURCES } from '../../constants';
+
chai.should();
const expectAfterDelete = (projectId, workStreamId, phaseId, id, err, next) => {
@@ -33,7 +35,7 @@ const expectAfterDelete = (projectId, workStreamId, phaseId, id, err, next) => {
chai.assert.isNotNull(res.deletedBy);
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${phaseId}/workitems/${id}`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${phaseId}/workitems/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -192,13 +194,13 @@ describe('DELETE Work Item', () => {
describe('DELETE /projects/{projectId}/workstreams/{workStreamId}/works/{workId}/workitems/{productId}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
+ .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
.expect(403, done);
});
it('should return 403 for copilot', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
+ .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -207,7 +209,7 @@ describe('DELETE Work Item', () => {
it('should return 404 when no work stream with specific workStreamId', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/999/works/${workId}/workitems/${productId}`)
+ .delete(`/v5/projects/${projectId}/workstreams/999/works/${workId}/workitems/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -217,7 +219,7 @@ describe('DELETE Work Item', () => {
it('should return 404 when no work with specific workId', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/999/workitems/${productId}`)
+ .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/999/workitems/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -227,7 +229,7 @@ describe('DELETE Work Item', () => {
it('should return 204 for member', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
+ .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -236,7 +238,7 @@ describe('DELETE Work Item', () => {
it('should return 204 when user have project permission', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
+ .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -261,9 +263,9 @@ describe('DELETE Work Item', () => {
sandbox.restore();
});
- it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when work item removed', (done) => {
+ it('should send correct BUS API messages when work item removed', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
+ .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -273,7 +275,12 @@ describe('DELETE Work Item', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.notCalled.should.be.true;
+ createEventSpy.callCount.should.be.eql(1);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_DELETED, sinon.match({
+ resource: RESOURCES.PHASE_PRODUCT,
+ })).should.be.true;
+
done();
});
}
diff --git a/src/routes/workItems/get.js b/src/routes/workItems/get.js
index 5169baff..44d20716 100644
--- a/src/routes/workItems/get.js
+++ b/src/routes/workItems/get.js
@@ -5,7 +5,6 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import models from '../../models';
-import util from '../../util';
const permissions = require('tc-core-library-js').middleware.permissions;
@@ -68,7 +67,7 @@ module.exports = [
err.status = 404;
throw err;
} else {
- res.json(util.wrapResponse(req.id, product));
+ res.json(product);
}
}).catch(err => next(err));
},
diff --git a/src/routes/workItems/get.spec.js b/src/routes/workItems/get.spec.js
index ef0edb88..b3acd08c 100644
--- a/src/routes/workItems/get.spec.js
+++ b/src/routes/workItems/get.spec.js
@@ -145,7 +145,7 @@ describe('GET Work Item', () => {
describe('GET /projects/{projectId}/workstreams/{workStreamId}/works/{workId}/workitems/{productId}', () => {
it('should return 403 when user have no permission (non team member)', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -155,7 +155,7 @@ describe('GET Work Item', () => {
it('should return 404 when no project with specific projectId', (done) => {
request(server)
- .get(`/v4/projects/9999/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
+ .get(`/v5/projects/9999/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -165,7 +165,7 @@ describe('GET Work Item', () => {
it('should return 404 when no work stream with specific workStreamId', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/999/works/${workId}/workitems/${productId}`)
+ .get(`/v5/projects/${projectId}/workstreams/999/works/${workId}/workitems/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -175,7 +175,7 @@ describe('GET Work Item', () => {
it('should return 404 when no work with specific workId', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/999/workitems/${productId}`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/999/workitems/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -185,7 +185,7 @@ describe('GET Work Item', () => {
it('should return 1 phase when user have project permission (customer)', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -195,7 +195,7 @@ describe('GET Work Item', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.name.should.be.eql(body.name);
resJson.type.should.be.eql(body.type);
@@ -209,7 +209,7 @@ describe('GET Work Item', () => {
it('should return 1 phase when user have project permission (copilot)', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -219,7 +219,7 @@ describe('GET Work Item', () => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.name.should.be.eql(body.name);
resJson.type.should.be.eql(body.type);
diff --git a/src/routes/workItems/list.js b/src/routes/workItems/list.js
index f60bd8e6..b9dd656d 100644
--- a/src/routes/workItems/list.js
+++ b/src/routes/workItems/list.js
@@ -4,7 +4,6 @@
import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
-import util from '../../util';
import models from '../../models';
const permissions = require('tc-core-library-js').middleware.permissions;
@@ -58,7 +57,7 @@ module.exports = [
},
});
})
- .then(products => res.json(util.wrapResponse(req.id, products, products.length)))
+ .then(products => res.json(products))
.catch(err => next(err));
},
];
diff --git a/src/routes/workItems/list.spec.js b/src/routes/workItems/list.spec.js
index 8be04654..bb2cd9fb 100644
--- a/src/routes/workItems/list.spec.js
+++ b/src/routes/workItems/list.spec.js
@@ -141,18 +141,18 @@ describe('LIST Work Items', () => {
describe('GET /projects/{projectId}/workstreams/{workStreamId}/works/{workId}/workitems', () => {
it('should return 403 when user have no permission (non team member)', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(403, done);
});
it('should return 404 when no project with specific projectId', (done) => {
request(server)
- .get(`/v4/projects/9999/workstreams/${workStreamId}/works/${workId}/workitems`)
+ .get(`/v5/projects/9999/workstreams/${workStreamId}/works/${workId}/workitems`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -162,7 +162,7 @@ describe('LIST Work Items', () => {
it('should return 404 when no work stream with specific workStreamId', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/999/works/${workId}/workitems`)
+ .get(`/v5/projects/${projectId}/workstreams/999/works/${workId}/workitems`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -172,7 +172,7 @@ describe('LIST Work Items', () => {
it('should return 404 when no work with specific workId', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/999/workitems`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/999/workitems`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -182,18 +182,18 @@ describe('LIST Work Items', () => {
it('should return 1 phase when user have project permission (customer)', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(1);
done();
@@ -203,18 +203,18 @@ describe('LIST Work Items', () => {
it('should return 1 phase when user have project permission (copilot)', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: body })
+ .send(body)
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.should.have.lengthOf(1);
done();
diff --git a/src/routes/workItems/update.js b/src/routes/workItems/update.js
index 274f2da0..d22e00bd 100644
--- a/src/routes/workItems/update.js
+++ b/src/routes/workItems/update.js
@@ -7,7 +7,7 @@ import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import models from '../../models';
import util from '../../util';
-import { EVENT, ROUTES } from '../../constants';
+import { EVENT, RESOURCES, ROUTES } from '../../constants';
const permissions = tcMiddleware.permissions;
@@ -19,16 +19,14 @@ const schema = {
id: Joi.number().integer().positive().required(),
},
body: {
- param: Joi.object().keys({
- name: Joi.string().optional(),
- type: Joi.string().optional(),
- templateId: Joi.number().positive().optional(),
- directProjectId: Joi.number().positive().optional(),
- billingAccountId: Joi.number().positive().optional(),
- estimatedPrice: Joi.number().positive().optional(),
- actualPrice: Joi.number().positive().optional(),
- details: Joi.any().optional(),
- }).required(),
+ name: Joi.string().optional(),
+ type: Joi.string().optional(),
+ templateId: Joi.number().positive().optional(),
+ directProjectId: Joi.number().positive().optional(),
+ billingAccountId: Joi.number().positive().optional(),
+ estimatedPrice: Joi.number().positive().optional(),
+ actualPrice: Joi.number().positive().optional(),
+ details: Joi.any().optional(),
},
};
@@ -45,7 +43,7 @@ module.exports = [
const phaseId = _.parseInt(req.params.workId);
const productId = _.parseInt(req.params.id);
- const updatedProps = req.body.param;
+ const updatedProps = req.body;
updatedProps.updatedBy = req.authUser.userId;
let previousValue;
@@ -106,14 +104,16 @@ module.exports = [
{ original: previousValue, updated: updatedValue },
{ correlationId: req.id },
);
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED, {
+ util.sendResourceToKafkaBus(
req,
- original: previousValue,
- updated: updatedValue,
- route: ROUTES.WORK_ITEMS.UPDATE,
- });
+ EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED,
+ RESOURCES.PHASE_PRODUCT,
+ updatedValue,
+ previousValue,
+ ROUTES.WORK_ITEMS.UPDATE,
+ );
- res.json(util.wrapResponse(req.id, updated));
+ res.json(updated);
}).catch(err => next(err));
},
];
diff --git a/src/routes/workItems/update.spec.js b/src/routes/workItems/update.spec.js
index 14667601..311e7160 100644
--- a/src/routes/workItems/update.spec.js
+++ b/src/routes/workItems/update.spec.js
@@ -11,7 +11,7 @@ import server from '../../app';
import models from '../../models';
import testUtil from '../../tests/util';
import busApi from '../../services/busApi';
-import { BUS_API_EVENT } from '../../constants';
+import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants';
const should = chai.should();
@@ -175,46 +175,46 @@ describe('UPDATE Work Item', () => {
describe('PATCH/projects/{projectId}/workstreams/{workStreamId}/works/{workId}/workitems/{productId}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
- .send({ param: updateBody })
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
+ .send(updateBody)
.expect(403, done);
});
it('should return 403 for copilot', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect(403, done);
});
it('should return 404 when no work stream with specific workStreamId', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/999/works/${workId}/workitems/${productId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/999/works/${workId}/workitems/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect('Content-Type', /json/)
.expect(404, done);
});
it('should return 404 when no work with specific workId', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/999/workitems/${productId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/999/workitems/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect('Content-Type', /json/)
.expect(404, done);
});
- it('should return 422 when parameters are invalid', (done) => {
+ it('should return 400 when parameters are invalid', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/999/workitems/99999`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/999/workitems/99999`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -224,33 +224,33 @@ describe('UPDATE Work Item', () => {
},
})
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 200 for member', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect(200, done);
});
it('should return updated product when user have permission and parameters are valid', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
should.exist(resJson);
resJson.name.should.be.eql(updateBody.name);
resJson.type.should.be.eql(updateBody.type);
@@ -279,158 +279,187 @@ describe('UPDATE Work Item', () => {
sandbox.restore();
});
- it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when name updated', (done) => {
+ it('should send correct BUS API messages when name updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.member}`,
- })
- .send({
- param: {
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.member}`,
+ })
+ .send({
name: 'new name',
- },
- })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err) => {
- if (err) {
- done(err);
- } else {
- testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
- projectId: 1,
- projectName: 'test1',
- projectUrl: 'https://local.topcoder-dev.com/projects/1',
- userId: 40051331,
- initiatorUserId: 40051331,
- })).should.be.true;
- done();
- });
- }
- });
+ })
+ .expect('Content-Type', /json/)
+ .expect(200)
+ .end((err) => {
+ if (err) {
+ done(err);
+ } else {
+ testUtil.wait(() => {
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE_PRODUCT,
+ name: 'new name',
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
+ projectId: 1,
+ projectName: 'test1',
+ projectUrl: 'https://local.topcoder-dev.com/projects/1',
+ userId: 40051331,
+ initiatorUserId: 40051331,
+ })).should.be.true;
+
+ done();
+ });
+ }
+ });
});
- it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when estimatedPrice updated', (done) => {
+ it('should send correct BUS API messages when estimatedPrice updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.member}`,
- })
- .send({
- param: {
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.member}`,
+ })
+ .send({
estimatedPrice: 123,
- },
- })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err) => {
- if (err) {
- done(err);
- } else {
- testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
- projectId: 1,
- projectName: 'test1',
- projectUrl: 'https://local.topcoder-dev.com/projects/1',
- userId: 40051331,
- initiatorUserId: 40051331,
- })).should.be.true;
- done();
- });
- }
- });
+ })
+ .expect('Content-Type', /json/)
+ .expect(200)
+ .end((err) => {
+ if (err) {
+ done(err);
+ } else {
+ testUtil.wait(() => {
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE_PRODUCT,
+ estimatedPrice: 123,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
+ projectId: 1,
+ projectName: 'test1',
+ projectUrl: 'https://local.topcoder-dev.com/projects/1',
+ userId: 40051331,
+ initiatorUserId: 40051331,
+ })).should.be.true;
+
+ done();
+ });
+ }
+ });
});
- it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when actualPrice updated', (done) => {
+ it('should send correct BUS API messages when actualPrice updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.member}`,
- })
- .send({
- param: {
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.member}`,
+ })
+ .send({
actualPrice: 123,
- },
- })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err) => {
- if (err) {
- done(err);
- } else {
- testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
- projectId: 1,
- projectName: 'test1',
- projectUrl: 'https://local.topcoder-dev.com/projects/1',
- userId: 40051331,
- initiatorUserId: 40051331,
- })).should.be.true;
- done();
- });
- }
- });
+ })
+ .expect('Content-Type', /json/)
+ .expect(200)
+ .end((err) => {
+ if (err) {
+ done(err);
+ } else {
+ testUtil.wait(() => {
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE_PRODUCT,
+ actualPrice: 123,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
+ projectId: 1,
+ projectName: 'test1',
+ projectUrl: 'https://local.topcoder-dev.com/projects/1',
+ userId: 40051331,
+ initiatorUserId: 40051331,
+ })).should.be.true;
+
+ done();
+ });
+ }
+ });
});
- it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when details updated', (done) => {
+ it('should send correct BUS API messages when details updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.member}`,
- })
- .send({
- param: {
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.member}`,
+ })
+ .send({
details: 'something',
- },
- })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err) => {
- if (err) {
- done(err);
- } else {
- testUtil.wait(() => {
- createEventSpy.calledTwice.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_WORKITEM_SPECIFICATION_MODIFIED);
- createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
- projectId: 1,
- projectName: 'test1',
- projectUrl: 'https://local.topcoder-dev.com/projects/1',
- userId: 40051331,
- initiatorUserId: 40051331,
- })).should.be.true;
- done();
- });
- }
- });
+ })
+ .expect('Content-Type', /json/)
+ .expect(200)
+ .end((err) => {
+ if (err) {
+ done(err);
+ } else {
+ testUtil.wait(() => {
+ createEventSpy.callCount.should.be.eql(3);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE_PRODUCT,
+ details: 'something',
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_WORKITEM_SPECIFICATION_MODIFIED)
+ .should.be.true;
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
+ projectId: 1,
+ projectName: 'test1',
+ projectUrl: 'https://local.topcoder-dev.com/projects/1',
+ userId: 40051331,
+ initiatorUserId: 40051331,
+ })).should.be.true;
+
+ done();
+ });
+ }
+ });
});
- it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when type updated', (done) => {
+ it('should send correct BUS API messages when type updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.member}`,
- })
- .send({
- param: {
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.member}`,
+ })
+ .send({
type: 'another type',
- },
- })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err) => {
- if (err) {
- done(err);
- } else {
- testUtil.wait(() => {
- createEventSpy.notCalled.should.be.true;
- done();
- });
- }
- });
+ })
+ .expect('Content-Type', /json/)
+ .expect(200)
+ .end((err) => {
+ if (err) {
+ done(err);
+ } else {
+ testUtil.wait(() => {
+ createEventSpy.callCount.should.be.eql(1);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE_PRODUCT,
+ type: 'another type',
+ })).should.be.true;
+
+ done();
+ });
+ }
+ });
});
});
});
diff --git a/src/routes/workManagementPermissions/create.js b/src/routes/workManagementPermissions/create.js
index cecaf9e7..d5be1c7b 100644
--- a/src/routes/workManagementPermissions/create.js
+++ b/src/routes/workManagementPermissions/create.js
@@ -6,32 +6,29 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../util';
import models from '../../models';
const permissions = tcMiddleware.permissions;
const schema = {
- body: {
- param: Joi.object().keys({
- policy: Joi.string().max(255).required(),
- permission: Joi.object().required(),
- projectTemplateId: Joi.number().integer().positive().required(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ body: Joi.object().keys({
+ policy: Joi.string().max(255).required(),
+ permission: Joi.object().required(),
+ projectTemplateId: Joi.number().integer().positive().required(),
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
};
module.exports = [
validate(schema),
permissions('workManagementPermission.create'),
(req, res, next) => {
- const entity = _.assign(req.body.param, {
+ const entity = _.assign(req.body, {
createdBy: req.authUser.userId,
updatedBy: req.authUser.userId,
});
@@ -47,7 +44,7 @@ module.exports = [
.then((existing) => {
if (existing) {
const apiErr = new Error(`Work Management Permission already exists (may be deleted) for policy "${entity.policy}" and project template id ${entity.projectTemplateId}`);
- apiErr.status = 422;
+ apiErr.status = 400;
return Promise.reject(apiErr);
}
@@ -55,8 +52,7 @@ module.exports = [
return models.WorkManagementPermission.create(entity);
}).then((createdEntity) => {
// Omit deletedAt, deletedBy
- res.status(201).json(util.wrapResponse(
- req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201));
+ res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'));
})
.catch(next);
},
diff --git a/src/routes/workManagementPermissions/create.spec.js b/src/routes/workManagementPermissions/create.spec.js
index cc542d38..8c8eb190 100644
--- a/src/routes/workManagementPermissions/create.spec.js
+++ b/src/routes/workManagementPermissions/create.spec.js
@@ -15,18 +15,16 @@ describe('CREATE work management permission', () => {
let templateId;
const body = {
- param: {
- policy: 'work.create',
- permission: {
- allowRule: {
- projectRoles: ['customer', 'copilot'],
- topcoderRoles: ['Connect Manager', 'Connect Admin', 'administrator'],
- },
- denyRule: { projectRoles: ['copilot'] },
+ policy: 'work.create',
+ permission: {
+ allowRule: {
+ projectRoles: ['customer', 'copilot'],
+ topcoderRoles: ['Connect Manager', 'Connect Admin', 'administrator'],
},
- createdBy: 1,
- updatedBy: 1,
+ denyRule: { projectRoles: ['copilot'] },
},
+ createdBy: 1,
+ updatedBy: 1,
};
beforeEach((done) => {
@@ -47,24 +45,26 @@ describe('CREATE work management permission', () => {
})
.then((t) => {
templateId = t.id;
- body.param = _.assign({}, body.param, { projectTemplateId: templateId });
+ body.projectTemplateId = templateId;
}).then(() => done());
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('POST /projects/metadata/workManagementPermission', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post('/v4/projects/metadata/workManagementPermission')
+ .post('/v5/projects/metadata/workManagementPermission')
.send(body)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .post('/v4/projects/metadata/workManagementPermission')
+ .post('/v5/projects/metadata/workManagementPermission')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -74,7 +74,7 @@ describe('CREATE work management permission', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .post('/v4/projects/metadata/workManagementPermission')
+ .post('/v5/projects/metadata/workManagementPermission')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -84,7 +84,7 @@ describe('CREATE work management permission', () => {
it('should return 403 for manager', (done) => {
request(server)
- .post('/v4/projects/metadata/workManagementPermission')
+ .post('/v5/projects/metadata/workManagementPermission')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -94,7 +94,7 @@ describe('CREATE work management permission', () => {
it('should return 403 for non-member', (done) => {
request(server)
- .post('/v4/projects/metadata/workManagementPermission')
+ .post('/v5/projects/metadata/workManagementPermission')
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -102,82 +102,82 @@ describe('CREATE work management permission', () => {
.expect(403, done);
});
- it('should return 422 for missing policy', (done) => {
+ it('should return 400 for missing policy', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.policy;
+ delete invalidBody.policy;
request(server)
- .post('/v4/projects/metadata/workManagementPermission')
+ .post('/v5/projects/metadata/workManagementPermission')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for missing permission', (done) => {
+ it('should return 400 for missing permission', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.permission;
+ delete invalidBody.permission;
request(server)
- .post('/v4/projects/metadata/workManagementPermission')
+ .post('/v5/projects/metadata/workManagementPermission')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for missing projectTemplateId', (done) => {
+ it('should return 400 for missing projectTemplateId', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.projectTemplateId;
+ delete invalidBody.projectTemplateId;
request(server)
- .post('/v4/projects/metadata/workManagementPermission')
+ .post('/v5/projects/metadata/workManagementPermission')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for duplicated policy and projectTemplateId', (done) => {
- models.WorkManagementPermission.create(body.param)
+ it('should return 400 for duplicated policy and projectTemplateId', (done) => {
+ models.WorkManagementPermission.create(body)
.then(() => {
request(server)
- .post('/v4/projects/metadata/workManagementPermission')
+ .post('/v5/projects/metadata/workManagementPermission')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(body)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
});
- it('should return 422 for deleted but duplicated policy and projectTemplateId', (done) => {
- models.WorkManagementPermission.create(body.param)
+ it('should return 400 for deleted but duplicated policy and projectTemplateId', (done) => {
+ models.WorkManagementPermission.create(body)
.then((permission) => {
models.WorkManagementPermission.destroy({ where: { id: permission.id } });
})
.then(() => {
request(server)
- .post('/v4/projects/metadata/workManagementPermission')
+ .post('/v5/projects/metadata/workManagementPermission')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(body)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
});
it('should return 201 for admin', (done) => {
request(server)
- .post('/v4/projects/metadata/workManagementPermission')
+ .post('/v5/projects/metadata/workManagementPermission')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -185,10 +185,10 @@ describe('CREATE work management permission', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
- resJson.policy.should.be.eql(body.param.policy);
- resJson.permission.should.be.eql(body.param.permission);
- resJson.projectTemplateId.should.be.eql(body.param.projectTemplateId);
+ const resJson = res.body;
+ resJson.policy.should.be.eql(body.policy);
+ resJson.permission.should.be.eql(body.permission);
+ resJson.projectTemplateId.should.be.eql(body.projectTemplateId);
resJson.createdBy.should.be.eql(40051333); // admin
should.exist(resJson.createdAt);
resJson.updatedBy.should.be.eql(40051333); // admin
diff --git a/src/routes/workManagementPermissions/delete.js b/src/routes/workManagementPermissions/delete.js
index cae7d69c..1228918a 100644
--- a/src/routes/workManagementPermissions/delete.js
+++ b/src/routes/workManagementPermissions/delete.js
@@ -19,7 +19,7 @@ module.exports = [
permissions('workManagementPermission.delete'),
(req, res, next) =>
models.sequelize.transaction(() =>
- models.WorkManagementPermission.findById(req.params.id)
+ models.WorkManagementPermission.findByPk(req.params.id)
.then((entity) => {
if (!entity) {
const apiErr = new Error(`Work Management Permission not found for id ${req.params.id}`);
diff --git a/src/routes/workManagementPermissions/delete.spec.js b/src/routes/workManagementPermissions/delete.spec.js
index ef263872..9a24dcff 100644
--- a/src/routes/workManagementPermissions/delete.spec.js
+++ b/src/routes/workManagementPermissions/delete.spec.js
@@ -25,11 +25,12 @@ const expectAfterDelete = (permissionId, err, next) => {
chai.assert.isNotNull(res.deletedBy);
request(server)
- .get(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .get(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .expect(404, next);
+ .expect(404)
+ .end(next);
}
}), 500);
};
@@ -127,19 +128,21 @@ describe('DELETE work management permission', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('DELETE /projects/metadata/workManagementPermission/{permissionId}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .delete(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .delete(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .delete(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .delete(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -148,7 +151,7 @@ describe('DELETE work management permission', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .delete(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .delete(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -157,7 +160,7 @@ describe('DELETE work management permission', () => {
it('should return 403 for manager', (done) => {
request(server)
- .delete(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .delete(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -166,7 +169,7 @@ describe('DELETE work management permission', () => {
it('should return 404 for non-existed permission', (done) => {
request(server)
- .delete('/v4/projects/metadata/workManagementPermission/123')
+ .delete('/v5/projects/metadata/workManagementPermission/123')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -177,7 +180,7 @@ describe('DELETE work management permission', () => {
models.WorkManagementPermission.destroy({ where: { id: permissionId } })
.then(() => {
request(server)
- .delete(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .delete(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -187,7 +190,7 @@ describe('DELETE work management permission', () => {
it('should return 204, for admin, if permission was successfully removed', (done) => {
request(server)
- .delete(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .delete(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -197,7 +200,7 @@ describe('DELETE work management permission', () => {
it('should return 204, for connect admin, if permission was successfully removed', (done) => {
request(server)
- .delete(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .delete(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
diff --git a/src/routes/workManagementPermissions/get.js b/src/routes/workManagementPermissions/get.js
index cb6bb927..eab1bb1b 100644
--- a/src/routes/workManagementPermissions/get.js
+++ b/src/routes/workManagementPermissions/get.js
@@ -4,7 +4,6 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../util';
import models from '../../models';
const permissions = tcMiddleware.permissions;
@@ -32,7 +31,7 @@ module.exports = [
return Promise.reject(apiErr);
}
- res.json(util.wrapResponse(req.id, existing));
+ res.json(existing);
return Promise.resolve();
})
.catch(next),
diff --git a/src/routes/workManagementPermissions/get.spec.js b/src/routes/workManagementPermissions/get.spec.js
index 8138a260..29457681 100644
--- a/src/routes/workManagementPermissions/get.spec.js
+++ b/src/routes/workManagementPermissions/get.spec.js
@@ -54,18 +54,20 @@ describe('GET work management permission', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/workManagementPermission/{permissionId}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .get(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .get(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .get(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -74,7 +76,7 @@ describe('GET work management permission', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .get(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .get(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -83,7 +85,7 @@ describe('GET work management permission', () => {
it('should return 403 for manager', (done) => {
request(server)
- .get(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .get(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -92,7 +94,7 @@ describe('GET work management permission', () => {
it('should return 403 for non-member', (done) => {
request(server)
- .get(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .get(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -101,7 +103,7 @@ describe('GET work management permission', () => {
it('should return 404 for non-existed permission', (done) => {
request(server)
- .get('/v4/projects/metadata/workManagementPermission/1234')
+ .get('/v5/projects/metadata/workManagementPermission/1234')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -112,7 +114,7 @@ describe('GET work management permission', () => {
models.WorkManagementPermission.destroy({ where: { id: permissionId } })
.then(() => {
request(server)
- .get(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .get(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -122,13 +124,13 @@ describe('GET work management permission', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .get(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(permissionId);
resJson.policy.should.be.eql(permission.policy);
resJson.permission.should.be.eql(permission.permission);
diff --git a/src/routes/workManagementPermissions/list.js b/src/routes/workManagementPermissions/list.js
index 1f6992e9..cef27bd8 100644
--- a/src/routes/workManagementPermissions/list.js
+++ b/src/routes/workManagementPermissions/list.js
@@ -23,7 +23,7 @@ module.exports = [
const filters = util.parseQueryFilter(req.query.filter);
// Throw error if projectTemplateId is not present in filter
if (!filters.projectTemplateId) {
- return next(util.buildApiError('Missing filter projectTemplateId', 422));
+ return next(util.buildApiError('Missing filter projectTemplateId', 400));
}
if (!util.isValidFilter(filters, ['projectTemplateId'])) {
return util.handleError('Invalid filters', null, req, next);
@@ -36,7 +36,7 @@ module.exports = [
raw: true,
})
.then((result) => {
- res.json(util.wrapResponse(req.id, result));
+ res.json(result);
})
.catch(next);
},
diff --git a/src/routes/workManagementPermissions/list.spec.js b/src/routes/workManagementPermissions/list.spec.js
index bc0c0d55..0a9728ad 100644
--- a/src/routes/workManagementPermissions/list.spec.js
+++ b/src/routes/workManagementPermissions/list.spec.js
@@ -106,18 +106,20 @@ describe('LIST work management permissions', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/metadata/workManagementPermission', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get('/v4/projects/metadata/workManagementPermission?filter=projectTemplateId%3D1')
+ .get('/v5/projects/metadata/workManagementPermission?filter=projectTemplateId%3D1')
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .get('/v4/projects/metadata/workManagementPermission?filter=projectTemplateId%3D1')
+ .get('/v5/projects/metadata/workManagementPermission?filter=projectTemplateId%3D1')
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -126,7 +128,7 @@ describe('LIST work management permissions', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .get('/v4/projects/metadata/workManagementPermission?filter=projectTemplateId%3D1')
+ .get('/v5/projects/metadata/workManagementPermission?filter=projectTemplateId%3D1')
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -135,7 +137,7 @@ describe('LIST work management permissions', () => {
it('should return 403 for manager', (done) => {
request(server)
- .get('/v4/projects/metadata/workManagementPermission?filter=projectTemplateId%3D1')
+ .get('/v5/projects/metadata/workManagementPermission?filter=projectTemplateId%3D1')
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -144,36 +146,36 @@ describe('LIST work management permissions', () => {
it('should return 403 for non-member', (done) => {
request(server)
- .get('/v4/projects/metadata/workManagementPermission?filter=projectTemplateId%3D1')
+ .get('/v5/projects/metadata/workManagementPermission?filter=projectTemplateId%3D1')
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
.expect(403, done);
});
- it('should return 422 for missing filter', (done) => {
+ it('should return 400 for missing filter', (done) => {
request(server)
- .get('/v4/projects/metadata/workManagementPermission')
+ .get('/v5/projects/metadata/workManagementPermission')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for missing projectTemplateId', (done) => {
+ it('should return 400 for missing projectTemplateId', (done) => {
request(server)
- .get('/v4/projects/metadata/workManagementPermission?filter=template')
+ .get('/v5/projects/metadata/workManagementPermission?filter=template')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for invalid filter', (done) => {
+ it('should return 400 for invalid filter', (done) => {
request(server)
- .get(`/v4/projects/metadata/workManagementPermission?filter=invalid%3D2%26projectTemplateId%3D${templateIds[0]}`)
+ .get(`/v5/projects/metadata/workManagementPermission?filter=invalid%3D2%26projectTemplateId%3D${templateIds[0]}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -184,13 +186,13 @@ describe('LIST work management permissions', () => {
it('should return 200 for admin for projectTemplateId=1', (done) => {
request(server)
- .get(`/v4/projects/metadata/workManagementPermission?filter=projectTemplateId%3D${templateIds[0]}`)
+ .get(`/v5/projects/metadata/workManagementPermission?filter=projectTemplateId%3D${templateIds[0]}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
resJson[0].policy.should.be.eql(permissions[0].policy);
resJson[0].permission.should.be.eql(permissions[0].permission);
@@ -215,13 +217,13 @@ describe('LIST work management permissions', () => {
it('should return 200 for admin for projectTemplateId=2', (done) => {
request(server)
- .get(`/v4/projects/metadata/workManagementPermission?filter=projectTemplateId%3D${templateIds[1]}`)
+ .get(`/v5/projects/metadata/workManagementPermission?filter=projectTemplateId%3D${templateIds[1]}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(1);
resJson[0].policy.should.be.eql(permissions[0].policy);
resJson[0].permission.should.be.eql(permissions[0].permission);
diff --git a/src/routes/workManagementPermissions/update.js b/src/routes/workManagementPermissions/update.js
index cb51d732..7e18851b 100644
--- a/src/routes/workManagementPermissions/update.js
+++ b/src/routes/workManagementPermissions/update.js
@@ -6,7 +6,6 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../util';
import models from '../../models';
const permissions = tcMiddleware.permissions;
@@ -15,26 +14,24 @@ const schema = {
params: {
id: Joi.number().integer().positive().required(),
},
- body: {
- param: Joi.object().keys({
- policy: Joi.string().max(255).optional(),
- permission: Joi.object().optional(),
- projectTemplateId: Joi.number().integer().positive().optional(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ body: Joi.object().keys({
+ policy: Joi.string().max(255).optional(),
+ permission: Joi.object().optional(),
+ projectTemplateId: Joi.number().integer().positive().optional(),
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
};
module.exports = [
validate(schema),
permissions('workManagementPermission.edit'),
(req, res, next) => {
- const entityToUpdate = _.assign(req.body.param, {
+ const entityToUpdate = _.assign(req.body, {
updatedBy: req.authUser.userId,
});
@@ -68,7 +65,7 @@ module.exports = [
.then((existing) => {
if (existing) {
const apiErr = new Error(`Work Management Permission already exists (may be deleted) for policy "${entityToUpdate.policy}" and project template id ${entityToUpdate.projectTemplateId}`);
- apiErr.status = 422;
+ apiErr.status = 400;
return Promise.reject(apiErr);
}
@@ -76,7 +73,7 @@ module.exports = [
}),
)
.then((updated) => {
- res.json(util.wrapResponse(req.id, updated));
+ res.json(updated);
return Promise.resolve();
})
.catch(next);
diff --git a/src/routes/workManagementPermissions/update.spec.js b/src/routes/workManagementPermissions/update.spec.js
index 8d3fc64a..cb162481 100644
--- a/src/routes/workManagementPermissions/update.spec.js
+++ b/src/routes/workManagementPermissions/update.spec.js
@@ -56,34 +56,34 @@ describe('UPDATE work management permission', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('PATCH /projects/metadata/workManagementPermission/{permissionId}', () => {
const body = {
- param: {
- policy: 'work.edit',
- permission: {
- allowRule: {
- projectRoles: ['customer', 'copilot'],
- topcoderRoles: ['Connect Manager', 'Connect Admin', 'administrator'],
- },
- denyRule: { projectRoles: ['copilot'] },
+ policy: 'work.edit',
+ permission: {
+ allowRule: {
+ projectRoles: ['customer', 'copilot'],
+ topcoderRoles: ['Connect Manager', 'Connect Admin', 'administrator'],
},
- createdBy: 1,
- updatedBy: 1,
+ denyRule: { projectRoles: ['copilot'] },
},
+ createdBy: 1,
+ updatedBy: 1,
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .patch(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.send(body)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .patch(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -93,7 +93,7 @@ describe('UPDATE work management permission', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .patch(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
@@ -103,7 +103,7 @@ describe('UPDATE work management permission', () => {
it('should return 403 for manager', (done) => {
request(server)
- .patch(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
@@ -113,7 +113,7 @@ describe('UPDATE work management permission', () => {
it('should return 403 for non-member', (done) => {
request(server)
- .patch(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member2}`,
})
@@ -123,87 +123,91 @@ describe('UPDATE work management permission', () => {
it('should return 404 for non-existed type', (done) => {
request(server)
- .patch('/v4/projects/metadata/workManagementPermission/1234')
+ .patch('/v5/projects/metadata/workManagementPermission/1234')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(body)
- .expect(404, done);
+ .expect(404)
+ .end(done);
});
it('should return 404 for deleted type', (done) => {
models.WorkManagementPermission.destroy({ where: { id: permissionId } })
.then(() => {
request(server)
- .patch(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(body)
- .expect(404, done);
+ .expect(404)
+ .end(done);
});
});
- it('should return 422 when updated with invalid param', (done) => {
+ it('should return 400 when updated with invalid param', (done) => {
request(server)
- .patch(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: { invalid: null } })
+ .send({ invalid: null })
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400)
+ .end(done);
});
- it('should return 422 for policy and projectTemplateId updated with existing(non-deleted) values', (done) => {
- const newParam = _.assign({}, body.param, { projectTemplateId: templateId });
+ it('should return 400 for policy and projectTemplateId updated with existing(non-deleted) values', (done) => {
+ const newParam = _.assign({}, body, { projectTemplateId: templateId });
models.WorkManagementPermission.create(newParam)
.then(() => {
request(server)
- .patch(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: newParam })
+ .send(newParam)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400)
+ .end(done);
});
});
- it('should return 422 for policy and projectTemplateId updated with existing(deleted) values', (done) => {
- const newParam = _.assign({}, body.param, { projectTemplateId: templateId });
+ it('should return 400 for policy and projectTemplateId updated with existing(deleted) values', (done) => {
+ const newParam = _.assign({}, body, { projectTemplateId: templateId });
models.WorkManagementPermission.create(newParam)
.then((p) => {
models.WorkManagementPermission.destroy({ where: { id: p.id } });
})
.then(() => {
request(server)
- .patch(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: newParam })
+ .send(newParam)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400)
+ .end(done);
});
});
- it('should return 200 for policy updated', (done) => {
- const partialBody = _.cloneDeep(body);
- delete partialBody.param.permission;
- delete partialBody.param.projectTemplateId;
+ it('should return 200 for permission updated', (done) => {
+ const partialBody = _.assign({}, body, { projectTemplateId: templateId });
+ delete partialBody.permission;
request(server)
- .patch(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(partialBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(permissionId);
- resJson.policy.should.be.eql(partialBody.param.policy);
+ resJson.policy.should.be.eql(partialBody.policy);
resJson.permission.should.be.eql(permission.permission);
resJson.projectTemplateId.should.be.eql(permission.projectTemplateId);
resJson.createdBy.should.be.eql(permission.createdBy); // should not update createdAt
@@ -216,76 +220,20 @@ describe('UPDATE work management permission', () => {
});
});
- it('should return 200 for admin permission updated', (done) => {
- const partialBody = _.cloneDeep(body);
- delete partialBody.param.policy;
- delete partialBody.param.projectTemplateId;
-
- request(server)
- .patch(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.admin}`,
- })
- .send(partialBody)
- .expect(200)
- .end((err, res) => {
- const resJson = res.body.result.content;
- resJson.id.should.be.eql(permissionId);
- resJson.policy.should.be.eql(permission.policy);
- resJson.permission.should.be.eql(partialBody.param.permission);
- resJson.projectTemplateId.should.be.eql(permission.projectTemplateId);
- resJson.createdBy.should.be.eql(permission.createdBy); // should not update createdAt
- resJson.updatedBy.should.be.eql(40051333); // admin
- should.exist(resJson.updatedAt);
- should.not.exist(resJson.deletedBy);
- should.not.exist(resJson.deletedAt);
-
- done();
- });
- });
-
- it('should return 200 for admin projectTemplateId updated', (done) => {
- const partialBody = _.cloneDeep(body);
- partialBody.param = _.assign({}, partialBody.param, { projectTemplateId: templateId });
- delete partialBody.param.policy;
- delete partialBody.param.permission;
- request(server)
- .patch(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.admin}`,
- })
- .send(partialBody)
- .expect(200)
- .end((err, res) => {
- const resJson = res.body.result.content;
- resJson.id.should.be.eql(permissionId);
- resJson.policy.should.be.eql(permission.policy);
- resJson.permission.should.be.eql(permission.permission);
- resJson.projectTemplateId.should.be.eql(partialBody.param.projectTemplateId);
- resJson.createdBy.should.be.eql(permission.createdBy); // should not update createdAt
- resJson.updatedBy.should.be.eql(40051333); // admin
- should.exist(resJson.updatedAt);
- should.not.exist(resJson.deletedBy);
- should.not.exist(resJson.deletedAt);
-
- done();
- });
- });
-
it('should return 200 for admin all fields updated', (done) => {
- const newParam = _.assign({}, body.param, { projectTemplateId: templateId });
+ const newParam = _.assign({}, body, { projectTemplateId: templateId });
request(server)
- .patch(`/v4/projects/metadata/workManagementPermission/${permissionId}`)
+ .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: newParam })
+ .send(newParam)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(permissionId);
- resJson.policy.should.be.eql(body.param.policy);
- resJson.permission.should.be.eql(body.param.permission);
+ resJson.policy.should.be.eql(body.policy);
+ resJson.permission.should.be.eql(body.permission);
resJson.projectTemplateId.should.be.eql(newParam.projectTemplateId);
resJson.createdBy.should.be.eql(permission.createdBy); // should not update createdAt
resJson.updatedBy.should.be.eql(40051333); // admin
diff --git a/src/routes/workStreams/create.js b/src/routes/workStreams/create.js
index ebda8308..bcd72213 100644
--- a/src/routes/workStreams/create.js
+++ b/src/routes/workStreams/create.js
@@ -15,19 +15,17 @@ const schema = {
params: {
projectId: Joi.number().integer().positive().required(),
},
- body: {
- param: Joi.object().keys({
- name: Joi.string().max(255).required(),
- type: Joi.string().max(45).required(),
- status: Joi.string().valid(_.values(WORKSTREAM_STATUS)).required(),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ body: Joi.object().keys({
+ name: Joi.string().max(255).required(),
+ type: Joi.string().max(45).required(),
+ status: Joi.string().valid(_.values(WORKSTREAM_STATUS)).required(),
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
};
module.exports = [
@@ -35,7 +33,7 @@ module.exports = [
permissions('workStream.create'),
// do the real work
(req, res, next) => {
- const data = req.body.param;
+ const data = req.body;
// default values
const projectId = _.parseInt(req.params.projectId);
_.assign(data, {
@@ -63,7 +61,7 @@ module.exports = [
.then((createdEntity) => {
req.log.debug('new work stream created (id# %d, name: %s)',
createdEntity.id, createdEntity.name);
- res.status(201).json(util.wrapResponse(req.id, _.omit(createdEntity, 'deletedBy', 'deletedAt'), 1, 201));
+ res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedBy', 'deletedAt'));
})
.catch((err) => {
util.handleError('Error creating work stream', err, req, next);
diff --git a/src/routes/workStreams/create.spec.js b/src/routes/workStreams/create.spec.js
index a6afa142..4a66cdb8 100644
--- a/src/routes/workStreams/create.spec.js
+++ b/src/routes/workStreams/create.spec.js
@@ -93,27 +93,27 @@ describe('CREATE work stream', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('POST /projects/{projectId}/workstreams', () => {
const body = {
- param: {
- name: 'Work Stream',
- type: 'generic',
- status: 'active',
- },
+ name: 'Work Stream',
+ type: 'generic',
+ status: 'active',
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/workstreams`)
+ .post(`/v5/projects/${projectId}/workstreams`)
.send(body)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/workstreams`)
+ .post(`/v5/projects/${projectId}/workstreams`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -123,7 +123,7 @@ describe('CREATE work stream', () => {
it('should return 404 for non-existed project id', (done) => {
request(server)
- .delete('/v4/projects/999/workstreams')
+ .delete('/v5/projects/999/workstreams')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -134,7 +134,7 @@ describe('CREATE work stream', () => {
models.Project.destroy({ where: { id: projectId } })
.then(() => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams`)
+ .delete(`/v5/projects/${projectId}/workstreams`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -144,7 +144,7 @@ describe('CREATE work stream', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/workstreams`)
+ .post(`/v5/projects/${projectId}/workstreams`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -154,7 +154,7 @@ describe('CREATE work stream', () => {
it('should return 403 for manager', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/workstreams`)
+ .post(`/v5/projects/${projectId}/workstreams`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -162,51 +162,51 @@ describe('CREATE work stream', () => {
.expect(403, done);
});
- it('should return 422 for missing type', (done) => {
+ it('should return 400 for missing type', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.type;
+ delete invalidBody.type;
request(server)
- .post(`/v4/projects/${projectId}/workstreams`)
+ .post(`/v5/projects/${projectId}/workstreams`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for missing name', (done) => {
+ it('should return 400 for missing name', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.name;
+ delete invalidBody.name;
request(server)
- .post(`/v4/projects/${projectId}/workstreams`)
+ .post(`/v5/projects/${projectId}/workstreams`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 for status', (done) => {
+ it('should return 400 for status', (done) => {
const invalidBody = _.cloneDeep(body);
- delete invalidBody.param.status;
+ delete invalidBody.status;
request(server)
- .post(`/v4/projects/${projectId}/workstreams`)
+ .post(`/v5/projects/${projectId}/workstreams`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(invalidBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 201 for admin', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/workstreams`)
+ .post(`/v5/projects/${projectId}/workstreams`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -214,10 +214,10 @@ describe('CREATE work stream', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
- resJson.name.should.be.eql(body.param.name);
- resJson.type.should.be.eql(body.param.type);
- resJson.status.should.be.eql(body.param.status);
+ const resJson = res.body;
+ resJson.name.should.be.eql(body.name);
+ resJson.type.should.be.eql(body.type);
+ resJson.status.should.be.eql(body.status);
resJson.projectId.should.be.eql(projectId);
resJson.createdBy.should.be.eql(40051333);
@@ -233,7 +233,7 @@ describe('CREATE work stream', () => {
it('should return 201 for connect admin', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/workstreams`)
+ .post(`/v5/projects/${projectId}/workstreams`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -241,10 +241,10 @@ describe('CREATE work stream', () => {
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
- resJson.name.should.be.eql(body.param.name);
- resJson.type.should.be.eql(body.param.type);
- resJson.status.should.be.eql(body.param.status);
+ const resJson = res.body;
+ resJson.name.should.be.eql(body.name);
+ resJson.type.should.be.eql(body.type);
+ resJson.status.should.be.eql(body.status);
resJson.projectId.should.be.eql(projectId);
resJson.createdBy.should.be.eql(40051336);
resJson.updatedBy.should.be.eql(40051336);
diff --git a/src/routes/workStreams/delete.spec.js b/src/routes/workStreams/delete.spec.js
index 7c079dd9..5a3b92b6 100644
--- a/src/routes/workStreams/delete.spec.js
+++ b/src/routes/workStreams/delete.spec.js
@@ -24,7 +24,7 @@ const expectAfterDelete = (id, projectId, err, next) => {
chai.assert.isNotNull(res.deletedBy);
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${id}`)
+ .get(`/v5/projects/${projectId}/workstreams/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -86,18 +86,20 @@ describe('DELETE work stream', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('DELETE /projects/{projectId}/workstreams/{id}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${id}`)
+ .delete(`/v5/projects/${projectId}/workstreams/${id}`)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${id}`)
+ .delete(`/v5/projects/${projectId}/workstreams/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -106,7 +108,7 @@ describe('DELETE work stream', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${id}`)
+ .delete(`/v5/projects/${projectId}/workstreams/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -115,7 +117,7 @@ describe('DELETE work stream', () => {
it('should return 403 for manager', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${id}`)
+ .delete(`/v5/projects/${projectId}/workstreams/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -124,7 +126,7 @@ describe('DELETE work stream', () => {
it('should return 404 for non-existed type', (done) => {
request(server)
- .delete('/v4/projects/metadata/projectTypes/not_existed')
+ .delete('/v5/projects/metadata/projectTypes/not_existed')
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -135,7 +137,7 @@ describe('DELETE work stream', () => {
models.WorkStream.destroy({ where: { id } })
.then(() => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${id}`)
+ .delete(`/v5/projects/${projectId}/workstreams/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -145,7 +147,7 @@ describe('DELETE work stream', () => {
it('should return 204, for admin, if type was successfully removed', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${id}`)
+ .delete(`/v5/projects/${projectId}/workstreams/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -155,7 +157,7 @@ describe('DELETE work stream', () => {
it('should return 204, for connect admin, if type was successfully removed', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${id}`)
+ .delete(`/v5/projects/${projectId}/workstreams/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
diff --git a/src/routes/workStreams/get.js b/src/routes/workStreams/get.js
index 4153db18..83ac929f 100644
--- a/src/routes/workStreams/get.js
+++ b/src/routes/workStreams/get.js
@@ -4,7 +4,6 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../util';
import models from '../../models';
const permissions = tcMiddleware.permissions;
@@ -35,7 +34,7 @@ module.exports = [
return Promise.reject(apiErr);
}
- res.json(util.wrapResponse(req.id, workStream));
+ res.json(workStream);
return Promise.resolve();
})
.catch(next),
diff --git a/src/routes/workStreams/get.spec.js b/src/routes/workStreams/get.spec.js
index cbb6b056..d696da90 100644
--- a/src/routes/workStreams/get.spec.js
+++ b/src/routes/workStreams/get.spec.js
@@ -65,12 +65,14 @@ describe('GET work stream', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/{projectId}/workstreams/{id}', () => {
it('should return 404 for non-existed work stream', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/1234`)
+ .get(`/v5/projects/${projectId}/workstreams/1234`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -81,7 +83,7 @@ describe('GET work stream', () => {
models.WorkStream.destroy({ where: { id } })
.then(() => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${id}`)
+ .get(`/v5/projects/${projectId}/workstreams/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -91,13 +93,13 @@ describe('GET work stream', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${id}`)
+ .get(`/v5/projects/${projectId}/workstreams/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.name.should.be.eql(workStream.name);
resJson.type.should.be.eql(workStream.type);
resJson.status.should.be.eql(workStream.status);
@@ -115,13 +117,13 @@ describe('GET work stream', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${id}`)
+ .get(`/v5/projects/${projectId}/workstreams/${id}`)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${id}`)
+ .get(`/v5/projects/${projectId}/workstreams/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -130,7 +132,7 @@ describe('GET work stream', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${id}`)
+ .get(`/v5/projects/${projectId}/workstreams/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -139,7 +141,7 @@ describe('GET work stream', () => {
it('should return 200 for connect admin', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${id}`)
+ .get(`/v5/projects/${projectId}/workstreams/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -149,7 +151,7 @@ describe('GET work stream', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${id}`)
+ .get(`/v5/projects/${projectId}/workstreams/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
diff --git a/src/routes/workStreams/list.js b/src/routes/workStreams/list.js
index 6d424f6d..c536e2b9 100644
--- a/src/routes/workStreams/list.js
+++ b/src/routes/workStreams/list.js
@@ -4,7 +4,6 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../util';
import models from '../../models';
const permissions = tcMiddleware.permissions;
@@ -40,7 +39,7 @@ module.exports = [
raw: true,
});
})
- .then(workStreams => res.json(util.wrapResponse(req.id, workStreams, workStreams.length)))
+ .then(workStreams => res.json(workStreams))
.catch(next);
},
];
diff --git a/src/routes/workStreams/list.spec.js b/src/routes/workStreams/list.spec.js
index a14e1684..371ccc95 100644
--- a/src/routes/workStreams/list.spec.js
+++ b/src/routes/workStreams/list.spec.js
@@ -67,12 +67,14 @@ describe('LIST work streams', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/{projectId}/workstreams', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams`)
+ .get(`/v5/projects/${projectId}/workstreams`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -80,7 +82,7 @@ describe('LIST work streams', () => {
.end((err, res) => {
const workStream = workStreams[0];
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
resJson[0].name.should.be.eql(workStream.name);
resJson[0].type.should.be.eql(workStream.type);
@@ -100,7 +102,7 @@ describe('LIST work streams', () => {
models.Project.destroy({ where: { id: projectId } })
.then(() => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams`)
+ .get(`/v5/projects/${projectId}/workstreams`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -110,13 +112,13 @@ describe('LIST work streams', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams`)
+ .get(`/v5/projects/${projectId}/workstreams`)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams`)
+ .get(`/v5/projects/${projectId}/workstreams`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -125,7 +127,7 @@ describe('LIST work streams', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams`)
+ .get(`/v5/projects/${projectId}/workstreams`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -134,7 +136,7 @@ describe('LIST work streams', () => {
it('should return 200 for connect admin', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams`)
+ .get(`/v5/projects/${projectId}/workstreams`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -144,7 +146,7 @@ describe('LIST work streams', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams`)
+ .get(`/v5/projects/${projectId}/workstreams`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
diff --git a/src/routes/workStreams/update.js b/src/routes/workStreams/update.js
index 0ce4d14c..e370c80a 100644
--- a/src/routes/workStreams/update.js
+++ b/src/routes/workStreams/update.js
@@ -5,7 +5,6 @@ import validate from 'express-validation';
import _ from 'lodash';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../util';
import models from '../../models';
import { WORKSTREAM_STATUS } from '../../constants';
@@ -16,27 +15,25 @@ const schema = {
projectId: Joi.number().integer().positive().required(),
id: Joi.number().integer().positive().required(),
},
- body: {
- param: Joi.object().keys({
- id: Joi.number().valid(Joi.ref('$params.id')),
- name: Joi.string().max(255),
- type: Joi.string().max(45),
- status: Joi.string().valid(_.values(WORKSTREAM_STATUS)),
- createdAt: Joi.any().strip(),
- updatedAt: Joi.any().strip(),
- deletedAt: Joi.any().strip(),
- createdBy: Joi.any().strip(),
- updatedBy: Joi.any().strip(),
- deletedBy: Joi.any().strip(),
- }).required(),
- },
+ body: Joi.object().keys({
+ id: Joi.number().valid(Joi.ref('$params.id')),
+ name: Joi.string().max(255),
+ type: Joi.string().max(45),
+ status: Joi.string().valid(_.values(WORKSTREAM_STATUS)),
+ createdAt: Joi.any().strip(),
+ updatedAt: Joi.any().strip(),
+ deletedAt: Joi.any().strip(),
+ createdBy: Joi.any().strip(),
+ updatedBy: Joi.any().strip(),
+ deletedBy: Joi.any().strip(),
+ }).required(),
};
module.exports = [
validate(schema),
permissions('workStream.edit'),
(req, res, next) => {
- const entityToUpdate = _.assign(req.body.param, {
+ const entityToUpdate = _.assign(req.body, {
updatedBy: req.authUser.userId,
});
const projectId = req.params.projectId;
@@ -60,7 +57,7 @@ module.exports = [
return workStream.update(entityToUpdate);
})
.then((workStream) => {
- res.json(util.wrapResponse(req.id, workStream));
+ res.json(workStream);
return Promise.resolve();
})
.catch(next);
diff --git a/src/routes/workStreams/update.spec.js b/src/routes/workStreams/update.spec.js
index 5b096d2f..96c074f5 100644
--- a/src/routes/workStreams/update.spec.js
+++ b/src/routes/workStreams/update.spec.js
@@ -81,27 +81,27 @@ describe('UPDATE Work Stream', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('PATCH /projects/{projectId}/workstreams/{id}', () => {
const body = {
- param: {
- name: 'Work Stream',
- type: 'generic',
- status: 'active',
- },
+ name: 'Work Stream',
+ type: 'generic',
+ status: 'active',
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${id}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${id}`)
.send(body)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${id}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -111,7 +111,7 @@ describe('UPDATE Work Stream', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${id}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${id}`)
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
@@ -121,7 +121,7 @@ describe('UPDATE Work Stream', () => {
it('should return 403 for manager', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${id}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${id}`)
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
@@ -131,7 +131,7 @@ describe('UPDATE Work Stream', () => {
it('should return 404 for non-existed work stream', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/1234`)
+ .patch(`/v5/projects/${projectId}/workstreams/1234`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -143,7 +143,7 @@ describe('UPDATE Work Stream', () => {
models.WorkStream.destroy({ where: { id } })
.then(() => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${id}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -154,17 +154,17 @@ describe('UPDATE Work Stream', () => {
it('should return 200 for admin name updated', (done) => {
const partialBody = _.cloneDeep(body);
- delete partialBody.param.type;
- delete partialBody.param.status;
+ delete partialBody.type;
+ delete partialBody.status;
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${id}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(partialBody)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(id);
resJson.name.should.be.eql(workStream.name);
resJson.type.should.be.eql(workStream.type);
@@ -183,14 +183,14 @@ describe('UPDATE Work Stream', () => {
it('should return 200 for admin all fields updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${id}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send(body)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(id);
resJson.name.should.be.eql(workStream.name);
resJson.type.should.be.eql(workStream.type);
@@ -208,14 +208,14 @@ describe('UPDATE Work Stream', () => {
it('should return 200 for connect admin', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${id}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
.send(body)
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.id.should.be.eql(id);
resJson.name.should.be.eql(workStream.name);
resJson.type.should.be.eql(workStream.type);
diff --git a/src/routes/works/create.js b/src/routes/works/create.js
index fc0184a5..a1d3cf5e 100644
--- a/src/routes/works/create.js
+++ b/src/routes/works/create.js
@@ -8,7 +8,7 @@ import Sequelize from 'sequelize';
import models from '../../models';
import util from '../../util';
-import { EVENT, TIMELINE_REFERENCES } from '../../constants';
+import { EVENT, RESOURCES, TIMELINE_REFERENCES } from '../../constants';
const permissions = require('tc-core-library-js').middleware.permissions;
@@ -17,23 +17,21 @@ const schema = {
projectId: Joi.number().integer().positive().required(),
workStreamId: Joi.number().integer().positive().required(),
},
- body: {
- param: Joi.object().keys({
- name: Joi.string().required(),
- description: Joi.string().optional(),
- requirements: Joi.string().optional(),
- status: Joi.string().required(),
- startDate: Joi.date().optional(),
- endDate: Joi.date().optional(),
- duration: Joi.number().min(0).optional(),
- budget: Joi.number().min(0).optional(),
- spentBudget: Joi.number().min(0).optional(),
- progress: Joi.number().min(0).optional(),
- details: Joi.any().optional(),
- order: Joi.number().integer().optional(),
- productTemplateId: Joi.number().integer().positive().optional(),
- }).required(),
- },
+ body: Joi.object().keys({
+ name: Joi.string().required(),
+ description: Joi.string().optional(),
+ requirements: Joi.string().optional(),
+ status: Joi.string().required(),
+ startDate: Joi.date().optional(),
+ endDate: Joi.date().optional(),
+ duration: Joi.number().min(0).optional(),
+ budget: Joi.number().min(0).optional(),
+ spentBudget: Joi.number().min(0).optional(),
+ progress: Joi.number().min(0).optional(),
+ details: Joi.any().optional(),
+ order: Joi.number().integer().optional(),
+ productTemplateId: Joi.number().integer().positive().optional(),
+ }).required(),
};
module.exports = [
@@ -47,7 +45,7 @@ module.exports = [
const projectId = _.parseInt(req.params.projectId);
const workStreamId = _.parseInt(req.params.workStreamId);
- const data = req.body.param;
+ const data = req.body;
_.assign(data, {
projectId,
createdBy: req.authUser.userId,
@@ -58,96 +56,104 @@ module.exports = [
let newProjectPhase = null;
req.log.debug('Create Work - Starting transaction');
- models.sequelize.transaction(() => models.WorkStream.findOne({
- where: {
- id: workStreamId,
- projectId,
- deletedAt: { $eq: null },
- },
- })
- .then((_existingWorkStream) => {
- if (!_existingWorkStream) {
- // handle 404
- const err = new Error(`active work stream not found for project id ${projectId} ` +
- `and work stream id ${workStreamId}`);
- err.status = 404;
- throw err;
- }
-
- existingWorkStream = _existingWorkStream;
-
- if (data.startDate !== null && data.endDate !== null && data.startDate > data.endDate) {
- const err = new Error('startDate must not be after endDate.');
- err.status = 422;
- throw err;
- }
- return models.ProjectPhase.create(data);
+ return models.sequelize.transaction(() =>
+ models.WorkStream.findOne({
+ where: {
+ id: workStreamId,
+ projectId,
+ deletedAt: { $eq: null },
+ },
})
- .then((_newProjectPhase) => {
- newProjectPhase = _.omit(_newProjectPhase, ['deletedAt', 'deletedBy']);
- return existingWorkStream.addProjectPhase(_newProjectPhase.id);
- })
- .then(() => {
- req.log.debug('re-ordering the other phases');
+ .then((_existingWorkStream) => {
+ if (!_existingWorkStream) {
+ // handle 404
+ const err = new Error(`active work stream not found for project id ${projectId} ` +
+ `and work stream id ${workStreamId}`);
+ err.status = 404;
+ throw err;
+ }
- if (_.isNil(newProjectPhase.order)) {
- return Promise.resolve();
- }
- // Increase the order of the other phases in the same project,
- // which have `order` >= this phase order
- return models.ProjectPhase.update({ order: Sequelize.literal('"order" + 1') }, {
- where: {
- projectId,
- id: { $ne: newProjectPhase.id },
- order: { $gte: newProjectPhase.order },
- },
- });
- })
- .then(() => {
- if (_.isNil(data.productTemplateId)) {
- return Promise.resolve();
- }
+ existingWorkStream = _existingWorkStream;
- // Get the product template
- return models.ProductTemplate.findById(data.productTemplateId)
- .then((productTemplate) => {
- if (!productTemplate) {
- const err = new Error(`Product template does not exist with id = ${data.productTemplateId}`);
- err.status = 422;
- throw err;
- }
+ if (data.startDate !== null && data.endDate !== null && data.startDate > data.endDate) {
+ const err = new Error('startDate must not be after endDate.');
+ err.status = 400;
+ throw err;
+ }
+ return models.ProjectPhase.create(data);
+ })
+ .then((_newProjectPhase) => {
+ newProjectPhase = _.omit(_newProjectPhase.toJSON(), ['deletedAt', 'deletedBy']);
+ return existingWorkStream.addProjectPhase(_newProjectPhase.id);
+ })
+ .then(() => {
+ req.log.debug('re-ordering the other phases');
- // Create the phase product
- return models.PhaseProduct.create({
- name: productTemplate.name,
- templateId: data.productTemplateId,
- type: productTemplate.productKey,
+ if (_.isNil(newProjectPhase.order)) {
+ return Promise.resolve();
+ }
+ // Increase the order of the other phases in the same project,
+ // which have `order` >= this phase order
+ return models.ProjectPhase.update({ order: Sequelize.literal('"order" + 1') }, {
+ where: {
projectId,
- phaseId: newProjectPhase.id,
- createdBy: req.authUser.userId,
- updatedBy: req.authUser.userId,
- })
- .then((phaseProduct) => {
- newProjectPhase.products = [
- _.omit(phaseProduct.toJSON(), ['deletedAt', 'deletedBy']),
- ];
- });
+ id: { $ne: newProjectPhase.id },
+ order: { $gte: newProjectPhase.order },
+ },
});
- }))
- .then(() => {
- // Send events to buses
- req.log.debug('Sending event to RabbitMQ bus for project phase %d', newProjectPhase.id);
- req.app.services.pubsub.publish(EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED,
+ })
+ .then(() => {
+ if (_.isNil(data.productTemplateId)) {
+ return Promise.resolve();
+ }
+
+ // Get the product template
+ return models.ProductTemplate.findByPk(data.productTemplateId)
+ .then((productTemplate) => {
+ if (!productTemplate) {
+ const err = new Error(`Product template does not exist with id = ${data.productTemplateId}`);
+ err.status = 400;
+ throw err;
+ }
+
+ // Create the phase product
+ return models.PhaseProduct.create({
+ name: productTemplate.name,
+ templateId: data.productTemplateId,
+ type: productTemplate.productKey,
+ projectId,
+ phaseId: newProjectPhase.id,
+ createdBy: req.authUser.userId,
+ updatedBy: req.authUser.userId,
+ })
+ .then((phaseProduct) => {
+ newProjectPhase.products = [
+ _.omit(phaseProduct.toJSON(), ['deletedAt', 'deletedBy']),
+ ];
+ });
+ });
+ }),
+ )
+ .then(() => {
+ // Send events to buses
+ req.log.debug('Sending event to RabbitMQ bus for project phase %d', newProjectPhase.id);
+ req.app.services.pubsub.publish(EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED,
{ added: newProjectPhase, route: TIMELINE_REFERENCES.WORK },
{ correlationId: req.id },
- );
- req.log.debug('Sending event to Kafka bus for project phase %d', newProjectPhase.id);
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED, { req, created: newProjectPhase });
+ );
- res.status(201).json(util.wrapResponse(req.id, newProjectPhase, 1, 201));
- })
- .catch((err) => {
- util.handleError('Error creating work', err, req, next);
- });
+ req.log.debug('Sending event to Kafka bus for project phase %d', newProjectPhase.id);
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED,
+ RESOURCES.PHASE,
+ newProjectPhase,
+ );
+
+ res.status(201).json(newProjectPhase);
+ })
+ .catch((err) => {
+ util.handleError('Error creating work', err, req, next);
+ });
},
];
diff --git a/src/routes/works/create.spec.js b/src/routes/works/create.spec.js
index e2061431..0d3c6d67 100644
--- a/src/routes/works/create.spec.js
+++ b/src/routes/works/create.spec.js
@@ -15,7 +15,7 @@ import busApi from '../../services/busApi';
import messageService from '../../services/messageService';
import RabbitMQService from '../../services/rabbitmq';
import mockRabbitMQ from '../../tests/mockRabbitMQ';
-import { BUS_API_EVENT } from '../../constants';
+import { BUS_API_EVENT, CONNECT_NOTIFICATION_EVENT, RESOURCES } from '../../constants';
const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName');
const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType');
@@ -25,8 +25,6 @@ const should = chai.should();
const validatePhase = (resJson, expectedPhase) => {
should.exist(resJson);
resJson.name.should.be.eql(expectedPhase.name);
- resJson.description.should.be.eql(expectedPhase.description);
- resJson.requirements.should.be.eql(expectedPhase.requirements);
resJson.status.should.be.eql(expectedPhase.status);
resJson.budget.should.be.eql(expectedPhase.budget);
resJson.progress.should.be.eql(expectedPhase.progress);
@@ -64,7 +62,7 @@ describe('CREATE work', () => {
lastActivityAt: 1,
lastActivityUserId: '1',
};
-
+ let productTemplateId;
beforeEach((done) => {
testUtil.clearDb()
.then(() => {
@@ -130,7 +128,40 @@ describe('CREATE work', () => {
isPrimary: true,
createdBy: 1,
updatedBy: 1,
- }]).then(() => done());
+ }])
+ .then(() =>
+ models.ProductTemplate.create({
+ name: 'name 1',
+ productKey: 'productKey 1',
+ category: 'generic',
+ subCategory: 'generic',
+ icon: 'http://example.com/icon1.ico',
+ brief: 'brief 1',
+ details: 'details 1',
+ aliases: ['product key 1', 'product_key_1'],
+ template: {
+ template1: {
+ name: 'template 1',
+ details: {
+ anyDetails: 'any details 1',
+ },
+ others: ['others 11', 'others 12'],
+ },
+ template2: {
+ name: 'template 2',
+ details: {
+ anyDetails: 'any details 2',
+ },
+ others: ['others 21', 'others 22'],
+ },
+ },
+ createdBy: 1,
+ updatedBy: 2,
+ }).then((productTemplate) => {
+ productTemplateId = productTemplate.id;
+ done();
+ }),
+ );
});
});
});
@@ -138,37 +169,37 @@ describe('CREATE work', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('PATCH /projects/{projectId}/workstreams/{workStreamId}/works', () => {
const body = {
- param: {
- name: 'test project phase',
- description: 'test project phase description',
- requirements: 'test project phase requirements',
- status: 'active',
- startDate: '2018-05-15T00:00:00Z',
- endDate: '2018-05-15T12:00:00Z',
- budget: 20.0,
- progress: 1.23456,
- spentBudget: 10.0,
- duration: 10,
- details: {
- message: 'This can be any json',
- },
+ name: 'test project phase',
+ description: 'test project phase description',
+ requirements: 'test project phase requirements',
+ status: 'active',
+ startDate: '2018-05-15T00:00:00Z',
+ endDate: '2018-05-15T12:00:00Z',
+ budget: 20.0,
+ progress: 1.23456,
+ spentBudget: 10.0,
+ duration: 10,
+ details: {
+ message: 'This can be any json',
},
};
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works`)
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`)
.send(body)
.expect(403, done);
});
it('should return 403 for copilot', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works`)
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`)
.send(body)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
@@ -178,7 +209,7 @@ describe('CREATE work', () => {
it('should return 404 for non-existed work stream', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/workstreams/1234/works`)
+ .post(`/v5/projects/${projectId}/workstreams/1234/works`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -190,7 +221,7 @@ describe('CREATE work', () => {
models.WorkStream.destroy({ where: { id: workStreamId } })
.then(() => {
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works`)
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -199,92 +230,92 @@ describe('CREATE work', () => {
});
});
- it('should return 422 when name not provided', (done) => {
+ it('should return 400 when name not provided', (done) => {
const reqBody = _.cloneDeep(body);
- delete reqBody.param.name;
+ delete reqBody.name;
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works`)
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send(reqBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 when status not provided', (done) => {
+ it('should return 400 when status not provided', (done) => {
const reqBody = _.cloneDeep(body);
- delete reqBody.param.status;
+ delete reqBody.status;
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works`)
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send(reqBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 when startDate > endDate', (done) => {
+ it('should return 400 when startDate > endDate', (done) => {
const reqBody = _.cloneDeep(body);
- reqBody.param.startDate = '2018-05-16T12:00:00';
+ reqBody.startDate = '2018-05-16T12:00:00';
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works`)
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.send(reqBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 when budget is negative', (done) => {
+ it('should return 400 when budget is negative', (done) => {
const reqBody = _.cloneDeep(body);
- reqBody.param.budget = -20;
+ reqBody.budget = -20;
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works`)
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send(reqBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
- it('should return 422 when progress is negative', (done) => {
+ it('should return 400 when progress is negative', (done) => {
const reqBody = _.cloneDeep(body);
- reqBody.param.progress = -20;
+ reqBody.progress = -20;
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works`)
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send(reqBody)
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 201 for member', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works`)
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
- .send(body)
+ .send(_.assign({ productTemplateId }, body))
.expect(201, done);
});
- it('should return 200 for connect admin', (done) => {
+ it('should return 201 for connect admin', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works`)
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
- .send(body)
- .expect(200)
+ .send(_.assign({ productTemplateId }, body))
+ .expect(201)
.end((err, res) => {
- const resJson = res.body.result.content;
- validatePhase(resJson, body.param);
+ const resJson = res.body;
+ validatePhase(resJson, body);
done();
});
});
@@ -306,9 +337,9 @@ describe('CREATE work', () => {
sandbox.restore();
});
- it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when work added', (done) => {
+ it('should send correct BUS API messages when work added', (done) => {
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works`)
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -320,14 +351,26 @@ describe('CREATE work', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, sinon.match({
+ resource: RESOURCES.PHASE,
+ name: body.name,
+ status: body.status,
+ budget: body.budget,
+ progress: body.progress,
+ projectId,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
projectId,
projectName,
projectUrl: `https://local.topcoder-dev.com/projects/${projectId}`,
userId: 40051331,
initiatorUserId: 40051331,
})).should.be.true;
+
done();
});
}
@@ -340,12 +383,12 @@ describe('CREATE work', () => {
let publishSpy;
let sandbox;
- before(async (done) => {
+ before((done) => {
// Wait for 500ms in order to wait for createEvent calls from previous tests to complete
testUtil.wait(done);
});
- beforeEach(async (done) => {
+ beforeEach(async () => {
sandbox = sinon.sandbox.create();
server.services.pubsub = new RabbitMQService(server.logger);
@@ -366,11 +409,11 @@ describe('CREATE work', () => {
},
});
- testUtil.wait(() => {
+ return new Promise(resolve => setTimeout(() => {
publishSpy = sandbox.spy(server.services.pubsub, 'publish');
createMessageSpy = sandbox.spy(messageService, 'createTopic');
- done();
- });
+ resolve();
+ }, 500));
});
afterEach(() => {
@@ -398,24 +441,24 @@ describe('CREATE work', () => {
});
sandbox.stub(messageService, 'getClient', () => mockHttpClient);
request(server)
- .post(`/v4/projects/${projectId}/workstreams/${workStreamId}/works`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
- })
- .send(body)
- .expect(201)
- .end((err) => {
- if (err) {
- done(err);
- } else {
- testUtil.wait(() => {
- publishSpy.calledOnce.should.be.true;
- publishSpy.calledWith('project.phase.added').should.be.true;
- createMessageSpy.calledTwice.should.be.true;
- done();
- });
- }
- });
+ .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
+ })
+ .send(body)
+ .expect(201)
+ .end((err) => {
+ if (err) {
+ done(err);
+ } else {
+ testUtil.wait(() => {
+ publishSpy.calledOnce.should.be.true;
+ publishSpy.calledWith('project.phase.added').should.be.true;
+ createMessageSpy.calledTwice.should.be.true;
+ done();
+ });
+ }
+ });
});
});
});
diff --git a/src/routes/works/delete.js b/src/routes/works/delete.js
index 6102db20..885c8c5b 100644
--- a/src/routes/works/delete.js
+++ b/src/routes/works/delete.js
@@ -1,11 +1,13 @@
/**
* API to delete a work
*/
+import _ from 'lodash';
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import models from '../../models';
-import { EVENT, TIMELINE_REFERENCES } from '../../constants';
+import util from '../../util';
+import { EVENT, RESOURCES, TIMELINE_REFERENCES } from '../../constants';
const permissions = tcMiddleware.permissions;
@@ -65,7 +67,13 @@ module.exports = [
{ deleted, route: TIMELINE_REFERENCES.WORK },
{ correlationId: req.id },
);
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED, { req, deleted });
+
+ // emit event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED,
+ RESOURCES.PHASE,
+ _.pick(deleted.toJSON(), 'id'));
res.status(204).json({});
}).catch(err => next(err));
diff --git a/src/routes/works/delete.spec.js b/src/routes/works/delete.spec.js
index cffe9034..e1ed3bab 100644
--- a/src/routes/works/delete.spec.js
+++ b/src/routes/works/delete.spec.js
@@ -14,7 +14,7 @@ import busApi from '../../services/busApi';
import messageService from '../../services/messageService';
import RabbitMQService from '../../services/rabbitmq';
import mockRabbitMQ from '../../tests/mockRabbitMQ';
-import { BUS_API_EVENT } from '../../constants';
+import { BUS_API_EVENT, CONNECT_NOTIFICATION_EVENT, RESOURCES } from '../../constants';
const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName');
const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType');
@@ -38,7 +38,7 @@ const expectAfterDelete = (workId, projectId, workStreamId, err, next) => {
chai.assert.isNotNull(res.deletedBy);
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -182,18 +182,20 @@ describe('DELETE work', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('DELETE /projects/{projectId}/workstreams/{workStreamId}/works/{workId}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.expect(403, done);
});
it('should return 403 for copilot', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -202,7 +204,7 @@ describe('DELETE work', () => {
it('should return 404 when no work stream with specific workStreamId', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/999/works/${workId}`)
+ .delete(`/v5/projects/${projectId}/workstreams/999/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -212,7 +214,7 @@ describe('DELETE work', () => {
it('should return 404 when no work with specific workId', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/999`)
+ .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/999`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -224,7 +226,7 @@ describe('DELETE work', () => {
models.ProjectPhase.destroy({ where: { id: workId } })
.then(() => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -234,7 +236,7 @@ describe('DELETE work', () => {
it('should return 204 for member', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -244,7 +246,7 @@ describe('DELETE work', () => {
it('should return 204, for admin, if type was successfully removed', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -254,7 +256,7 @@ describe('DELETE work', () => {
it('should return 204, for connect admin, if type was successfully removed', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -279,9 +281,9 @@ describe('DELETE work', () => {
sandbox.restore();
});
- it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when work removed', (done) => {
+ it('should send correct BUS API messages when work removed', (done) => {
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -291,14 +293,22 @@ describe('DELETE work', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_DELETED, sinon.match({
+ resource: RESOURCES.PHASE,
+ id: workId,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
projectId,
projectName,
projectUrl: `https://local.topcoder-dev.com/projects/${projectId}`,
userId: 40051336,
initiatorUserId: 40051336,
})).should.be.true;
+
done();
});
}
@@ -312,12 +322,12 @@ describe('DELETE work', () => {
let publishSpy;
let sandbox;
- before(async (done) => {
+ before((done) => {
// Wait for 500ms in order to wait for createEvent calls from previous tests to complete
testUtil.wait(done);
});
- beforeEach(async (done) => {
+ beforeEach(async () => {
sandbox = sinon.sandbox.create();
server.services.pubsub = new RabbitMQService(server.logger);
@@ -351,13 +361,13 @@ describe('DELETE work', () => {
},
});
- testUtil.wait(() => {
+ return new Promise(resolve => setTimeout(() => {
publishSpy = sandbox.spy(server.services.pubsub, 'publish');
deleteTopicSpy = sandbox.spy(messageService, 'deleteTopic');
deletePostsSpy = sandbox.spy(messageService, 'deletePosts');
sandbox.stub(messageService, 'getTopicByTag', () => Promise.resolve(topic));
- done();
- });
+ resolve();
+ }, 500));
});
afterEach(() => {
@@ -374,24 +384,24 @@ describe('DELETE work', () => {
});
sandbox.stub(messageService, 'getClient', () => mockHttpClient);
request(server)
- .delete(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.admin}`,
- })
- .expect(204)
- .end((err) => {
- if (err) {
- done(err);
- } else {
- testUtil.wait(() => {
- publishSpy.calledOnce.should.be.true;
- publishSpy.calledWith('project.phase.removed').should.be.true;
- deleteTopicSpy.calledTwice.should.be.true;
- deletePostsSpy.calledTwice.should.be.true;
- done();
- });
- }
- });
+ .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.admin}`,
+ })
+ .expect(204)
+ .end((err) => {
+ if (err) {
+ done(err);
+ } else {
+ testUtil.wait(() => {
+ publishSpy.calledOnce.should.be.true;
+ publishSpy.calledWith('project.phase.removed').should.be.true;
+ deleteTopicSpy.calledTwice.should.be.true;
+ deletePostsSpy.calledTwice.should.be.true;
+ done();
+ });
+ }
+ });
});
});
});
diff --git a/src/routes/works/get.js b/src/routes/works/get.js
index dbe002a6..b94b974a 100644
--- a/src/routes/works/get.js
+++ b/src/routes/works/get.js
@@ -4,7 +4,6 @@
import validate from 'express-validation';
import Joi from 'joi';
import { middleware as tcMiddleware } from 'tc-core-library-js';
-import util from '../../util';
import models from '../../models';
const permissions = tcMiddleware.permissions;
@@ -53,7 +52,7 @@ module.exports = [
return Promise.reject(apiErr);
}
- return res.json(util.wrapResponse(req.id, phase));
+ return res.json(phase);
})
.catch(next),
];
diff --git a/src/routes/works/get.spec.js b/src/routes/works/get.spec.js
index 43f03b66..768fd785 100644
--- a/src/routes/works/get.spec.js
+++ b/src/routes/works/get.spec.js
@@ -97,18 +97,20 @@ describe('GET work', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/{projectId}/workstreams/{workStreamId}/works/{workId}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -117,7 +119,7 @@ describe('GET work', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -126,7 +128,7 @@ describe('GET work', () => {
it('should return 404 when no project with specific projectId', (done) => {
request(server)
- .get(`/v4/projects/9999/workstreams/${workStreamId}/works/${workId}`)
+ .get(`/v5/projects/9999/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -136,7 +138,7 @@ describe('GET work', () => {
it('should return 404 when no work stream with specific workStreamId', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/999/works/${workId}`)
+ .get(`/v5/projects/${projectId}/workstreams/999/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -146,7 +148,7 @@ describe('GET work', () => {
it('should return 404 when no work with specific workId', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/999`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/999`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -158,7 +160,7 @@ describe('GET work', () => {
models.ProjectPhase.destroy({ where: { id: workId } })
.then(() => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -168,13 +170,13 @@ describe('GET work', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.name.should.be.eql(body.name);
resJson.status.should.be.eql(body.status);
resJson.budget.should.be.eql(body.budget);
@@ -193,7 +195,7 @@ describe('GET work', () => {
it('should return 200 for connect admin', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -203,7 +205,7 @@ describe('GET work', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
diff --git a/src/routes/works/list.js b/src/routes/works/list.js
index 4d34de26..78f00f03 100644
--- a/src/routes/works/list.js
+++ b/src/routes/works/list.js
@@ -90,7 +90,7 @@ module.exports = [
return phaseObj;
});
})
- .then(phases => res.json(util.wrapResponse(req.id, phases, phases.length)))
+ .then(phases => res.json(phases))
.catch(next);
},
];
diff --git a/src/routes/works/list.spec.js b/src/routes/works/list.spec.js
index b73bd12a..4edc1796 100644
--- a/src/routes/works/list.spec.js
+++ b/src/routes/works/list.spec.js
@@ -122,12 +122,14 @@ describe('LIST works', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('GET /projects/{projectId}/workstreams/{workStreamId}/works', () => {
it('should return 200 for admin', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
@@ -135,7 +137,7 @@ describe('LIST works', () => {
.end((err, res) => {
const phase = phases[0];
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
resJson[0].name.should.be.eql(phase.name);
resJson[0].status.should.be.eql(phase.status);
@@ -154,13 +156,13 @@ describe('LIST works', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`)
.expect(403, done);
});
it('should return 403 for member', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
@@ -169,7 +171,7 @@ describe('LIST works', () => {
it('should return 403 for copilot', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
@@ -178,7 +180,7 @@ describe('LIST works', () => {
it('should return 200 for connect admin', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`)
.set({
Authorization: `Bearer ${testUtil.jwts.connectAdmin}`,
})
@@ -188,7 +190,7 @@ describe('LIST works', () => {
it('should return 200 for connect manager', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
@@ -198,13 +200,13 @@ describe('LIST works', () => {
it('should return with populated workItems if fields=workItems is used', (done) => {
request(server)
- .get(`/v4/projects/${projectId}/workstreams/${workStreamId}/works?fields=workItems`)
+ .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works?fields=workItems`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.expect(200)
.end((err, res) => {
- const resJson = res.body.result.content;
+ const resJson = res.body;
resJson.should.have.length(2);
resJson[0].should.have.property('workItems');
resJson[0].workItems.should.be.a('array');
diff --git a/src/routes/works/update.js b/src/routes/works/update.js
index 82aaa08c..0dd343a8 100644
--- a/src/routes/works/update.js
+++ b/src/routes/works/update.js
@@ -8,7 +8,7 @@ import Sequelize from 'sequelize';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import models from '../../models';
import util from '../../util';
-import { EVENT, ROUTES, TIMELINE_REFERENCES } from '../../constants';
+import { EVENT, RESOURCES, TIMELINE_REFERENCES, ROUTES } from '../../constants';
const permissions = tcMiddleware.permissions;
@@ -18,22 +18,20 @@ const schema = {
workStreamId: Joi.number().integer().positive().required(),
id: Joi.number().integer().positive().required(),
},
- body: {
- param: Joi.object().keys({
- name: Joi.string().optional(),
- description: Joi.string().optional(),
- requirements: Joi.string().optional(),
- status: Joi.string().optional(),
- startDate: Joi.date().optional(),
- endDate: Joi.date().optional(),
- duration: Joi.number().min(0).optional(),
- budget: Joi.number().min(0).optional(),
- spentBudget: Joi.number().min(0).optional(),
- progress: Joi.number().min(0).optional(),
- details: Joi.any().optional(),
- order: Joi.number().integer().optional(),
- }).required(),
- },
+ body: Joi.object().keys({
+ name: Joi.string().optional(),
+ description: Joi.string().optional(),
+ requirements: Joi.string().optional(),
+ status: Joi.string().optional(),
+ startDate: Joi.date().optional(),
+ endDate: Joi.date().optional(),
+ duration: Joi.number().min(0).optional(),
+ budget: Joi.number().min(0).optional(),
+ spentBudget: Joi.number().min(0).optional(),
+ progress: Joi.number().min(0).optional(),
+ details: Joi.any().optional(),
+ order: Joi.number().integer().optional(),
+ }).required(),
};
@@ -48,7 +46,7 @@ module.exports = [
const workStreamId = _.parseInt(req.params.workStreamId);
const phaseId = _.parseInt(req.params.id);
- const updatedProps = req.body.param;
+ const updatedProps = req.body;
updatedProps.updatedBy = req.authUser.userId;
let previousValue;
@@ -172,20 +170,24 @@ module.exports = [
.then((allPhases) => {
req.log.debug('updated project phase', JSON.stringify(updated, null, 2));
+ const updatedValue = updated.get({ plain: true });
+
// emit original and updated project phase information
req.app.services.pubsub.publish(
EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED,
- { original: previousValue, updated, allPhases, route: TIMELINE_REFERENCES.WORK },
+ { original: previousValue, updated: updatedValue, allPhases, route: TIMELINE_REFERENCES.WORK },
{ correlationId: req.id },
);
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, {
+ util.sendResourceToKafkaBus(
req,
- original: previousValue,
- updated: _.clone(updated.get({ plain: true })),
- route: ROUTES.WORKS.UPDATE,
- });
+ EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED,
+ RESOURCES.PHASE,
+ updatedValue,
+ previousValue,
+ ROUTES.WORKS.UPDATE,
+ );
- res.json(util.wrapResponse(req.id, updated));
+ res.json(updated);
})
.catch(err => next(err));
},
diff --git a/src/routes/works/update.spec.js b/src/routes/works/update.spec.js
index 5fd1014a..ce4301b1 100644
--- a/src/routes/works/update.spec.js
+++ b/src/routes/works/update.spec.js
@@ -14,7 +14,7 @@ import busApi from '../../services/busApi';
import messageService from '../../services/messageService';
import RabbitMQService from '../../services/rabbitmq';
import mockRabbitMQ from '../../tests/mockRabbitMQ';
-import { BUS_API_EVENT } from '../../constants';
+import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants';
const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName');
const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType');
@@ -54,8 +54,6 @@ const updateBody = {
const validatePhase = (resJson, expectedPhase) => {
should.exist(resJson);
resJson.name.should.be.eql(expectedPhase.name);
- resJson.description.should.be.eql(expectedPhase.description);
- resJson.requirements.should.be.eql(expectedPhase.requirements);
resJson.status.should.be.eql(expectedPhase.status);
resJson.budget.should.be.eql(expectedPhase.budget);
resJson.progress.should.be.eql(expectedPhase.progress);
@@ -200,73 +198,71 @@ describe('UPDATE work', () => {
});
});
- after(testUtil.clearDb);
+ after((done) => {
+ testUtil.clearDb(done);
+ });
describe('PATCH /projects/{projectId}/workstreams/{workStreamId}/works/{workId}', () => {
it('should return 403 if user is not authenticated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
- .send({ param: updateBody })
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .send(updateBody)
.expect(403, done);
});
it('should return 403 for copilot', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect(403, done);
});
it('should return 404 when no work stream with specific workStreamId', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/999/works/${workId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/999/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect('Content-Type', /json/)
.expect(404, done);
});
it('should return 404 when no work with specific workId', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/999`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/999`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect('Content-Type', /json/)
.expect(404, done);
});
- it('should return 422 when parameters are invalid', (done) => {
+ it('should return 400 when parameters are invalid', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.send({
- param: {
- progress: -15,
- },
+ progress: -15,
})
.expect('Content-Type', /json/)
- .expect(422, done);
+ .expect(400, done);
});
it('should return 400 when startDate > endDate', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.send({
- param: {
- endDate: '2018-05-13T00:00:00Z',
- },
+ endDate: '2018-05-13T00:00:00Z',
})
.expect('Content-Type', /json/)
.expect(400, done);
@@ -274,28 +270,28 @@ describe('UPDATE work', () => {
it('should return 200 for member', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.member}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect(200, done);
});
it('should return updated phase when user have permission and parameters are valid', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: updateBody })
+ .send(updateBody)
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
validatePhase(resJson, updateBody);
done();
}
@@ -309,18 +305,18 @@ describe('UPDATE work', () => {
bodyWithZeros.budget = 0.0;
bodyWithZeros.progress = 0.0;
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: bodyWithZeros })
+ .send(bodyWithZeros)
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
validatePhase(resJson, bodyWithZeros);
done();
}
@@ -329,18 +325,18 @@ describe('UPDATE work', () => {
it('should return updated phase if the order is specified', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
- .send({ param: _.assign({ order: 1 }, updateBody) })
+ .send(_.assign({ order: 1 }, updateBody))
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
if (err) {
done(err);
} else {
- const resJson = res.body.result.content;
+ const resJson = res.body;
validatePhase(resJson, updateBody);
resJson.order.should.be.eql(1);
@@ -358,29 +354,30 @@ describe('UPDATE work', () => {
let createEventSpy;
const sandbox = sinon.sandbox.create();
+
before((done) => {
// Wait for 500ms in order to wait for createEvent calls from previous tests to complete
testUtil.wait(done);
});
+
beforeEach(() => {
createEventSpy = sandbox.spy(busApi, 'createEvent');
});
+
afterEach(() => {
sandbox.restore();
});
- it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when spentBudget updated', (done) => {
+ it('should send correct BUS API messages when spentBudget updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- spentBudget: 123,
- },
+ spentBudget: 123,
})
.expect('Content-Type', /json/)
.expect(200)
@@ -389,25 +386,31 @@ describe('UPDATE work', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE,
+ id: workId,
+ updatedBy: testUtil.userIds.admin,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_WORK_UPDATE_PAYMENT).should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_WORK_UPDATE_PAYMENT);
done();
});
}
});
});
- it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when progress updated', (done) => {
+ it('should send correct BUS API messages when progress updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- progress: 50,
- },
+ progress: 50,
})
.expect('Content-Type', /json/)
.expect(200)
@@ -416,26 +419,32 @@ describe('UPDATE work', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.callCount.should.be.eql(2);
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_WORK_UPDATE_PROGRESS);
- createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_PROGRESS_MODIFIED);
+ createEventSpy.callCount.should.be.eql(3);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE,
+ id: workId,
+ updatedBy: testUtil.userIds.admin,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_WORK_UPDATE_PROGRESS).should.be.true;
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PROGRESS_MODIFIED).should.be.true;
done();
});
}
});
});
- it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when details updated', (done) => {
+ it('should send correct BUS API messages when details updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- details: {
- text: 'something',
- },
+ details: {
+ text: 'something',
},
})
.expect('Content-Type', /json/)
@@ -445,24 +454,31 @@ describe('UPDATE work', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_WORK_UPDATE_SCOPE);
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE,
+ id: workId,
+ updatedBy: testUtil.userIds.admin,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_WORK_UPDATE_SCOPE).should.be.true;
+
done();
});
}
});
});
- it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when status updated (active)', (done) => {
+ it('should send correct BUS API messages when status updated (completed)', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId3}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- status: 'active',
- },
+ status: 'completed',
})
.expect('Content-Type', /json/)
.expect(200)
@@ -471,24 +487,31 @@ describe('UPDATE work', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_WORK_TRANSITION_ACTIVE);
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE,
+ id: workId,
+ updatedBy: testUtil.userIds.admin,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_WORK_TRANSITION_COMPLETED).should.be.true;
+
done();
});
}
});
});
- it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when status updated (completed)', (done) => {
+ it('should send correct BUS API messages when status updated (active)', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId3}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- status: 'completed',
- },
+ status: 'active',
})
.expect('Content-Type', /json/)
.expect(200)
@@ -497,24 +520,31 @@ describe('UPDATE work', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_WORK_TRANSITION_COMPLETED);
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE,
+ id: workId3,
+ updatedBy: testUtil.userIds.admin,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_WORK_TRANSITION_ACTIVE).should.be.true;
+
done();
});
}
});
});
- it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when budget updated', (done) => {
+ it('should send correct BUS API messages when budget updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- budget: 123,
- },
+ budget: 123,
})
.expect('Content-Type', /json/)
.expect(200)
@@ -523,23 +553,28 @@ describe('UPDATE work', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.notCalled.should.be.true;
+ createEventSpy.callCount.should.be.eql(1);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE,
+ id: workId,
+ updatedBy: testUtil.userIds.admin,
+ })).should.be.true;
+
done();
});
}
});
});
- it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when startDate updated', (done) => {
+ it('should send correct BUS API messages when startDate updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- startDate: 123,
- },
+ startDate: 123,
})
.expect('Content-Type', /json/)
.expect(200)
@@ -548,32 +583,38 @@ describe('UPDATE work', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE,
+ id: workId,
+ updatedBy: testUtil.userIds.admin,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
projectId,
projectName,
projectUrl: `https://local.topcoder-dev.com/projects/${projectId}`,
- // originalPhase: sinon.match(originalPhase),
- // updatedPhase: sinon.match(updatedPhase),
userId: 40051333,
initiatorUserId: 40051333,
})).should.be.true;
+
done();
});
}
});
});
- it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when duration updated', (done) => {
+
+ it('should send correct BUS API messages when duration updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- duration: 100,
- },
+ duration: 100,
})
.expect('Content-Type', /json/)
.expect(200)
@@ -582,30 +623,35 @@ describe('UPDATE work', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.calledOnce.should.be.true;
- createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
+ createEventSpy.callCount.should.be.eql(2);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({
+ duration: 100,
+ })).should.be.true;
+
+ // Check Notification Service events
+ createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({
projectId,
projectName,
projectUrl: `https://local.topcoder-dev.com/projects/${projectId}`,
userId: 40051333,
initiatorUserId: 40051333,
})).should.be.true;
+
done();
});
}
});
});
- it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when order updated', (done) => {
+ it('should send correct BUS API messages when order updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- order: 100,
- },
+ order: 100,
})
.expect('Content-Type', /json/)
.expect(200)
@@ -614,23 +660,30 @@ describe('UPDATE work', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.notCalled.should.be.true;
+ createEventSpy.callCount.should.be.eql(1);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE,
+ id: workId,
+ updatedBy: testUtil.userIds.admin,
+ })).should.be.true;
+
+ // NOTE: no other event should be called, as this phase doesn't move any other phases
+
done();
});
}
});
});
- it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when endDate updated', (done) => {
+ it('should send correct BUS API messages when endDate updated', (done) => {
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
.set({
Authorization: `Bearer ${testUtil.jwts.admin}`,
})
.send({
- param: {
- endDate: new Date(),
- },
+ endDate: new Date(),
})
.expect('Content-Type', /json/)
.expect(200)
@@ -639,7 +692,14 @@ describe('UPDATE work', () => {
done(err);
} else {
testUtil.wait(() => {
- createEventSpy.notCalled.should.be.true;
+ createEventSpy.callCount.should.be.eql(1);
+
+ createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({
+ resource: RESOURCES.PHASE,
+ id: workId,
+ updatedBy: testUtil.userIds.admin,
+ })).should.be.true;
+
done();
});
}
@@ -652,12 +712,12 @@ describe('UPDATE work', () => {
let publishSpy;
let sandbox;
- before(async (done) => {
+ before((done) => {
// Wait for 500ms in order to wait for createEvent calls from previous tests to complete
testUtil.wait(done);
});
- beforeEach(async (done) => {
+ beforeEach(async () => {
sandbox = sinon.sandbox.create();
server.services.pubsub = new RabbitMQService(server.logger);
@@ -678,12 +738,12 @@ describe('UPDATE work', () => {
},
});
- testUtil.wait(() => {
+ return new Promise(resolve => setTimeout(() => {
publishSpy = sandbox.spy(server.services.pubsub, 'publish');
updateMessageSpy = sandbox.spy(messageService, 'updateTopic');
sandbox.stub(messageService, 'getTopicByTag', () => Promise.resolve(topic));
- done();
- });
+ resolve();
+ }, 500));
});
afterEach(() => {
@@ -711,25 +771,25 @@ describe('UPDATE work', () => {
});
sandbox.stub(messageService, 'getClient', () => mockHttpClient);
request(server)
- .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
- .set({
- Authorization: `Bearer ${testUtil.jwts.admin}`,
- })
- .send({ param: _.assign(updateBody, { budget: 123 }) })
- .expect('Content-Type', /json/)
- .expect(200)
- .end((err) => {
- if (err) {
- done(err);
- } else {
- testUtil.wait(() => {
- publishSpy.calledOnce.should.be.true;
- publishSpy.calledWith('project.phase.updated').should.be.true;
- updateMessageSpy.calledTwice.should.be.true;
- done();
- });
- }
- });
+ .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`)
+ .set({
+ Authorization: `Bearer ${testUtil.jwts.admin}`,
+ })
+ .send(_.assign(updateBody, { budget: 123 }))
+ .expect('Content-Type', /json/)
+ .expect(200)
+ .end((err) => {
+ if (err) {
+ done(err);
+ } else {
+ testUtil.wait(() => {
+ publishSpy.calledOnce.should.be.true;
+ publishSpy.calledWith('project.phase.updated').should.be.true;
+ updateMessageSpy.calledTwice.should.be.true;
+ done();
+ });
+ }
+ });
});
});
});
diff --git a/src/services/busApi.js b/src/services/busApi.js
index 0197523c..5916c03e 100644
--- a/src/services/busApi.js
+++ b/src/services/busApi.js
@@ -46,19 +46,19 @@ async function getClient() {
function createEvent(topic, payload, logger) {
logger.debug(`Sending message to topic ${topic}: ${JSON.stringify(payload)}`);
return getClient().then((busClient) => {
- logger.debug('calling bus-api');
+ logger.debug(`calling bus-api for topic ${topic}`);
return busClient.post('/bus/events', {
topic,
- originator: 'tc-project-service',
+ originator: 'project-api',
timestamp: (new Date()).toISOString(),
'mime-type': 'application/json',
payload,
}).then((resp) => {
- logger.debug('Sent event to bus-api');
- logger.debug(`Sent event to bus-api [data]: ${_.get(resp, 'data')}`);
- logger.debug(`Sent event to bus-api [status]: ${_.get(resp, 'status')}`);
+ logger.debug(`Sent event to bus-api for topic ${topic}`);
+ logger.debug(`Sent event to bus-api for topic ${topic} [data]: ${_.get(resp, 'data')}`);
+ logger.debug(`Sent event to bus-api for topic ${topic} [status]: ${_.get(resp, 'status')}`);
}).catch((error) => {
- logger.debug('Error sending event to bus-api');
+ logger.debug(`Error sending event to bus-api for topic ${topic}`);
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
diff --git a/src/services/directProject.js b/src/services/directProject.js
deleted file mode 100644
index 6d4c0e42..00000000
--- a/src/services/directProject.js
+++ /dev/null
@@ -1,124 +0,0 @@
-
-import _ from 'lodash';
-import config from 'config';
-import util from '../util';
-
-/**
- * Service methods to handle direct project.
- */
-
-/**
- * Build custom http client for request
- * @param {Object} req request
- * @returns {Object} custom http client
- * @private
- */
-function getHttpClient(req) {
- const httpClient = util.getHttpClient(req);
- httpClient.defaults.headers.common.Authorization = req.headers.authorization;
- httpClient.defaults.headers.common['Content-Type'] = 'application/json';
- httpClient.defaults.baseURL = config.get('directProjectServiceEndpoint');
- httpClient.defaults.timeout = _.get(config, 'directProjectServiceTimeout', 5000);
- httpClient.interceptors.response.use((resp) => {
- // req.log.debug('resp: ', JSON.stringify(resp.data, null, 2))
- if (resp.status !== 200 || resp.data.result.status !== 200) {
- req.log.error('error resp: ', JSON.stringify(resp.data, null, 2));
- return Promise.reject(new Error(resp.data.result.content.message));
- }
- return Promise.resolve(resp);
- });
- return httpClient;
-}
-
-export default {
- /**
- * Create direct project
- * @param {Object} req request
- * @param {Object} body the body contains project information
- * @returns {Promise} create direct project promise
- */
- createDirectProject: (req, body) => getHttpClient(req)
- .post('/projects/', body),
-
- /**
- * Add direct project copilot
- * @param {Object} req request
- * @param {Object} directProjectId the id of direct project
- * @param {Integer} copilotUserId copilot user idenitifier
- * @returns {Promise} add pilot promise
- */
- addCopilot: (req, directProjectId, copilotUserId) => getHttpClient(req)
- .post(`/projects/${directProjectId}/copilot`, { copilotUserId }),
-
- /**
- * Remove direct project copilot
- * @param {Object} req the request
- * @param {Integer} directProjectId the id of direct project
- * @param {Integer} copilotUserId copilot user idenitifier
- * @returns {Promise} response promise
- */
- deleteCopilot: (req, directProjectId, copilotUserId) => getHttpClient(req).request({
- method: 'delete',
- url: `/projects/${directProjectId}/copilot`,
- data: { copilotUserId },
- }),
-
- /**
- * Add billing account for direct project
- * @param {Object} req the request
- * @param {String} directProjectId the id of direct project
- * @param {Object} body the body contains billing account information
- * @returns {Promise} add billing account promise
- */
- 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 {Object} req the request
- * @param {Integer} directProjectId the id of direct project
- * @param {Object} body the body contains permissions information
- * @returns {Promise} promise
- */
- editProjectPermissions: (req, directProjectId, body) => getHttpClient(req)
- .post(`/projects/${directProjectId}/permissions`, body),
-
- /**
- * Add direct project manager
- * @param {Object} req request
- * @param {Object} directProjectId the id of direct project
- * @param {Object} userId user idenitifier
- * @returns {Promise} add pilot promise
- */
- addManager: (req, directProjectId, userId) => getHttpClient(req)
- .post(`/projects/${directProjectId}/permissions`, {
- permissions: [{
- userId,
- permissionType: {
- permissionTypeId: 3,
- name: 'project_full',
- },
- studio: false,
- }],
- }),
-
- /**
- * Remove direct project manager
- * @param {Object} req the request
- * @param {Integer} directProjectId the id of direct project
- * @param {Object} userId user idenitifier
- * @returns {Promise} response promise
- */
- deleteManager: (req, directProjectId, userId) => getHttpClient(req)
- .post(`/projects/${directProjectId}/permissions`, {
- permissions: [{
- userId,
- permissionType: {
- permissionTypeId: '',
- name: 'project_full',
- },
- studio: false,
- }],
- }),
-};
diff --git a/src/tests/seed.js b/src/tests/seed.js
index 8790ad96..ffa5fb0f 100644
--- a/src/tests/seed.js
+++ b/src/tests/seed.js
@@ -362,6 +362,7 @@ models.sequelize.sync({ force: true })
name: 'template 2',
productKey: 'productKey 2',
category: 'category',
+ subCategory: 'category',
icon: 'http://example.com/icon1.ico',
question: 'question 1',
info: 'info 1',
@@ -375,6 +376,7 @@ models.sequelize.sync({ force: true })
{
name: 'Generic work',
productKey: 'generic_work',
+ subCategory: 'category',
category: 'category',
icon: 'http://example.com/icon1.ico',
question: 'question 1',
@@ -404,6 +406,7 @@ models.sequelize.sync({ force: true })
name: 'Website product',
productKey: 'website_development',
category: 'category',
+ subCategory: 'category',
icon: 'http://example.com/icon1.ico',
question: 'question 1',
info: 'info 1',
@@ -432,6 +435,7 @@ models.sequelize.sync({ force: true })
name: 'Application product',
productKey: 'application_development',
category: 'category',
+ subCategory: 'category',
icon: 'http://example.com/icon1.ico',
brief: 'brief 1',
details: 'details 1',
diff --git a/src/tests/util.js b/src/tests/util.js
index 2996f4c9..08f0a657 100644
--- a/src/tests/util.js
+++ b/src/tests/util.js
@@ -1,6 +1,7 @@
/* eslint-disable max-len */
import models from '../models';
+import elasticsearchSync from '../../migrations/elasticsearch_sync';
const jwt = require('jsonwebtoken');
@@ -9,6 +10,10 @@ export default {
.then(() => {
if (done) done();
}),
+ clearES: done => elasticsearchSync.sync()
+ .then(() => {
+ if (done) done();
+ }),
mockHttpClient: {
defaults: { headers: { common: {} } },
interceptors: { response: { use: () => {} } },
diff --git a/src/util.js b/src/util.js
index 4dea1ca4..a593751e 100644
--- a/src/util.js
+++ b/src/util.js
@@ -1,3 +1,4 @@
+
/* globals Promise */
/*
* Copyright (C) 2016 TopCoder Inc., All Rights Reserved.
@@ -17,10 +18,17 @@ import elasticsearch from 'elasticsearch';
// import jp from 'jsonpath';
import Promise from 'bluebird';
import models from './models';
-
// import AWS from 'aws-sdk';
-import { ADMIN_ROLES, TOKEN_SCOPES, EVENT, PROJECT_MEMBER_ROLE, VALUE_TYPE, ESTIMATION_TYPE } from './constants';
+import {
+ ADMIN_ROLES,
+ TOKEN_SCOPES,
+ EVENT,
+ PROJECT_MEMBER_ROLE,
+ VALUE_TYPE,
+ ESTIMATION_TYPE,
+ RESOURCES,
+} from './constants';
const exec = require('child_process').exec;
const tcCoreLibAuth = require('tc-core-library-js').auth;
@@ -390,9 +398,82 @@ _.assignIn(util, {
} else {
esClient = new elasticsearch.Client(_.cloneDeep(config.elasticsearchConfig));
}
+ // during unit tests, we need to refresh the indices
+ // before making get/search requests to make sure all ES data can be visible.
+ if (process.env.NODE_ENV === 'test') {
+ esClient.originalSearch = esClient.search;
+ esClient.search = (params, cb) => esClient.indices.refresh({ index: '' })
+ .then(() => esClient.originalSearch(params, cb)); // refresh index before reply
+ esClient.originalGet = esClient.get;
+ esClient.get = (params, cb) => esClient.indices.refresh({ index: '' })
+ .then(() => esClient.originalGet(params, cb)); // refresh index before reply
+ }
return esClient;
},
+ /**
+ * Return the searched resource from elastic search
+ * @param resource resource name
+ * @param query search query
+ * @param index index to search from
+ * @return {Object} the searched resource
+ */
+ fetchFromES: Promise.coroutine(function* (resource, query, index) { // eslint-disable-line func-names
+ let INDEX = config.get('elasticsearchConfig.metadataIndexName');
+ let TYPE = config.get('elasticsearchConfig.metadataDocType');
+ if (index === 'timeline') {
+ INDEX = config.get('elasticsearchConfig.timelineIndexName');
+ TYPE = config.get('elasticsearchConfig.timelineDocType');
+ } else if (index === 'project') {
+ INDEX = config.get('elasticsearchConfig.indexName');
+ TYPE = config.get('elasticsearchConfig.docType');
+ }
+
+ const data = query ? (yield esClient.search({ index: INDEX, type: TYPE, body: query })) :
+ (yield esClient.search({ index: INDEX, type: TYPE }));
+ if (data.hits.hits.length > 0 && data.hits.hits[0].inner_hits) {
+ return data.hits.hits[0].inner_hits;
+ }
+
+ return data.hits.hits.length > 0 ? data.hits.hits[0]._source : { // eslint-disable-line no-underscore-dangle
+ productTemplates: [],
+ forms: [],
+ projectTemplates: [],
+ planConfigs: [],
+ priceConfigs: [],
+ projectTypes: [],
+ productCategories: [],
+ orgConfigs: [],
+ milestoneTemplates: [],
+ };
+ }),
+
+ /**
+ * Return the searched resource from elastic search PROJECT index
+ * @param resource resource name
+ * @param query search query
+ * @param index index to search from
+ * @return {Array} the searched resource
+ */
+ fetchByIdFromES: Promise.coroutine(function* (resource, query, index) { // eslint-disable-line func-names
+ let INDEX = config.get('elasticsearchConfig.indexName');
+ let TYPE = config.get('elasticsearchConfig.docType');
+ if (index === 'timeline') {
+ INDEX = config.get('elasticsearchConfig.timelineIndexName');
+ TYPE = config.get('elasticsearchConfig.timelineDocType');
+ } else if (index === 'metadata') {
+ INDEX = config.get('elasticsearchConfig.metadataIndexName');
+ TYPE = config.get('elasticsearchConfig.metadataDocType');
+ }
+
+ return (yield esClient.search({
+ index: INDEX,
+ type: TYPE,
+ _source: false,
+ body: query,
+ })).hits.hits;
+ }),
+
/**
* Retrieve member traits from user handle
*/
@@ -502,7 +583,6 @@ _.assignIn(util, {
if (isAdmin) {
return data;
}
-
jp.apply(data, jsonPath, (value) => {
if (_.isObject(value)) {
_.assign(value, { email: util.maskEmail(value.email) });
@@ -625,6 +705,29 @@ _.assignIn(util, {
}
}),
+ /**
+ * Send resource to kafka bus
+ * @param {object} req Request object
+ * @param {String} key the event key
+ * @param {String} name the resource name
+ * @param {object} resource the resource
+ * @param {object} [originalResource] original resource in case resource was updated
+ * @param {String} [route] route which called the event (for phases and works)
+ * @param {Boolean}[skipNotification] if true, than event is not send to Notification Service
+ */
+ sendResourceToKafkaBus: Promise.coroutine(function* (req, key, name, resource, originalResource, route, skipNotification) { // eslint-disable-line
+ req.log.debug('Sending event to Kafka bus for resource %s %s', name, resource.id || resource.key);
+
+ // emit event
+ req.app.emit(key, {
+ req,
+ resource: _.assign({ resource: name }, resource),
+ originalResource: originalResource ? _.assign({ resource: name }, originalResource) : undefined,
+ route,
+ skipNotification,
+ });
+ }),
+
/**
* Add userId to project
* @param {object} req Request object that should contain project info and user info
@@ -644,6 +747,7 @@ _.assignIn(util, {
req.log.debug('creating member', member);
let newMember = null;
// register member
+
return models.ProjectMember.create(member)
.then((_newMember) => {
newMember = _newMember.get({ plain: true });
@@ -653,7 +757,13 @@ _.assignIn(util, {
newMember,
{ correlationId: req.id },
);
- req.app.emit(EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED, { req, member: newMember });
+ // emit the event
+ util.sendResourceToKafkaBus(
+ req,
+ EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED,
+ RESOURCES.PROJECT_MEMBER,
+ newMember);
+
return newMember;
})
.catch((err) => {
@@ -782,6 +892,67 @@ _.assignIn(util, {
*/
isSSO: project => ssoRefCodes.indexOf(_.get(project, 'details.utm.code')) > -1,
+ /**
+ * Set paginated header and respond with data
+ * @param {Object} req HTTP request
+ * @param {Object} res HTTP response
+ * @param {Object} data Data for which pagination need to be applied
+ * @return {Array} data rows to be returned
+ */
+ setPaginationHeaders: (req, res, data) => {
+ const totalPages = Math.ceil(data.count / data.pageSize);
+ let fullUrl = `${req.protocol}://${req.get('host')}${req.url.replace(`&page=${data.page}`, '')}`;
+ // URL formatting to add pagination parameters accordingly
+ if (fullUrl.indexOf('?') === -1) {
+ fullUrl += '?';
+ } else {
+ fullUrl += '&';
+ }
+
+ // Pagination follows github style
+ if (data.count > 0) { // Set Pagination headers only if there is data to paginate
+ let link = ''; // Content for Link header
+
+ // Set first and last page in Link header
+ link += `<${fullUrl}page=1>; rel="first"`;
+ link += `, <${fullUrl}page=${totalPages}>; rel="last"`;
+
+ // Set Prev-Page only if it's not first page and within page limits
+ if (data.page > 1 && data.page <= totalPages) {
+ const prevPage = (data.page - 1);
+ res.set({
+ 'X-Prev-Page': prevPage,
+ });
+ link += `, <${fullUrl}page=${prevPage}>; rel="prev"`;
+ }
+
+ // Set Next-Page only if it's not Last page and within page limits
+ if (data.page < totalPages) {
+ const nextPage = (_.parseInt(data.page) + 1);
+ res.set({
+ 'X-Next-Page': (_.parseInt(data.page) + 1),
+ });
+ link += `, <${fullUrl}page=${nextPage}>; rel="next"`;
+ }
+
+ // Allow browsers access pagination data in headers
+ let accessControlExposeHeaders = res.get('Access-Control-Expose-Headers') || '';
+ accessControlExposeHeaders += accessControlExposeHeaders ? ', ' : '';
+ accessControlExposeHeaders += 'X-Page, X-Per-Page, X-Total, X-Total-Pages';
+
+ res.set({
+ 'Access-Control-Expose-Headers': accessControlExposeHeaders,
+ 'X-Page': data.page,
+ 'X-Per-Page': data.pageSize,
+ 'X-Total': data.count,
+ 'X-Total-Pages': totalPages,
+ Link: link,
+ });
+ }
+ // Return the data after setting pagination headers
+ res.json(data.rows);
+ },
+
/**
* Check if the following model exist
* @param {Object} keyInfo key information, it includes version and key
@@ -809,7 +980,7 @@ _.assignIn(util, {
})).then((record) => {
if (_.isNil(record)) {
const apiErr = new Error(errorMessage);
- apiErr.status = 422;
+ apiErr.status = 400;
throw apiErr;
}
});
@@ -823,7 +994,7 @@ _.assignIn(util, {
})).then((record) => {
if (_.isNil(record)) {
const apiErr = new Error(errorMessage);
- apiErr.status = 422;
+ apiErr.status = 400;
throw apiErr;
}
});
diff --git a/src/utils/es-config.js b/src/utils/es-config.js
new file mode 100644
index 00000000..657add4b
--- /dev/null
+++ b/src/utils/es-config.js
@@ -0,0 +1,719 @@
+import config from 'config';
+
+const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName');
+const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType');
+const ES_TIMELINE_INDEX = config.get('elasticsearchConfig.timelineIndexName');
+const ES_TIMELINE_TYPE = config.get('elasticsearchConfig.timelineDocType');
+const ES_METADATA_INDEX = config.get('elasticsearchConfig.metadataIndexName');
+const ES_METADATA_TYPE = config.get('elasticsearchConfig.metadataDocType');
+
+// form config can be present inside 3 models, so we reuse it
+const formConfig = {
+ type: 'object',
+ properties: {
+ sections: {
+ type: 'nested',
+ properties: {
+ subSections: {
+ type: 'nested',
+ properties: {
+ questions: {
+ type: 'nested',
+ properties: {
+ options: {
+ type: 'nested',
+ properties: {
+ value: {
+ type: 'string',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+};
+
+const MAPPINGS = {};
+
+/**
+ * 'project' index mapping
+ */
+MAPPINGS[ES_PROJECT_INDEX] = {
+ _all: { enabled: false },
+ properties: {
+ actualPrice: {
+ type: 'double',
+ },
+ attachments: {
+ type: 'nested',
+ properties: {
+ category: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ contentType: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ createdAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ createdBy: {
+ type: 'integer',
+ },
+ description: {
+ type: 'string',
+ },
+ filePath: {
+ type: 'string',
+ },
+ id: {
+ type: 'long',
+ },
+ projectId: {
+ type: 'long',
+ },
+ size: {
+ type: 'double',
+ },
+ title: {
+ type: 'string',
+ },
+ updatedAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ updatedBy: {
+ type: 'integer',
+ },
+ },
+ },
+ billingAccountId: {
+ type: 'long',
+ },
+ bookmarks: {
+ type: 'nested',
+ properties: {
+ address: {
+ type: 'string',
+ },
+ title: {
+ type: 'string',
+ },
+ },
+ },
+ cancelReason: {
+ type: 'string',
+ },
+ challengeEligibility: {
+ type: 'nested',
+ properties: {
+ groups: {
+ type: 'long',
+ },
+ role: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ users: {
+ type: 'long',
+ },
+ },
+ },
+ createdAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ createdBy: {
+ type: 'integer',
+ },
+ description: {
+ type: 'string',
+ },
+ details: {
+ type: 'nested',
+ properties: {
+ TBD_features: {
+ type: 'nested',
+ properties: {
+ description: {
+ type: 'string',
+ },
+ id: {
+ type: 'integer',
+ },
+ isCustom: {
+ type: 'boolean',
+ },
+ title: {
+ type: 'string',
+ },
+ },
+ },
+ TBD_usageDescription: {
+ type: 'string',
+ },
+ appDefinition: {
+ properties: {
+ goal: {
+ properties: {
+ value: {
+ type: 'string',
+ },
+ },
+ },
+ primaryTarget: {
+ type: 'string',
+ },
+ users: {
+ properties: {
+ value: {
+ type: 'string',
+ },
+ },
+ },
+ },
+ },
+ hideDiscussions: {
+ type: 'boolean',
+ },
+ products: {
+ type: 'string',
+ },
+ summary: {
+ type: 'string',
+ },
+ utm: {
+ type: 'nested',
+ properties: {
+ code: {
+ type: 'string',
+ },
+ },
+ },
+ },
+ },
+ directProjectId: {
+ type: 'long',
+ },
+ estimatedPrice: {
+ type: 'double',
+ },
+ external: {
+ properties: {
+ data: {
+ type: 'string',
+ },
+ id: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ type: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ },
+ },
+ id: {
+ type: 'long',
+ },
+ members: {
+ type: 'nested',
+ properties: {
+ createdAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ createdBy: {
+ type: 'integer',
+ },
+ email: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ firstName: {
+ type: 'string',
+ },
+ handle: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ id: {
+ type: 'long',
+ },
+ isPrimary: {
+ type: 'boolean',
+ },
+ lastName: {
+ type: 'string',
+ },
+ projectId: {
+ type: 'long',
+ },
+ role: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ updatedAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ updatedBy: {
+ type: 'integer',
+ },
+ userId: {
+ type: 'long',
+ },
+ },
+ },
+ invites: {
+ type: 'nested',
+ properties: {
+ createdAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ createdBy: {
+ type: 'integer',
+ },
+ email: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ id: {
+ type: 'long',
+ },
+ role: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ updatedAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ updatedBy: {
+ type: 'integer',
+ },
+ userId: {
+ type: 'long',
+ },
+ projectId: {
+ type: 'long',
+ },
+ },
+ },
+ name: {
+ type: 'string',
+ },
+ status: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ terms: {
+ type: 'integer',
+ },
+ type: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ updatedAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ updatedBy: {
+ type: 'integer',
+ },
+ lastActivityAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ lastActivityUserId: {
+ type: 'string',
+ },
+ utm: {
+ properties: {
+ campaign: {
+ type: 'string',
+ },
+ medium: {
+ type: 'string',
+ },
+ source: {
+ type: 'string',
+ },
+ },
+ },
+ phases: {
+ type: 'nested',
+ dynamic: true,
+ },
+ },
+};
+
+/**
+ * 'timeline' index mapping
+ */
+MAPPINGS[ES_TIMELINE_INDEX] = {
+ _all: { enabled: false },
+ properties: {
+ milestones: {
+ type: 'nested',
+ properties: {
+ id: {
+ type: 'long',
+ },
+ timelineId: {
+ type: 'long',
+ },
+ },
+ },
+ },
+};
+
+/**
+ * 'metadata' index mapping
+ */
+MAPPINGS[ES_METADATA_INDEX] = {
+ _all: { enabled: false },
+ properties: {
+ projectTemplates: {
+ type: 'nested',
+ properties: {
+ createdAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ createdBy: {
+ type: 'integer',
+ },
+ key: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ category: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ name: {
+ type: 'string',
+ },
+ id: {
+ type: 'long',
+ },
+ scope: formConfig,
+ form: {
+ type: 'object',
+ },
+ priceConfig: {
+ type: 'object',
+ },
+ planConfig: {
+ type: 'object',
+ },
+ phases: {
+ type: 'object',
+ },
+ updatedAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ updatedBy: {
+ type: 'integer',
+ },
+ },
+ },
+ forms: {
+ type: 'nested',
+ properties: {
+ createdAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ createdBy: {
+ type: 'integer',
+ },
+ key: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ config: formConfig,
+ version: {
+ type: 'integer',
+ },
+ revision: {
+ type: 'integer',
+ },
+ id: {
+ type: 'long',
+ },
+ updatedAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ updatedBy: {
+ type: 'integer',
+ },
+ },
+ },
+
+ planConfigs: {
+ type: 'nested',
+ properties: {
+ createdAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ createdBy: {
+ type: 'integer',
+ },
+ key: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ version: {
+ type: 'integer',
+ },
+ revision: {
+ type: 'integer',
+ },
+ id: {
+ type: 'long',
+ },
+ updatedAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ updatedBy: {
+ type: 'integer',
+ },
+ },
+ },
+
+ priceConfigs: {
+ type: 'nested',
+ properties: {
+ createdAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ createdBy: {
+ type: 'integer',
+ },
+ key: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ version: {
+ type: 'integer',
+ },
+ revision: {
+ type: 'integer',
+ },
+ id: {
+ type: 'long',
+ },
+ updatedAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ updatedBy: {
+ type: 'integer',
+ },
+ },
+ },
+
+ orgConfigs: {
+ type: 'nested',
+ properties: {
+ createdAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ createdBy: {
+ type: 'integer',
+ },
+ orgId: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ configName: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ configValue: {
+ type: 'string',
+ },
+ id: {
+ type: 'long',
+ },
+ updatedAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ updatedBy: {
+ type: 'integer',
+ },
+ },
+ },
+
+ productTemplates: {
+ type: 'nested',
+ properties: {
+ createdAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ createdBy: {
+ type: 'integer',
+ },
+ name: {
+ type: 'string',
+ },
+ template: formConfig,
+ productKey: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ category: {
+ type: 'string',
+ },
+ subCategory: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ id: {
+ type: 'long',
+ },
+ updatedAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ updatedBy: {
+ type: 'integer',
+ },
+ },
+ },
+
+ projectTypes: {
+ type: 'nested',
+ properties: {
+ createdAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ createdBy: {
+ type: 'integer',
+ },
+ displayName: {
+ type: 'string',
+ },
+ key: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ id: {
+ type: 'long',
+ },
+ updatedAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ updatedBy: {
+ type: 'integer',
+ },
+ },
+ },
+
+ productCategories: {
+ type: 'nested',
+ properties: {
+ createdAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ createdBy: {
+ type: 'integer',
+ },
+ displayName: {
+ type: 'string',
+ },
+ key: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ id: {
+ type: 'long',
+ },
+ updatedAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ updatedBy: {
+ type: 'integer',
+ },
+ },
+ },
+
+ buildingBlocks: {
+ type: 'nested',
+ properties: {
+ createdAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ createdBy: {
+ type: 'integer',
+ },
+ key: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ id: {
+ type: 'long',
+ },
+ updatedAt: {
+ type: 'date',
+ format: 'strict_date_optional_time||epoch_millis',
+ },
+ updatedBy: {
+ type: 'integer',
+ },
+ },
+ },
+
+ milestoneTemplates: {
+ type: 'nested',
+ properties: {
+ referenceId: {
+ type: 'long',
+ },
+ reference: {
+ type: 'string',
+ index: 'not_analyzed',
+ },
+ id: {
+ type: 'long',
+ },
+ order: {
+ type: 'long',
+ },
+ },
+ },
+ },
+};
+
+// mapping between indexes and their docTypes
+const INDEX_TO_DOC_TYPE = {};
+INDEX_TO_DOC_TYPE[ES_PROJECT_INDEX] = ES_PROJECT_TYPE;
+INDEX_TO_DOC_TYPE[ES_TIMELINE_INDEX] = ES_TIMELINE_TYPE;
+INDEX_TO_DOC_TYPE[ES_METADATA_INDEX] = ES_METADATA_TYPE;
+
+module.exports = {
+ MAPPINGS,
+ INDEX_TO_DOC_TYPE,
+};
diff --git a/src/utils/es.js b/src/utils/es.js
new file mode 100644
index 00000000..c404001a
--- /dev/null
+++ b/src/utils/es.js
@@ -0,0 +1,105 @@
+/**
+ * Methods to index data from DB in ES.
+ */
+import _ from 'lodash';
+import config from 'config';
+import util from '../util';
+import models from '../models';
+import { MAPPINGS } from './es-config';
+
+const ES_METADATA_INDEX = config.get('elasticsearchConfig.metadataIndexName');
+const ES_METADATA_TYPE = config.get('elasticsearchConfig.metadataDocType');
+
+const eClient = util.getElasticSearchClient();
+
+const modelConfigs = {
+ ProjectTemplate: {
+ indexProperty: 'projectTemplates',
+ },
+ ProductTemplate: {
+ indexProperty: 'productTemplates',
+ },
+ ProjectType: {
+ indexProperty: 'projectTypes',
+ },
+ ProductCategory: {
+ indexProperty: 'productCategories',
+ },
+ MilestoneTemplate: {
+ indexProperty: 'milestoneTemplates',
+ },
+ OrgConfig: {
+ indexProperty: 'orgConfigs',
+ },
+ Form: {
+ indexProperty: 'forms',
+ },
+ PlanConfig: {
+ indexProperty: 'planConfigs',
+ },
+ PriceConfig: {
+ indexProperty: 'priceConfigs',
+ },
+ BuildingBlock: {
+ indexProperty: 'buildingBlocks',
+ },
+};
+
+/**
+ * Index metadata models defined by `modelConfigs`
+ *
+ * @returns {Promise} esClient.index result
+ */
+async function indexMetadata() {
+ const modelNames = _.keys(modelConfigs);
+ const body = {};
+
+ for (let i = 0; i < modelNames.length; i += 1) {
+ const modelName = modelNames[i];
+ const modelConfig = modelConfigs[modelName];
+ const records = await models[modelName].findAll({ raw: true }); // eslint-disable-line no-await-in-loop
+
+ body[modelConfig.indexProperty] = records;
+ }
+
+ // TODO add check that there is no data in ES_METADATA_INDEX yet, or throw an error
+
+ return eClient.index({
+ index: ES_METADATA_INDEX,
+ type: ES_METADATA_TYPE,
+ body,
+ refresh: 'wait_for',
+ });
+}
+
+/**
+ * Build the request for creating index
+ *
+ * @param {String} indexName the index name
+ * @param {String} docType docType for index
+ *
+ * @return {Object} create index request
+ */
+function buildCreateIndexRequest(indexName, docType) {
+ const indexMapping = MAPPINGS[indexName];
+
+ if (!indexMapping) {
+ throw new Error(`Mapping is not found for index name '${indexName}'.`);
+ }
+
+ const indexCreateRequest = {
+ index: indexName,
+ updateAllTypes: true,
+ body: {
+ mappings: {},
+ },
+ };
+ indexCreateRequest.body.mappings[docType] = indexMapping;
+
+ return indexCreateRequest;
+}
+
+module.exports = {
+ indexMetadata,
+ buildCreateIndexRequest,
+};
diff --git a/src/utils/fixMetadataForES.js b/src/utils/fixMetadataForES.js
new file mode 100644
index 00000000..8d665727
--- /dev/null
+++ b/src/utils/fixMetadataForES.js
@@ -0,0 +1,161 @@
+/* eslint-disable no-param-reassign, no-restricted-syntax, no-await-in-loop */
+/**
+ * Temporary script to fix metadata in DB to be indexed in ES
+ *
+ * Update all records in the ProjectTemplate table.
+ * - inside “scope” field update “buildingBlocks..price” (for any ) to be a string if it’s not a string.
+ * - inside “scope” field replace all the ‘“wizard”: true’ with ‘“wizard”: {“enabled”: true}’,
+ * and ‘“wizard”: false’ replace with ‘“wizard”: {“enabled”: false}’.
+ * Update all records in the ProductTemplate table.
+ * - inside "template" field update all "required" properties which is not of "boolean" type to boolean.
+ */
+import _ from 'lodash';
+import models from '../models';
+
+/**
+ * Update the wizard property of an object.
+ *
+ * @param {Object} data any object
+ * @returns {undefined}
+ */
+function updateWizardProperty(data) {
+ if (typeof data.wizard === 'boolean') {
+ data.wizard = { enabled: data.wizard };
+ }
+}
+
+/**
+ * Update the scope property of a projectTemplate.
+ *
+ * @param {Object} scope the scope property
+ * @returns {Object} the updated scope
+ */
+function updateScope(scope) {
+ // update price properties
+ if (scope.buildingBlocks) {
+ for (const key of Object.keys(scope.buildingBlocks)) {
+ const price = scope.buildingBlocks[key].price;
+ if (price !== undefined) {
+ if (typeof price !== 'string') {
+ scope.buildingBlocks[key].price = price.toString();
+ }
+ }
+ }
+ }
+ // update wizard properties
+ updateWizardProperty(scope);
+ if (scope.sections) {
+ for (const section of scope.sections) {
+ updateWizardProperty(section);
+ if (section.subSections) {
+ for (const subSection of section.subSections) {
+ updateWizardProperty(subSection);
+ }
+ }
+ }
+ }
+ return scope;
+}
+
+/**
+ * Fix all projectTemplates.
+ *
+ * @param {Object} logger logger
+ *
+ * @returns {Promise} resolved when dene
+ */
+async function fixProjectTemplates(logger) {
+ const projectTemplates = await models.ProjectTemplate.findAll();
+ for (const projectTemplate of projectTemplates) {
+ if (projectTemplate.scope) {
+ const updatedScope = updateScope(JSON.parse(JSON.stringify(projectTemplate.scope)));
+ if (!_.isEqual(updatedScope, projectTemplate.scope)) {
+ projectTemplate.scope = updatedScope;
+ await projectTemplate.save();
+ logger.info(`updated record of ProjectTemplate with id ${projectTemplate.id}`);
+ }
+ }
+ }
+}
+
+/**
+ * Update the required property of an object.
+ *
+ * @param {Object} data any object
+ *
+ * @returns {undefined}
+ */
+function updateRequiredProperty(data) {
+ if (typeof data.required !== 'undefined' && typeof data.required !== 'boolean') {
+ if (data.required === 'false') {
+ data.required = false;
+ } else if (data.required === 'true') {
+ data.required = true;
+ } else {
+ throw new Error(`"required" value ${data.required} cannot be converted to boolean.`);
+ }
+ }
+}
+
+/**
+ * Update the template property of a productTemplate.
+ *
+ * @param {Object} template the template property
+ * @returns {Object} the updated template
+ */
+function updateTemplate(template) {
+ // update wizard properties
+ updateRequiredProperty(template);
+ if (template.sections) {
+ for (const section of template.sections) {
+ updateRequiredProperty(section);
+ if (section.subSections) {
+ for (const subSection of section.subSections) {
+ updateRequiredProperty(subSection);
+ if (subSection.questions) {
+ for (const question of subSection.questions) {
+ updateRequiredProperty(question);
+ }
+ }
+ }
+ }
+ }
+ }
+ return template;
+}
+
+/**
+ * Fix all productTemplates.
+ *
+ * @param {Object} logger logger
+ *
+ * @returns {Promise} resolved when dene
+ */
+async function fixProductTemplates(logger) {
+ const productTemplates = await models.ProductTemplate.findAll();
+
+ for (const productTemplate of productTemplates) {
+ if (productTemplate.template) {
+ const updatedTemplate = updateTemplate(JSON.parse(JSON.stringify(productTemplate.template)));
+ if (!_.isEqual(updatedTemplate, productTemplate.template)) {
+ productTemplate.template = updatedTemplate;
+ await productTemplate.save();
+ logger.info(`updated record of ProductTemplate with id ${productTemplate.id}`);
+ }
+ }
+ }
+}
+
+/**
+ * Fix all metadata models.
+ *
+ * @param {Object} logger logger
+ *
+ * @returns {undefined}
+ */
+async function fixMetadataForES(logger) {
+ await fixProjectTemplates(logger);
+ await fixProductTemplates(logger);
+}
+
+module.exports = fixMetadataForES;
diff --git a/src/utils/fixProjectsForES.js b/src/utils/fixProjectsForES.js
new file mode 100644
index 00000000..e6ef3dd6
--- /dev/null
+++ b/src/utils/fixProjectsForES.js
@@ -0,0 +1,45 @@
+/* eslint-disable no-param-reassign, no-restricted-syntax, no-await-in-loop */
+/**
+ * Temporary script to fix project in DB to be indexed in ES
+ *
+ * Update all records in the Project table.
+ */
+import _ from 'lodash';
+import models from '../models';
+
+/**
+ * Fix all projects.
+ *
+ * @param {Object} logger logger
+ *
+ * @returns {Promise} resolved when dene
+ */
+async function fixProjects(logger) {
+ const path = 'taasDefinition.team.skills';
+ const projects = await models.Project.findAll();
+ for (const project of projects) {
+ if (project.details) {
+ const details = JSON.parse(JSON.stringify(project.details));
+ const skills = _.get(details, path);
+ if (!_.isUndefined(skills) && !_.isArray(skills)) {
+ _.set(details, path, []);
+ project.details = details;
+ await project.save();
+ logger.info(`updated record of Project with id ${project.id}`);
+ }
+ }
+ }
+}
+
+/**
+ * Fix project model.
+ *
+ * @param {Object} logger logger
+ *
+ * @returns {undefined}
+ */
+async function fixProjectsForES(logger) {
+ await fixProjects(logger);
+}
+
+module.exports = fixProjectsForES;