diff --git a/.circleci/config.yml b/.circleci/config.yml
index 0222864..3dc37e3 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -72,6 +72,7 @@ workflows:
only:
- dev
- dev-circleci
+ - change-validatations-in-job-j
# Production builds are exectuted only on tagged commits to the
# master branch.
diff --git a/.gitignore b/.gitignore
index 7d71a33..f00801a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@ coverage
.DS_Store
.env
api.env
+.eslintrc.y*ml
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000..48082f7
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+12
diff --git a/README.md b/README.md
index d8409ab..647f29f 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,9 @@ The following parameters can be set in config files or in env variables:
- `KAFKA_CLIENT_CERT_KEY`: Kafka connection private key, optional;
if not provided, then SSL connection is not used, direct insecure connection is used;
if provided, it can be either path to private key file or private key content
+- `KAFKA_MESSAGE_ORIGINATOR`: The originator value for the kafka messages
- `KAFKA_GROUP_ID`: the Kafka group id
+- `topics.KAFKA_ERROR_TOPIC`: the error topic at which bus api will publish any errors
- `topics.TAAS_JOB_CREATE_TOPIC`: the create job entity Kafka message topic
- `topics.TAAS_JOB_UPDATE_TOPIC`: the update job entity Kafka message topic
- `topics.TAAS_JOB_DELETE_TOPIC`: the delete job entity Kafka message topic
@@ -33,6 +35,18 @@ The following parameters can be set in config files or in env variables:
- `topics.TAAS_WORK_PERIOD_CREATE_TOPIC`: the create work period entity Kafka message topic
- `topics.TAAS_WORK_PERIOD_UPDATE_TOPIC`: the update work period entity Kafka message topic
- `topics.TAAS_WORK_PERIOD_DELETE_TOPIC`: the delete work period entity Kafka message topic
+- `topics.TAAS_WORK_PERIOD_PAYMENT_CREATE_TOPIC`: the create work period payment entity Kafka message topic
+- `topics.TAAS_WORK_PERIOD_PAYMENT_UPDATE_TOPIC`: the update work period payment entity Kafka message topic
+- `topics.TAAS_INTERVIEW_REQUEST_TOPIC`: the request interview entity Kafka message topic
+- `topics.TAAS_INTERVIEW_UPDATE_TOPIC`: the update interview entity Kafka message topic
+- `topics.TAAS_INTERVIEW_BULK_UPDATE_TOPIC`: the bulk update interview entity Kafka message topic
+- `topics.TAAS_ROLE_CREATE_TOPIC`: the create role entity Kafka message topic
+- `topics.TAAS_ROLE_UPDATE_TOPIC`: the update role entity Kafka message topic
+- `topics.TAAS_ROLE_DELETE_TOPIC`: the delete role entity Kafka message topic
+- `topics.TAAS_ACTION_RETRY_TOPIC`: the retry process Kafka message topic
+- `MAX_RETRY`: maximum allowed retry count for failed operations for sending `taas.action.retry` message
+- `BASE_RETRY_DELAY`: base amount of retry delay (ms) for failed operations
+- `BUSAPI_URL`: Topcoder Bus API URL
- `esConfig.HOST`: Elasticsearch host
- `esConfig.AWS_REGION`: The Amazon region to use when using AWS Elasticsearch service
- `esConfig.ELASTICCLOUD.id`: The elastic cloud id, if your elasticsearch instance is hosted on elastic cloud. DO NOT provide a value for ES_HOST if you are using this
@@ -41,13 +55,14 @@ The following parameters can be set in config files or in env variables:
- `esConfig.ES_INDEX_JOB`: the index name for job
- `esConfig.ES_INDEX_JOB_CANDIDATE`: the index name for job candidate
- `esConfig.ES_INDEX_RESOURCE_BOOKING`: the index name for resource booking
-- `esConfig.ES_INDEX_WORK_PERIOD`: the index name for work period
+- `esConfig.ES_INDEX_ROLE`: the index name for role
- `auth0.AUTH0_URL`: Auth0 URL, used to get TC M2M token
- `auth0.AUTH0_AUDIENCE`: Auth0 audience, used to get TC M2M token
- `auth0.AUTH0_CLIENT_ID`: Auth0 client id, used to get TC M2M token
- `auth0.AUTH0_CLIENT_SECRET`: Auth0 client secret, used to get TC M2M token
- `auth0.AUTH0_PROXY_SERVER_URL`: Proxy Auth0 URL, used to get TC M2M token
+- `auth0.TOKEN_CACHE_TIME`: Auth0 token cache time, used to get TC M2M token
- `zapier.ZAPIER_COMPANYID_SLUG`: your company id in zapier; numeric value
- `zapier.ZAPIER_CONTACTID_SLUG`: your contact id in zapier; numeric value
@@ -75,7 +90,13 @@ The following parameters can be set in config files or in env variables:
## Local deployment
-1. Make sure that Kafka and Elasticsearch is running as per instructions above.
+0. Make sure that Kafka and Elasticsearch is running as per instructions above.
+
+1. Make sure to use Node v12+ by command `node -v`. We recommend using [NVM](https://github.com/nvm-sh/nvm) to quickly switch to the right version:
+
+ ```bash
+ nvm use
+ ```
2. From the project root directory, run the following command to install the dependencies
@@ -95,6 +116,21 @@ The following parameters can be set in config files or in env variables:
npm run lint:fix
```
+4. Local config
+
+ In the `taas-es-processor` root directory create `.env` file with the next environment variables. Values for **Auth0 config** should be shared with you on the forum.
+
+ ```bash
+ # Auth0 config
+ AUTH0_URL=
+ AUTH0_AUDIENCE=
+ AUTH0_CLIENT_ID=
+ AUTH0_CLIENT_SECRET=
+ ```
+
+ - Values from this file would be automatically used by many `npm` commands.
+ - ⚠️ Never commit this file or its copy to the repository!
+
5. Start the processor and health check dropin
```bash
diff --git a/VERIFICATION.md b/VERIFICATION.md
index c6930b9..d6696f1 100644
--- a/VERIFICATION.md
+++ b/VERIFICATION.md
@@ -2,39 +2,51 @@
## Create documents in ES
-- Run the following commands to create `Job`, `JobCandidate`, `ResourceBooking`, `WorkPeriod` documents in ES.
+- Run the following commands to create `Job`, `JobCandidate`, `Interview`, `ResourceBooking`, `WorkPeriod`, `WorkPeriodPayment`, `Role` documents in ES.
``` bash
# for Job
docker exec -i taas-es-processor_kafka /opt/kafka/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic taas.job.create < test/messages/taas.job.create.event.json
# for JobCandidate
docker exec -i taas-es-processor_kafka /opt/kafka/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic taas.jobcandidate.create < test/messages/taas.jobcandidate.create.event.json
+ # for Interview
+ docker exec -i taas-es-processor_kafka /opt/kafka/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic taas.interview.requested < test/messages/taas.interview.requested.event.json
# for ResourceBooking
docker exec -i taas-es-processor_kafka /opt/kafka/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic taas.resourcebooking.create < test/messages/taas.resourcebooking.create.event.json
# for WorkPeriod
docker exec -i taas-es-processor_kafka /opt/kafka/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic taas.workperiod.create < test/messages/taas.workperiod.create.event.json
+ # for WorkPeriodPayment
+ docker exec -i taas-es-processor_kafka /opt/kafka/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic taas.workperiodpayment.create < test/messages/taas.workperiodpayment.create.event.json
+ # for Role
+ docker exec -i taas-es-processor_kafka /opt/kafka/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic taas.role.requested < test/messages/taas.role.create.event.json
```
- Run `npm run view-data ` to see if documents were created.
## Update documents in ES
-- Run the following commands to update `Job`, `JobCandidate`, `ResourceBooking`, `WorkPeriod` documents in ES.
+- Run the following commands to update `Job`, `JobCandidate`, `Interview`, `ResourceBooking`, `WorkPeriod`, `WorkPeriodPayment`, `Role` documents in ES.
``` bash
# for Job
docker exec -i taas-es-processor_kafka /opt/kafka/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic taas.job.update < test/messages/taas.job.update.event.json
# for JobCandidate
docker exec -i taas-es-processor_kafka /opt/kafka/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic taas.jobcandidate.update < test/messages/taas.jobcandidate.update.event.json
+ # for Interview
+ docker exec -i taas-es-processor_kafka /opt/kafka/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic taas.interview.update < test/messages/taas.interview.update.event.json
# for ResourceBooking
docker exec -i taas-es-processor_kafka /opt/kafka/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic taas.resourcebooking.update < test/messages/taas.resourcebooking.update.event.json
# for WorkPeriod
docker exec -i taas-es-processor_kafka /opt/kafka/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic taas.workperiod.update < test/messages/taas.workperiod.update.event.json
+ # for WorkPeriodPayment
+ docker exec -i taas-es-processor_kafka /opt/kafka/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic taas.workperiodpayment.update < test/messages/taas.workperiodpayment.update.event.json
+ # for Role
+ docker exec -i taas-es-processor_kafka /opt/kafka/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic taas.role.update < test/messages/taas.role.update.event.json
```
- Run `npm run view-data ` to see if documents were updated.
## Delete documents in ES
-- Run the following commands to delete `Job`, `JobCandidate`, `ResourceBooking`, `WorkPeriod` documents in ES.
+- Run the following commands to delete `Job`, `JobCandidate`, `ResourceBooking`, `WorkPeriod`, `Role` documents in ES.
``` bash
# for Job
@@ -45,6 +57,8 @@
docker exec -i taas-es-processor_kafka /opt/kafka/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic taas.resourcebooking.delete < test/messages/taas.resourcebooking.delete.event.json
# for WorkPeriod
docker exec -i taas-es-processor_kafka /opt/kafka/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic taas.workperiod.delete < test/messages/taas.workperiod.delete.event.json
+ # for Role
+ docker exec -i taas-es-processor_kafka /opt/kafka/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic taas.role.delete < test/messages/taas.role.delete.event.json
```
- Run `npm run view-data ` to see if documents were deleted.
diff --git a/config/default.js b/config/default.js
index 446ab4e..059ecdc 100644
--- a/config/default.js
+++ b/config/default.js
@@ -1,7 +1,7 @@
/**
* The default configuration file.
*/
-
+require('dotenv').config()
module.exports = {
PORT: process.env.PORT || 3001,
LOG_LEVEL: process.env.LOG_LEVEL || 'debug',
@@ -14,8 +14,12 @@ module.exports = {
// Kafka group id
KAFKA_GROUP_ID: process.env.KAFKA_GROUP_ID || 'taas-es-processor',
+ // The originator value for the kafka messages
+ KAFKA_MESSAGE_ORIGINATOR: process.env.KAFKA_MESSAGE_ORIGINATOR || 'taas-es-processor',
topics: {
+ // The error topic at which bus api will publish any errors
+ KAFKA_ERROR_TOPIC: process.env.KAFKA_ERROR_TOPIC || 'common.error.reporting',
// topics for job service
TAAS_JOB_CREATE_TOPIC: process.env.TAAS_JOB_CREATE_TOPIC || 'taas.job.create',
TAAS_JOB_UPDATE_TOPIC: process.env.TAAS_JOB_UPDATE_TOPIC || 'taas.job.update',
@@ -31,8 +35,28 @@ module.exports = {
// topics for work period service
TAAS_WORK_PERIOD_CREATE_TOPIC: process.env.TAAS_WORK_PERIOD_CREATE_TOPIC || 'taas.workperiod.create',
TAAS_WORK_PERIOD_UPDATE_TOPIC: process.env.TAAS_WORK_PERIOD_UPDATE_TOPIC || 'taas.workperiod.update',
- TAAS_WORK_PERIOD_DELETE_TOPIC: process.env.TAAS_WORK_PERIOD_DELETE_TOPIC || 'taas.workperiod.delete'
+ TAAS_WORK_PERIOD_DELETE_TOPIC: process.env.TAAS_WORK_PERIOD_DELETE_TOPIC || 'taas.workperiod.delete',
+ // topics for work period payment service
+ TAAS_WORK_PERIOD_PAYMENT_CREATE_TOPIC: process.env.TAAS_WORK_PERIOD_PAYMENT_CREATE_TOPIC || 'taas.workperiodpayment.create',
+ TAAS_WORK_PERIOD_PAYMENT_UPDATE_TOPIC: process.env.TAAS_WORK_PERIOD_PAYMENT_UPDATE_TOPIC || 'taas.workperiodpayment.update',
+ // topics for interview service
+ TAAS_INTERVIEW_REQUEST_TOPIC: process.env.TAAS_INTERVIEW_REQUEST_TOPIC || 'taas.interview.requested',
+ TAAS_INTERVIEW_UPDATE_TOPIC: process.env.TAAS_INTERVIEW_UPDATE_TOPIC || 'taas.interview.update',
+ TAAS_INTERVIEW_BULK_UPDATE_TOPIC: process.env.TAAS_INTERVIEW_BULK_UPDATE_TOPIC || 'taas.interview.bulkUpdate',
+ // topics for role service
+ TAAS_ROLE_CREATE_TOPIC: process.env.TAAS_ROLE_CREATE_TOPIC || 'taas.role.requested',
+ TAAS_ROLE_UPDATE_TOPIC: process.env.TAAS_ROLE_UPDATE_TOPIC || 'taas.role.update',
+ TAAS_ROLE_DELETE_TOPIC: process.env.TAAS_ROLE_DELETE_TOPIC || 'taas.role.delete',
+ // special kafka topics
+ TAAS_ACTION_RETRY_TOPIC: process.env.TAAS_ACTION_RETRY_TOPIC || 'taas.action.retry'
+
},
+ // maximum allowed retry count for failed operations for sending `action.retry` message
+ MAX_RETRY: process.env.MAX_RETRY || 3,
+ // base amount of retry delay for failed operations
+ BASE_RETRY_DELAY: process.env.BASE_RETRY_DELAY || 500,
+ // Topcoder Bus API URL
+ BUSAPI_URL: process.env.BUSAPI_URL || 'https://api.topcoder-dev.com/v5',
esConfig: {
HOST: process.env.ES_HOST || 'http://localhost:9200',
@@ -48,7 +72,7 @@ module.exports = {
ES_INDEX_JOB: process.env.ES_INDEX_JOB || 'job',
ES_INDEX_JOB_CANDIDATE: process.env.ES_INDEX_JOB_CANDIDATE || 'job_candidate',
ES_INDEX_RESOURCE_BOOKING: process.env.ES_INDEX_RESOURCE_BOOKING || 'resource_booking',
- ES_INDEX_WORK_PERIOD: process.env.ES_INDEX_WORK_PERIOD || 'work_period'
+ ES_INDEX_ROLE: process.env.ES_INDEX_ROLE || 'role'
},
auth0: {
@@ -56,7 +80,8 @@ module.exports = {
AUTH0_AUDIENCE: process.env.AUTH0_AUDIENCE,
AUTH0_CLIENT_ID: process.env.AUTH0_CLIENT_ID,
AUTH0_CLIENT_SECRET: process.env.AUTH0_CLIENT_SECRET,
- AUTH0_PROXY_SERVER_URL: process.env.AUTH0_PROXY_SERVER_URL
+ AUTH0_PROXY_SERVER_URL: process.env.AUTH0_PROXY_SERVER_URL,
+ TOKEN_CACHE_TIME: process.env.TOKEN_CACHE_TIME
},
zapier: {
diff --git a/config/test.js b/config/test.js
index c462fb0..c5b51db 100644
--- a/config/test.js
+++ b/config/test.js
@@ -6,5 +6,7 @@ module.exports = {
zapier: {
ZAPIER_SWITCH: process.env.ZAPIER_SWITCH || 'ON',
ZAPIER_JOB_CANDIDATE_SWITCH: process.env.ZAPIER_JOB_CANDIDATE_SWITCH || 'ON'
- }
+ },
+ // don't retry actions during tests because tests for now don't expect it and should be updated first
+ MAX_RETRY: 0
}
diff --git a/local/docker-compose.yml b/local/docker-compose.yml
index 5d2d803..936f378 100644
--- a/local/docker-compose.yml
+++ b/local/docker-compose.yml
@@ -12,7 +12,7 @@ services:
- "9092:9092"
environment:
KAFKA_ADVERTISED_HOST_NAME: localhost
- KAFKA_CREATE_TOPICS: "taas.job.create:1:1,taas.jobcandidate.create:1:1,taas.resourcebooking.create:1:1,taas.job.update:1:1,taas.jobcandidate.update:1:1,taas.resourcebooking.update:1:1,taas.job.delete:1:1,taas.jobcandidate.delete:1:1,taas.resourcebooking.delete:1:1"
+ KAFKA_CREATE_TOPICS: "taas.job.create:1:1,taas.jobcandidate.create:1:1,taas.interview.requested:1:1,taas.resourcebooking.create:1:1,taas.workperiod.create:1:1,taas.workperiodpayment.create:1:1,taas.job.update:1:1,taas.jobcandidate.update:1:1,taas.interview.update:1:1,taas.interview.bulkUpdate:1:1,taas.resourcebooking.update:1:1,taas.workperiod.update:1:1,taas.workperiodpayment.update:1:1,taas.job.delete:1:1,taas.jobcandidate.delete:1:1,taas.resourcebooking.delete:1:1,taas.workperiod.delete:1:1"
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
esearch:
image: elasticsearch:7.7.1
diff --git a/package-lock.json b/package-lock.json
index 6a4fb26..fec285f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -216,6 +216,12 @@
}
}
},
+ "@sindresorhus/is": {
+ "version": "0.14.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
+ "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==",
+ "dev": true
+ },
"@sinonjs/commons": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz",
@@ -251,11 +257,93 @@
"integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==",
"dev": true
},
+ "@szmarczak/http-timer": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz",
+ "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==",
+ "dev": true,
+ "requires": {
+ "defer-to-connect": "^1.0.1"
+ }
+ },
"@tootallnate/once": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
"integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw=="
},
+ "@topcoder-platform/topcoder-bus-api-wrapper": {
+ "version": "github:topcoder-platform/tc-bus-api-wrapper#f8cbd335a0e0b4d6edd7cae859473593271fd97f",
+ "from": "github:topcoder-platform/tc-bus-api-wrapper",
+ "requires": {
+ "joi": "^13.4.0",
+ "lodash": "^4.17.15",
+ "superagent": "^3.8.3",
+ "tc-core-library-js": "github:appirio-tech/tc-core-library-js#v2.6.4"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "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": "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"
+ }
+ },
+ "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"
+ }
+ },
+ "tc-core-library-js": {
+ "version": "github:appirio-tech/tc-core-library-js#df0b36c51cf80918194cbff777214b3c0cf5a151",
+ "from": "github:appirio-tech/tc-core-library-js#v2.6.4",
+ "requires": {
+ "axios": "^0.19.0",
+ "bunyan": "^1.8.12",
+ "jsonwebtoken": "^8.5.1",
+ "jwks-rsa": "^1.6.0",
+ "lodash": "^4.17.15",
+ "millisecond": "^0.1.2",
+ "r7insight_node": "^1.8.4",
+ "request": "^2.88.0"
+ }
+ }
+ }
+ },
"@types/bluebird": {
"version": "3.5.0",
"resolved": "https://registry.npm.taobao.org/@types/bluebird/download/@types/bluebird-3.5.0.tgz",
@@ -350,6 +438,12 @@
"@types/node": "*"
}
},
+ "abbrev": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+ "dev": true
+ },
"accepts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
@@ -407,6 +501,43 @@
"integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==",
"dev": true
},
+ "ansi-align": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz",
+ "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==",
+ "dev": true,
+ "requires": {
+ "string-width": "^3.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
+ },
+ "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"
+ }
+ }
+ }
+ },
"ansi-colors": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz",
@@ -639,6 +770,111 @@
"type-is": "~1.6.17"
}
},
+ "boxen": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz",
+ "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==",
+ "dev": true,
+ "requires": {
+ "ansi-align": "^3.0.0",
+ "camelcase": "^5.3.1",
+ "chalk": "^3.0.0",
+ "cli-boxes": "^2.2.0",
+ "string-width": "^4.1.0",
+ "term-size": "^2.1.0",
+ "type-fest": "^0.8.1",
+ "widest-line": "^3.1.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "chalk": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true
+ },
+ "string-width": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
+ "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -699,6 +935,38 @@
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
},
+ "cacheable-request": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz",
+ "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==",
+ "dev": true,
+ "requires": {
+ "clone-response": "^1.0.2",
+ "get-stream": "^5.1.0",
+ "http-cache-semantics": "^4.0.0",
+ "keyv": "^3.0.0",
+ "lowercase-keys": "^2.0.0",
+ "normalize-url": "^4.1.0",
+ "responselike": "^1.0.2"
+ },
+ "dependencies": {
+ "get-stream": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "dev": true,
+ "requires": {
+ "pump": "^3.0.0"
+ }
+ },
+ "lowercase-keys": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
+ "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
+ "dev": true
+ }
+ }
+ },
"caching-transform": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz",
@@ -772,12 +1040,24 @@
"readdirp": "~3.2.0"
}
},
+ "ci-info": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
+ "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
+ "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
},
+ "cli-boxes": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz",
+ "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==",
+ "dev": true
+ },
"cli-cursor": {
"version": "2.1.0",
"resolved": "https://registry.npm.taobao.org/cli-cursor/download/cli-cursor-2.1.0.tgz",
@@ -821,6 +1101,23 @@
}
}
},
+ "clone-response": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
+ "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=",
+ "dev": true,
+ "requires": {
+ "mimic-response": "^1.0.0"
+ },
+ "dependencies": {
+ "mimic-response": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
+ "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
+ "dev": true
+ }
+ }
+ },
"code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
@@ -909,8 +1206,7 @@
"component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
- "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
- "dev": true
+ "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
},
"concat-map": {
"version": "0.0.1",
@@ -925,6 +1221,49 @@
"json5": "^1.0.1"
}
},
+ "configstore": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz",
+ "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==",
+ "dev": true,
+ "requires": {
+ "dot-prop": "^5.2.0",
+ "graceful-fs": "^4.1.2",
+ "make-dir": "^3.0.0",
+ "unique-string": "^2.0.0",
+ "write-file-atomic": "^3.0.0",
+ "xdg-basedir": "^4.0.0"
+ },
+ "dependencies": {
+ "make-dir": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "dev": true,
+ "requires": {
+ "semver": "^6.0.0"
+ }
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ },
+ "write-file-atomic": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
+ "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
+ "dev": true,
+ "requires": {
+ "imurmurhash": "^0.1.4",
+ "is-typedarray": "^1.0.0",
+ "signal-exit": "^3.0.2",
+ "typedarray-to-buffer": "^3.1.5"
+ }
+ }
+ }
+ },
"connection-parse": {
"version": "0.0.7",
"resolved": "https://registry.npm.taobao.org/connection-parse/download/connection-parse-0.0.7.tgz",
@@ -971,8 +1310,7 @@
"cookiejar": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz",
- "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==",
- "dev": true
+ "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA=="
},
"core-js": {
"version": "2.6.12",
@@ -1062,6 +1400,12 @@
"which": "^1.2.9"
}
},
+ "crypto-random-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
+ "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==",
+ "dev": true
+ },
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -1105,6 +1449,12 @@
"mimic-response": "^2.0.0"
}
},
+ "deep-extend": {
+ "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": {
"version": "0.1.3",
"resolved": "https://registry.npm.taobao.org/deep-is/download/deep-is-0.1.3.tgz",
@@ -1120,6 +1470,12 @@
"strip-bom": "^3.0.0"
}
},
+ "defer-to-connect": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz",
+ "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==",
+ "dev": true
+ },
"define-properties": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
@@ -1191,6 +1547,20 @@
"esutils": "^2.0.2"
}
},
+ "dot-prop": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
+ "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==",
+ "dev": true,
+ "requires": {
+ "is-obj": "^2.0.0"
+ }
+ },
+ "dotenv": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
+ "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q=="
+ },
"dtrace-provider": {
"version": "0.8.8",
"resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz",
@@ -1200,6 +1570,12 @@
"nan": "^2.14.0"
}
},
+ "duplexer3": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
+ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=",
+ "dev": true
+ },
"ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -1302,6 +1678,12 @@
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
"dev": true
},
+ "escape-goat": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz",
+ "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==",
+ "dev": true
+ },
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npm.taobao.org/escape-html/download/escape-html-1.0.3.tgz",
@@ -2004,7 +2386,6 @@
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.0.tgz",
"integrity": "sha512-WXieX3G/8side6VIqx44ablyULoGruSde5PNTxoUyo5CeyAMX6nVWUd0rgist/EuX655cjhUhTo1Fo3tRYqbcA==",
- "dev": true,
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
@@ -2014,8 +2395,7 @@
"formidable": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz",
- "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==",
- "dev": true
+ "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg=="
},
"forwarded": {
"version": "0.1.2",
@@ -2109,12 +2489,57 @@
"is-glob": "^4.0.1"
}
},
+ "global-dirs": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz",
+ "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==",
+ "dev": true,
+ "requires": {
+ "ini": "1.3.7"
+ }
+ },
"globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"dev": true
},
+ "got": {
+ "version": "9.6.0",
+ "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz",
+ "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==",
+ "dev": true,
+ "requires": {
+ "@sindresorhus/is": "^0.14.0",
+ "@szmarczak/http-timer": "^1.1.2",
+ "cacheable-request": "^6.0.0",
+ "decompress-response": "^3.3.0",
+ "duplexer3": "^0.1.4",
+ "get-stream": "^4.1.0",
+ "lowercase-keys": "^1.0.1",
+ "mimic-response": "^1.0.1",
+ "p-cancelable": "^1.0.0",
+ "to-readable-stream": "^1.0.0",
+ "url-parse-lax": "^3.0.0"
+ },
+ "dependencies": {
+ "decompress-response": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
+ "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=",
+ "dev": true,
+ "requires": {
+ "mimic-response": "^1.0.0"
+ }
+ },
+ "mimic-response": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
+ "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
+ "dev": true
+ }
+ }
+ },
"graceful-fs": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz",
@@ -2189,6 +2614,12 @@
"integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=",
"dev": true
},
+ "has-yarn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz",
+ "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==",
+ "dev": true
+ },
"hasha": {
"version": "3.0.0",
"resolved": "https://registry.npm.taobao.org/hasha/download/hasha-3.0.0.tgz",
@@ -2213,6 +2644,11 @@
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true
},
+ "hoek": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.4.tgz",
+ "integrity": "sha512-Alr4ZQgoMlnere5FZJsIyfIjORBqZll5POhDsF4q64dPuJR6rNxXdDxtHSQq8OXRurhmx+PWYEE8bXRROY8h0w=="
+ },
"hosted-git-info": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
@@ -2225,6 +2661,12 @@
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"dev": true
},
+ "http-cache-semantics": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
+ "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==",
+ "dev": true
+ },
"http-errors": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
@@ -2305,6 +2747,18 @@
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true
},
+ "ignore-by-default": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+ "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=",
+ "dev": true
+ },
+ "import-lazy": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
+ "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=",
+ "dev": true
+ },
"imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npm.taobao.org/imurmurhash/download/imurmurhash-0.1.4.tgz",
@@ -2325,6 +2779,12 @@
"resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Finherits%2Fdownload%2Finherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
+ "ini": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz",
+ "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==",
+ "dev": true
+ },
"inquirer": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz",
@@ -2429,6 +2889,15 @@
"integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==",
"dev": true
},
+ "is-ci": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
+ "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==",
+ "dev": true,
+ "requires": {
+ "ci-info": "^2.0.0"
+ }
+ },
"is-date-object": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
@@ -2456,12 +2925,40 @@
"is-extglob": "^2.1.1"
}
},
+ "is-installed-globally": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz",
+ "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==",
+ "dev": true,
+ "requires": {
+ "global-dirs": "^2.0.1",
+ "is-path-inside": "^3.0.1"
+ }
+ },
+ "is-npm": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz",
+ "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==",
+ "dev": true
+ },
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
},
+ "is-obj": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
+ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
+ "dev": true
+ },
+ "is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true
+ },
"is-promise": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
@@ -2502,11 +2999,32 @@
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
},
+ "is-yarn-global": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz",
+ "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==",
+ "dev": true
+ },
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npm.taobao.org/isarray/download/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
+ "isemail": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz",
+ "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==",
+ "requires": {
+ "punycode": "2.x.x"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
+ }
+ }
+ },
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npm.taobao.org/isexe/download/isexe-2.0.0.tgz",
@@ -2622,6 +3140,16 @@
"resolved": "https://registry.npm.taobao.org/jmespath/download/jmespath-0.15.0.tgz",
"integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
},
+ "joi": {
+ "version": "13.7.0",
+ "resolved": "https://registry.npmjs.org/joi/-/joi-13.7.0.tgz",
+ "integrity": "sha512-xuY5VkHfeOYK3Hdi91ulocfuFopwgbSORmIwzcwHKESQhC7w1kD5jaVSPnqDxS2I8t3RZ9omCKAxNwXN5zG1/Q==",
+ "requires": {
+ "hoek": "5.x.x",
+ "isemail": "3.x.x",
+ "topo": "3.x.x"
+ }
+ },
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -2649,6 +3177,12 @@
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
"dev": true
},
+ "json-buffer": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
+ "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=",
+ "dev": true
+ },
"json-parse-better-errors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
@@ -2774,6 +3308,15 @@
"safe-buffer": "^5.0.1"
}
},
+ "keyv": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz",
+ "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==",
+ "dev": true,
+ "requires": {
+ "json-buffer": "3.0.0"
+ }
+ },
"kuler": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz",
@@ -2782,6 +3325,15 @@
"colornames": "^1.1.1"
}
},
+ "latest-version": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz",
+ "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==",
+ "dev": true,
+ "requires": {
+ "package-json": "^6.3.0"
+ }
+ },
"lcid": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
@@ -2959,6 +3511,12 @@
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
+ "lowercase-keys": {
+ "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": "4.1.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
@@ -3441,6 +3999,59 @@
"semver": "^5.7.0"
}
},
+ "nodemon": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.7.tgz",
+ "integrity": "sha512-XHzK69Awgnec9UzHr1kc8EomQh4sjTQ8oRf8TsGrSmHDx9/UmiGG9E/mM3BuTfNeFwdNBvrqQq/RHL0xIeyFOA==",
+ "dev": true,
+ "requires": {
+ "chokidar": "^3.2.2",
+ "debug": "^3.2.6",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^3.0.4",
+ "pstree.remy": "^1.1.7",
+ "semver": "^5.7.1",
+ "supports-color": "^5.5.0",
+ "touch": "^3.1.0",
+ "undefsafe": "^2.0.3",
+ "update-notifier": "^4.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "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": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
+ "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=",
+ "dev": true,
+ "requires": {
+ "abbrev": "1"
+ }
+ },
"normalize-package-data": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
@@ -3459,6 +4070,12 @@
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true
},
+ "normalize-url": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz",
+ "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==",
+ "dev": true
+ },
"npm-run-path": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
@@ -3688,6 +4305,12 @@
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"dev": true
},
+ "p-cancelable": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz",
+ "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==",
+ "dev": true
+ },
"p-defer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
@@ -3742,6 +4365,26 @@
"release-zalgo": "^1.0.0"
}
},
+ "package-json": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz",
+ "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==",
+ "dev": true,
+ "requires": {
+ "got": "^9.6.0",
+ "registry-auth-token": "^4.0.0",
+ "registry-url": "^5.0.0",
+ "semver": "^6.2.0"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
+ }
+ },
"parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npm.taobao.org/parse-json/download/parse-json-4.0.0.tgz?cache=0&sync_timestamp=1598129684464&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fparse-json%2Fdownload%2Fparse-json-4.0.0.tgz",
@@ -3917,6 +4560,12 @@
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
"dev": true
},
+ "prepend-http": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
+ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=",
+ "dev": true
+ },
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -3974,6 +4623,12 @@
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
},
+ "pstree.remy": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
+ "dev": true
+ },
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@@ -3988,6 +4643,15 @@
"resolved": "https://registry.npm.taobao.org/punycode/download/punycode-1.3.2.tgz",
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
},
+ "pupa": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz",
+ "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==",
+ "dev": true,
+ "requires": {
+ "escape-goat": "^2.0.0"
+ }
+ },
"qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
@@ -4039,6 +4703,18 @@
"unpipe": "1.0.0"
}
},
+ "rc": {
+ "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.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ }
+ },
"react-is": {
"version": "16.8.6",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz",
@@ -4099,6 +4775,24 @@
"integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
"dev": true
},
+ "registry-auth-token": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz",
+ "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==",
+ "dev": true,
+ "requires": {
+ "rc": "^1.2.8"
+ }
+ },
+ "registry-url": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz",
+ "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==",
+ "dev": true,
+ "requires": {
+ "rc": "^1.2.8"
+ }
+ },
"release-zalgo": {
"version": "1.0.0",
"resolved": "https://registry.npm.taobao.org/release-zalgo/download/release-zalgo-1.0.0.tgz",
@@ -4197,6 +4891,15 @@
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
},
+ "responselike": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz",
+ "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=",
+ "dev": true,
+ "requires": {
+ "lowercase-keys": "^1.0.0"
+ }
+ },
"restore-cursor": {
"version": "2.0.0",
"resolved": "https://registry.npm.taobao.org/restore-cursor/download/restore-cursor-2.0.0.tgz",
@@ -4271,6 +4974,23 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
},
+ "semver-diff": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz",
+ "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==",
+ "dev": true,
+ "requires": {
+ "semver": "^6.3.0"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
+ }
+ },
"send": {
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
@@ -5003,6 +5723,12 @@
"request": "^2.88.0"
}
},
+ "term-size": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz",
+ "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==",
+ "dev": true
+ },
"test-exclude": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz",
@@ -5047,6 +5773,12 @@
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
"dev": true
},
+ "to-readable-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz",
+ "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==",
+ "dev": true
+ },
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -5069,6 +5801,30 @@
"express": "^4.16.3"
}
},
+ "topo": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz",
+ "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==",
+ "requires": {
+ "hoek": "6.x.x"
+ },
+ "dependencies": {
+ "hoek": {
+ "version": "6.1.3",
+ "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz",
+ "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ=="
+ }
+ }
+ },
+ "touch": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
+ "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
+ "dev": true,
+ "requires": {
+ "nopt": "~1.0.10"
+ }
+ },
"tough-cookie": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
@@ -5129,6 +5885,12 @@
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
"dev": true
},
+ "type-fest": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+ "dev": true
+ },
"type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@@ -5138,17 +5900,116 @@
"mime-types": "~2.1.24"
}
},
+ "typedarray-to-buffer": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
+ "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
+ "dev": true,
+ "requires": {
+ "is-typedarray": "^1.0.0"
+ }
+ },
+ "undefsafe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz",
+ "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==",
+ "dev": true,
+ "requires": {
+ "debug": "^2.2.0"
+ }
+ },
"uniq": {
"version": "1.0.1",
"resolved": "https://registry.npm.taobao.org/uniq/download/uniq-1.0.1.tgz",
"integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=",
"dev": true
},
+ "unique-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz",
+ "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==",
+ "dev": true,
+ "requires": {
+ "crypto-random-string": "^2.0.0"
+ }
+ },
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npm.taobao.org/unpipe/download/unpipe-1.0.0.tgz",
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
},
+ "update-notifier": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz",
+ "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==",
+ "dev": true,
+ "requires": {
+ "boxen": "^4.2.0",
+ "chalk": "^3.0.0",
+ "configstore": "^5.0.1",
+ "has-yarn": "^2.1.0",
+ "import-lazy": "^2.1.0",
+ "is-ci": "^2.0.0",
+ "is-installed-globally": "^0.3.1",
+ "is-npm": "^4.0.0",
+ "is-yarn-global": "^0.3.0",
+ "latest-version": "^5.0.0",
+ "pupa": "^2.0.1",
+ "semver-diff": "^3.1.1",
+ "xdg-basedir": "^4.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "chalk": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
"uri-js": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
@@ -5173,6 +6034,15 @@
"querystring": "0.2.0"
}
},
+ "url-parse-lax": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz",
+ "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=",
+ "dev": true,
+ "requires": {
+ "prepend-http": "^2.0.0"
+ }
+ },
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npm.taobao.org/util-deprecate/download/util-deprecate-1.0.2.tgz",
@@ -5237,6 +6107,55 @@
"string-width": "^1.0.2 || 2"
}
},
+ "widest-line": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz",
+ "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==",
+ "dev": true,
+ "requires": {
+ "string-width": "^4.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true
+ },
+ "string-width": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
+ "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.0"
+ }
+ }
+ }
+ },
"winston": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz",
@@ -5351,6 +6270,12 @@
"lodash": "^4.17.11"
}
},
+ "xdg-basedir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz",
+ "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==",
+ "dev": true
+ },
"xml2js": {
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
diff --git a/package.json b/package.json
index 73f7cc9..77228e9 100644
--- a/package.json
+++ b/package.json
@@ -5,6 +5,7 @@
"main": "src/app.js",
"scripts": {
"start": "node src/app.js",
+ "dev": "nodemon src/app.js",
"lint": "standard",
"lint:fix": "standard --fix",
"create-index": "node src/scripts/createIndex.js",
@@ -22,6 +23,7 @@
"mocha": "^7.1.2",
"mocha-prepare": "^0.1.0",
"nock": "^12.0.3",
+ "nodemon": "^2.0.7",
"nyc": "^14.1.1",
"should": "^13.2.3",
"sinon": "^10.0.1",
@@ -32,10 +34,12 @@
"dependencies": {
"@elastic/elasticsearch": "^7.9.1",
"@hapi/joi": "^15.1.0",
+ "@topcoder-platform/topcoder-bus-api-wrapper": "github:topcoder-platform/tc-bus-api-wrapper",
"async-mutex": "^0.2.4",
"aws-sdk": "^2.476.0",
"bluebird": "^3.5.5",
"config": "^3.1.0",
+ "dotenv": "^10.0.0",
"get-parameter-names": "^0.3.0",
"lodash": "^4.17.20",
"no-kafka": "^3.4.3",
diff --git a/src/app.js b/src/app.js
index 1d3ae47..bc6bb2a 100644
--- a/src/app.js
+++ b/src/app.js
@@ -13,6 +13,10 @@ const JobProcessorService = require('./services/JobProcessorService')
const JobCandidateProcessorService = require('./services/JobCandidateProcessorService')
const ResourceBookingProcessorService = require('./services/ResourceBookingProcessorService')
const WorkPeriodProcessorService = require('./services/WorkPeriodProcessorService')
+const InterviewProcessorService = require('./services/InterviewProcessorService')
+const WorkPeriodPaymentProcessorService = require('./services/WorkPeriodPaymentProcessorService')
+const RoleProcessorService = require('./services/RoleProcessorService')
+const ActionProcessorService = require('./services/ActionProcessorService')
const Mutex = require('async-mutex').Mutex
const events = require('events')
@@ -20,7 +24,6 @@ const eventEmitter = new events.EventEmitter()
// healthcheck listening port
process.env.PORT = config.PORT
-
const localLogger = {
info: (message) => logger.info({ component: 'app', message }),
debug: (message) => logger.debug({ component: 'app', message }),
@@ -43,7 +46,20 @@ const topicServiceMapping = {
// work period
[config.topics.TAAS_WORK_PERIOD_CREATE_TOPIC]: WorkPeriodProcessorService.processCreate,
[config.topics.TAAS_WORK_PERIOD_UPDATE_TOPIC]: WorkPeriodProcessorService.processUpdate,
- [config.topics.TAAS_WORK_PERIOD_DELETE_TOPIC]: WorkPeriodProcessorService.processDelete
+ [config.topics.TAAS_WORK_PERIOD_DELETE_TOPIC]: WorkPeriodProcessorService.processDelete,
+ // work period payment
+ [config.topics.TAAS_WORK_PERIOD_PAYMENT_CREATE_TOPIC]: WorkPeriodPaymentProcessorService.processCreate,
+ [config.topics.TAAS_WORK_PERIOD_PAYMENT_UPDATE_TOPIC]: WorkPeriodPaymentProcessorService.processUpdate,
+ // interview
+ [config.topics.TAAS_INTERVIEW_REQUEST_TOPIC]: InterviewProcessorService.processRequestInterview,
+ [config.topics.TAAS_INTERVIEW_UPDATE_TOPIC]: InterviewProcessorService.processUpdateInterview,
+ [config.topics.TAAS_INTERVIEW_BULK_UPDATE_TOPIC]: InterviewProcessorService.processBulkUpdateInterviews,
+ // role
+ [config.topics.TAAS_ROLE_CREATE_TOPIC]: RoleProcessorService.processCreate,
+ [config.topics.TAAS_ROLE_UPDATE_TOPIC]: RoleProcessorService.processUpdate,
+ [config.topics.TAAS_ROLE_DELETE_TOPIC]: RoleProcessorService.processDelete,
+ // action
+ [config.topics.TAAS_ACTION_RETRY_TOPIC]: ActionProcessorService.processRetry
}
// Start kafka consumer
@@ -144,6 +160,7 @@ async function initConsumer () {
subscriptions: topics,
handler: async (messageSet, topic, partition) => {
eventEmitter.emit('start_handling_message')
+ localLogger.debug(`Consumer handler. Topic: ${topic}, partition: ${partition}, message set length: ${messageSet.length}`)
await dataHandler(messageSet, topic, partition)
eventEmitter.emit('end_handling_message')
}
@@ -164,5 +181,6 @@ if (!module.parent) {
module.exports = {
initConsumer,
- eventEmitter
+ eventEmitter,
+ topicServiceMapping
}
diff --git a/src/bootstrap.js b/src/bootstrap.js
index 58e2858..8361092 100644
--- a/src/bootstrap.js
+++ b/src/bootstrap.js
@@ -1,20 +1,29 @@
const Joi = require('@hapi/joi')
const config = require('config')
+const _ = require('lodash')
+const { Interview } = require('../src/common/constants')
const constants = require('./common/constants')
+const allowedXAITemplates = _.values(Interview.XaiTemplate)
+const allowedInterviewStatuses = _.values(Interview.Status)
+
global.Promise = require('bluebird')
-Joi.rateType = () => Joi.string().valid('hourly', 'daily', 'weekly', 'monthly')
+Joi.rateType = () => Joi.string().valid('hourly', 'daily', 'weekly', 'monthly', 'annual')
Joi.jobStatus = () => Joi.string().valid('sourcing', 'in-review', 'assigned', 'closed', 'cancelled')
-Joi.resourceBookingStatus = () => Joi.string().valid('assigned', 'closed', 'cancelled')
-Joi.jobCandidateStatus = () => Joi.string().valid('open', 'selected', 'shortlist', 'rejected', 'cancelled', 'interview', 'topcoder-rejected')
+Joi.resourceBookingStatus = () => Joi.string().valid('placed', 'closed', 'cancelled')
+Joi.jobCandidateStatus = () => Joi.string().valid('open', 'placed', 'selected', 'client rejected - screening', 'client rejected - interview', 'rejected - other', 'cancelled', 'interview', 'topcoder-rejected', 'applied', 'rejected-pre-screen', 'skills-test', 'skills-test', 'phone-screen', 'job-closed', 'offered')
Joi.workload = () => Joi.string().valid('full-time', 'fractional')
Joi.title = () => Joi.string().max(128)
-Joi.paymentStatus = () => Joi.string().valid('pending', 'partially-completed', 'completed', 'cancelled')
+Joi.paymentStatus = () => Joi.string().valid('pending', 'in-progress', 'partially-completed', 'completed', 'failed', 'no-days')
+Joi.xaiTemplate = () => Joi.string().valid(...allowedXAITemplates)
+Joi.interviewStatus = () => Joi.string().valid(...allowedInterviewStatuses)
+Joi.workPeriodPaymentStatus = () => Joi.string().valid('completed', 'scheduled', 'in-progress', 'failed', 'cancelled')
// Empty string is not allowed by Joi by default and must be enabled with allow('').
// See https://joi.dev/api/?v=17.3.0#string fro details why it's like this.
// In many cases we would like to allow empty string to make it easier to create UI for editing data.
Joi.stringAllowEmpty = () => Joi.string().allow('')
+Joi.smallint = () => Joi.number().min(-32768).max(32767)
const zapierSwitch = Joi.string().label('ZAPIER_SWITCH').valid(...Object.values(constants.Zapier.Switch))
diff --git a/src/common/constants.js b/src/common/constants.js
index 62d8720..c2a154a 100644
--- a/src/common/constants.js
+++ b/src/common/constants.js
@@ -16,5 +16,19 @@ module.exports = {
JobCandidateCreate: 'jobcandidate:create',
JobCandidateUpdate: 'jobcandidate:update'
}
+ },
+ Interview: {
+ Status: {
+ Scheduling: 'Scheduling',
+ Scheduled: 'Scheduled',
+ RequestedForReschedule: 'Requested for reschedule',
+ Rescheduled: 'Rescheduled',
+ Completed: 'Completed',
+ Cancelled: 'Cancelled'
+ },
+ XaiTemplate: {
+ '30MinInterview': 'interview-30',
+ '60MinInterview': 'interview-60'
+ }
}
}
diff --git a/src/common/errors.js b/src/common/errors.js
new file mode 100644
index 0000000..1fee2b6
--- /dev/null
+++ b/src/common/errors.js
@@ -0,0 +1,40 @@
+/**
+ * This file defines application errors
+ */
+const util = require('util')
+
+/**
+ * Helper function to create generic error object with http status code
+ * @param {String} name the error name
+ * @param {Number} statusCode the http status code
+ * @returns {Function} the error constructor
+ * @private
+ */
+function createError (name, statusCode) {
+ /**
+ * The error constructor
+ * @param {String} message the error message
+ * @param {String} [cause] the error cause
+ * @constructor
+ */
+ function ErrorCtor (message, cause) {
+ Error.call(this)
+ Error.captureStackTrace(this)
+ this.message = message || name
+ this.cause = cause
+ this.httpStatus = statusCode
+ }
+
+ util.inherits(ErrorCtor, Error)
+ ErrorCtor.prototype.name = name
+ return ErrorCtor
+}
+
+module.exports = {
+ BadRequestError: createError('BadRequestError', 400),
+ UnauthorizedError: createError('UnauthorizedError', 401),
+ ForbiddenError: createError('ForbiddenError', 403),
+ NotFoundError: createError('NotFoundError', 404),
+ ConflictError: createError('ConflictError', 409),
+ InternalServerError: createError('InternalServerError', 500)
+}
diff --git a/src/common/helper.js b/src/common/helper.js
index 281b335..78f78a0 100644
--- a/src/common/helper.js
+++ b/src/common/helper.js
@@ -6,10 +6,12 @@ const AWS = require('aws-sdk')
const config = require('config')
const request = require('superagent')
const logger = require('./logger')
+const errors = require('./errors')
const elasticsearch = require('@elastic/elasticsearch')
const _ = require('lodash')
const { Mutex } = require('async-mutex')
const m2mAuth = require('tc-core-library-js').auth.m2m
+const busApi = require('@topcoder-platform/topcoder-bus-api-wrapper')
AWS.config.region = config.esConfig.AWS_REGION
@@ -91,7 +93,7 @@ function getESClient () {
await esClient.create(data)
} catch (err) {
if (err.statusCode === 409) {
- throw new Error(`id: ${data.id} "${data.index}" already exists`)
+ throw new errors.ConflictError(`id: ${data.id} "${data.index}" already exists`)
}
throw err
}
@@ -103,7 +105,7 @@ function getESClient () {
await esClient.update(data)
} catch (err) {
if (err.statusCode === 404) {
- throw new Error(`id: ${data.id} "${data.index}" not found`)
+ throw new errors.NotFoundError(`id: ${data.id} "${data.index}" not found`)
}
throw err
}
@@ -117,7 +119,7 @@ function getESClient () {
doc = await esClient.getSource(data)
} catch (err) {
if (err.statusCode === 404) {
- throw new Error(`id: ${data.id} "${data.index}" not found`)
+ throw new errors.NotFoundError(`id: ${data.id} "${data.index}" not found`)
}
throw err
}
@@ -131,7 +133,7 @@ function getESClient () {
await esClient.delete(data)
} catch (err) {
if (err.statusCode === 404) {
- throw new Error(`id: ${data.id} "${data.index}" not found`)
+ throw new errors.NotFoundError(`id: ${data.id} "${data.index}" not found`)
}
throw err
}
@@ -178,10 +180,68 @@ async function postMessageViaWebhook (webhook, message) {
await request.post(webhook).send(message)
}
+let busApiClient
+
+/**
+ * Get bus api client.
+ *
+ * @returns {Object} the bus api client
+ */
+function getBusApiClient () {
+ if (busApiClient) {
+ return busApiClient
+ }
+ busApiClient = busApi(
+ _.assign(_.pick(config.auth0, [
+ 'AUTH0_URL',
+ 'AUTH0_AUDIENCE',
+ 'TOKEN_CACHE_TIME',
+ 'AUTH0_CLIENT_ID',
+ 'AUTH0_CLIENT_SECRET',
+ 'AUTH0_PROXY_SERVER_URL'
+ ]), _.pick(config, 'BUSAPI_URL'),
+ _.pick(config.topics, 'KAFKA_ERROR_TOPIC'))
+
+ )
+ return busApiClient
+}
+
+/**
+ * Send Kafka event message
+ * @param {String} topic the topic name
+ * @param {Object} payload the payload
+ */
+async function postEvent (topic, payload) {
+ logger.debug({ component: 'helper', context: 'postEvent', message: `Posting event to Kafka topic ${topic}, ${JSON.stringify(payload)}` })
+
+ const client = getBusApiClient()
+ const message = {
+ topic,
+ originator: config.KAFKA_MESSAGE_ORIGINATOR,
+ timestamp: new Date().toISOString(),
+ 'mime-type': 'application/json',
+ payload
+ }
+ await client.postEvent(message)
+}
+
+/**
+ * Sleep for a given number of milliseconds.
+ *
+ * @param {Number} milliseconds the sleep time
+ * @returns {undefined}
+ */
+async function sleep (milliseconds) {
+ return new Promise((resolve) => setTimeout(resolve, milliseconds))
+}
+
module.exports = {
+ sleep,
getKafkaOptions,
getESClient,
checkEsMutexRelease,
getM2MToken,
- postMessageViaWebhook
+ postMessageViaWebhook,
+ getBusApiClient,
+ postEvent
}
diff --git a/src/common/logger.js b/src/common/logger.js
index b533070..2204596 100644
--- a/src/common/logger.js
+++ b/src/common/logger.js
@@ -56,6 +56,24 @@ logger.logFullError = (err, context = {}) => {
err.logged = true
}
+/**
+ * Log warning details
+ * @param {Object} err the error
+ * @param {Object} context contains extra info about errors
+ */
+logger.logFullWarning = (err, context = {}) => {
+ if (!err) {
+ return
+ }
+ if (err.logged) {
+ return
+ }
+ const signature = context.signature ? `${context.signature} : ` : ''
+ const errMessage = err.message || util.inspect(err).split('\n')[0]
+ logger.warn({ ..._.pick(context, ['component', 'context']), message: `${signature}${errMessage}` })
+ err.logged = true
+}
+
/**
* Remove invalid properties from the object and hide long arrays
* @param {Object} obj the object
diff --git a/src/scripts/createIndex.js b/src/scripts/createIndex.js
index 095633e..1cb89ce 100644
--- a/src/scripts/createIndex.js
+++ b/src/scripts/createIndex.js
@@ -26,8 +26,16 @@ async function createIndex () {
rateType: { type: 'keyword' },
workload: { type: 'keyword' },
skills: { type: 'keyword' },
+ roles: { type: 'keyword' },
status: { type: 'keyword' },
isApplicationPageActive: { type: 'boolean' },
+ minSalary: { type: 'integer' },
+ maxSalary: { type: 'integer' },
+ hoursPerWeek: { type: 'integer' },
+ jobLocation: { type: 'keyword' },
+ jobTimezone: { type: 'keyword' },
+ currency: { type: 'keyword' },
+ roleIds: { type: 'keyword' },
createdAt: { type: 'date' },
createdBy: { type: 'keyword' },
updatedAt: { type: 'date' },
@@ -46,6 +54,36 @@ async function createIndex () {
status: { type: 'keyword' },
externalId: { type: 'keyword' },
resume: { type: 'text' },
+ remark: { type: 'keyword' },
+ interviews: {
+ type: 'nested',
+ properties: {
+ id: { type: 'keyword' },
+ xaiId: { type: 'keyword' },
+ jobCandidateId: { type: 'keyword' },
+ calendarEventId: { type: 'keyword' },
+ templateUrl: { type: 'keyword' },
+ templateId: { type: 'keyword' },
+ templateType: { type: 'keyword' },
+ title: { type: 'keyword' },
+ locationDetails: { type: 'keyword' },
+ duration: { type: 'integer' },
+ startTimestamp: { type: 'date' },
+ endTimestamp: { type: 'date' },
+ hostName: { type: 'keyword' },
+ hostEmail: { type: 'keyword' },
+ guestNames: { type: 'keyword' },
+ guestEmails: { type: 'keyword' },
+ round: { type: 'integer' },
+ status: { type: 'keyword' },
+ rescheduleUrl: { type: 'keyword' },
+ createdAt: { type: 'date' },
+ createdBy: { type: 'keyword' },
+ updatedAt: { type: 'date' },
+ updatedBy: { type: 'keyword' },
+ deletedAt: { type: 'date' }
+ }
+ },
createdAt: { type: 'date' },
createdBy: { type: 'keyword' },
updatedAt: { type: 'date' },
@@ -63,11 +101,61 @@ async function createIndex () {
userId: { type: 'keyword' },
jobId: { type: 'keyword' },
status: { type: 'keyword' },
- startDate: { type: 'date' },
- endDate: { type: 'date' },
+ startDate: { type: 'date', format: 'yyyy-MM-dd' },
+ endDate: { type: 'date', format: 'yyyy-MM-dd' },
memberRate: { type: 'float' },
customerRate: { type: 'float' },
rateType: { type: 'keyword' },
+ billingAccountId: { type: 'integer' },
+ workPeriods: {
+ type: 'nested',
+ properties: {
+ id: { type: 'keyword' },
+ resourceBookingId: { type: 'keyword' },
+ userHandle: { type: 'keyword',
+ normalizer: 'lowercaseNormalizer' },
+ projectId: { type: 'integer' },
+ userId: { type: 'keyword' },
+ startDate: { type: 'date', format: 'yyyy-MM-dd' },
+ endDate: { type: 'date', format: 'yyyy-MM-dd' },
+ daysWorked: { type: 'integer' },
+ daysPaid: { type: 'integer' },
+ paymentTotal: { type: 'float' },
+ paymentStatus: { type: 'keyword' },
+ payments: {
+ type: 'nested',
+ properties: {
+ id: { type: 'keyword' },
+ workPeriodId: { type: 'keyword' },
+ challengeId: { type: 'keyword' },
+ memberRate: { type: 'float' },
+ customerRate: { type: 'float' },
+ days: { type: 'integer' },
+ amount: { type: 'float' },
+ status: { type: 'keyword' },
+ statusDetails: {
+ type: 'nested',
+ properties: {
+ errorMessage: { type: 'text' },
+ errorCode: { type: 'integer' },
+ retry: { type: 'integer' },
+ step: { type: 'keyword' },
+ challengeId: { type: 'keyword' }
+ }
+ },
+ billingAccountId: { type: 'integer' },
+ createdAt: { type: 'date' },
+ createdBy: { type: 'keyword' },
+ updatedAt: { type: 'date' },
+ updatedBy: { type: 'keyword' }
+ }
+ },
+ createdAt: { type: 'date' },
+ createdBy: { type: 'keyword' },
+ updatedAt: { type: 'date' },
+ updatedBy: { type: 'keyword' }
+ }
+ },
createdAt: { type: 'date' },
createdBy: { type: 'keyword' },
updatedAt: { type: 'date' },
@@ -76,21 +164,33 @@ async function createIndex () {
}
}
},
- {
- index: config.get('esConfig.ES_INDEX_WORK_PERIOD'),
+ { index: config.get('esConfig.ES_INDEX_ROLE'),
body: {
mappings: {
properties: {
- resourceBookingId: { type: 'keyword' },
- userHandle: { type: 'keyword' },
- projectId: { type: 'integer' },
- userId: { type: 'keyword' },
- startDate: { type: 'date', format: 'yyyy-MM-dd' },
- endDate: { type: 'date', format: 'yyyy-MM-dd' },
- daysWorked: { type: 'integer' },
- memberRate: { type: 'float' },
- customerRate: { type: 'float' },
- paymentStatus: { type: 'keyword' },
+ name: { type: 'keyword',
+ normalizer: 'lowercaseNormalizer' },
+ description: { type: 'keyword' },
+ listOfSkills: { type: 'keyword',
+ normalizer: 'lowercaseNormalizer' },
+ rates: {
+ properties: {
+ global: { type: 'integer' },
+ inCountry: { type: 'integer' },
+ offShore: { type: 'integer' },
+ rate30Global: { type: 'integer' },
+ rate30InCountry: { type: 'integer' },
+ rate30OffShore: { type: 'integer' },
+ rate20Global: { type: 'integer' },
+ rate20InCountry: { type: 'integer' },
+ rate20OffShore: { type: 'integer' }
+ }
+ },
+ numberOfMembers: { type: 'integer' },
+ numberOfMembersAvailable: { type: 'integer' },
+ imageUrl: { type: 'keyword' },
+ timeToCandidate: { type: 'integer' },
+ timeToInterview: { type: 'integer' },
createdAt: { type: 'date' },
createdBy: { type: 'keyword' },
updatedAt: { type: 'date' },
@@ -98,10 +198,33 @@ async function createIndex () {
}
}
}
- }]
+ }
+ ]
for (const index of indices) {
- await esClient.indices.create(index)
+ await esClient.indices.create({ index: index.index })
+ await esClient.indices.close({ index: index.index })
+ await esClient.indices.putSettings({
+ index: index.index,
+ body: {
+ settings: {
+ analysis: {
+ normalizer: {
+ lowercaseNormalizer: {
+ filter: ['lowercase']
+ }
+ }
+ }
+ }
+ }
+ })
+ await esClient.indices.open({ index: index.index })
+ await esClient.indices.putMapping({
+ index: index.index,
+ body: {
+ properties: index.body.mappings.properties
+ }
+ })
logger.info({ component: 'createIndex', message: `ES Index ${index.index} creation succeeded!` })
}
process.exit(0)
diff --git a/src/scripts/deleteIndex.js b/src/scripts/deleteIndex.js
index 69594b4..84e15bc 100644
--- a/src/scripts/deleteIndex.js
+++ b/src/scripts/deleteIndex.js
@@ -12,7 +12,7 @@ async function deleteIndex () {
const indices = [config.get('esConfig.ES_INDEX_JOB'),
config.get('esConfig.ES_INDEX_JOB_CANDIDATE'),
config.get('esConfig.ES_INDEX_RESOURCE_BOOKING'),
- config.get('esConfig.ES_INDEX_WORK_PERIOD')]
+ config.get('esConfig.ES_INDEX_ROLE')]
for (const index of indices) {
await esClient.indices.delete({
index
diff --git a/src/scripts/view-data.js b/src/scripts/view-data.js
index 9c3d0ce..c422c0c 100644
--- a/src/scripts/view-data.js
+++ b/src/scripts/view-data.js
@@ -12,7 +12,7 @@ const modelIndexMapping = {
Job: 'ES_INDEX_JOB',
JobCandidate: 'ES_INDEX_JOB_CANDIDATE',
ResourceBooking: 'ES_INDEX_RESOURCE_BOOKING',
- WorkPeriod: 'ES_INDEX_WORK_PERIOD'
+ Role: 'ES_INDEX_ROLE'
}
async function showESData () {
diff --git a/src/services/ActionProcessorService.js b/src/services/ActionProcessorService.js
new file mode 100644
index 0000000..662b0c7
--- /dev/null
+++ b/src/services/ActionProcessorService.js
@@ -0,0 +1,83 @@
+/**
+ * Action Processor Service
+ */
+
+const Joi = require('@hapi/joi')
+const logger = require('../common/logger')
+const helper = require('../common/helper')
+const config = require('config')
+
+const localLogger = {
+ debug: ({ context, message }) => logger.debug({ component: 'ActionProcessorService', context, message })
+}
+
+/**
+ * Process retry operation message
+ * @param {Object} message the kafka message
+ * @param {String} transactionId
+ */
+async function processRetry (message, transactionId) {
+ if (message.originator !== config.KAFKA_MESSAGE_ORIGINATOR) {
+ localLogger.debug({ context: 'processRetry', message: `originator: ${message.originator} does not match with ${config.KAFKA_MESSAGE_ORIGINATOR} - ignored` })
+ return
+ }
+ const { topicServiceMapping } = require('../app')
+ const retry = message.payload.retry
+ message.topic = message.payload.originalTopic
+ message.payload = message.payload.originalPayload
+ await topicServiceMapping[message.topic](message, transactionId, { retry })
+}
+
+processRetry.schema = {
+ message: Joi.object().keys({
+ topic: Joi.string().required(),
+ originator: Joi.string().required(),
+ timestamp: Joi.date().required(),
+ 'mime-type': Joi.string().required(),
+ payload: Joi.object().keys({
+ originalTopic: Joi.string().required(),
+ originalPayload: Joi.object().required(),
+ retry: Joi.number().integer().min(1).required()
+ }).required()
+ }).required(),
+ transactionId: Joi.string().required()
+}
+
+/**
+ * Analyzes the failed process and sends it to bus api to be received again.
+ * @param {String} originalTopic the failed topic name
+ * @param {Object} originalPayload the payload
+ * @param {Number} retry how many times has it been retried
+ *
+ * @returns {Promise|null} returns Promise which would be resolved when retry event sent to Kafka,
+ * or `null` if it would not be scheduled
+ */
+function scheduleRetry (originalTopic, originalPayload, retry) {
+ retry = retry + 1
+ if (retry > config.MAX_RETRY) {
+ localLogger.debug({ context: 'scheduleRetry', message: `retry: ${retry} for topic: ${originalTopic} id: ${originalPayload.id} exceeds the max retry: ${config.MAX_RETRY} - ignored` })
+ return
+ }
+
+ localLogger.debug({ context: 'scheduleRetry', message: `retry: ${retry} for topic: ${originalTopic} id: ${originalPayload.id}` })
+
+ const payload = {
+ originalTopic,
+ originalPayload,
+ retry
+ }
+
+ return helper.sleep(2 ** retry * config.BASE_RETRY_DELAY).then(() =>
+ helper.postEvent(config.topics.TAAS_ACTION_RETRY_TOPIC, payload)
+ )
+}
+
+module.exports = {
+ processRetry
+}
+
+logger.buildService(module.exports, 'ActionProcessorService')
+
+// we don't want to wrap this method into service wrappers
+// because it would transform this method to `async` while we want to keep it sync
+module.exports.scheduleRetry = scheduleRetry
diff --git a/src/services/InterviewProcessorService.js b/src/services/InterviewProcessorService.js
new file mode 100644
index 0000000..3af01a8
--- /dev/null
+++ b/src/services/InterviewProcessorService.js
@@ -0,0 +1,202 @@
+/**
+ * Interview Processor Service
+ */
+
+const Joi = require('@hapi/joi')
+const _ = require('lodash')
+const logger = require('../common/logger')
+const helper = require('../common/helper')
+const constants = require('../common/constants')
+const config = require('config')
+
+const esClient = helper.getESClient()
+
+/**
+ * Updates jobCandidate via a painless script
+ *
+ * @param {String} jobCandidateId job candidate id
+ * @param {String} script script definition
+ * @param {String} transactionId transaction id
+ */
+async function updateJobCandidateViaScript (jobCandidateId, script, transactionId) {
+ await esClient.updateExtra({
+ index: config.get('esConfig.ES_INDEX_JOB_CANDIDATE'),
+ id: jobCandidateId,
+ transactionId,
+ body: { script },
+ refresh: constants.esRefreshOption
+ })
+}
+
+/**
+ * Process request interview entity message.
+ * Creates an interview record under jobCandidate.
+ *
+ * @param {Object} message the kafka message
+ * @param {String} transactionId
+ */
+async function processRequestInterview (message, transactionId) {
+ const interview = message.payload
+ // add interview in collection if there's already an existing collection
+ // or initiate a new one with this interview
+ const script = {
+ source: `
+ ctx._source.containsKey("interviews")
+ ? ctx._source.interviews.add(params.interview)
+ : ctx._source.interviews = [params.interview]
+ `,
+ params: { interview }
+ }
+ await updateJobCandidateViaScript(interview.jobCandidateId, script, transactionId)
+}
+
+processRequestInterview.schema = {
+ message: Joi.object().keys({
+ topic: Joi.string().required(),
+ originator: Joi.string().required(),
+ timestamp: Joi.date().required(),
+ 'mime-type': Joi.string().required(),
+ key: Joi.string().allow(null),
+ payload: Joi.object().keys({
+ id: Joi.string().uuid().required(),
+ xaiId: Joi.string().allow(null),
+ jobCandidateId: Joi.string().uuid().required(),
+ calendarEventId: Joi.string().allow(null),
+ templateUrl: Joi.xaiTemplate().required(),
+ templateId: Joi.string().allow(null),
+ templateType: Joi.string().allow(null),
+ title: Joi.string().allow(null),
+ locationDetails: Joi.string().allow(null),
+ round: Joi.number().integer().positive().required(),
+ duration: Joi.number().integer().positive().required(),
+ startTimestamp: Joi.date().allow(null),
+ endTimestamp: Joi.date().allow(null),
+ hostName: Joi.string().required(),
+ hostEmail: Joi.string().email().required(),
+ guestNames: Joi.array().items(Joi.string()).allow(null),
+ guestEmails: Joi.array().items(Joi.string().email()).allow(null),
+ status: Joi.interviewStatus().required(),
+ rescheduleUrl: Joi.string().allow(null),
+ createdAt: Joi.date().required(),
+ createdBy: Joi.string().uuid().required(),
+ updatedAt: Joi.date().allow(null),
+ updatedBy: Joi.string().uuid().allow(null),
+ deletedAt: Joi.date().allow(null)
+ }).required()
+ }).required(),
+ transactionId: Joi.string().required()
+}
+
+/**
+ * Process update interview entity message
+ * Updates the interview record under jobCandidate.
+ *
+ * @param {Object} message the kafka message
+ * @param {String} transactionId
+ */
+async function processUpdateInterview (message, transactionId) {
+ const interview = message.payload
+ // if there's an interview with this id,
+ // update it with the payload
+ const script = {
+ source: `
+ if (ctx._source.containsKey("interviews")) {
+ def target = ctx._source.interviews.find(i -> i.id == params.interview.id);
+ if (target != null) {
+ for (prop in params.interview.entrySet()) {
+ target[prop.getKey()] = prop.getValue()
+ }
+ }
+ }
+ `,
+ params: { interview }
+ }
+ await updateJobCandidateViaScript(interview.jobCandidateId, script, transactionId)
+}
+
+processUpdateInterview.schema = processRequestInterview.schema
+
+/**
+ * Process bulk (partially) update interviews entity message.
+ * Currently supports status, updatedAt and updatedBy fields.
+ * Update Joi schema to allow more fields.
+ * (implementation should already handle new fields - just updating Joi schema should be enough)
+ *
+ * payload format:
+ * {
+ * "jobCandidateId": {
+ * "interviewId": { ...fields },
+ * "interviewId2": { ...fields },
+ * ...
+ * },
+ * "jobCandidateId2": { // like above... },
+ * ...
+ * }
+ *
+ * @param {Object} message the kafka message
+ * @param {String} transactionId
+ */
+async function processBulkUpdateInterviews (message, transactionId) {
+ const jobCandidates = message.payload
+ // script to update & params
+ const script = {
+ source: `
+ def completedInterviews = params.jobCandidates[ctx._id];
+ for (interview in completedInterviews.entrySet()) {
+ def interviewId = interview.getKey();
+ def affectedFields = interview.getValue();
+ def target = ctx._source.interviews.find(i -> i.id == interviewId);
+ if (target != null) {
+ for (field in affectedFields.entrySet()) {
+ target[field.getKey()] = field.getValue();
+ }
+ }
+ }
+ `,
+ params: { jobCandidates }
+ }
+ // update interviews
+ await esClient.updateByQuery({
+ index: config.get('esConfig.ES_INDEX_JOB_CANDIDATE'),
+ transactionId,
+ body: {
+ script,
+ query: {
+ ids: {
+ values: _.keys(jobCandidates)
+ }
+ }
+ },
+ refresh: true
+ })
+}
+
+processBulkUpdateInterviews.schema = {
+ message: Joi.object().keys({
+ topic: Joi.string().required(),
+ originator: Joi.string().required(),
+ timestamp: Joi.date().required(),
+ 'mime-type': Joi.string().required(),
+ key: Joi.string().allow(null),
+ payload: Joi.object().pattern(
+ Joi.string().uuid(), // key - jobCandidateId
+ Joi.object().pattern(
+ Joi.string().uuid(), // inner key - interviewId
+ Joi.object().keys({
+ status: Joi.interviewStatus(),
+ updatedAt: Joi.date(),
+ updatedBy: Joi.string().uuid()
+ }) // inner value - affected fields of interview
+ ) // value - object containing interviews
+ ).min(1) // at least one key - i.e. don't allow empty object
+ }).required(),
+ transactionId: Joi.string().required()
+}
+
+module.exports = {
+ processRequestInterview,
+ processUpdateInterview,
+ processBulkUpdateInterviews
+}
+
+logger.buildService(module.exports, 'InterviewProcessorService')
diff --git a/src/services/JobCandidateProcessorService.js b/src/services/JobCandidateProcessorService.js
index 3eeaaaa..5e6a044 100644
--- a/src/services/JobCandidateProcessorService.js
+++ b/src/services/JobCandidateProcessorService.js
@@ -25,7 +25,8 @@ async function updateCandidateStatus ({ type, payload, previousData }) {
localLogger.debug({ context: 'updateCandidateStatus', message: `jobCandidate is already in status: ${payload.status}` })
return
}
- if (!['rejected', 'shortlist'].includes(payload.status)) {
+ // if (!['rejected', 'shortlist',].includes(payload.status)) {
+ if (!['client rejected - screening', 'client rejected - interview', 'interview', 'selected'].includes(payload.status)) {
localLogger.debug({ context: 'updateCandidateStatus', message: `not interested status: ${payload.status}` })
return
}
@@ -85,24 +86,30 @@ async function processCreate (message, transactionId) {
}
processCreate.schema = {
- message: Joi.object().keys({
- topic: Joi.string().required(),
- originator: Joi.string().required(),
- timestamp: Joi.date().required(),
- 'mime-type': Joi.string().required(),
- payload: Joi.object().keys({
- id: Joi.string().uuid().required(),
- jobId: Joi.string().uuid().required(),
- userId: Joi.string().uuid().required(),
- createdAt: Joi.date().required(),
- createdBy: Joi.string().uuid().required(),
- updatedAt: Joi.date().allow(null),
- updatedBy: Joi.string().uuid().allow(null),
- status: Joi.jobCandidateStatus().required(),
- externalId: Joi.string().allow(null),
- resume: Joi.string().uri().allow(null)
- }).required()
- }).required(),
+ message: Joi.object()
+ .keys({
+ topic: Joi.string().required(),
+ originator: Joi.string().required(),
+ timestamp: Joi.date().required(),
+ 'mime-type': Joi.string().required(),
+ key: Joi.string().allow(null),
+ payload: Joi.object()
+ .keys({
+ id: Joi.string().uuid().required(),
+ jobId: Joi.string().uuid().required(),
+ userId: Joi.string().uuid().required(),
+ createdAt: Joi.date().required(),
+ createdBy: Joi.string().uuid().required(),
+ updatedAt: Joi.date().allow(null),
+ updatedBy: Joi.string().uuid().allow(null),
+ status: Joi.jobCandidateStatus().required(),
+ externalId: Joi.string().allow(null),
+ resume: Joi.string().uri().allow(null).allow(''),
+ remark: Joi.string().allow(null).allow('')
+ })
+ .required()
+ })
+ .required(),
transactionId: Joi.string().required()
}
@@ -158,6 +165,7 @@ processDelete.schema = {
originator: Joi.string().required(),
timestamp: Joi.date().required(),
'mime-type': Joi.string().required(),
+ key: Joi.string().allow(null),
payload: Joi.object().keys({
id: Joi.string().uuid().required()
}).required()
diff --git a/src/services/JobProcessorService.js b/src/services/JobProcessorService.js
index 678a3c3..491a1b3 100644
--- a/src/services/JobProcessorService.js
+++ b/src/services/JobProcessorService.js
@@ -65,6 +65,7 @@ processCreate.schema = {
originator: Joi.string().required(),
timestamp: Joi.date().required(),
'mime-type': Joi.string().required(),
+ key: Joi.string().allow(null),
payload: Joi.object().keys({
id: Joi.string().uuid().required(),
projectId: Joi.number().integer().required(),
@@ -78,12 +79,20 @@ processCreate.schema = {
rateType: Joi.rateType().allow(null),
workload: Joi.workload().allow(null),
skills: Joi.array().items(Joi.string().uuid()).required(),
+ roles: Joi.array().items(Joi.string().uuid()).allow(null),
createdAt: Joi.date().required(),
createdBy: Joi.string().uuid().required(),
updatedAt: Joi.date().allow(null),
updatedBy: Joi.string().uuid().allow(null),
status: Joi.jobStatus().required(),
- isApplicationPageActive: Joi.boolean().required()
+ isApplicationPageActive: Joi.boolean().required(),
+ minSalary: Joi.number().integer().allow(null),
+ maxSalary: Joi.number().integer().allow(null),
+ hoursPerWeek: Joi.number().integer().allow(null),
+ jobLocation: Joi.string().allow(null).allow(''),
+ jobTimezone: Joi.string().allow(null).allow(''),
+ currency: Joi.string().allow(null).allow(''),
+ roleIds: Joi.array().items(Joi.string().uuid().required()).allow(null)
}).required()
}).required(),
transactionId: Joi.string().required()
@@ -134,6 +143,7 @@ processDelete.schema = {
originator: Joi.string().required(),
timestamp: Joi.date().required(),
'mime-type': Joi.string().required(),
+ key: Joi.string().allow(null),
payload: Joi.object().keys({
id: Joi.string().uuid().required()
}).required()
diff --git a/src/services/ResourceBookingProcessorService.js b/src/services/ResourceBookingProcessorService.js
index 4972afe..836e3e1 100644
--- a/src/services/ResourceBookingProcessorService.js
+++ b/src/services/ResourceBookingProcessorService.js
@@ -32,13 +32,14 @@ processCreate.schema = {
originator: Joi.string().required(),
timestamp: Joi.date().required(),
'mime-type': Joi.string().required(),
+ key: Joi.string().allow(null),
payload: Joi.object().keys({
id: Joi.string().uuid().required(),
projectId: Joi.number().integer().required(),
userId: Joi.string().uuid().required(),
jobId: Joi.string().uuid().allow(null),
- startDate: Joi.date().allow(null),
- endDate: Joi.date().allow(null),
+ startDate: Joi.string().regex(/^(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$/).allow(null),
+ endDate: Joi.string().regex(/^(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$/).allow(null),
memberRate: Joi.number().allow(null),
customerRate: Joi.number().allow(null),
rateType: Joi.rateType().required(),
@@ -46,7 +47,8 @@ processCreate.schema = {
createdBy: Joi.string().uuid().required(),
updatedAt: Joi.date().allow(null),
updatedBy: Joi.string().uuid().allow(null),
- status: Joi.resourceBookingStatus().required()
+ status: Joi.resourceBookingStatus().required(),
+ billingAccountId: Joi.number().allow(null)
}).required()
}).required(),
transactionId: Joi.string().required()
@@ -93,6 +95,7 @@ processDelete.schema = {
originator: Joi.string().required(),
timestamp: Joi.date().required(),
'mime-type': Joi.string().required(),
+ key: Joi.string().allow(null),
payload: Joi.object().keys({
id: Joi.string().uuid().required()
}).required()
diff --git a/src/services/RoleProcessorService.js b/src/services/RoleProcessorService.js
new file mode 100644
index 0000000..b183577
--- /dev/null
+++ b/src/services/RoleProcessorService.js
@@ -0,0 +1,121 @@
+/**
+ * Role Processor Service
+ */
+
+const Joi = require('@hapi/joi')
+const logger = require('../common/logger')
+const helper = require('../common/helper')
+const constants = require('../common/constants')
+const config = require('config')
+
+const esClient = helper.getESClient()
+
+/**
+ * Process create entity message
+ * @param {Object} message the kafka message
+ * @param {String} transactionId
+ */
+async function processCreate (message, transactionId) {
+ const role = message.payload
+ await esClient.createExtra({
+ index: config.get('esConfig.ES_INDEX_ROLE'),
+ id: role.id,
+ transactionId,
+ body: role,
+ refresh: constants.esRefreshOption
+ })
+}
+
+processCreate.schema = {
+ message: Joi.object().keys({
+ topic: Joi.string().required(),
+ originator: Joi.string().required(),
+ timestamp: Joi.date().required(),
+ 'mime-type': Joi.string().required(),
+ key: Joi.string().allow(null),
+ payload: Joi.object().keys({
+ id: Joi.string().uuid().required(),
+ name: Joi.string().max(50).required(),
+ description: Joi.string().max(1000).allow(null),
+ listOfSkills: Joi.array().items(Joi.string().max(50).required()).allow(null),
+ rates: Joi.array().items(Joi.object().keys({
+ global: Joi.smallint().required(),
+ inCountry: Joi.smallint().required(),
+ offShore: Joi.smallint().required(),
+ rate30Global: Joi.smallint().allow(null),
+ rate30InCountry: Joi.smallint().allow(null),
+ rate30OffShore: Joi.smallint().allow(null),
+ rate20Global: Joi.smallint().allow(null),
+ rate20InCountry: Joi.smallint().allow(null),
+ rate20OffShore: Joi.smallint().allow(null)
+ }).required()).required(),
+ numberOfMembers: Joi.number().allow(null),
+ numberOfMembersAvailable: Joi.smallint().allow(null),
+ imageUrl: Joi.string().uri().max(255).allow(null),
+ timeToCandidate: Joi.smallint().allow(null),
+ timeToInterview: Joi.smallint().allow(null),
+ createdAt: Joi.date().required(),
+ createdBy: Joi.string().uuid().required(),
+ updatedAt: Joi.date().allow(null),
+ updatedBy: Joi.string().uuid().allow(null)
+ }).required()
+ }).required(),
+ transactionId: Joi.string().required()
+}
+
+/**
+ * Process update entity message
+ * @param {Object} message the kafka message
+ * @param {String} transactionId
+ */
+async function processUpdate (message, transactionId) {
+ const data = message.payload
+ await esClient.updateExtra({
+ index: config.get('esConfig.ES_INDEX_ROLE'),
+ id: data.id,
+ transactionId,
+ body: {
+ doc: data
+ },
+ refresh: constants.esRefreshOption
+ })
+}
+
+processUpdate.schema = processCreate.schema
+
+/**
+ * Process delete entity message
+ * @param {Object} message the kafka message
+ * @param {String} transactionId
+ */
+async function processDelete (message, transactionId) {
+ const id = message.payload.id
+ await esClient.deleteExtra({
+ index: config.get('esConfig.ES_INDEX_ROLE'),
+ id,
+ transactionId,
+ refresh: constants.esRefreshOption
+ })
+}
+
+processDelete.schema = {
+ message: Joi.object().keys({
+ topic: Joi.string().required(),
+ originator: Joi.string().required(),
+ timestamp: Joi.date().required(),
+ 'mime-type': Joi.string().required(),
+ key: Joi.string().allow(null),
+ payload: Joi.object().keys({
+ id: Joi.string().uuid().required()
+ }).required()
+ }).required(),
+ transactionId: Joi.string().required()
+}
+
+module.exports = {
+ processCreate,
+ processUpdate,
+ processDelete
+}
+
+logger.buildService(module.exports, 'RoleProcessorService')
diff --git a/src/services/WorkPeriodPaymentProcessorService.js b/src/services/WorkPeriodPaymentProcessorService.js
new file mode 100644
index 0000000..33d76df
--- /dev/null
+++ b/src/services/WorkPeriodPaymentProcessorService.js
@@ -0,0 +1,131 @@
+/**
+ * WorkPeriodPayment Processor Service
+ */
+
+const Joi = require('@hapi/joi')
+const config = require('config')
+const logger = require('../common/logger')
+const helper = require('../common/helper')
+const constants = require('../common/constants')
+
+const esClient = helper.getESClient()
+
+/**
+ * Process create entity message
+ * @param {Object} message the kafka message
+ * @param {String} transactionId
+ */
+async function processCreate (message, transactionId) {
+ const workPeriodPayment = message.payload
+ // find related resourceBooking
+ const resourceBooking = await esClient.search({
+ index: config.get('esConfig.ES_INDEX_RESOURCE_BOOKING'),
+ body: {
+ query: {
+ nested: {
+ path: 'workPeriods',
+ query: {
+ match: { 'workPeriods.id': workPeriodPayment.workPeriodId }
+ }
+ }
+ }
+ }
+ })
+ if (!resourceBooking.body.hits.total.value) {
+ throw new Error(`id: ${workPeriodPayment.workPeriodId} "WorkPeriod" not found`)
+ }
+ await esClient.update({
+ index: config.get('esConfig.ES_INDEX_RESOURCE_BOOKING'),
+ id: resourceBooking.body.hits.hits[0]._id,
+ transactionId,
+ body: {
+ script: {
+ lang: 'painless',
+ source: 'def wp = ctx._source.workPeriods.find(workPeriod -> workPeriod.id == params.workPeriodPayment.workPeriodId); if(!wp.containsKey("payments") || wp.payments == null){wp["payments"]=[]}wp.payments.add(params.workPeriodPayment)',
+ params: { workPeriodPayment }
+ }
+ },
+ refresh: constants.esRefreshOption
+ })
+}
+
+processCreate.schema = {
+ message: Joi.object().keys({
+ topic: Joi.string().required(),
+ originator: Joi.string().required(),
+ timestamp: Joi.date().required(),
+ 'mime-type': Joi.string().required(),
+ key: Joi.string().allow(null),
+ payload: Joi.object().keys({
+ id: Joi.string().uuid().required(),
+ workPeriodId: Joi.string().uuid().required(),
+ challengeId: Joi.string().uuid().allow(null),
+ memberRate: Joi.number().required(),
+ customerRate: Joi.number().allow(null),
+ days: Joi.number().integer().min(1).max(5).required(),
+ amount: Joi.number().greater(0).allow(null),
+ status: Joi.workPeriodPaymentStatus().required(),
+ billingAccountId: Joi.number().allow(null),
+ statusDetails: Joi.object().keys({
+ errorMessage: Joi.string().required(),
+ errorCode: Joi.number().integer().allow(null),
+ retry: Joi.number().integer().allow(null),
+ step: Joi.string().allow(null),
+ challengeId: Joi.string().uuid().allow(null)
+ }).unknown(true).allow(null),
+ createdAt: Joi.date().required(),
+ createdBy: Joi.string().uuid().required(),
+ updatedAt: Joi.date().allow(null),
+ updatedBy: Joi.string().uuid().allow(null)
+ }).required()
+ }).required(),
+ transactionId: Joi.string().required()
+}
+
+/**
+ * Process update entity message
+ * @param {Object} message the kafka message
+ * @param {String} transactionId
+ */
+async function processUpdate (message, transactionId) {
+ const data = message.payload
+ // find workPeriodPayment in it's parent ResourceBooking
+ const resourceBooking = await esClient.search({
+ index: config.get('esConfig.ES_INDEX_RESOURCE_BOOKING'),
+ body: {
+ query: {
+ nested: {
+ path: 'workPeriods.payments',
+ query: {
+ match: { 'workPeriods.payments.id': data.id }
+ }
+ }
+ }
+ }
+ })
+ if (!resourceBooking.body.hits.total.value) {
+ throw new Error(`id: ${data.id} "WorkPeriodPayment" not found`)
+ }
+ await esClient.update({
+ index: config.get('esConfig.ES_INDEX_RESOURCE_BOOKING'),
+ id: resourceBooking.body.hits.hits[0]._id,
+ transactionId,
+ body: {
+ script: {
+ lang: 'painless',
+ source: 'def wp = ctx._source.workPeriods.find(workPeriod -> workPeriod.id == params.data.workPeriodId); wp.payments.removeIf(payment -> payment.id == params.data.id); wp.payments.add(params.data)',
+ params: { data }
+ }
+ },
+ refresh: constants.esRefreshOption
+ })
+}
+
+processUpdate.schema = processCreate.schema
+
+module.exports = {
+ processCreate,
+ processUpdate
+}
+
+logger.buildService(module.exports, 'WorkPeriodPaymentProcessorService')
diff --git a/src/services/WorkPeriodProcessorService.js b/src/services/WorkPeriodProcessorService.js
index 568c746..bcf6fc9 100644
--- a/src/services/WorkPeriodProcessorService.js
+++ b/src/services/WorkPeriodProcessorService.js
@@ -7,21 +7,53 @@ const logger = require('../common/logger')
const helper = require('../common/helper')
const constants = require('../common/constants')
const config = require('config')
-
const esClient = helper.getESClient()
+const ActionProcessorService = require('../services/ActionProcessorService')
/**
- * Process create entity message
- * @param {Object} message the kafka message
- * @param {String} transactionId
- */
-async function processCreate (message, transactionId) {
+ * Process create entity message
+ * @param {Object} message the kafka message
+ * @param {String} transactionId
+ * @param {Object} options
+ */
+async function processCreate (message, transactionId, options) {
const workPeriod = message.payload
- await esClient.createExtra({
- index: config.get('esConfig.ES_INDEX_WORK_PERIOD'),
- id: workPeriod.id,
+ // Find related resourceBooking
+ let resourceBooking
+ try {
+ resourceBooking = await esClient.getExtra({
+ index: config.get('esConfig.ES_INDEX_RESOURCE_BOOKING'),
+ transactionId,
+ id: workPeriod.resourceBookingId
+ })
+ } catch (err) {
+ // if resource booking was not found, it may be because
+ // it has not yet been created. We should send a retry request.
+ if (err.httpStatus === 404) {
+ const schedulePromise = ActionProcessorService.scheduleRetry(message.topic, workPeriod, options.retry)
+ if (schedulePromise) {
+ // as retry was scheduled, log this error as warning
+ logger.logFullWarning(err, { component: 'WorkPeriodProcessorService', context: 'processCreate' })
+ } else {
+ // as retry was not scheduled, then log this error as error
+ logger.logFullError(err, { component: 'WorkPeriodProcessorService', context: 'processCreate' })
+ }
+ return
+ } else {
+ throw err
+ }
+ }
+ await esClient.update({
+ index: config.get('esConfig.ES_INDEX_RESOURCE_BOOKING'),
+ id: resourceBooking.body.id,
transactionId,
- body: workPeriod,
+ body: {
+ script: {
+ lang: 'painless',
+ source: 'if(!ctx._source.containsKey("workPeriods") || ctx._source.workPeriods == null){ctx._source["workPeriods"]=[]}ctx._source.workPeriods.add(params.workPeriod)',
+ params: { workPeriod }
+ }
+ },
refresh: constants.esRefreshOption
})
}
@@ -32,6 +64,7 @@ processCreate.schema = {
originator: Joi.string().required(),
timestamp: Joi.date().required(),
'mime-type': Joi.string().required(),
+ key: Joi.string().allow(null),
payload: Joi.object().keys({
id: Joi.string().uuid().required(),
resourceBookingId: Joi.string().uuid().required(),
@@ -39,9 +72,9 @@ processCreate.schema = {
projectId: Joi.number().integer().required(),
startDate: Joi.string().required(),
endDate: Joi.string().required(),
- daysWorked: Joi.number().integer().min(0).allow(null),
- memberRate: Joi.number().allow(null),
- customerRate: Joi.number().allow(null),
+ daysWorked: Joi.number().integer().min(0).max(5).required(),
+ daysPaid: Joi.number().integer().min(0).max(5).required(),
+ paymentTotal: Joi.number().min(0).required(),
paymentStatus: Joi.paymentStatus().required(),
createdAt: Joi.date().required(),
createdBy: Joi.string().uuid().required(),
@@ -49,22 +82,49 @@ processCreate.schema = {
updatedBy: Joi.string().uuid().allow(null)
}).required()
}).required(),
- transactionId: Joi.string().required()
+ transactionId: Joi.string().required(),
+ options: Joi.object().keys({
+ retry: Joi.number().integer().min(0).default(0)
+ }).default({
+ retry: 0
+ })
}
/**
- * Process update entity message
- * @param {Object} message the kafka message
- * @param {String} transactionId
- */
+ * Process update entity message
+ * @param {Object} message the kafka message
+ * @param {String} transactionId
+ */
async function processUpdate (message, transactionId) {
const data = message.payload
- await esClient.updateExtra({
- index: config.get('esConfig.ES_INDEX_WORK_PERIOD'),
- id: data.id,
+ // find workPeriod in it's parent ResourceBooking
+ const resourceBooking = await esClient.search({
+ index: config.get('esConfig.ES_INDEX_RESOURCE_BOOKING'),
transactionId,
body: {
- doc: data
+ query: {
+ nested: {
+ path: 'workPeriods',
+ query: {
+ match: { 'workPeriods.id': data.id }
+ }
+ }
+ }
+ }
+ })
+ if (!resourceBooking.body.hits.total.value) {
+ throw new Error(`id: ${data.id} "WorkPeriod" not found`)
+ }
+ await esClient.update({
+ index: config.get('esConfig.ES_INDEX_RESOURCE_BOOKING'),
+ id: resourceBooking.body.hits.hits[0]._id,
+ transactionId,
+ body: {
+ script: {
+ lang: 'painless',
+ source: 'def wp = ctx._source.workPeriods.find(workPeriod -> workPeriod.id == params.data.id); ctx._source.workPeriods.removeIf(workPeriod -> workPeriod.id == params.data.id); params.data.payments = wp.payments; ctx._source.workPeriods.add(params.data)',
+ params: { data }
+ }
},
refresh: constants.esRefreshOption
})
@@ -73,16 +133,41 @@ async function processUpdate (message, transactionId) {
processUpdate.schema = processCreate.schema
/**
- * Process delete entity message
- * @param {Object} message the kafka message
- * @param {String} transactionId
- */
+ * Process delete entity message
+ * @param {Object} message the kafka message
+ * @param {String} transactionId
+ */
async function processDelete (message, transactionId) {
- const id = message.payload.id
- await esClient.deleteExtra({
- index: config.get('esConfig.ES_INDEX_WORK_PERIOD'),
- id,
+ const data = message.payload
+ // Find related ResourceBooking
+ const resourceBooking = await esClient.search({
+ index: config.get('esConfig.ES_INDEX_RESOURCE_BOOKING'),
+ transactionId,
+ body: {
+ query: {
+ nested: {
+ path: 'workPeriods',
+ query: {
+ match: { 'workPeriods.id': data.id }
+ }
+ }
+ }
+ }
+ })
+ if (!resourceBooking.body.hits.total.value) {
+ throw new Error(`id: ${data.id} "WorkPeriod" not found`)
+ }
+ await esClient.update({
+ index: config.get('esConfig.ES_INDEX_RESOURCE_BOOKING'),
+ id: resourceBooking.body.hits.hits[0]._id,
transactionId,
+ body: {
+ script: {
+ lang: 'painless',
+ source: 'ctx._source.workPeriods.removeIf(workPeriod -> workPeriod.id == params.data.id)',
+ params: { data }
+ }
+ },
refresh: constants.esRefreshOption
})
}
@@ -93,6 +178,7 @@ processDelete.schema = {
originator: Joi.string().required(),
timestamp: Joi.date().required(),
'mime-type': Joi.string().required(),
+ key: Joi.string().allow(null),
payload: Joi.object().keys({
id: Joi.string().uuid().required()
}).required()
diff --git a/test/common/testData.js b/test/common/testData.js
index 7e18dde..47a0dd1 100644
--- a/test/common/testData.js
+++ b/test/common/testData.js
@@ -17,6 +17,20 @@ const messages = {
update: { topic: 'taas.resourcebooking.update', message: require('../messages/taas.resourcebooking.update.event.json') },
delete: { topic: 'taas.resourcebooking.delete', message: require('../messages/taas.resourcebooking.delete.event.json') }
},
+ WorkPeriod: {
+ create: { topic: 'taas.workperiod.create', message: require('../messages/taas.workperiod.create.event.json') },
+ update: { topic: 'taas.workperiod.update', message: require('../messages/taas.workperiod.update.event.json') },
+ delete: { topic: 'taas.workperiod.delete', message: require('../messages/taas.workperiod.delete.event.json') }
+ },
+ WorkPeriodPayment: {
+ create: { topic: 'taas.workperiodpayment.create', message: require('../messages/taas.workperiodpayment.create.event.json') },
+ update: { topic: 'taas.workperiodpayment.update', message: require('../messages/taas.workperiodpayment.update.event.json') }
+ },
+ Role: {
+ create: { topic: 'taas.role.requested', message: require('../messages/taas.role.create.event.json') },
+ update: { topic: 'taas.role.update', message: require('../messages/taas.role.update.event.json') },
+ delete: { topic: 'taas.role.delete', message: require('../messages/taas.role.delete.event .json') }
+ },
messageInvalid: '{ "topic": "taas.job.create", }'
}
diff --git a/test/common/testHelper.js b/test/common/testHelper.js
index fd310d8..6410492 100644
--- a/test/common/testHelper.js
+++ b/test/common/testHelper.js
@@ -40,7 +40,8 @@ async function clearES () {
query: {
match_all: {}
}
- }
+ },
+ refresh: true
})
}
}
diff --git a/test/e2e/test.js b/test/e2e/test.js
index 1e09ccc..020b060 100644
--- a/test/e2e/test.js
+++ b/test/e2e/test.js
@@ -10,6 +10,7 @@ const should = require('should')
const logger = require('../../src/common/logger')
const testData = require('../common/testData')
const testHelper = require('../common/testHelper')
+const _ = require('lodash')
describe('Taas ES Processor E2E Test', () => {
let infoLogs = []
@@ -54,102 +55,233 @@ describe('Taas ES Processor E2E Test', () => {
errorLogs = []
await testHelper.clearES()
})
+ describe('General Logic Tests', () => {
+ it('Should setup healthcheck with check on kafka connection', async () => {
+ const healthcheckEndpoint = `http://localhost:${config.PORT}/health`
+ const result = await request.get(healthcheckEndpoint)
+ should.equal(result.status, 200)
+ should.deepEqual(result.body, { checksRun: 1 })
+ })
- it('Should setup healthcheck with check on kafka connection', async () => {
- const healthcheckEndpoint = `http://localhost:${config.PORT}/health`
- const result = await request.get(healthcheckEndpoint)
- should.equal(result.status, 200)
- should.deepEqual(result.body, { checksRun: 1 })
- })
+ it('Should handle invalid json message', async () => {
+ await testHelper.sendMessage(testData.messages.messageInvalid, config.topics.TAAS_JOB_CREATE_TOPIC)
+ await waitForMessageHandled()
+ errorLogs[0].should.match(/Invalid message JSON/)
+ })
- it('Should handle invalid json message', async () => {
- await testHelper.sendMessage(testData.messages.messageInvalid, config.topics.TAAS_JOB_CREATE_TOPIC)
- await waitForMessageHandled()
- errorLogs[0].should.match(/Invalid message JSON/)
+ it('Should handle incorrect topic field message', async () => {
+ await testHelper.sendMessage(testData.messages.Job.create.message, config.topics.TAAS_JOB_UPDATE_TOPIC)
+ await waitForMessageHandled()
+ should.equal(errorLogs[0], `The message topic ${testData.messages.Job.create.topic} doesn't match the Kafka topic ${config.topics.TAAS_JOB_UPDATE_TOPIC}.`)
+ })
})
+ describe('Job, JobCandidate, ResourceBooking tests', () => {
+ for (const [index, model] of [
+ [config.esConfig.ES_INDEX_JOB, 'Job'],
+ [config.esConfig.ES_INDEX_JOB_CANDIDATE, 'JobCandidate'],
+ [config.esConfig.ES_INDEX_RESOURCE_BOOKING, 'ResourceBooking']
+ ]) {
+ const modelInSpaceCase = stringcase.spacecase(model)
- it('Should handle incorrect topic field message', async () => {
- await testHelper.sendMessage(testData.messages.Job.create.message, config.topics.TAAS_JOB_UPDATE_TOPIC)
- await waitForMessageHandled()
- should.equal(errorLogs[0], `The message topic ${testData.messages.Job.create.topic} doesn't match the Kafka topic ${config.topics.TAAS_JOB_UPDATE_TOPIC}.`)
- })
+ it(`Should handle ${modelInSpaceCase} creation message`, async () => {
+ await testHelper.sendMessage(testData.messages[model].create.message)
+ await waitForMessageHandled()
+ const doc = await testHelper.esClient.get({
+ index,
+ id: testData.messages[model].create.message.payload.id
+ })
+ should.deepEqual(doc.body._source, testData.messages[model].create.message.payload, ['id'])
+ })
+
+ it(`Should handle ${modelInSpaceCase} updating message`, async () => {
+ await testHelper.esClient.create({
+ index,
+ id: testData.messages[model].create.message.payload.id,
+ body: testData.messages[model].create.message.payload,
+ refresh: 'true'
+ })
+ await testHelper.sendMessage(testData.messages[model].update.message)
+ await waitForMessageHandled()
+ const doc = await testHelper.esClient.get({
+ index,
+ id: testData.messages[model].update.message.payload.id
+ })
+ should.deepEqual(doc.body._source, testData.messages[model].update.message.payload)
+ })
+
+ it(`Should handle ${modelInSpaceCase} deletion message`, async () => {
+ await testHelper.esClient.create({
+ index,
+ id: testData.messages[model].create.message.payload.id,
+ body: testData.messages[model].create.message.payload,
+ refresh: 'true'
+ })
+ await testHelper.sendMessage(testData.messages[model].delete.message)
+ await waitForMessageHandled()
+ const doc = await testHelper.esClient.get({
+ index,
+ id: testData.messages[model].delete.message.payload.id
+ }).catch(err => {
+ if (err.statusCode === 404) {
+ return
+ }
+ throw err
+ })
+ should.not.exist(doc)
+ })
+
+ it(`Failure - creation message - ${modelInSpaceCase} already exists`, async () => {
+ await testHelper.esClient.create({
+ index,
+ id: testData.messages[model].create.message.payload.id,
+ body: testData.messages[model].create.message.payload,
+ refresh: 'true'
+ })
+ await testHelper.sendMessage(testData.messages[model].create.message)
+ await waitForMessageHandled()
+ should.equal(errorLogs[0], `id: ${testData.messages[model].create.message.payload.id} "${index}" already exists`)
+ })
+
+ it(`Failure - updating message - ${modelInSpaceCase} not found`, async () => {
+ await testHelper.sendMessage(testData.messages[model].update.message)
+ await waitForMessageHandled()
+ should.equal(errorLogs[0], `id: ${testData.messages[model].update.message.payload.id} "${index}" not found`)
+ })
- for (const [index, model] of [
- [config.esConfig.ES_INDEX_JOB, 'Job'],
- [config.esConfig.ES_INDEX_JOB_CANDIDATE, 'JobCandidate'],
- [config.esConfig.ES_INDEX_RESOURCE_BOOKING, 'ResourceBooking']
- ]) {
+ it(`Failure - deletion message - ${modelInSpaceCase} not found`, async () => {
+ await testHelper.sendMessage(testData.messages[model].delete.message)
+ await waitForMessageHandled()
+ should.equal(errorLogs[0], `id: ${testData.messages[model].delete.message.payload.id} "${index}" not found`)
+ })
+ }
+ })
+ describe('Nested WorkPeriod tests', () => {
+ const index = config.esConfig.ES_INDEX_RESOURCE_BOOKING
+ const model = 'WorkPeriod'
+ const parentModel = 'ResourceBooking'
+ const nestedName = 'workPeriods'
const modelInSpaceCase = stringcase.spacecase(model)
it(`Should handle ${modelInSpaceCase} creation message`, async () => {
+ await testHelper.esClient.create({
+ index,
+ id: testData.messages[parentModel].create.message.payload.id,
+ body: testData.messages[parentModel].create.message.payload,
+ refresh: 'true'
+ })
await testHelper.sendMessage(testData.messages[model].create.message)
await waitForMessageHandled()
const doc = await testHelper.esClient.get({
index,
- id: testData.messages[model].create.message.payload.id
+ id: testData.messages[parentModel].create.message.payload.id
})
- should.deepEqual(doc.body._source, testData.messages[model].create.message.payload, ['id'])
+ should.deepEqual(doc.body._source,
+ _.assign(testData.messages[parentModel].create.message.payload, { [nestedName]: [testData.messages[model].create.message.payload] }))
})
it(`Should handle ${modelInSpaceCase} updating message`, async () => {
await testHelper.esClient.create({
index,
- id: testData.messages[model].create.message.payload.id,
- body: testData.messages[model].create.message.payload,
+ id: testData.messages[parentModel].create.message.payload.id,
+ body: _.assign(testData.messages[parentModel].create.message.payload, { [nestedName]: [testData.messages[model].create.message.payload] }),
refresh: 'true'
})
await testHelper.sendMessage(testData.messages[model].update.message)
await waitForMessageHandled()
const doc = await testHelper.esClient.get({
index,
- id: testData.messages[model].update.message.payload.id
+ id: testData.messages[parentModel].create.message.payload.id
})
- should.deepEqual(doc.body._source, testData.messages[model].update.message.payload)
+ should.deepEqual(doc.body._source,
+ _.assign(testData.messages[parentModel].create.message.payload, { [nestedName]: [testData.messages[model].update.message.payload] }))
})
it(`Should handle ${modelInSpaceCase} deletion message`, async () => {
await testHelper.esClient.create({
index,
- id: testData.messages[model].create.message.payload.id,
- body: testData.messages[model].create.message.payload,
+ id: testData.messages[parentModel].create.message.payload.id,
+ body: _.assign(testData.messages[parentModel].create.message.payload, { [nestedName]: [testData.messages[model].create.message.payload] }),
refresh: 'true'
})
await testHelper.sendMessage(testData.messages[model].delete.message)
await waitForMessageHandled()
const doc = await testHelper.esClient.get({
index,
- id: testData.messages[model].delete.message.payload.id
- }).catch(err => {
- if (err.statusCode === 404) {
- return
- }
- throw err
- })
- should.not.exist(doc)
+ id: testData.messages[parentModel].create.message.payload.id
+ })
+ should.deepEqual(doc.body._source,
+ _.assign(testData.messages[parentModel].create.message.payload, { [nestedName]: [] }))
+ })
+
+ it(`Failure - creation message - ${modelInSpaceCase} not found`, async () => {
+ await testHelper.sendMessage(testData.messages[model].create.message)
+ await waitForMessageHandled()
+ should.equal(errorLogs[0], `id: ${testData.messages[parentModel].create.message.payload.id} "${index}" not found`)
+ })
+
+ it(`Failure - updating message - ${modelInSpaceCase} not found`, async () => {
+ await testHelper.sendMessage(testData.messages[model].update.message)
+ await waitForMessageHandled()
+ should.equal(errorLogs[0], `id: ${testData.messages[model].update.message.payload.id} "${model}" not found`)
+ })
+
+ it(`Failure - deletion message - ${modelInSpaceCase} not found`, async () => {
+ await testHelper.sendMessage(testData.messages[model].delete.message)
+ await waitForMessageHandled()
+ should.equal(errorLogs[0], `id: ${testData.messages[model].delete.message.payload.id} "${model}" not found`)
})
+ })
+ describe('Nested WorkPeriodPayment tests', () => {
+ const index = config.esConfig.ES_INDEX_RESOURCE_BOOKING
+ const model = 'WorkPeriodPayment'
+ const parentModel = 'WorkPeriod'
+ const rootModel = 'ResourceBooking'
+ const nestedName = 'payments'
+ const parentNestedName = 'workPeriods'
+ const modelInSpaceCase = stringcase.spacecase(model)
- it(`Failure - creation message - ${modelInSpaceCase} already exists`, async () => {
+ it(`Should handle ${modelInSpaceCase} creation message`, async () => {
await testHelper.esClient.create({
index,
- id: testData.messages[model].create.message.payload.id,
- body: testData.messages[model].create.message.payload,
+ id: testData.messages[rootModel].create.message.payload.id,
+ body: _.assign(testData.messages[rootModel].create.message.payload, { [parentNestedName]: [testData.messages[parentModel].create.message.payload] }),
refresh: 'true'
})
await testHelper.sendMessage(testData.messages[model].create.message)
await waitForMessageHandled()
- should.equal(errorLogs[0], `id: ${testData.messages[model].create.message.payload.id} "${index}" already exists`)
+ const doc = await testHelper.esClient.get({
+ index,
+ id: testData.messages[rootModel].create.message.payload.id
+ })
+ should.deepEqual(doc.body._source, _.assign(testData.messages[rootModel].create.message.payload, { [parentNestedName]: [_.assign(testData.messages[parentModel].create.message.payload, { [nestedName]: [testData.messages[model].create.message.payload] })] }))
})
- it(`Failure - updating message - ${modelInSpaceCase} not found`, async () => {
+ it(`Should handle ${modelInSpaceCase} updating message`, async () => {
+ await testHelper.esClient.create({
+ index,
+ id: testData.messages[rootModel].create.message.payload.id,
+ body: _.assign(testData.messages[rootModel].create.message.payload, { [parentNestedName]: [_.assign(testData.messages[parentModel].create.message.payload, { [nestedName]: [testData.messages[model].create.message.payload] })] }),
+ refresh: 'true'
+ })
await testHelper.sendMessage(testData.messages[model].update.message)
await waitForMessageHandled()
- should.equal(errorLogs[0], `id: ${testData.messages[model].update.message.payload.id} "${index}" not found`)
+ const doc = await testHelper.esClient.get({
+ index,
+ id: testData.messages[rootModel].create.message.payload.id
+ })
+ should.deepEqual(doc.body._source, _.assign(testData.messages[rootModel].create.message.payload, { [parentNestedName]: [_.assign(testData.messages[parentModel].create.message.payload, { [nestedName]: [testData.messages[model].update.message.payload] })] }))
})
- it(`Failure - deletion message - ${modelInSpaceCase} not found`, async () => {
- await testHelper.sendMessage(testData.messages[model].delete.message)
+ it(`Failure - creation message - ${modelInSpaceCase} not found`, async () => {
+ await testHelper.sendMessage(testData.messages[model].create.message)
await waitForMessageHandled()
- should.equal(errorLogs[0], `id: ${testData.messages[model].delete.message.payload.id} "${index}" not found`)
+ should.equal(errorLogs[0], `id: ${testData.messages[parentModel].create.message.payload.id} "${parentModel}" not found`)
})
- }
+
+ it(`Failure - updating message - ${modelInSpaceCase} not found`, async () => {
+ await testHelper.sendMessage(testData.messages[model].update.message)
+ await waitForMessageHandled()
+ should.equal(errorLogs[0], `id: ${testData.messages[model].update.message.payload.id} "${model}" not found`)
+ })
+ })
})
diff --git a/test/messages/taas.job.create.event.json b/test/messages/taas.job.create.event.json
index e2b12bb..0813259 100644
--- a/test/messages/taas.job.create.event.json
+++ b/test/messages/taas.job.create.event.json
@@ -1 +1,38 @@
-{"topic":"taas.job.create","originator":"taas-api","timestamp":"2020-11-05T19:00:17.563Z","mime-type":"application/json","payload":{"title":"Job Title","projectId":21,"externalId":"1212","description":"Dummy Description","startDate":"2020-09-27T04:17:23.131Z","duration":17,"numPositions":13,"resourceType":"Dummy Resource Type","rateType":"hourly","skills":["56fdc405-eccc-4189-9e83-c78abf844f50","f91ae184-aba2-4485-a8cb-9336988c05ab","edfc7b4f-636f-44bd-96fc-949ffc58e38b","4ca63bb6-f515-4ab0-a6bc-c2d8531e084f","ee03c041-d53b-4c08-b7d9-80d7461da3e4"],"id":"ffbc24f7-301e-48d3-bf01-c056916056a2","createdAt":"2020-11-05T19:00:16.268Z","createdBy":"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a","status":"sourcing","isApplicationPageActive":false}}
\ No newline at end of file
+{
+ "topic": "taas.job.create",
+ "originator": "taas-api",
+ "timestamp": "2020-11-05T19:00:17.563Z",
+ "mime-type": "application/json",
+ "payload": {
+ "title": "Job Title",
+ "projectId": 21,
+ "externalId": "1212",
+ "description": "Dummy Description",
+ "startDate": "2020-09-27T04:17:23.131Z",
+ "duration": 17,
+ "numPositions": 13,
+ "resourceType": "Dummy Resource Type",
+ "rateType": "hourly",
+ "skills": [
+ "56fdc405-eccc-4189-9e83-c78abf844f50",
+ "f91ae184-aba2-4485-a8cb-9336988c05ab",
+ "edfc7b4f-636f-44bd-96fc-949ffc58e38b",
+ "4ca63bb6-f515-4ab0-a6bc-c2d8531e084f",
+ "ee03c041-d53b-4c08-b7d9-80d7461da3e4"
+ ],
+ "id": "ffbc24f7-301e-48d3-bf01-c056916056a2",
+ "createdAt": "2020-11-05T19:00:16.268Z",
+ "createdBy": "a55fe1bc-1754-45fa-9adc-cf3d6d7c377a",
+ "status": "sourcing",
+ "isApplicationPageActive": false,
+ "minSalary": 100,
+ "maxSalary": 200,
+ "hoursPerWeek": 20,
+ "jobLocation": "Any location",
+ "jobTimezone": "GMT",
+ "currency": "USD",
+ "roleIds": [
+ "e7b7e818-40d4-4102-b486-09bdd21400b8"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/test/messages/taas.job.delete.event.json b/test/messages/taas.job.delete.event.json
index 00ddbe9..9ad9844 100644
--- a/test/messages/taas.job.delete.event.json
+++ b/test/messages/taas.job.delete.event.json
@@ -1 +1,9 @@
-{"topic":"taas.job.delete","originator":"taas-api","timestamp":"2020-11-05T19:00:19.035Z","mime-type":"application/json","payload":{"id":"ffbc24f7-301e-48d3-bf01-c056916056a2"}}
\ No newline at end of file
+{
+ "topic": "taas.job.delete",
+ "originator": "taas-api",
+ "timestamp": "2020-11-05T19:00:19.035Z",
+ "mime-type": "application/json",
+ "payload": {
+ "id": "ffbc24f7-301e-48d3-bf01-c056916056a2"
+ }
+}
\ No newline at end of file
diff --git a/test/messages/taas.job.update.event.json b/test/messages/taas.job.update.event.json
index fced890..56a7b0d 100644
--- a/test/messages/taas.job.update.event.json
+++ b/test/messages/taas.job.update.event.json
@@ -1 +1,37 @@
-{"topic":"taas.job.update","originator":"taas-api","timestamp":"2020-11-05T19:00:19.015Z","mime-type":"application/json","payload":{"id":"ffbc24f7-301e-48d3-bf01-c056916056a2","title":"Job Title Updated","projectId":21,"externalId":"1212","description":"Dummy Description","startDate":"2020-09-27T04:17:23.131Z","duration":19,"numPositions":13,"resourceType":"Dummy Resource Type","rateType":"hourly","skills":["3fa85f64-5717-4562-b3fc-2c963f66afa6","cc41ddc4-cacc-4570-9bdb-1229c12b9784"],"status":"sourcing","updatedAt":"2020-11-05T19:00:17.612Z","updatedBy":"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a","createdAt":"2020-11-05T19:00:16.268Z","createdBy":"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a","isApplicationPageActive":false}}
\ No newline at end of file
+{
+ "topic": "taas.job.update",
+ "originator": "taas-api",
+ "timestamp": "2020-11-05T19:00:19.015Z",
+ "mime-type": "application/json",
+ "payload": {
+ "id": "ffbc24f7-301e-48d3-bf01-c056916056a2",
+ "title": "Job Title Updated",
+ "projectId": 21,
+ "externalId": "1212",
+ "description": "Dummy Description",
+ "startDate": "2020-09-27T04:17:23.131Z",
+ "duration": 19,
+ "numPositions": 13,
+ "resourceType": "Dummy Resource Type",
+ "rateType": "hourly",
+ "skills": [
+ "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "cc41ddc4-cacc-4570-9bdb-1229c12b9784"
+ ],
+ "status": "sourcing",
+ "updatedAt": "2020-11-05T19:00:17.612Z",
+ "updatedBy": "a55fe1bc-1754-45fa-9adc-cf3d6d7c377a",
+ "createdAt": "2020-11-05T19:00:16.268Z",
+ "createdBy": "a55fe1bc-1754-45fa-9adc-cf3d6d7c377a",
+ "isApplicationPageActive": false,
+ "minSalary": 100,
+ "maxSalary": 200,
+ "hoursPerWeek": 20,
+ "jobLocation": "Any location",
+ "jobTimezone": "GMT",
+ "currency": "USD",
+ "roleIds": [
+ "e7b7e818-40d4-4102-b486-09bdd21400b8"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/test/messages/taas.jobcandidate.create.event.json b/test/messages/taas.jobcandidate.create.event.json
index 18cabc3..28ba870 100644
--- a/test/messages/taas.jobcandidate.create.event.json
+++ b/test/messages/taas.jobcandidate.create.event.json
@@ -1 +1,15 @@
-{"topic":"taas.jobcandidate.create","originator":"taas-api","timestamp":"2020-11-05T19:00:21.597Z","mime-type":"application/json","payload":{"jobId":"ffbc24f7-301e-48d3-bf01-c056916056a2","userId":"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a","id":"0cb99adb-8bcd-4952-9203-9867dd45ef6f","createdAt":"2020-11-05T19:00:19.052Z","createdBy":"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a","status":"open"}}
\ No newline at end of file
+{
+ "topic": "taas.jobcandidate.create",
+ "originator": "taas-api",
+ "timestamp": "2020-11-05T19:00:21.597Z",
+ "mime-type": "application/json",
+ "payload": {
+ "jobId": "ffbc24f7-301e-48d3-bf01-c056916056a2",
+ "userId": "a55fe1bc-1754-45fa-9adc-cf3d6d7c377a",
+ "id": "0cb99adb-8bcd-4952-9203-9867dd45ef6f",
+ "createdAt": "2020-11-05T19:00:19.052Z",
+ "createdBy": "a55fe1bc-1754-45fa-9adc-cf3d6d7c377a",
+ "status": "open",
+ "remark": "excellent"
+ }
+}
\ No newline at end of file
diff --git a/test/messages/taas.jobcandidate.delete.event.json b/test/messages/taas.jobcandidate.delete.event.json
index 714cce6..0aa13d5 100644
--- a/test/messages/taas.jobcandidate.delete.event.json
+++ b/test/messages/taas.jobcandidate.delete.event.json
@@ -1 +1,9 @@
-{"topic":"taas.jobcandidate.delete","originator":"taas-api","timestamp":"2020-11-05T19:00:23.021Z","mime-type":"application/json","payload":{"id":"0cb99adb-8bcd-4952-9203-9867dd45ef6f"}}
\ No newline at end of file
+{
+ "topic": "taas.jobcandidate.delete",
+ "originator": "taas-api",
+ "timestamp": "2020-11-05T19:00:23.021Z",
+ "mime-type": "application/json",
+ "payload": {
+ "id": "0cb99adb-8bcd-4952-9203-9867dd45ef6f"
+ }
+}
\ No newline at end of file
diff --git a/test/messages/taas.jobcandidate.update.event.json b/test/messages/taas.jobcandidate.update.event.json
index 79530f9..cc6e506 100644
--- a/test/messages/taas.jobcandidate.update.event.json
+++ b/test/messages/taas.jobcandidate.update.event.json
@@ -1 +1,17 @@
-{"topic":"taas.jobcandidate.update","originator":"taas-api","timestamp":"2020-11-05T19:00:23.003Z","mime-type":"application/json","payload":{"id":"0cb99adb-8bcd-4952-9203-9867dd45ef6f","jobId":"ffbc24f7-301e-48d3-bf01-c056916056a2","userId":"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a","status":"selected","updatedAt":"2020-11-05T19:00:21.625Z","updatedBy":"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a","createdAt":"2020-11-05T19:00:16.268Z","createdBy":"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a"}}
\ No newline at end of file
+{
+ "topic": "taas.jobcandidate.update",
+ "originator": "taas-api",
+ "timestamp": "2020-11-05T19:00:23.003Z",
+ "mime-type": "application/json",
+ "payload": {
+ "id": "0cb99adb-8bcd-4952-9203-9867dd45ef6f",
+ "jobId": "ffbc24f7-301e-48d3-bf01-c056916056a2",
+ "userId": "a55fe1bc-1754-45fa-9adc-cf3d6d7c377a",
+ "status": "selected",
+ "remark": "excellent",
+ "updatedAt": "2020-11-05T19:00:21.625Z",
+ "updatedBy": "a55fe1bc-1754-45fa-9adc-cf3d6d7c377a",
+ "createdAt": "2020-11-05T19:00:16.268Z",
+ "createdBy": "a55fe1bc-1754-45fa-9adc-cf3d6d7c377a"
+ }
+}
\ No newline at end of file
diff --git a/test/messages/taas.resourcebooking.create.event.json b/test/messages/taas.resourcebooking.create.event.json
index 1a358ba..2d7fd00 100644
--- a/test/messages/taas.resourcebooking.create.event.json
+++ b/test/messages/taas.resourcebooking.create.event.json
@@ -1 +1,21 @@
-{"topic":"taas.resourcebooking.create","originator":"taas-api","timestamp":"2020-11-05T19:00:25.038Z","mime-type":"application/json","payload":{"projectId":21,"userId":"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a","jobId":"ffbc24f7-301e-48d3-bf01-c056916056a2","startDate":"2020-09-27T04:17:23.131Z","endDate":"2020-09-27T04:17:23.131Z","memberRate":13.23,"customerRate":13,"rateType":"hourly","id":"60d97713-8621-476e-b006-7cb9589c7777","createdAt":"2020-11-05T19:00:23.036Z","createdBy":"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a","status":"sourcing"}}
\ No newline at end of file
+{
+ "topic": "taas.resourcebooking.create",
+ "originator": "taas-api",
+ "timestamp": "2020-11-05T19:00:25.038Z",
+ "mime-type": "application/json",
+ "payload": {
+ "projectId": 21,
+ "userId": "a55fe1bc-1754-45fa-9adc-cf3d6d7c377a",
+ "jobId": "ffbc24f7-301e-48d3-bf01-c056916056a2",
+ "startDate": "2020-09-27",
+ "endDate": "2020-09-27",
+ "memberRate": 13.23,
+ "customerRate": 13,
+ "rateType": "hourly",
+ "id": "60d97713-8621-476e-b006-7cb9589c7777",
+ "createdAt": "2020-11-05T19:00:23.036Z",
+ "createdBy": "a55fe1bc-1754-45fa-9adc-cf3d6d7c377a",
+ "status": "placed",
+ "billingAccountId": 80000071
+ }
+}
\ No newline at end of file
diff --git a/test/messages/taas.resourcebooking.delete.event.json b/test/messages/taas.resourcebooking.delete.event.json
index 46a5e6f..644037e 100644
--- a/test/messages/taas.resourcebooking.delete.event.json
+++ b/test/messages/taas.resourcebooking.delete.event.json
@@ -1 +1,9 @@
-{"topic":"taas.resourcebooking.delete","originator":"taas-api","timestamp":"2020-11-05T19:00:26.433Z","mime-type":"application/json","payload":{"id":"60d97713-8621-476e-b006-7cb9589c7777"}}
\ No newline at end of file
+{
+ "topic": "taas.resourcebooking.delete",
+ "originator": "taas-api",
+ "timestamp": "2020-11-05T19:00:26.433Z",
+ "mime-type": "application/json",
+ "payload": {
+ "id": "60d97713-8621-476e-b006-7cb9589c7777"
+ }
+}
\ No newline at end of file
diff --git a/test/messages/taas.resourcebooking.update.event.json b/test/messages/taas.resourcebooking.update.event.json
index 2c07a4e..036b6c6 100644
--- a/test/messages/taas.resourcebooking.update.event.json
+++ b/test/messages/taas.resourcebooking.update.event.json
@@ -1 +1,23 @@
-{"topic":"taas.resourcebooking.update","originator":"taas-api","timestamp":"2020-11-05T19:00:26.407Z","mime-type":"application/json","payload":{"id":"60d97713-8621-476e-b006-7cb9589c7777","projectId":21,"userId":"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a","jobId":"ffbc24f7-301e-48d3-bf01-c056916056a2","startDate":"2020-09-27T04:17:23.131Z","endDate":"2020-09-27T04:17:23.131Z","memberRate":13.23,"customerRate":13,"rateType":"hourly","status":"assigned","updatedAt":"2020-11-05T19:00:25.062Z","updatedBy":"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a","createdAt":"2020-11-05T19:00:16.268Z","createdBy":"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a"}}
\ No newline at end of file
+{
+ "topic": "taas.resourcebooking.update",
+ "originator": "taas-api",
+ "timestamp": "2020-11-05T19:00:26.407Z",
+ "mime-type": "application/json",
+ "payload": {
+ "id": "60d97713-8621-476e-b006-7cb9589c7777",
+ "projectId": 21,
+ "userId": "a55fe1bc-1754-45fa-9adc-cf3d6d7c377a",
+ "jobId": "ffbc24f7-301e-48d3-bf01-c056916056a2",
+ "startDate": "2020-09-27",
+ "endDate": "2020-09-27",
+ "memberRate": 13.23,
+ "customerRate": 13,
+ "rateType": "hourly",
+ "status": "placed",
+ "billingAccountId": 80000071,
+ "updatedAt": "2020-11-05T19:00:25.062Z",
+ "updatedBy": "a55fe1bc-1754-45fa-9adc-cf3d6d7c377a",
+ "createdAt": "2020-11-05T19:00:16.268Z",
+ "createdBy": "a55fe1bc-1754-45fa-9adc-cf3d6d7c377a"
+ }
+}
\ No newline at end of file
diff --git a/test/messages/taas.role.create.event.json b/test/messages/taas.role.create.event.json
new file mode 100644
index 0000000..a45b6a8
--- /dev/null
+++ b/test/messages/taas.role.create.event.json
@@ -0,0 +1,50 @@
+{
+ "topic": "taas.role.requested",
+ "originator": "taas-api",
+ "timestamp": "2021-05-27T21:43:09.388Z",
+ "mime-type": "application/json",
+ "payload": {
+ "name": "Salesforce Developer",
+ "description": "A Salesforce developer is a programmer who builds Salesforce applications across various PaaS (Platform as a Service) platforms.",
+ "listOfSkills": [
+ "Docker",
+ ".NET",
+ "appcelerator",
+ "Flux"
+ ],
+ "rates": [
+ {
+ "global": 50,
+ "inCountry": 20,
+ "offShore": 10,
+ "rate30Global": 20,
+ "rate30InCountry": 15,
+ "rate30OffShore": 35,
+ "rate20Global": 20,
+ "rate20InCountry": 15,
+ "rate20OffShore": 35
+ },
+ {
+ "global": 25,
+ "inCountry": 15,
+ "offShore": 5,
+ "rate30Global": 20,
+ "rate30InCountry": 15,
+ "rate30OffShore": 35,
+ "rate20Global": 20,
+ "rate20InCountry": 15,
+ "rate20OffShore": 35
+ }
+ ],
+ "numberOfMembers": 10,
+ "numberOfMembersAvailable": 6,
+ "imageUrl": "http: //images.topcoder.com/member",
+ "timeToCandidate": 105,
+ "timeToInterview": 100,
+ "id": "e7b7e818-40d4-4102-b486-09bdd21400b8",
+ "createdBy": "00000000-0000-0000-0000-000000000000",
+ "updatedAt": "2021-05-27T21:43:09.342Z",
+ "createdAt": "2021-05-27T21:43:09.342Z",
+ "updatedBy": null
+ }
+}
\ No newline at end of file
diff --git a/test/messages/taas.role.delete.event .json b/test/messages/taas.role.delete.event .json
new file mode 100644
index 0000000..3bd7c32
--- /dev/null
+++ b/test/messages/taas.role.delete.event .json
@@ -0,0 +1,9 @@
+{
+ "topic": "taas.role.delete",
+ "originator": "taas-api",
+ "timestamp": "2021-05-27T21:45:09.388Z",
+ "mime-type": "application/json",
+ "payload": {
+ "id": "e7b7e818-40d4-4102-b486-09bdd21400b8"
+ }
+}
\ No newline at end of file
diff --git a/test/messages/taas.role.update.event.json b/test/messages/taas.role.update.event.json
new file mode 100644
index 0000000..5c2a482
--- /dev/null
+++ b/test/messages/taas.role.update.event.json
@@ -0,0 +1,48 @@
+{
+ "topic": "taas.role.update",
+ "originator": "taas-api",
+ "timestamp": "2021-05-27T21:44:09.388Z",
+ "mime-type": "application/json",
+ "payload": {
+ "name": "Salesforce Developer",
+ "description": "A Salesforce developer is a programmer who builds Salesforce applications across various PaaS (Platform as a Service) platforms.",
+ "listOfSkills": [
+ "Docker",
+ ".NET"
+ ],
+ "rates": [
+ {
+ "global": 50,
+ "inCountry": 20,
+ "offShore": 10,
+ "rate30Global": 20,
+ "rate30InCountry": 15,
+ "rate30OffShore": 35,
+ "rate20Global": 20,
+ "rate20InCountry": 15,
+ "rate20OffShore": 35
+ },
+ {
+ "global": 25,
+ "inCountry": 15,
+ "offShore": 5,
+ "rate30Global": 20,
+ "rate30InCountry": 15,
+ "rate30OffShore": 35,
+ "rate20Global": 20,
+ "rate20InCountry": 15,
+ "rate20OffShore": 35
+ }
+ ],
+ "numberOfMembers": 10,
+ "numberOfMembersAvailable": 6,
+ "imageUrl": "http: //images.topcoder.com/member",
+ "timeToCandidate": 105,
+ "timeToInterview": 100,
+ "id": "e7b7e818-40d4-4102-b486-09bdd21400b8",
+ "createdBy": "00000000-0000-0000-0000-000000000000",
+ "updatedAt": "2021-05-27T21:43:09.342Z",
+ "createdAt": "2021-05-27T21:43:09.342Z",
+ "updatedBy": "00000000-0000-0000-0000-000000000000"
+ }
+}
\ No newline at end of file
diff --git a/test/messages/taas.workperiod.create.event.json b/test/messages/taas.workperiod.create.event.json
index 7afea61..eb26743 100644
--- a/test/messages/taas.workperiod.create.event.json
+++ b/test/messages/taas.workperiod.create.event.json
@@ -4,7 +4,7 @@
"timestamp": "2021-03-30T20:24:17.555Z",
"mime-type": "application/json",
"payload": {
- "resourceBookingId": "6cf2edf6-4b2c-40ef-96db-e1ddb771fdd3",
+ "resourceBookingId": "60d97713-8621-476e-b006-7cb9589c7777",
"startDate": "2021-03-14",
"endDate": "2021-03-20",
"daysWorked": 3,
diff --git a/test/messages/taas.workperiod.update.event.json b/test/messages/taas.workperiod.update.event.json
index 0e8ca71..e8798dd 100644
--- a/test/messages/taas.workperiod.update.event.json
+++ b/test/messages/taas.workperiod.update.event.json
@@ -4,18 +4,19 @@
"timestamp": "2021-03-30T20:13:53.179Z",
"mime-type": "application/json",
"payload": {
- "id": "926040c4-1709-4de2-b2b6-52adf6e5e72d",
- "resourceBookingId": "79317ff6-5b30-45c2-ace8-b97282b042a8",
- "startDate": "2021-03-14",
- "endDate": "2021-03-20",
- "daysWorked": 3,
+ "resourceBookingId": "60d97713-8621-476e-b006-7cb9589c7777",
+ "startDate": "2021-03-21",
+ "endDate": "2021-03-28",
+ "daysWorked": 4,
"memberRate": 13.13,
"customerRate": 13.13,
- "paymentStatus": "pending",
+ "paymentStatus": "cancelled",
"projectId": 111,
"userHandle": "pshah_manager",
+ "id": "926040c4-1709-4de2-b2b6-52adf6e5e72d",
"createdBy": "00000000-0000-0000-0000-000000000000",
- "createdAt": "2021-03-30T20:13:34.670Z",
- "updatedAt": "2021-03-30T20:13:45.354Z"
+ "updatedAt": "2021-03-30T20:24:17.541Z",
+ "createdAt": "2021-03-30T20:24:17.541Z",
+ "updatedBy": "00000000-0000-0000-0000-000000000000"
}
}
\ No newline at end of file
diff --git a/test/messages/taas.workperiodpayment.create.event.json b/test/messages/taas.workperiodpayment.create.event.json
new file mode 100644
index 0000000..1ecbc84
--- /dev/null
+++ b/test/messages/taas.workperiodpayment.create.event.json
@@ -0,0 +1,18 @@
+{
+ "topic": "taas.workperiodpayment.create",
+ "originator": "taas-api",
+ "timestamp": "2021-04-09T20:10:33.770Z",
+ "mime-type": "application/json",
+ "payload": {
+ "challengeId": "00000000-0000-0000-0000-000000000000",
+ "workPeriodId": "926040c4-1709-4de2-b2b6-52adf6e5e72d",
+ "amount": 600,
+ "status": "completed",
+ "id": "09c80ee6-21be-45a4-9c3c-7ec4c75ece79",
+ "billingAccountId": 80000071,
+ "createdBy": "57646ff9-1cd3-4d3c-88ba-eb09a395366c",
+ "updatedAt": "2021-04-09T20:10:33.755Z",
+ "createdAt": "2021-04-09T20:10:33.755Z",
+ "updatedBy": null
+ }
+}
\ No newline at end of file
diff --git a/test/messages/taas.workperiodpayment.update.event.json b/test/messages/taas.workperiodpayment.update.event.json
new file mode 100644
index 0000000..ea1e859
--- /dev/null
+++ b/test/messages/taas.workperiodpayment.update.event.json
@@ -0,0 +1,18 @@
+{
+ "topic": "taas.workperiodpayment.update",
+ "originator": "taas-api",
+ "timestamp": "2021-04-09T20:12:26.994Z",
+ "mime-type": "application/json",
+ "payload": {
+ "id": "09c80ee6-21be-45a4-9c3c-7ec4c75ece79",
+ "workPeriodId": "926040c4-1709-4de2-b2b6-52adf6e5e72d",
+ "challengeId": "00000000-0000-0000-0000-000000000000",
+ "amount": 1600,
+ "status": "completed",
+ "billingAccountId": 80000071,
+ "createdBy": "57646ff9-1cd3-4d3c-88ba-eb09a395366c",
+ "updatedBy": "57646ff9-1cd3-4d3c-88ba-eb09a395366c",
+ "createdAt": "2021-04-09T20:10:33.755Z",
+ "updatedAt": "2021-04-09T20:12:26.966Z"
+ }
+}
\ No newline at end of file
diff --git a/test/unit/prepare.js b/test/unit/prepare.js
index 320b0a8..f7966ed 100644
--- a/test/unit/prepare.js
+++ b/test/unit/prepare.js
@@ -32,7 +32,7 @@ prepare(function (done) {
.reply((uri, body) => {
const id = idFromUri(uri)
if (testData.esStorage.content[id]) {
- testData.esStorage.content[id] = body.doc
+ _.assign(testData.esStorage.content[id], body.doc)
return [200]
} else {
return [404]
@@ -59,6 +59,31 @@ prepare(function (done) {
return [404]
}
})
+ .post(uri => uri.includes('_search'))
+ .query(true)
+ .reply(uri => {
+ if (Object.keys(testData.esStorage.content).length > 0) {
+ return [200, {
+ hits: {
+ total: {
+ value: 1
+ },
+ hits: [{
+ _source: testData.esStorage.content[Object.keys(testData.esStorage.content)[0]]
+ }]
+ }
+ } ]
+ } else {
+ return [200, {
+ hits: {
+ total: {
+ value: 0
+ },
+ hits: []
+ }
+ } ]
+ }
+ })
done()
}, function (done) {
nock.cleanAll()
diff --git a/test/unit/test.js b/test/unit/test.js
index 6f9ca67..b89a18c 100644
--- a/test/unit/test.js
+++ b/test/unit/test.js
@@ -15,7 +15,10 @@ const constants = require('../../src/common/constants')
const services = {
JobProcessorService: require('../../src/services/JobProcessorService'),
JobCandidateProcessorService: require('../../src/services/JobCandidateProcessorService'),
- ResourceBookingProcessorService: require('../../src/services/ResourceBookingProcessorService')
+ ResourceBookingProcessorService: require('../../src/services/ResourceBookingProcessorService'),
+ WorkPeriodProcessorService: require('../../src/services/WorkPeriodProcessorService'),
+ WorkPeriodPaymentProcessorService: require('../../src/services/WorkPeriodPaymentProcessorService'),
+ ActionProcessorService: require('../../src/services/ActionProcessorService')
}
// random transaction id here
@@ -48,58 +51,193 @@ describe('General Logic Tests', () => {
sandbox.restore()
})
+ describe('Job, JobCandidate, ResourceBooking tests', () => {
+ for (const [index, model] of [
+ [config.esConfig.ES_INDEX_JOB, 'Job'],
+ [config.esConfig.ES_INDEX_JOB_CANDIDATE, 'JobCandidate'],
+ [config.esConfig.ES_INDEX_RESOURCE_BOOKING, 'ResourceBooking']
+ ]) {
+ const modelInSpaceCase = stringcase.spacecase(model)
+ it(`processCreate - ${modelInSpaceCase} success`, async () => {
+ await services[`${model}ProcessorService`].processCreate(testData.messages[model].create.message, transactionId)
+ should.deepEqual(
+ testData.esStorage.content[testData.messages[model].create.message.payload.id],
+ testData.messages[model].create.message.payload
+ )
+ })
+
+ it(`processUpdate - ${modelInSpaceCase} success`, async () => {
+ await testHelper.esClient.create({
+ index,
+ id: testData.messages[model].create.message.payload.id,
+ body: testData.messages[model].create.message.payload,
+ refresh: 'true'
+ })
+ await services[`${model}ProcessorService`].processUpdate(testData.messages[model].update.message, transactionId)
+ should.deepEqual(
+ testData.esStorage.content[testData.messages[model].create.message.payload.id],
+ testData.messages[model].update.message.payload
+ )
+ })
- for (const [index, model] of [
- [config.esConfig.ES_INDEX_JOB, 'Job'],
- [config.esConfig.ES_INDEX_JOB_CANDIDATE, 'JobCandidate'],
- [config.esConfig.ES_INDEX_RESOURCE_BOOKING, 'ResourceBooking']
- ]) {
+ it(`processDelete - ${modelInSpaceCase} success`, async () => {
+ await testHelper.esClient.create({
+ index,
+ id: testData.messages[model].create.message.payload.id,
+ body: testData.messages[model].create.message.payload,
+ refresh: 'true'
+ })
+ await services[`${model}ProcessorService`].processDelete(testData.messages[model].delete.message, transactionId)
+ should.not.exist(testData.esStorage.content[testData.messages[model].create.message.payload.id])
+ })
+
+ it(`Failure - processCreate - ${modelInSpaceCase} already exists`, async () => {
+ await testHelper.esClient.create({
+ index,
+ id: testData.messages[model].create.message.payload.id,
+ body: testData.messages[model].create.message.payload,
+ refresh: 'true'
+ })
+ try {
+ await services[`${model}ProcessorService`].processCreate(testData.messages[model].create.message, transactionId)
+ throw new Error()
+ } catch (err) {
+ should.equal(err.message, `id: ${testData.messages[model].create.message.payload.id} "${index}" already exists`)
+ }
+ })
+
+ it(`Failure - processUpdate - ${modelInSpaceCase} not found`, async () => {
+ try {
+ await services[`${model}ProcessorService`].processUpdate(testData.messages[model].update.message, transactionId)
+ throw new Error()
+ } catch (err) {
+ should.equal(err.message, `id: ${testData.messages[model].update.message.payload.id} "${index}" not found`)
+ }
+ })
+
+ it(`Failure - processDelete - ${modelInSpaceCase} not found`, async () => {
+ try {
+ await services[`${model}ProcessorService`].processDelete(testData.messages[model].delete.message, transactionId)
+ throw new Error()
+ } catch (err) {
+ should.equal(err.message, `id: ${testData.messages[model].delete.message.payload.id} "${index}" not found`)
+ }
+ })
+ }
+ })
+ describe('Nested WorkPeriod tests', () => {
+ const index = config.esConfig.ES_INDEX_RESOURCE_BOOKING
+ const model = 'WorkPeriod'
+ const parentModel = 'ResourceBooking'
+ const nestedName = 'workPeriods'
const modelInSpaceCase = stringcase.spacecase(model)
- it('processCreate - success', async () => {
+ it(`processCreate - ${modelInSpaceCase} success`, async () => {
+ await testHelper.esClient.create({
+ index,
+ id: testData.messages[parentModel].create.message.payload.id,
+ body: testData.messages[parentModel].create.message.payload,
+ refresh: 'true'
+ })
await services[`${model}ProcessorService`].processCreate(testData.messages[model].create.message, transactionId)
should.deepEqual(
- testData.esStorage.content[testData.messages[model].create.message.payload.id],
- testData.messages[model].create.message.payload
+ testData.esStorage.content[testData.messages[parentModel].create.message.payload.id],
+ _.assign(testData.messages[parentModel].create.message.payload, { [nestedName]: [testData.messages[model].create.message.payload] })
)
})
- it('processUpdate - success', async () => {
+ it(`processUpdate - ${modelInSpaceCase} success`, async () => {
await testHelper.esClient.create({
index,
- id: testData.messages[model].create.message.payload.id,
- body: testData.messages[model].create.message.payload,
+ id: testData.messages[parentModel].create.message.payload.id,
+ body: _.assign(testData.messages[parentModel].create.message.payload, { [nestedName]: [testData.messages[model].create.message.payload] }),
refresh: 'true'
})
await services[`${model}ProcessorService`].processUpdate(testData.messages[model].update.message, transactionId)
should.deepEqual(
- testData.esStorage.content[testData.messages[model].create.message.payload.id],
- testData.messages[model].update.message.payload
+ testData.esStorage.content[testData.messages[parentModel].create.message.payload.id],
+ _.assign(testData.messages[parentModel].create.message.payload, { [nestedName]: [testData.messages[model].update.message.payload] })
)
})
- it('processDelete - success', async () => {
+ it(`processDelete - ${modelInSpaceCase} success`, async () => {
await testHelper.esClient.create({
index,
- id: testData.messages[model].create.message.payload.id,
- body: testData.messages[model].create.message.payload,
+ id: testData.messages[parentModel].create.message.payload.id,
+ body: _.assign(testData.messages[parentModel].create.message.payload, { [nestedName]: [testData.messages[model].create.message.payload] }),
refresh: 'true'
})
await services[`${model}ProcessorService`].processDelete(testData.messages[model].delete.message, transactionId)
- should.not.exist(testData.esStorage.content[testData.messages[model].create.message.payload.id])
+ should.deepEqual(
+ testData.esStorage.content[testData.messages[parentModel].create.message.payload.id],
+ _.assign(testData.messages[parentModel].create.message.payload, { [nestedName]: [] })
+ )
+ })
+ it(`Failure - processCreate - ${modelInSpaceCase} not found`, async () => {
+ const processCreateStub = sandbox.stub(services.ActionProcessorService, 'processCreate').callsFake(() => {})
+ await services[`${model}ProcessorService`].processCreate(testData.messages[model].create.message, transactionId)
+ should.equal(processCreateStub.getCall(0).args[0], testData.messages[model].create.topic)
+ })
+
+ it(`Failure - processUpdate - ${modelInSpaceCase} not found`, async () => {
+ try {
+ await services[`${model}ProcessorService`].processUpdate(testData.messages[model].update.message, transactionId)
+ throw new Error()
+ } catch (err) {
+ should.equal(err.message, `id: ${testData.messages[model].update.message.payload.id} "${model}" not found`)
+ }
})
- it(`Failure - processCreate - ${modelInSpaceCase} already exists`, async () => {
+ it(`Failure - processDelete - ${modelInSpaceCase} not found`, async () => {
+ try {
+ await services[`${model}ProcessorService`].processDelete(testData.messages[model].delete.message, transactionId)
+ throw new Error()
+ } catch (err) {
+ should.equal(err.message, `id: ${testData.messages[model].delete.message.payload.id} "${model}" not found`)
+ }
+ })
+ })
+ describe('Nested WorkPeriodPayment tests', () => {
+ const index = config.esConfig.ES_INDEX_RESOURCE_BOOKING
+ const model = 'WorkPeriodPayment'
+ const parentModel = 'WorkPeriod'
+ const rootModel = 'ResourceBooking'
+ const nestedName = 'payments'
+ const parentNestedName = 'workPeriods'
+ const modelInSpaceCase = stringcase.spacecase(model)
+ it(`processCreate - ${modelInSpaceCase} success`, async () => {
+ await testHelper.esClient.create({
+ index,
+ id: testData.messages[rootModel].create.message.payload.id,
+ body: _.assign(testData.messages[rootModel].create.message.payload, { [parentNestedName]: [testData.messages[parentModel].create.message.payload] }),
+ refresh: 'true'
+ })
+ await services[`${model}ProcessorService`].processCreate(testData.messages[model].create.message, transactionId)
+ should.deepEqual(
+ testData.esStorage.content[testData.messages[rootModel].create.message.payload.id],
+ _.assign(testData.messages[rootModel].create.message.payload, { [parentNestedName]: [_.assign(testData.messages[parentModel].create.message.payload, { [nestedName]: [testData.messages[model].create.message.payload] })] })
+ )
+ })
+
+ it(`processUpdate - ${modelInSpaceCase} success`, async () => {
await testHelper.esClient.create({
index,
- id: testData.messages[model].create.message.payload.id,
- body: testData.messages[model].create.message.payload,
+ id: testData.messages[rootModel].create.message.payload.id,
+ body: _.assign(testData.messages[rootModel].create.message.payload, { [parentNestedName]: [_.assign(testData.messages[parentModel].create.message.payload, { [nestedName]: [testData.messages[model].create.message.payload] })] }),
refresh: 'true'
})
+ await services[`${model}ProcessorService`].processUpdate(testData.messages[model].update.message, transactionId)
+ should.deepEqual(
+ testData.esStorage.content[testData.messages[rootModel].create.message.payload.id],
+ _.assign(testData.messages[rootModel].create.message.payload, { [parentNestedName]: [_.assign(testData.messages[parentModel].create.message.payload, { [nestedName]: [testData.messages[model].update.message.payload] })] })
+ )
+ })
+
+ it(`Failure - processCreate - ${modelInSpaceCase} not found`, async () => {
try {
await services[`${model}ProcessorService`].processCreate(testData.messages[model].create.message, transactionId)
throw new Error()
} catch (err) {
- should.equal(err.message, `id: ${testData.messages[model].create.message.payload.id} "${index}" already exists`)
+ should.equal(err.message, `id: ${testData.messages[parentModel].create.message.payload.id} "${parentModel}" not found`)
}
})
@@ -108,19 +246,10 @@ describe('General Logic Tests', () => {
await services[`${model}ProcessorService`].processUpdate(testData.messages[model].update.message, transactionId)
throw new Error()
} catch (err) {
- should.equal(err.message, `id: ${testData.messages[model].update.message.payload.id} "${index}" not found`)
- }
- })
-
- it(`Failure - processDelete - ${modelInSpaceCase} not found`, async () => {
- try {
- await services[`${model}ProcessorService`].processDelete(testData.messages[model].delete.message, transactionId)
- throw new Error()
- } catch (err) {
- should.equal(err.message, `id: ${testData.messages[model].delete.message.payload.id} "${index}" not found`)
+ should.equal(err.message, `id: ${testData.messages[model].update.message.payload.id} "${model}" not found`)
}
})
- }
+ })
})
describe('Zapier Logic Tests', () => {
@@ -158,10 +287,10 @@ describe('Zapier Logic Tests', () => {
})
describe('Job Candidate Update', () => {
- it('should post to Zapier if status is changed to "rejected"', async () => {
+ it('should post to Zapier if status is changed to "client rejected - screening"', async () => {
const previousData = _.assign({}, testData.messages.JobCandidate.create.message.payload, { status: 'open', externalId: '123' })
const updateMessage = _.assign({}, testData.messages.JobCandidate.update.message, {
- payload: _.assign({}, testData.messages.JobCandidate.update.message.payload, { status: 'rejected', externalId: '123' })
+ payload: _.assign({}, testData.messages.JobCandidate.update.message.payload, { status: 'client rejected - screening', externalId: '123' })
})
await testHelper.esClient.create({
@@ -181,10 +310,10 @@ describe('Zapier Logic Tests', () => {
helper.postMessageViaWebhook.callCount.should.equal(1)
})
- it('should post to Zapier if status is changed to "shortlist"', async () => {
+ it('should post to Zapier if status is changed to "interview"', async () => {
const previousData = _.assign({}, testData.messages.JobCandidate.create.message.payload, { status: 'open', externalId: '123' })
const updateMessage = _.assign({}, testData.messages.JobCandidate.update.message, {
- payload: _.assign({}, testData.messages.JobCandidate.update.message.payload, { status: 'shortlist', externalId: '123' })
+ payload: _.assign({}, testData.messages.JobCandidate.update.message.payload, { status: 'interview', externalId: '123' })
})
await testHelper.esClient.create({
@@ -204,10 +333,10 @@ describe('Zapier Logic Tests', () => {
helper.postMessageViaWebhook.callCount.should.equal(1)
})
- it('should not post to Zapier if status was already "rejected"', async () => {
- const previousData = _.assign({}, testData.messages.JobCandidate.create.message.payload, { status: 'rejected', externalId: '123' })
+ it('should not post to Zapier if status was already "client rejected - screening"', async () => {
+ const previousData = _.assign({}, testData.messages.JobCandidate.create.message.payload, { status: 'client rejected - screening', externalId: '123' })
const updateMessage = _.assign({}, testData.messages.JobCandidate.update.message, {
- payload: _.assign({}, testData.messages.JobCandidate.update.message.payload, { status: 'rejected', externalId: '123' })
+ payload: _.assign({}, testData.messages.JobCandidate.update.message.payload, { status: 'client rejected - screening', externalId: '123' })
})
await testHelper.esClient.create({
@@ -227,10 +356,10 @@ describe('Zapier Logic Tests', () => {
helper.postMessageViaWebhook.callCount.should.equal(0)
})
- it('should not post to Zapier if status was already "shortlist"', async () => {
- const previousData = _.assign({}, testData.messages.JobCandidate.create.message.payload, { status: 'shortlist', externalId: '123' })
+ it('should not post to Zapier if status was already "interview"', async () => {
+ const previousData = _.assign({}, testData.messages.JobCandidate.create.message.payload, { status: 'interview', externalId: '123' })
const updateMessage = _.assign({}, testData.messages.JobCandidate.update.message, {
- payload: _.assign({}, testData.messages.JobCandidate.update.message.payload, { status: 'shortlist', externalId: '123' })
+ payload: _.assign({}, testData.messages.JobCandidate.update.message.payload, { status: 'interview', externalId: '123' })
})
await testHelper.esClient.create({
@@ -250,10 +379,10 @@ describe('Zapier Logic Tests', () => {
helper.postMessageViaWebhook.callCount.should.equal(0)
})
- it('should not post to Zapier if status is changed to "interview" (not "rejected" or "shortlist")', async () => {
+ it('should not post to Zapier if status is changed to "placed" (not "rejected" or "shortlist")', async () => {
const previousData = _.assign({}, testData.messages.JobCandidate.create.message.payload, { status: 'open', externalId: '123' })
const updateMessage = _.assign({}, testData.messages.JobCandidate.update.message, {
- payload: _.assign({}, testData.messages.JobCandidate.update.message.payload, { status: 'interview', externalId: '123' })
+ payload: _.assign({}, testData.messages.JobCandidate.update.message.payload, { status: 'placed', externalId: '123' })
})
await testHelper.esClient.create({