diff --git a/README.md b/README.md index 5c3af8d..94b5f2b 100755 --- a/README.md +++ b/README.md @@ -2,10 +2,11 @@ ## Install software -- node 12.x -- npm 6.x +- node 12.x+ +- npm 6.x+ - docker -- elasticsearch 7.7 +- elasticsearch 7.7+ +- PostgreSQL ## Configuration @@ -13,15 +14,17 @@ Configuration for the application is at config/default.js and config/production. - LOG_LEVEL: the log level - PORT: the server port +- CASCADE_PAUSE_MS: how many milliseconds to pause between deleting records during cascade delete (default to 1000) - AUTH_SECRET: TC Authentication secret - VALID_ISSUERS: valid issuers for TC authentication - PAGE_SIZE: the default pagination limit - MAX_PAGE_SIZE: the maximum pagination size - API_VERSION: the API version -- AWS_ACCESS_KEY_ID: The AWS access key -- AWS_SECRET_ACCESS_KEY: The AWS secret key -- AWS_REGION: The Amazon region to use when connecting. -- DATABASE: The QLDB ledger name +- DB_NAME: the database name +- DB_USERNAME: the database username +- DB_PASSWORD: the database password +- DB_HOST: the database port +- DB_PORT: the database port - AUTH0_URL: Auth0 URL, used to get TC M2M token - AUTH0_AUDIENCE: Auth0 audience, used to get TC M2M token - TOKEN_CACHE_TIME: Auth0 token cache time, used to get TC M2M token @@ -57,26 +60,24 @@ For `ES.DOCUMENTS` configuration, you will find multiple other configurations be Setup your Elasticsearch instance and ensure that it is up and running. -1. Visit [this link](https://console.aws.amazon.com/qldb/home?region=us-east-1#gettingStarted), login and create one **ledger** databases named `ubahn-db` -2. Visit [this link](https://console.aws.amazon.com/iam/home?region=us-east-1#/security_credentials) to download your "Access keys" -3. Follow *Configuration* section to update config values, like database, aws key/secret etc .. -4. Goto *UBahn-api*, run `npm i` and `npm run lint` -5. Import mock data, `node scripts/db/genData.js`, this will create tables and gen some data for test (if you need this) -6. Startup server `node app.js` or `npm run start` +1. Follow *Configuration* section to update config values, like database, etc .. +2. Goto *UBahn-api*, run `npm i` and `npm run lint` +3. Import mock data, `node scripts/db/genData.js`, this will gen some data for test (if you need this) +4. Startup server `node app.js` or `npm run start` ## Working with mock data -You can use the scripts `npm run insert-data` (and `npm run delete-data`) to insert mock data (and delete mock data respectively). The data is inserted into QLDB and Elasticsearch. You need to setup the configurations beforehand and also start the elasticsearch instance before you run these scripts +You can use the scripts `npm run migrations up` (and `npm run migrations down`) to insert mock data (and delete mock data respectively). The data is inserted into Postgres and Elasticsearch. You need to setup the configurations beforehand and also start the elasticsearch instance before you run these scripts. You can run the script `npm run migrate-db-to-es` to dump data in db into es. ## Local Deployment with Docker -Make sure all config values are right(aws key and secret), and you can run on local successful, then run below commands +Make sure all config values are right, and you can run on local successfully, then run below commands 1. Navigate to the directory `docker` -2. Rename the file `sample.api.env` to `api.env` +2. Rename the file `sample.env` to `.env` -3. Set the required AUTH0 configurations, AWS credentials and ElasticSearch host in the file `api.env` +3. Set the required AUTH0 configurations, DB configurations and ElasticSearch host in the file `.env` 4. Once that is done, run the following command @@ -86,128 +87,6 @@ Make sure all config values are right(aws key and secret), and you can run on lo 5. When you are running the application for the first time, It will take some time initially to download the image and install the dependencies -## API endpoints verification - -1. open postman -2. import *docs/UBahn_API.postman_collection.json* , *UBahn_ENV.postman_environment.json* and then check endpoints - -## Test token - -you can use below token to test role and permissions +## Verification -### 01 Topcoder User - -- payload - - ```json - { - "roles": [ - "Topcoder User" - ], - "iss": "https://api.topcoder.com", - "handle": "tc-user", - "exp": 1685571460, - "userId": "23166766", - "iat": 1585570860, - "email": "tc-user@gmail.com", - "jti": "0f1ef1d3-2b33-4900-bb43-48f2285f9627" - } - ``` - -- token - - ```text - eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRjLXVzZXIiLCJleHAiOjE2ODU1NzE0NjAsInVzZXJJZCI6IjIzMTY2NzY2IiwiaWF0IjoxNTg1NTcwODYwLCJlbWFpbCI6InRjLXVzZXJAZ21haWwuY29tIiwianRpIjoiMGYxZWYxZDMtMmIzMy00OTAwLWJiNDMtNDhmMjI4NWY5NjI3In0.eBhXqSBe8zMRg2nBeGeZDgKiJdAYs0zOMzGfJCjWfcs - ``` - -#### 02 Copilot - -- payload - - ```json - { - "roles": [ - "Topcoder User","Copilot" - ], - "iss": "https://api.topcoder.com", - "handle": "tc-Copilot", - "exp": 1685571460, - "userId": "23166767", - "iat": 1585570860, - "email": "tc-Copilot@gmail.com", - "jti": "0f1ef1d3-2b33-4900-bb43-48f2285f9628" - } - ``` - -- token - - ```json - eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiQ29waWxvdCJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci5jb20iLCJoYW5kbGUiOiJ0Yy1Db3BpbG90IiwiZXhwIjoxNjg1NTcxNDYwLCJ1c2VySWQiOiIyMzE2Njc2NyIsImlhdCI6MTU4NTU3MDg2MCwiZW1haWwiOiJ0Yy1Db3BpbG90QGdtYWlsLmNvbSIsImp0aSI6IjBmMWVmMWQzLTJiMzMtNDkwMC1iYjQzLTQ4ZjIyODVmOTYyOCJ9.gP5JqJGCnOjO_gYs2r3-AQt5x8YIym15m3t43603cgc - ``` - -#### 03 Admin - -- payload - - ```json - { - "roles": [ - "Topcoder User","Copilot","Admin" - ], - "iss": "https://api.topcoder.com", - "handle": "tc-Admin", - "exp": 1685571460, - "userId": "23166768", - "iat": 1585570860, - "email": "tc-Admin@gmail.com", - "jti": "0f1ef1d3-2b33-4900-bb43-48f2285f9630" - } - ``` - -- token - - ```json - eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiQ29waWxvdCIsIkFkbWluIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRjLUFkbWluIiwiZXhwIjoxNjg1NTcxNDYwLCJ1c2VySWQiOiIyMzE2Njc2OCIsImlhdCI6MTU4NTU3MDg2MCwiZW1haWwiOiJ0Yy1BZG1pbkBnbWFpbC5jb20iLCJqdGkiOiIwZjFlZjFkMy0yYjMzLTQ5MDAtYmI0My00OGYyMjg1Zjk2MzAifQ.eR97kePT0Gu-t7vUE0Ed8A88Dnmtgebyml2jrRyxhOk - ``` - -#### M2M token 01 - -- payload, this token missing `all:usersSkill`, so all endpoints in usersSkill group will return 403 - - ```json - { - "scopes": "all:user all:role all:skill all:usersRole all:organization all:skillsProvider", - "iss": "https://api.topcoder.com", - "handle":"tc-mm-01", - "exp": 1685571460, - "iat": 1585570860, - "jti": "0f1ef1d3-2b33-4900-bb43-48f2285f9630" - } - ``` - -- token - - ```json - eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzY29wZXMiOiJhbGw6dXNlciBhbGw6cm9sZSBhbGw6c2tpbGwgYWxsOnVzZXJzUm9sZSBhbGw6b3JnYW5pemF0aW9uIGFsbDpza2lsbHNQcm92aWRlciIsImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRjLW1tLTAxIiwiZXhwIjoxNjg1NTcxNDYwLCJpYXQiOjE1ODU1NzA4NjAsImp0aSI6IjBmMWVmMWQzLTJiMzMtNDkwMC1iYjQzLTQ4ZjIyODVmOTYzMCJ9.BlDIYsCTcHTib9XhpyzpO-KkMTTMy0egq_7qlLWRmoM - ``` - -#### M2M token 02 - -- payload, this token contains scope, can request all endpoints - - ```json - { - "scopes": "all:user all:role all:skill all:usersRole all:organization all:skillsProvider all:usersSkill all:externalProfile all:achievementsProvider all:achievement all:attributeGroup all:attribute all:userAttribute", - "iss": "https://api.topcoder.com", - "handle": "tc-mm-02", - "exp": 1685571460, - "iat": 1585570860, - "jti": "0f1ef1d3-2b33-4900-bb43-48f2285f9630" - } - ``` - -- token - - ```json - eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzY29wZXMiOiJhbGw6dXNlciBhbGw6cm9sZSBhbGw6c2tpbGwgYWxsOnVzZXJzUm9sZSBhbGw6b3JnYW5pemF0aW9uIGFsbDpza2lsbHNQcm92aWRlciBhbGw6dXNlcnNTa2lsbCBhbGw6ZXh0ZXJuYWxQcm9maWxlIGFsbDphY2hpZXZlbWVudHNQcm92aWRlciBhbGw6YWNoaWV2ZW1lbnQgYWxsOmF0dHJpYnV0ZUdyb3VwIGFsbDphdHRyaWJ1dGUgYWxsOnVzZXJBdHRyaWJ1dGUiLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci5jb20iLCJoYW5kbGUiOiJ0Yy1tbS0wMiIsImV4cCI6MTY4NTU3MTQ2MCwiaWF0IjoxNTg1NTcwODYwLCJqdGkiOiIwZjFlZjFkMy0yYjMzLTQ5MDAtYmI0My00OGYyMjg1Zjk2MzAifQ.8XJahLdv9mkgkL7EsOwsf8uKg4J9u-1UM73pvZ9n3JY - ``` +See `Verification.md` diff --git a/VERIFICATION.md b/VERIFICATION.md index 5d6c7f9..0ecfbca 100644 --- a/VERIFICATION.md +++ b/VERIFICATION.md @@ -1,3 +1,6 @@ + + + ## Verification The verification needs data in ES, there are two ways to populate data to ES. diff --git a/config/default.js b/config/default.js index 2d48677..e4bd562 100755 --- a/config/default.js +++ b/config/default.js @@ -4,7 +4,9 @@ module.exports = { LOG_LEVEL: process.env.LOG_LEVEL || 'debug', - PORT: process.env.PORT || 3001, + PORT: process.env.PORT || 3002, + + CASCADE_PAUSE_MS: process.env.CASCADE_PAUSE_MS || 1000, AUTH_SECRET: process.env.AUTH_SECRET || 'CLIENT_SECRET', VALID_ISSUERS: process.env.VALID_ISSUERS ? process.env.VALID_ISSUERS.replace(/\\"/g, '') @@ -14,10 +16,11 @@ module.exports = { MAX_PAGE_SIZE: parseInt(process.env.MAX_PAGE_SIZE) || 100, API_VERSION: process.env.API_VERSION || 'api/1.0', - AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID, - AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY, - AWS_REGION: process.env.AWS_REGION || 'us-east-1', - DATABASE: process.env.DATABASE || 'ubahn-db', + DB_NAME: process.env.DB_NAME || 'ubahn-db', + DB_USERNAME: process.env.DB_USER || 'postgres', + DB_PASSWORD: process.env.DB_PASSWORD || 'password', + DB_HOST: process.env.DB_HOST || 'localhost', + DB_PORT: process.env.DB_PORT || 5432, AUTH0_URL: process.env.AUTH0_URL, AUTH0_AUDIENCE: process.env.AUTH0_AUDIENCE, diff --git a/docker/Dockerfile b/docker/Dockerfile index 1a27218..c6fef8c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -9,4 +9,9 @@ WORKDIR /ubahn_api # Install the dependencies from package.json RUN npm install + +# Expose port +EXPOSE 3002 + +# start api CMD npm start diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index de4804b..2d38bb3 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,11 +1,23 @@ +# TODO - Update BEFORE merge + version: '3' services: - ubahn_api: - image: ubahn_api:latest - build: - context: ../ - dockerfile: docker/Dockerfile - env_file: - - api.env + postgres: + image: "postgres:13.1" + volumes: + - database-data:/var/lib/postgresql/data/ + ports: + - "5432:5432" + environment: + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_USER: ${DB_USERNAME} + POSTGRES_DB: ${DB_NAME} + esearch: + image: elasticsearch:7.7.1 + container_name: ubahn-data-processor-es_es ports: - - "3001:3001" + - "9200:9200" + environment: + - discovery.type=single-node +volumes: + database-data: diff --git a/docker/sample.api.env b/docker/sample.env similarity index 66% rename from docker/sample.api.env rename to docker/sample.env index 14ff185..3e3ad37 100644 --- a/docker/sample.api.env +++ b/docker/sample.env @@ -1,5 +1,9 @@ -AWS_ACCESS_KEY_ID= -AWS_SECRET_ACCESS_KEY= +DB_NAME=ubahn-db +DB_USERNAME=postgres +DB_PASSWORD= +DB_HOST=postgres +DB_PORT=5432 + ES_HOST= AUTH0_URL= diff --git a/package-lock.json b/package-lock.json index a2b1144..bf6b5f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -256,16 +256,6 @@ "uri-js": "^4.2.2" } }, - "amazon-qldb-driver-nodejs": { - "version": "0.1.1-preview.2", - "resolved": "https://registry.npmjs.org/amazon-qldb-driver-nodejs/-/amazon-qldb-driver-nodejs-0.1.1-preview.2.tgz", - "integrity": "sha512-ia1benMLRS25KWDbg3d6MhX48+2rTUZzQ3LXM4mxu0/1V9ooUFKfZeyCmueZ1zrqn572Z8uO8DPq/1T1TZsI4Q==", - "requires": { - "@types/node": "12.0.2", - "ion-hash-js": "^1.0.2", - "semaphore-async-await": "^1.5.1" - } - }, "ansi-escapes": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", @@ -298,6 +288,11 @@ "color-convert": "^1.9.0" } }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -354,29 +349,6 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, - "aws-sdk": { - "version": "2.668.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.668.0.tgz", - "integrity": "sha512-mmZJmeenNM9hRR4k+JAStBhYFym2+VCPTRWv0Vn2oqqXIaIaNVdNf9xag/WMG8b8M80R3XXfVHKmDPST0/EfHA==", - "requires": { - "buffer": "4.9.1", - "events": "1.1.1", - "ieee754": "1.1.13", - "jmespath": "0.15.0", - "querystring": "0.2.0", - "sax": "1.2.1", - "url": "0.10.3", - "uuid": "3.3.2", - "xml2js": "0.4.19" - }, - "dependencies": { - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - } - } - }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -416,11 +388,6 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, - "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" - }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -429,6 +396,11 @@ "tweetnacl": "^0.14.3" } }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, "body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", @@ -455,21 +427,16 @@ "concat-map": "0.0.1" } }, - "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" }, + "buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + }, "bunyan": { "version": "1.8.14", "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.14.tgz", @@ -492,6 +459,11 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -529,11 +501,59 @@ "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", "dev": true }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, "codependency": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/codependency/-/codependency-0.1.4.tgz", @@ -713,6 +733,11 @@ "integrity": "sha1-IwdjLUwEOCuN+KMvcLiVBG1SdF8=", "dev": true }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, "decompress-response": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", @@ -806,6 +831,11 @@ "esutils": "^2.0.2" } }, + "dottie": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", + "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" + }, "dtrace-provider": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", @@ -873,7 +903,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "requires": { "is-arrayish": "^0.2.1" }, @@ -881,8 +910,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" } } }, @@ -1228,11 +1256,6 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, - "events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" - }, "express": { "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", @@ -1472,6 +1495,16 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "generic-pool": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.3.tgz", + "integrity": "sha1-eAw29p360FpaBF3Te+etyhGk9v8=" + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + }, "get-parameter-names": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/get-parameter-names/-/get-parameter-names-0.3.0.tgz", @@ -1525,8 +1558,7 @@ "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" }, "har-schema": { "version": "2.0.0", @@ -1584,8 +1616,7 @@ "hosted-git-info": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", - "dev": true + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" }, "http-errors": { "version": "1.7.2", @@ -1666,11 +1697,6 @@ "safer-buffer": ">= 2.1.2 < 3" } }, - "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" - }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -1693,6 +1719,11 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "inflection": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", + "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1789,13 +1820,10 @@ } } }, - "ion-hash-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ion-hash-js/-/ion-hash-js-1.0.3.tgz", - "integrity": "sha512-c1U7DW9icOrof1cfzktq/8zaPXLneDJqZhYydhTmNBbHJ+D6JHTFydp5fu+PlwirgEt6qB+wPUcaVdJPQgqKug==", - "requires": { - "ion-js": "^3.1.2" - } + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" }, "ion-js": { "version": "3.1.2", @@ -1882,6 +1910,11 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -1913,11 +1946,6 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, - "jmespath": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/jmespath/-/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", @@ -1928,6 +1956,11 @@ "topo": "3.x.x" } }, + "js-string-escape": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", + "integrity": "sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2091,6 +2124,14 @@ "colornames": "^1.1.1" } }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "requires": { + "invert-kv": "^1.0.0" + } + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -2133,6 +2174,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" + }, "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -2294,6 +2340,14 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.25.1.tgz", "integrity": "sha512-nRKMf9wDS4Fkyd0C9LXh2FFXinD+iwbJ5p/lh3CHitW9kZbRbJ8hCruiadiIXZVbeAqKZzqcTvHnK3mRhFjb6w==" }, + "moment-timezone": { + "version": "0.5.32", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.32.tgz", + "integrity": "sha512-Z8QNyuQHQAmWucp8Knmgei8YNo28aLjJq6Ma+jy1ZSpSk5nyfRT8xgUbSQvD2+2UajISfenndwvFuH3NGS+nvA==", + "requires": { + "moment": ">= 2.9.0" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -2357,7 +2411,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, "requires": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -2365,6 +2418,11 @@ "validate-npm-package-license": "^3.0.1" } }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -2479,6 +2537,14 @@ "word-wrap": "~1.2.3" } }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "^1.0.0" + } + }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -2509,6 +2575,11 @@ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, + "packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -2522,7 +2593,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, "requires": { "error-ex": "^1.2.0" } @@ -2552,8 +2622,7 @@ "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-to-regexp": { "version": "0.1.7", @@ -2574,11 +2643,164 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, + "pg": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.5.1.tgz", + "integrity": "sha512-9wm3yX9lCfjvA98ybCyw2pADUivyNWT/yIP4ZcDVpMN0og70BUWYEGXPCTAQdGTAqnytfRADb7NERrY1qxhIqw==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.4.0", + "pg-pool": "^3.2.2", + "pg-protocol": "^1.4.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + } + }, + "pg-connection-string": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.4.0.tgz", + "integrity": "sha512-3iBXuv7XKvxeMrIgym7njT+HlZkwZqqGX4Bu9cci8xHZNT+Um1gWKqCsAzcC0d95rcKMU5WBg6YRUcHyV0HZKQ==" + }, + "pg-hstore": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/pg-hstore/-/pg-hstore-2.3.3.tgz", + "integrity": "sha512-qpeTpdkguFgfdoidtfeTho1Q1zPVPbtMHgs8eQ+Aan05iLmIs3Z3oo5DOZRclPGoQ4i68I1kCtQSJSa7i0ZVYg==", + "requires": { + "underscore": "^1.7.0" + } + }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-pool": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.2.2.tgz", + "integrity": "sha512-ORJoFxAlmmros8igi608iVEbQNNZlp89diFVx6yV5v+ehmpMY9sK6QgpmgoXbmkNaBAx8cOOZh9g80kJv1ooyA==" + }, + "pg-protocol": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.4.0.tgz", + "integrity": "sha512-El+aXWcwG/8wuFICMQjM5ZSAm6OWiJicFdNYo+VY3QP+8vI4SvLIWVe51PppTzMhikUJR+PsyIFKqfdXPz/yxA==" + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "pgpass": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.4.tgz", + "integrity": "sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w==", + "requires": { + "split2": "^3.1.1" + } + }, + "pgtools": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/pgtools/-/pgtools-0.3.0.tgz", + "integrity": "sha512-8NxDCJ8xJ6hOp9hVNZqxi+TZl7hM1Jc8pQyj8DlAbyaWnk5OsGwf3gB/UyDODdOguiim9QzbzPsslp//apO+Uw==", + "requires": { + "bluebird": "^3.3.5", + "pg": "^6.1.0", + "pg-connection-string": "^0.1.3", + "yargs": "^5.0.0" + }, + "dependencies": { + "buffer-writer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-1.0.1.tgz", + "integrity": "sha1-Iqk2kB4wKa/NdUfrRIfOtpejvwg=" + }, + "object-assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", + "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=" + }, + "packet-reader": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-0.3.1.tgz", + "integrity": "sha1-zWLmCvjX/qinBexP+ZCHHEaHHyc=" + }, + "pg": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/pg/-/pg-6.4.2.tgz", + "integrity": "sha1-w2QBEGDqx6UHoq4GPrhX7OkQ4n8=", + "requires": { + "buffer-writer": "1.0.1", + "js-string-escape": "1.0.1", + "packet-reader": "0.3.1", + "pg-connection-string": "0.1.3", + "pg-pool": "1.*", + "pg-types": "1.*", + "pgpass": "1.*", + "semver": "4.3.2" + } + }, + "pg-connection-string": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz", + "integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=" + }, + "pg-pool": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-1.8.0.tgz", + "integrity": "sha1-9+xzgkw3oD8Hb1G/33DjQBR8Tzc=", + "requires": { + "generic-pool": "2.4.3", + "object-assign": "4.1.0" + } + }, + "pg-types": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.13.0.tgz", + "integrity": "sha512-lfKli0Gkl/+za/+b6lzENajczwZHc7D5kiUCZfgm914jipD2kIOIvEkAhZ8GrW3/TUoP9w8FHjwpPObBye5KQQ==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~1.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.0", + "postgres-interval": "^1.1.0" + } + }, + "postgres-array": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.3.tgz", + "integrity": "sha512-5wClXrAP0+78mcsNX3/ithQ5exKvCyK5lr5NEEEeGwwM6NJdQgzIJBVxLvRW+huFpX92F2QnZ5CcokH0VhK2qQ==" + }, + "semver": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", + "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" + } + } + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "^2.0.0" + } }, "pkg-conf": { "version": "3.1.0", @@ -2690,6 +2912,29 @@ "find-up": "^2.1.0" } }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" + }, + "postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "requires": { + "xtend": "^4.0.0" + } + }, "precond": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz", @@ -2751,11 +2996,6 @@ "once": "^1.3.1" } }, - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" - }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -2907,11 +3147,20 @@ } } }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + }, "resolve": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, "requires": { "path-parse": "^1.0.6" } @@ -2932,6 +3181,14 @@ "signal-exit": "^3.0.2" } }, + "retry-as-promised": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", + "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", + "requires": { + "any-promise": "^1.3.0" + } + }, "rimraf": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", @@ -2978,21 +3235,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "sax": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", - "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" - }, "secure-json-parse": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.1.0.tgz", "integrity": "sha512-GckO+MS/wT4UogDyoI/H/S1L0MCcKS1XX/vp48wfmU7Nw4woBmb8mIpu4zPBQjKlRT88/bt9xdoV4111jPpNJA==" }, - "semaphore-async-await": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/semaphore-async-await/-/semaphore-async-await-1.5.1.tgz", - "integrity": "sha1-hXvvXjZEYBykuVcLh+nfXKEpdPo=" - }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -3025,6 +3272,61 @@ } } }, + "sequelize": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.3.5.tgz", + "integrity": "sha512-MiwiPkYSA8NWttRKAXdU9h0TxP6HAc1fl7qZmMO/VQqQOND83G4nZLXd0kWILtAoT9cxtZgFqeb/MPYgEeXwsw==", + "requires": { + "debug": "^4.1.1", + "dottie": "^2.0.0", + "inflection": "1.12.0", + "lodash": "^4.17.15", + "moment": "^2.26.0", + "moment-timezone": "^0.5.31", + "retry-as-promised": "^3.2.0", + "semver": "^7.3.2", + "sequelize-pool": "^6.0.0", + "toposort-class": "^1.0.1", + "uuid": "^8.1.0", + "validator": "^10.11.0", + "wkx": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + }, + "uuid": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", + "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==" + } + } + }, + "sequelize-pool": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz", + "integrity": "sha512-4YwEw3ZgK/tY/so+GfnSgXkdwIJJ1I32uZJztIEgZeAO6HMgj64OzySbWLgxj+tXhZCJnzRfkY9gINw8Ft8ZMg==" + }, "serve-static": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", @@ -3036,6 +3338,11 @@ "send": "0.17.1" } }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", @@ -3093,7 +3400,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -3102,14 +3408,12 @@ "spdx-exceptions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" }, "spdx-expression-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -3118,8 +3422,15 @@ "spdx-license-ids": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" + }, + "split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "requires": { + "readable-stream": "^3.0.0" + } }, "sprintf-js": { "version": "1.0.3", @@ -3472,6 +3783,11 @@ } } }, + "toposort-class": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", + "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" + }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -3536,6 +3852,19 @@ "mime-types": "~2.1.24" } }, + "umzug": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.3.0.tgz", + "integrity": "sha512-Z274K+e8goZK8QJxmbRPhl89HPO1K+ORFtm6rySPhFKfKc5GHhqdzD0SGhSWHkzoXasqJuItdhorSvY7/Cgflw==", + "requires": { + "bluebird": "^3.7.2" + } + }, + "underscore": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.11.0.tgz", + "integrity": "sha512-xY96SsN3NA461qIRKZ/+qox37YXPtSBswMGfiNptr+wrt6ds4HaMw23TP612fEyGekRE6LNRiLYr/aqbHXNedw==" + }, "uniq": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", @@ -3562,15 +3891,6 @@ } } }, - "url": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", - "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -3596,12 +3916,16 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, + "validator": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", + "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -3626,6 +3950,16 @@ "isexe": "^2.0.0" } }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" + }, + "window-size": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz", + "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=" + }, "winston": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", @@ -3675,12 +4009,62 @@ } } }, + "wkx": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", + "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", + "requires": { + "@types/node": "*" + } + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -3695,30 +4079,149 @@ "mkdirp": "^0.5.1" } }, - "xml2js": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", - "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", - "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~9.0.1" - } - }, - "xmlbuilder": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" - }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" }, "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + }, + "yargs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-5.0.0.tgz", + "integrity": "sha1-M1UUSXfQV1fbuG1uOOwFYSOzpm4=", + "requires": { + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "lodash.assign": "^4.2.0", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "window-size": "^0.2.0", + "y18n": "^3.2.1", + "yargs-parser": "^3.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "requires": { + "is-utf8": "^0.2.0" + } + } + } + }, + "yargs-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-3.2.0.tgz", + "integrity": "sha1-UIE1XRnZ0MjF2BrakIy05tGGZk8=", + "requires": { + "camelcase": "^3.0.0", + "lodash.assign": "^4.1.0" + } } } } diff --git a/package.json b/package.json index b069f0d..80ff66e 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "lint": "standard \"**/*.js\"", "insert-data": "node scripts/db/genData.js", "delete-data": "node scripts/db/dropAll.js", - "migrate-db-to-es": "node scripts/db/dumpDbToEs.js" + "migrate-db-to-es": "node scripts/db/dumpDbToEs.js", + "migrations": "node scripts/db/migrations.js" }, "repository": { "type": "git", @@ -18,8 +19,6 @@ "@elastic/elasticsearch": "^7.9.1", "@hapi/joi": "^16.1.8", "@hapi/joi-date": "^2.0.1", - "amazon-qldb-driver-nodejs": "^0.1.1-preview.2", - "aws-sdk": "^2.627.0", "axios": "^0.19.2", "body-parser": "^1.19.0", "config": "^3.2.4", @@ -30,10 +29,15 @@ "js-yaml": "^3.13.1", "lodash": "^4.17.19", "node-cache": "^5.1.2", + "pg": "^8.5.1", + "pg-hstore": "^2.3.3", + "pgtools": "^0.3.0", "querystring": "^0.2.0", + "sequelize": "^6.3.5", "swagger-ui-express": "^4.1.4", "tc-bus-api-wrapper": "github:topcoder-platform/tc-bus-api-wrapper", "tc-core-library-js": "github:appirio-tech/tc-core-library-js#v2.6.4", + "umzug": "^2.3.0", "uuid": "^7.0.1", "winston": "^3.2.1" }, diff --git a/scripts/db/data/AttributeGroup.json b/scripts/db/data/AttributeGroup.json index aeaf9e5..51c54bb 100644 --- a/scripts/db/data/AttributeGroup.json +++ b/scripts/db/data/AttributeGroup.json @@ -5,7 +5,7 @@ "updated": null, "createdBy": "tc-user", "updatedBy": null, - "organizationId": "854f1bf3-6f51-424b-866f-e5f5a5803904", + "organizationId": "6a21394e-1278-4835-9e4d-cb4ff151fcd3", "name": "attributeGroup_03" }, { diff --git a/scripts/db/data/ExternalProfile.json b/scripts/db/data/ExternalProfile.json index 0cd4563..2e6671d 100644 --- a/scripts/db/data/ExternalProfile.json +++ b/scripts/db/data/ExternalProfile.json @@ -24,7 +24,7 @@ "uri": "http://www.new.com/new-uri" }, { - "id": "f2d1b567-8ea3-4eec-93b0-32378a19edb7", + "id": "f2d1b567-8eb3-4eec-93b0-32378a19edb7", "created": "2020-05-13T06:11:21.361Z", "updated": "2020-05-13T06:46:15.893Z", "createdBy": "tc-Admin", @@ -36,7 +36,7 @@ "uri": "http://www.new.com/new-uri" }, { - "id": "f2d1b567-8ea3-4eec-93b0-32378a19edb7", + "id": "f2d1b567-8ec3-4eec-93b0-32378a19edb7", "created": "2020-05-13T06:11:21.361Z", "updated": "2020-05-13T06:46:15.893Z", "createdBy": "tc-Admin", diff --git a/scripts/db/dropAll.js b/scripts/db/dropAll.js index a30a2cf..fa2aba2 100644 --- a/scripts/db/dropAll.js +++ b/scripts/db/dropAll.js @@ -2,7 +2,7 @@ * drop tables */ const _ = require('lodash') -const models = require('../../src/models') +const sequelize = require('../../src/models/index') const logger = require('../../src/common/logger') const { topResources, @@ -14,6 +14,7 @@ const { getESClient } = require('../../src/common/es-client') async function main () { const client = getESClient() + // delete es pipelines try { logger.info('Deleting all pipelines...') await client.ingest.deletePipeline({ @@ -31,42 +32,45 @@ async function main () { logger.warn('Delete all ingest pipelines failed') } - const keys = Object.keys(models) + // delete data in es + const keys = Object.keys(sequelize.models) for (let i = 0; i < keys.length; i++) { const key = keys[i] - if (models[key].tableName) { - const esResourceName = modelToESIndexMapping[key] - try { - if (_.includes(_.keys(topResources), esResourceName)) { - if (topResources[esResourceName].enrich) { - logger.info(`Deleting enrich policy for ${esResourceName}`) - await client.enrich.deletePolicy({ - name: topResources[esResourceName].enrich.policyName - }) - logger.info(`Successfully deleted enrich policy for ${esResourceName}`) - } - logger.info(`Deleting index for ${esResourceName}`) - await client.indices.delete({ - index: topResources[esResourceName].index - }) - logger.info(`Successfully deleted enrich policy for ${esResourceName}`) - } else if (_.includes(_.keys(organizationResources), esResourceName)) { - logger.info('Deleting enrich policy for organization') + const esResourceName = modelToESIndexMapping[key] + try { + if (_.includes(_.keys(topResources), esResourceName)) { + if (topResources[esResourceName].enrich) { + logger.info(`Deleting enrich policy for ${esResourceName}`) await client.enrich.deletePolicy({ - name: organizationResources[esResourceName].enrich.policyName + name: topResources[esResourceName].enrich.policyName }) - logger.info('Successfully deleted enrich policy for organization') + logger.info(`Successfully deleted enrich policy for ${esResourceName}`) } - - logger.info(`Deleting data in QLDB for ${esResourceName}`) - await models.DBHelper.clear(models[key]) - logger.info(`Successfully deleted data in QLDB for ${esResourceName}`) - } catch (e) { - console.error(e) - logger.warn(`drop table ${key} failed`) + logger.info(`Deleting index for ${esResourceName}`) + await client.indices.delete({ + index: topResources[esResourceName].index + }) + logger.info(`Successfully deleted enrich policy for ${esResourceName}`) + } else if (_.includes(_.keys(organizationResources), esResourceName)) { + logger.info('Deleting enrich policy for organization') + await client.enrich.deletePolicy({ + name: organizationResources[esResourceName].enrich.policyName + }) + logger.info('Successfully deleted enrich policy for organization') } + } catch (e) { + console.error(e) + logger.warn(`deleting data in es for ${key} failed`) } } + + // delete tables + try { + await sequelize.drop() + } catch (e) { + console.error(e) + logger.warn('deleting tables failed') + } } (async () => { diff --git a/scripts/db/dumpDbToEs.js b/scripts/db/dumpDbToEs.js index a16d8ed..cd7951f 100644 --- a/scripts/db/dumpDbToEs.js +++ b/scripts/db/dumpDbToEs.js @@ -1,5 +1,8 @@ +// TODO - Update BEFORE merge + const _ = require('lodash') -const models = require('../../src/models') +const sequelize = require('../../src/models/index') +const dbHelper = require('../../src/common/db-helper') const logger = require('../../src/common/logger') const { getESClient } = require('../../src/common/es-client') const { @@ -9,6 +12,8 @@ const { modelToESIndexMapping } = require('../constants') +const models = sequelize.models + // Declares the ordering of the resource data insertion, to ensure that enrichment happens correctly const RESOURCES_IN_ORDER = [ 'skillprovider', @@ -162,7 +167,7 @@ async function insertIntoES (modelName, body) { if (e.meta && e.meta.body.error.type === RESOURCE_NOT_FOUND) { logger.info(`The ${modelName} references user with id ${body.userId}, which does not exist. Deleting the reference...`) // The user does not exist. Delete the referece records - await models.DBHelper.delete(models[modelName], body.id) + await dbHelper.remove(models[modelName], body.id) logger.info('Reference deleted') return } else { @@ -222,7 +227,7 @@ async function insertIntoES (modelName, body) { if (e.meta && e.meta.body.error.type === RESOURCE_NOT_FOUND) { logger.info(`The ${modelName} references org with id ${body.organizationId}, which does not exist. Deleting the reference...`) // The user does not exist. Delete the referece records - await models.DBHelper.delete(models[modelName], body.id) + await dbHelper.remove(models[modelName], body.id) logger.info('Reference deleted') return } else { @@ -351,7 +356,7 @@ async function main () { // Sort the models in the order of insertion (for correct enrichment) const temp = Array(keys.length).fill(null) keys.forEach(k => { - if (models[k].tableName) { + if (sequelize.models[k].name) { const esResourceName = modelToESIndexMapping[k] const index = RESOURCES_IN_ORDER.indexOf(esResourceName) temp[index] = k @@ -364,7 +369,7 @@ async function main () { for (let i = 0; i < keys.length; i++) { const key = keys[i] try { - const data = await models.DBHelper.find(models[key], []) + const data = await dbHelper.find(models[key], {}) for (let i = 0; i < data.length; i++) { logger.info(`Inserting data ${i + 1} of ${data.length}`) diff --git a/scripts/db/genData.js b/scripts/db/genData.js index 97681ac..4059ad8 100644 --- a/scripts/db/genData.js +++ b/scripts/db/genData.js @@ -1,10 +1,11 @@ +// TODO - Update BEFORE merge + const _ = require('lodash') -const models = require('../../src/models') +const sequelize = require('../../src/models/index') const logger = require('../../src/common/logger') const { getESClient } = require('../../src/common/es-client') const { topResources, - userResources, organizationResources, modelToESIndexMapping } = require('../constants') @@ -29,98 +30,6 @@ const RESOURCES_IN_ORDER = [ const client = getESClient() -async function insertIntoES (modelName, body) { - const esResourceName = modelToESIndexMapping[modelName] - - if (!esResourceName) { - logger.error(`Cannot insert data into model ${modelName}. No equivalent elasticsearch index found`) - - return - } - - if (_.includes(_.keys(topResources), esResourceName)) { - await client.index({ - index: topResources[esResourceName].index, - type: topResources[esResourceName].type, - id: body.id, - body, - pipeline: topResources[esResourceName].ingest ? topResources[esResourceName].ingest.pipeline.id : undefined, - refresh: 'wait_for' - }) - } else if (_.includes(_.keys(userResources), esResourceName)) { - const userResource = userResources[esResourceName] - - const { body: user } = await client.getSource({ - index: topResources.user.index, - type: topResources.user.type, - id: body.userId - }) - - if (userResource.nested === true && userResource.mappingCreated !== true) { - await client.indices.putMapping({ - index: topResources.user.index, - type: topResources.user.type, - include_type_name: true, - body: { - properties: { - [userResource.propertyName]: { - type: 'nested' - } - } - } - }) - userResource.mappingCreated = true - } - - const relateId = body[userResource.relateKey] - - if (!user[userResource.propertyName]) { - user[userResource.propertyName] = [] - } - - if (_.some(user[userResource.propertyName], [userResource.relateKey, relateId])) { - logger.error(`Can't create existing ${esResourceName} with the ${userResource.relateKey}: ${relateId}, userId: ${body.userId}`) - } else { - user[userResource.propertyName].push(body) - await client.index({ - index: topResources.user.index, - type: topResources.user.type, - id: body.userId, - body: user, - pipeline: topResources.user.pipeline.id, - refresh: 'wait_for' - }) - } - } else if (_.includes(_.keys(organizationResources), esResourceName)) { - const orgResource = organizationResources[esResourceName] - - const { body: organization } = await client.getSource({ - index: topResources.organization.index, - type: topResources.organization.type, - id: body.organizationId - }) - - const relateId = body[orgResource.relateKey] - - if (!organization[orgResource.propertyName]) { - organization[orgResource.propertyName] = [] - } - - if (_.some(organization[orgResource.propertyName], [orgResource.relateKey, relateId])) { - logger.error(`Can't create existing ${esResourceName} with the ${orgResource.relateKey}: ${relateId}, organizationId: ${body.organizationId}`) - } else { - organization[orgResource.propertyName].push(body) - await client.index({ - index: topResources.organization.index, - type: topResources.organization.type, - id: body.organizationId, - body: organization, - refresh: 'wait_for' - }) - } - } -} - /** * Creates and executes the enrich policy for the provided model * @param {String} modelName The model name @@ -216,14 +125,12 @@ async function createEnrichProcessor (modelName) { * @return {Promise} */ async function main () { - await models.init() - - let keys = Object.keys(models) + let keys = Object.keys(sequelize.models) // Sort the models in the order of insertion (for correct enrichment) const temp = Array(keys.length).fill(null) keys.forEach(k => { - if (models[k].tableName) { + if (sequelize.models[k].name) { const esResourceName = modelToESIndexMapping[k] const index = RESOURCES_IN_ORDER.indexOf(esResourceName) temp[index] = k @@ -233,21 +140,6 @@ async function main () { for (let i = 0; i < keys.length; i++) { const key = keys[i] - try { - const data = require(`./data/${key}.json`) - await models.DBHelper.clear(models[key]) - for (let i = 0; i < data.length; i++) { - logger.info(`Inserting data ${i + 1} of ${data.length}`) - await models.DBHelper.save(models[key], new models[key]().from(data[i]), true) - await insertIntoES(key, data[i]) - } - logger.info('import data for ' + key + ' done') - } catch (e) { - logger.error(e) - logger.warn('import data for ' + key + ' failed') - continue - } - try { await createAndExecuteEnrichPolicy(key) logger.info('create and execute enrich policy for ' + key + ' done') diff --git a/scripts/db/migrations.js b/scripts/db/migrations.js new file mode 100644 index 0000000..43b1877 --- /dev/null +++ b/scripts/db/migrations.js @@ -0,0 +1,35 @@ +const sequelize = require('../../src/models/index') +const path = require('path') +const Umzug = require('umzug') +const { createDb } = require('../../src/common/db-helper') + +const umzug = new Umzug({ + migrations: { + // indicates the folder containing the migration .js files + path: path.join(__dirname, './migrations'), + // inject sequelize's QueryInterface in the migrations + params: [ + sequelize.getQueryInterface() + ] + }, + // indicates that the migration data should be store in the database + // itself through sequelize. The default configuration creates a table + // named `SequelizeMeta`. + storage: 'sequelize', + storageOptions: { + sequelize: sequelize + } +}); + +(async () => { + if (process.argv[2] === 'up') { + createDb() + await umzug.up() + console.log('All migrations performed successfully') + } else if (process.argv[2] === 'down') { + await umzug.down() + console.log('The last executed migration reverted successfully') + } else { + console.error('You should pass \'up\' or \'down\' as the parameter') + } +})() diff --git a/scripts/db/migrations/00_create-achievement.js b/scripts/db/migrations/00_create-achievement.js new file mode 100644 index 0000000..15a882b --- /dev/null +++ b/scripts/db/migrations/00_create-achievement.js @@ -0,0 +1,44 @@ +/** + * Achievement model + */ +const { DataTypes } = require('sequelize') + +module.exports = { + up: async (query) => { + await query.createTable('Achievements', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + name: { + type: DataTypes.STRING + }, + uri: { + type: DataTypes.STRING + }, + certifierId: { + type: DataTypes.STRING + }, + certifiedDate: { + type: DataTypes.DATE + }, + created: { + type: DataTypes.DATE, + allowNull: false + }, + updated: { + type: DataTypes.DATE + } + }) + }, + down: async (query) => { + await query.dropTable('Achievements') + } +} diff --git a/scripts/db/migrations/01_create-achievementsProvider.js b/scripts/db/migrations/01_create-achievementsProvider.js new file mode 100644 index 0000000..6e2088a --- /dev/null +++ b/scripts/db/migrations/01_create-achievementsProvider.js @@ -0,0 +1,35 @@ +/** + * AchievementsProvider model + */ +const { DataTypes } = require('sequelize') + +module.exports = { + up: async (query) => { + await query.createTable('AchievementsProviders', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + name: { + type: DataTypes.STRING + }, + created: { + type: DataTypes.DATE, + allowNull: false + }, + updated: { + type: DataTypes.DATE + } + }) + }, + down: async (query) => { + await query.dropTable('AchievementsProviders') + } +} diff --git a/scripts/db/migrations/02_create-attribute.js b/scripts/db/migrations/02_create-attribute.js new file mode 100644 index 0000000..0254ff1 --- /dev/null +++ b/scripts/db/migrations/02_create-attribute.js @@ -0,0 +1,35 @@ +/** + * Attribute model + */ +const { DataTypes } = require('sequelize') + +module.exports = { + up: async (query) => { + await query.createTable('Attributes', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + name: { + type: DataTypes.STRING + }, + created: { + type: DataTypes.DATE, + allowNull: false + }, + updated: { + type: DataTypes.DATE + } + }) + }, + down: async (query) => { + await query.dropTable('Attributes') + } +} diff --git a/scripts/db/migrations/03_create-attributeGroup.js b/scripts/db/migrations/03_create-attributeGroup.js new file mode 100644 index 0000000..72b5b17 --- /dev/null +++ b/scripts/db/migrations/03_create-attributeGroup.js @@ -0,0 +1,35 @@ +/** + * AttributeGroup model + */ +const { DataTypes } = require('sequelize') + +module.exports = { + up: async (query) => { + await query.createTable('AttributeGroups', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + name: { + type: DataTypes.STRING + }, + created: { + type: DataTypes.DATE, + allowNull: false + }, + updated: { + type: DataTypes.DATE + } + }) + }, + down: async (query) => { + await query.dropTable('AttributeGroups') + } +} diff --git a/scripts/db/migrations/04_create-externalProfile.js b/scripts/db/migrations/04_create-externalProfile.js new file mode 100644 index 0000000..f7cc785 --- /dev/null +++ b/scripts/db/migrations/04_create-externalProfile.js @@ -0,0 +1,41 @@ +/** + * ExternalProfile model + */ +const { DataTypes } = require('sequelize') + +module.exports = { + up: async (query) => { + await query.createTable('ExternalProfiles', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + externalId: { + type: DataTypes.STRING + }, + uri: { + type: DataTypes.STRING + }, + isInactive: { + type: DataTypes.BOOLEAN + }, + created: { + type: DataTypes.DATE, + allowNull: false + }, + updated: { + type: DataTypes.DATE + } + }) + }, + down: async (query) => { + await query.dropTable('ExternalProfiles') + } +} diff --git a/scripts/db/migrations/05_create-organization.js b/scripts/db/migrations/05_create-organization.js new file mode 100644 index 0000000..9080220 --- /dev/null +++ b/scripts/db/migrations/05_create-organization.js @@ -0,0 +1,35 @@ +/** + * Organization model + */ +const { DataTypes } = require('sequelize') + +module.exports = { + up: async (query) => { + await query.createTable('Organizations', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + name: { + type: DataTypes.STRING + }, + created: { + type: DataTypes.DATE, + allowNull: false + }, + updated: { + type: DataTypes.DATE + } + }) + }, + down: async (query) => { + await query.dropTable('Organizations') + } +} diff --git a/scripts/db/migrations/06_create-organizationSkillsProvider.js b/scripts/db/migrations/06_create-organizationSkillsProvider.js new file mode 100644 index 0000000..d867217 --- /dev/null +++ b/scripts/db/migrations/06_create-organizationSkillsProvider.js @@ -0,0 +1,32 @@ +/** + * OrganizationSkillsProvider model + */ +const { DataTypes } = require('sequelize') + +module.exports = { + up: async (query) => { + await query.createTable('OrganizationSkillsProviders', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + created: { + type: DataTypes.DATE, + allowNull: false + }, + updated: { + type: DataTypes.DATE + } + }) + }, + down: async (query) => { + await query.dropTable('OrganizationSkillsProviders') + } +} diff --git a/scripts/db/migrations/07_create-role.js b/scripts/db/migrations/07_create-role.js new file mode 100644 index 0000000..f10e8d9 --- /dev/null +++ b/scripts/db/migrations/07_create-role.js @@ -0,0 +1,35 @@ +/** + * Role model + */ +const { DataTypes } = require('sequelize') + +module.exports = { + up: async (query) => { + await query.createTable('Roles', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + name: { + type: DataTypes.STRING + }, + created: { + type: DataTypes.DATE, + allowNull: false + }, + updated: { + type: DataTypes.DATE + } + }) + }, + down: async (query) => { + await query.dropTable('Roles') + } +} diff --git a/scripts/db/migrations/08_create-skill.js b/scripts/db/migrations/08_create-skill.js new file mode 100644 index 0000000..cadf8dd --- /dev/null +++ b/scripts/db/migrations/08_create-skill.js @@ -0,0 +1,38 @@ +const { DataTypes } = require('sequelize') + +module.exports = { + up: async (query) => { + await query.createTable('Skills', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + name: { + type: DataTypes.STRING + }, + externalId: { + type: DataTypes.STRING + }, + uri: { + type: DataTypes.STRING + }, + created: { + type: DataTypes.DATE, + allowNull: false + }, + updated: { + type: DataTypes.DATE + } + }) + }, + down: async (query) => { + await query.dropTable('Skills') + } +} diff --git a/scripts/db/migrations/09_create-skillsProvider.js b/scripts/db/migrations/09_create-skillsProvider.js new file mode 100644 index 0000000..75b6dcf --- /dev/null +++ b/scripts/db/migrations/09_create-skillsProvider.js @@ -0,0 +1,32 @@ +const { DataTypes } = require('sequelize') + +module.exports = { + up: async (query) => { + await query.createTable('SkillsProviders', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + name: { + type: DataTypes.STRING + }, + created: { + type: DataTypes.DATE, + allowNull: false + }, + updated: { + type: DataTypes.DATE + } + }) + }, + down: async (query) => { + await query.dropTable('SkillsProviders') + } +} diff --git a/scripts/db/migrations/10_create-user.js b/scripts/db/migrations/10_create-user.js new file mode 100644 index 0000000..70d998f --- /dev/null +++ b/scripts/db/migrations/10_create-user.js @@ -0,0 +1,41 @@ +/** + * User model + */ +const { DataTypes } = require('sequelize') + +module.exports = { + up: async (query) => { + await query.createTable('Users', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + handle: { + type: DataTypes.STRING + }, + firstName: { + type: DataTypes.STRING + }, + lastName: { + type: DataTypes.STRING + }, + created: { + type: DataTypes.DATE, + allowNull: false + }, + updated: { + type: DataTypes.DATE + } + }) + }, + down: async (query) => { + await query.dropTable('Users') + } +} diff --git a/scripts/db/migrations/11_create-userAttribute.js b/scripts/db/migrations/11_create-userAttribute.js new file mode 100644 index 0000000..050ae90 --- /dev/null +++ b/scripts/db/migrations/11_create-userAttribute.js @@ -0,0 +1,35 @@ +/** + * UsersAttribute model + */ +const { DataTypes } = require('sequelize') + +module.exports = { + up: async (query) => { + await query.createTable('UserAttributes', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + value: { + type: DataTypes.STRING + }, + created: { + type: DataTypes.DATE, + allowNull: false + }, + updated: { + type: DataTypes.DATE + } + }) + }, + down: async (query) => { + await query.dropTable('UserAttributes') + } +} diff --git a/scripts/db/migrations/12_create-usersRole.js b/scripts/db/migrations/12_create-usersRole.js new file mode 100644 index 0000000..98c569c --- /dev/null +++ b/scripts/db/migrations/12_create-usersRole.js @@ -0,0 +1,32 @@ +/** + * UsersRole model + */ +const { DataTypes } = require('sequelize') + +module.exports = { + up: async (query) => { + await query.createTable('UsersRoles', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + created: { + type: DataTypes.DATE, + allowNull: false + }, + updated: { + type: DataTypes.DATE + } + }) + }, + down: async (query) => { + await query.dropTable('UsersRoles') + } +} diff --git a/scripts/db/migrations/13_create-usersSkill.js b/scripts/db/migrations/13_create-usersSkill.js new file mode 100644 index 0000000..b05d163 --- /dev/null +++ b/scripts/db/migrations/13_create-usersSkill.js @@ -0,0 +1,38 @@ +const { DataTypes } = require('sequelize') + +module.exports = { + up: async (query) => { + await query.createTable('UsersSkills', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + metricValue: { + type: DataTypes.STRING + }, + certifierId: { + type: DataTypes.STRING + }, + certifiedDate: { + type: DataTypes.DATE + }, + created: { + type: DataTypes.DATE, + allowNull: false + }, + updated: { + type: DataTypes.DATE + } + }) + }, + down: async (query) => { + await query.dropTable('UsersSkills') + } +} diff --git a/scripts/db/migrations/14_add-relationship.js b/scripts/db/migrations/14_add-relationship.js new file mode 100644 index 0000000..856329b --- /dev/null +++ b/scripts/db/migrations/14_add-relationship.js @@ -0,0 +1,158 @@ +const { DataTypes } = require('sequelize') + +module.exports = { + up: async (query) => { + await query.addColumn('Skills', 'skillProviderId', { + type: DataTypes.UUID, + references: { + model: 'SkillsProviders', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }) + await query.addColumn('OrganizationSkillsProviders', 'skillProviderId', { + type: DataTypes.UUID, + references: { + model: 'SkillsProviders', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }) + await query.addColumn('UsersSkills', 'userId', { + type: DataTypes.UUID, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }) + await query.addColumn('Achievements', 'userId', { + type: DataTypes.UUID, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }) + await query.addColumn('UserAttributes', 'userId', { + type: DataTypes.UUID, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }) + await query.addColumn('ExternalProfiles', 'userId', { + type: DataTypes.UUID, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }) + await query.addColumn('UsersRoles', 'userId', { + type: DataTypes.UUID, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }) + await query.addColumn('ExternalProfiles', 'organizationId', { + type: DataTypes.UUID, + references: { + model: 'Organizations', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }) + await query.addColumn('AttributeGroups', 'organizationId', { + type: DataTypes.UUID, + references: { + model: 'Organizations', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }) + await query.addColumn('OrganizationSkillsProviders', 'organizationId', { + type: DataTypes.UUID, + references: { + model: 'Organizations', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }) + await query.addColumn('UsersSkills', 'skillId', { + type: DataTypes.UUID, + references: { + model: 'Skills', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }) + await query.addColumn('UsersRoles', 'roleId', { + type: DataTypes.UUID, + references: { + model: 'Roles', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }) + await query.addColumn('Achievements', 'achievementsProviderId', { + type: DataTypes.UUID, + references: { + model: 'AchievementsProviders', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }) + await query.addColumn('UserAttributes', 'attributeId', { + type: DataTypes.UUID, + references: { + model: 'Attributes', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }) + await query.addColumn('Attributes', 'attributeGroupId', { + type: DataTypes.UUID, + references: { + model: 'AttributeGroups', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }) + }, + down: async (query) => { + await query.removeColumn('Skills', 'skillProviderId') + await query.removeColumn('OrganizationSkillsProviders', 'skillProviderId') + await query.removeColumn('UsersSkills', 'userId') + await query.removeColumn('Achievements', 'userId') + await query.removeColumn('UserAttributes', 'userId') + await query.removeColumn('ExternalProfiles', 'userId') + await query.removeColumn('UsersRoles', 'userId') + await query.removeColumn('ExternalProfiles', 'organizationId') + await query.removeColumn('AttributeGroups', 'organizationId') + await query.removeColumn('OrganizationSkillsProviders', 'organizationId') + await query.removeColumn('UsersSkills', 'skillId') + await query.removeColumn('UsersRoles', 'roleId') + await query.removeColumn('Achievements', 'achievementsProviderId') + await query.removeColumn('UserAttributes', 'attributeId') + await query.removeColumn('Attributes', 'attributeGroupId') + } +} diff --git a/scripts/db/migrations/15_add-data.js b/scripts/db/migrations/15_add-data.js new file mode 100644 index 0000000..f03109d --- /dev/null +++ b/scripts/db/migrations/15_add-data.js @@ -0,0 +1,16 @@ +const dataKeys = ['User', 'Organization', 'AchievementsProvider', 'Achievement', + 'AttributeGroup', 'Attribute', 'ExternalProfile', 'SkillsProvider', + 'OrganizationSkillsProvider', 'Role', 'Skill', 'UserAttribute', 'UsersRole', 'UsersSkill'] + +module.exports = { + up: async (query) => { + for (const key of dataKeys) { + await query.bulkInsert(`${key}s`, require(`../data/${key}.json`)) + } + }, + down: async (query) => { + for (const key of dataKeys) { + await query.bulkDelete(`${key}s`, null, {}) + } + } +} diff --git a/src/common/db-helper.js b/src/common/db-helper.js new file mode 100644 index 0000000..2c04595 --- /dev/null +++ b/src/common/db-helper.js @@ -0,0 +1,189 @@ +// TODO - Update BEFORE merge + +const _ = require('lodash') +const pgtools = require('pgtools') +const config = require('config') +const errors = require('../common/errors') +const logger = require('../common/logger') +const appConst = require('../consts') +const helper = require('../common/helper') + +/** + * create db + * @returns {Promise} + */ +async function createDb () { + const dbConfig = { + user: config.DB_USERNAME, + password: config.DB_PASSWORD, + port: config.DB_PORT, + host: config.DB_HOST + } + + pgtools.createdb(dbConfig, config.DB_NAME, (err, res) => { + if (err) { + if (err.name === 'duplicate_database') { + logger.info(`db ${config.DB_NAME} already exists`) + } else { + throw err + } + } else { + logger.info(`Created db ${config.DB_NAME} successfully`) + } + }) +} + +/** + * search objects + * @param model the sequelize model object + * @param query search query + * @param auth the user auth object + * @param inlcude what models to include + * @returns {Promise} + */ +async function find (model, query, auth = null, include = null) { + // for non-admin users, this endpoint will only return entities that the user has created. + if ( + auth && + auth.roles && + !helper.checkIfExists(auth.roles, appConst.AdminUser) && + !helper.checkIfExists(auth.roles, [appConst.UserRoles.ubahn]) + ) { + query.createdBy = helper.getAuthUser(auth) + } + + // set pagination params + const options = {} + if (!query.perPage) { + query.perPage = config.PER_PAGE + } + if (query.page) { + options.offset = (query.page - 1) * query.perPage + } + options.limit = query.perPage + delete query.perPage + delete query.page + + // set filter params + options.where = query + + // set model inlcudes + if (include) { + options.include = include + } + + // execute query + return model.findAll(options) +} + +/** + * get object by pk + * @param model the sequelize model object + * @param pk object pk (primary key) + * @param params the path params (use if id is null) + * @returns {Promise} + */ +async function get (model, pk, params) { + let instance + if (pk) { + instance = await model.findByPk(pk) + } + if (params) { + const instances = await model.findAll({ where: params }) + if (instances.length === 0) { + throw errors.newEntityNotFoundError(`cannot find ${model.name} where ${_.map(params, (v, k) => `${k}:${v}`).join(', ')}`) + } + return instances[0] + } + + if (!instance) { + throw errors.newEntityNotFoundError(`cannot find ${model.name} where id = ${pk}`) + } + return instance +} + +/** + * create object + * @param model the sequelize model object + * @param entity entity to create + * @param auth the user auth object + * @returns {Promise} + */ +async function create (model, entity, auth) { + if (auth) { + entity.createdBy = helper.getAuthUser(auth) + } + return model.create(entity) +} + +/** + * delete object by pk + * @param model the sequelize model object + * @param pk the primary key + * @returns {Promise} + */ +async function remove (model, pk, params) { + const instance = await get(model, pk, params) + return instance.destroy() +} + +/** + * update object by pk + * @param model the sequelize model object + * @param pk the primary key + * @param entity entity to create + * @param auth the auth object + * @param auth the path params + * @returns {Promise} + */ +async function update (model, pk, entity, auth, params) { + // insure that object exists + const instance = await get(model, pk, params) + entity.updatedBy = helper.getAuthUser(auth) + return instance.update(entity) +} + +/** + * make sure unique + * @param model the db model + * @param entity the entity + * @param uniqueFields the uniqueFields + * @param pathParams the path params + */ +async function makeSureUnique (model, entity, uniqueFields, pathParams = {}) { + if (_.isNil(uniqueFields) || _.isEmpty(uniqueFields)) { + return + } + for (let i = 0; i < uniqueFields.length; i++) { + const params = {} + _.each(uniqueFields[i], f => { + if (entity[f] && entity[f] !== pathParams[f]) { + params[f] = entity[f] + } + }) + if (Object.keys(params).length === 0) { + return + } + + const items = await find(model, params) + if (items.length <= 0) { + continue + } + + if ((items.length > 0 && !entity.id) || + (items[0].id !== entity.id) + ) { + throw errors.newConflictError(`${model.name} already exists with ${_.map(params, (v, k) => `${k}:${v}`).join(', ')}`) + } + } +} + +module.exports = { + createDb, + find, + create, + update, + get, + remove, + makeSureUnique +} diff --git a/src/common/error.middleware.js b/src/common/error.middleware.js index 5eb9430..4a89e2c 100755 --- a/src/common/error.middleware.js +++ b/src/common/error.middleware.js @@ -3,6 +3,7 @@ /** * Common error handling middleware */ +const { UniqueConstraintError } = require('sequelize') const logger = require('./logger') const DEFAULT_MESSAGE = 'Something is broken at API server-side.' @@ -18,7 +19,9 @@ const DEFAULT_MESSAGE = 'Something is broken at API server-side.' function middleware (err, req, res, next) { logger.logFullError(err) - if (err.isJoi) { + if (err instanceof UniqueConstraintError) { + res.status(409).json({ message: err.message }) + } else if (err.isJoi) { res.status(400).json({ message: err.details[0].message }) } else { const status = err.status || 500 diff --git a/src/common/errors.js b/src/common/errors.js index cafa4df..e0473e6 100644 --- a/src/common/errors.js +++ b/src/common/errors.js @@ -22,6 +22,7 @@ module.exports = { newAuthError: msg => new AppError(401, msg || 'Auth failed.'), newPermissionError: msg => new AppError(403, msg || 'The entity does not exist.'), newConflictError: msg => new AppError(409, msg || 'The entity does not exist.'), + deleteConflictError: msg => new AppError(400, msg || 'Please delete child records first'), serviceUnavailableError: msg => new AppError(503, msg || 'One or more services are not available'), elasticSearchEnrichError: msg => new AppError(500, msg || 'Elasticsearch enrich failed') } diff --git a/src/common/es-client.js b/src/common/es-client.js index c5a52d7..0bc306b 100644 --- a/src/common/es-client.js +++ b/src/common/es-client.js @@ -1,9 +1,6 @@ const config = require('config') -const AWS = require('aws-sdk') const elasticsearch = require('@elastic/elasticsearch') -AWS.config.region = config.AWS_REGION - // Elasticsearch client let esClient @@ -17,23 +14,21 @@ function getESClient () { } const host = config.ES.HOST const cloudId = config.ES.ELASTICCLOUD.id - if (!esClient) { - if (cloudId) { - // Elastic Cloud configuration - esClient = new elasticsearch.Client({ - cloud: { - id: cloudId - }, - auth: { - username: config.ES.ELASTICCLOUD.username, - password: config.ES.ELASTICCLOUD.password - } - }) - } else { - esClient = new elasticsearch.Client({ - node: host - }) - } + if (cloudId) { + // Elastic Cloud configuration + esClient = new elasticsearch.Client({ + cloud: { + id: cloudId + }, + auth: { + username: config.ES.ELASTICCLOUD.username, + password: config.ES.ELASTICCLOUD.password + } + }) + } else { + esClient = new elasticsearch.Client({ + node: host + }) } return esClient } diff --git a/src/common/es-helper.js b/src/common/es-helper.js index 3cdf833..20b503a 100644 --- a/src/common/es-helper.js +++ b/src/common/es-helper.js @@ -283,14 +283,20 @@ function escapeRegex (str) { } async function getOrganizationId (handle) { - const DBHelper = require('../models/index').DBHelper + const dBHelper = require('../common/db-helper') + const sequelize = require('../models/index') - // TODO Use the service method instead of raw query - const orgIdLookupResults = await DBHelper.find(require('../models/ExternalProfile'), [ - 'select * from DUser, ExternalProfile', - `DUser.handle='${handle}'`, - 'DUser.id = ExternalProfile.userId' - ]) + const orgIdLookupResults = await dBHelper.find( + sequelize.models.ExternalProfile, + { + '$User.handle$': handle + }, + [{ + model: sequelize.models.User, + as: 'User', + attributes: [] + }] + ) if (orgIdLookupResults.length > 0) { throw new Error(`Handle ${handle} is associated with multiple organizations. Cannot select one.`) @@ -302,15 +308,21 @@ async function getOrganizationId (handle) { } async function getAttributeId (organizationId, attributeName) { - const DBHelper = require('../models/index').DBHelper - - // TODO Use the service method instead of raw query - const attributeIdLookupResults = await DBHelper.find(require('../models/Attribute'), [ - 'select Attribute.id from AttributeGroup, Attribute', - `AttributeGroup.organizationId = '${organizationId}'`, - 'Attribute.attributeGroupId = AttributeGroup.id', - `Attribute.name = '${attributeName}'` - ]) + const dBHelper = require('../common/db-helper') + const sequelize = require('../models/index') + + const attributeIdLookupResults = await dBHelper.find( + sequelize.models.Attribute, + { + '$AttributeGroup.organizationId$': organizationId, + name: attributeName + }, + [{ + model: sequelize.models.AttributeGroup, + as: 'AttributeGroup', + attributes: [] + }] + ) if (attributeIdLookupResults.length > 0) { return attributeIdLookupResults[0].id diff --git a/src/common/helper.js b/src/common/helper.js index 56e269e..70f0821 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -9,7 +9,6 @@ const querystring = require('querystring') const errors = require('./errors') const appConst = require('../consts') const _ = require('lodash') -const { getServiceMethods } = require('./service-helper') const { getControllerMethods, getSubControllerMethods } = require('./controller-helper') const logger = require('./logger') const busApi = require('tc-bus-api-wrapper') @@ -266,6 +265,5 @@ module.exports = { injectSearchMeta, getControllerMethods, getSubControllerMethods, - getServiceMethods, postEvent } diff --git a/src/common/service-helper.js b/src/common/service-helper.js index 10eb361..ab3405a 100644 --- a/src/common/service-helper.js +++ b/src/common/service-helper.js @@ -1,9 +1,11 @@ -const joi = require('@hapi/joi') +// TODO - Update BEFORE merge + const config = require('config') -const appConst = require('../consts') const _ = require('lodash') const errors = require('./errors') +const dbHelper = require('./db-helper') const esHelper = require('./es-helper') +const helper = require('./helper') const logger = require('./logger') // mapping operation to topic @@ -35,85 +37,114 @@ const MODEL_TO_RESOURCE = { } /** - * Get the resource from model name - * @param modelName the model name - * @returns {string|*} the resource + * Create record in es + * @param resource the resource to create + * @param result the resource fields */ -function getResource (modelName) { - if (MODEL_TO_RESOURCE[modelName]) { - return MODEL_TO_RESOURCE[modelName] - } else { - return modelName.toLowerCase() +async function createRecordInEs (resource, entity) { + try { + await publishMessage('create', resource, entity) + } catch (err) { + logger.logFullError(err) } } /** - * make sure reference item exists - * @param entity the request entity + * Patch record in es + * @param resource the resource to create + * @param result the resource fields */ -async function makeSureRefExist (entity) { - const models = require('../models/index') - const modelMap = { - skillProviderId: models.SkillsProvider, - roleId: models.Role, - userId: models.User, - organizationId: models.Organization, - achievementsProviderId: models.AchievementsProvider, - attributeGroupId: models.AttributeGroup, - attributeId: models.Attribute - +async function patchRecordInEs (resource, entity) { + try { + await publishMessage('patch', resource, entity) + } catch (err) { + logger.logFullError(err) } - const keys = Object.keys(entity) - for (let i = 0; i < keys.length; i++) { - const key = keys[i] - if (modelMap[key]) { - await models.DBHelper.get(modelMap[key], entity[key], ['id']) +} + +/** + * post delete message to es + * @param id the id of record + * @param params the params of record (like nested ids) + * @param resource the resource to delete + */ +async function deleteRecordFromEs (id, params, resource) { + let payload + if (SUB_USER_DOCUMENTS[resource] || SUB_ORG_DOCUMENTS[resource]) { + payload = _.assign({}, params) + } else { + payload = { + id } } + try { + await publishMessage('remove', resource, payload) + } catch (err) { + logger.logFullError(err) + } } /** - * make sure unique - * @param model the db model - * @param entity the entity - * @param uniqueFields the uniqueFields + * get resource in es + * @param resource resource to get + * @param id resource id + * @param params resource params + * @param auth resource auth */ -async function makeSureUnique (model, entity, uniqueFields) { - if (_.isNil(uniqueFields) || _.isEmpty(uniqueFields)) { - return +async function getRecordInEs (resource, id, params, auth) { + // Merge path and query params + try { + const result = await esHelper.getFromElasticSearch(resource, id, auth, params) + // check permission + helper.permissionCheck(auth, result) + return result + } catch (err) { + // return error if enrich fails or permission fails + if ((resource === 'user' && params.enrich) || (err.status && err.status === 403)) { + throw errors.elasticSearchEnrichError(err.message) + } + logger.logFullError(err) } - const models = require('../models/index') - for (let i = 0; i < uniqueFields.length; i++) { - const params = {} - _.each(uniqueFields[i], f => { - if (entity[f]) { - params[f] = entity[f] - } - }) - const items = await models.DBHelper.find(model, buildQueryByParams(params)) - if (items.length <= 0) { - continue +} + +/** + * search resource in es + * @param resource the resource to delete + * @param query the search query + * @param auth the auth object + */ +async function searchRecordInEs (resource, query, auth) { + // remove dollar signs that are used for postgres + for (const key of Object.keys(query)) { + if (key[0] === '$' && key[key.length - 1] === '$') { + query[key.slice(1, -1)] = query[key] + delete query[key] } + } - if ((items.length > 0 && !entity.id) || - (items[0].id !== entity.id) - ) { - throw errors.newConflictError(`${model.tableName} already exists with ${_.map(params, (v, k) => `${k}:${v}`).join(', ')}`) + try { + return await esHelper.searchElasticSearch(resource, query, auth) + } catch (err) { + // return error if enrich fails + if (resource === 'user' && query.enrich) { + throw errors.elasticSearchEnrichError(err.message) } + logger.logFullError(err) } + return null } /** - * build db query by params - * @param params - * @return {[]} + * Get the resource from model name + * @param modelName the model name + * @returns {string|*} the resource */ -function buildQueryByParams (params) { - const dbQueries = [] - _.each(params, (v, k) => { - dbQueries.push(`${k} = '${v}'`) - }) - return dbQueries +function getResource (modelName) { + if (MODEL_TO_RESOURCE[modelName]) { + return MODEL_TO_RESOURCE[modelName] + } else { + return modelName.toLowerCase() + } } /** @@ -137,195 +168,50 @@ async function publishMessage (op, resource, result) { } /** - * get service methods - * @param Model the model - * @param createSchema the create joi schema - * @param patchSchema the patch joi schema - * @param searchSchema the search joi schema - * @param buildDBQuery the async build db query function - * @param uniqueFields the unique fields - * @return {any} methods + * Sleep for some time + * @param ms the time to sleep in ms */ -function getServiceMethods (Model, createSchema, patchSchema, searchSchema, buildDBQuery, uniqueFields) { - const models = require('../models/index') - const resource = getResource(Model.name) - const { permissionCheck, checkIfExists, getAuthUser } = require('./helper') - - /** - * create entity - * @param entity the request device entity - * @param auth the auth information - * @return {Promise} the created device - */ - async function create (entity, auth) { - await makeSureUnique(Model, entity, uniqueFields) - await makeSureRefExist(entity) - - const dbEntity = new Model() - _.extend(dbEntity, entity) - dbEntity.created = new Date() - dbEntity.createdBy = getAuthUser(auth) - await models.DBHelper.save(Model, dbEntity) - try { - await publishMessage('create', resource, dbEntity) - } catch (err) { - logger.logFullError(err) - } - return dbEntity - } - - create.schema = { - entity: createSchema, - auth: joi.object() - } - - /** - * patch device by id - * @param id the device id - * @param entity the request device entity - * @param auth the auth object - * @param params the query params - * @return {Promise} the updated device - */ - async function patch (id, entity, auth, params) { - await makeSureRefExist(entity) - - const dbEntity = await get(id, auth, params, {}, true) - const newEntity = new Model() - _.extend(newEntity, dbEntity, entity) - newEntity.updated = new Date() - newEntity.updatedBy = getAuthUser(auth) - await makeSureUnique(Model, newEntity, uniqueFields) - await models.DBHelper.save(Model, newEntity) - try { - await publishMessage('patch', resource, newEntity) - } catch (err) { - logger.logFullError(err) - } - return newEntity - } - - patch.schema = { - id: joi.string(), - entity: patchSchema, - auth: joi.object(), - params: joi.object() - } - - /** - * get device by id - * @param id the device id - * @param auth the auth obj - * @param params the path parameters - * @param query the query parameters - * @param fromDb Should we bypass Elasticsearch for the record and fetch from db instead? - * @return {Promise} the db device - */ - async function get (id, auth, params, query = {}, fromDb = false) { - let recordObj - // Merge path and query params - const trueParams = _.assign(params, query) - if (!fromDb) { - try { - const result = await esHelper.getFromElasticSearch(resource, id, auth, trueParams) - // check permission - permissionCheck(auth, result) - return result - } catch (err) { - // return error if enrich fails or permission fails - if ((resource === 'user' && trueParams.enrich) || (err.status && err.status === 403)) { - throw errors.elasticSearchEnrichError(err.message) - } - logger.logFullError(err) - } - } - if (_.isNil(trueParams) || _.isEmpty(trueParams)) { - recordObj = await models.DBHelper.get(Model, id) - } else { - const items = await models.DBHelper.find(Model, buildQueryByParams(trueParams)) - recordObj = items[0] - if (!recordObj) { - throw errors.newEntityNotFoundError(`cannot find ${Model.tableName} where ${_.map(trueParams, (v, k) => `${k}:${v}`).join(', ')}`) - } - } - permissionCheck(auth, recordObj) - return recordObj - } - - /** - * search devices by query - * @param query the search query - * @param auth the auth object - * @return {Promise} the results - */ - async function search (query, auth) { - try { - return await esHelper.searchElasticSearch(resource, query, auth) - } catch (err) { - // return error if enrich fails - if (resource === 'user' && query.enrich) { - throw errors.elasticSearchEnrichError(err.message) - } - logger.logFullError(err) - } - - // Elasticsearch failed. Hit the database - const dbQueries = await buildDBQuery(query, auth) - - // user token - // for non-admin users, this endpoint will only return entities that the user has created. - if ( - auth.roles && - !checkIfExists(auth.roles, appConst.AdminUser) && - !checkIfExists(auth.roles, [appConst.UserRoles.ubahn]) - ) { - dbQueries.push(`${Model.tableName}.createdBy = '${getAuthUser(auth)}'`) - } - const items = await models.DBHelper.find(Model, dbQueries) - // return fromDB:true to indicate it is got from db, - // and response headers ('X-Total', 'X-Page', etc.) are not set in this case - return { fromDb: true, result: items, total: items.length } - } - - search.schema = { - query: { - page: joi.id(), - perPage: joi.pageSize(), - ...searchSchema - }, - auth: joi.object() - } +function sleep (ms) { + return new Promise(resolve => setTimeout(resolve, ms)) +} - /** - * remove entity by id - * @param id the entity id - * @param auth the auth object - * @param params the query params - * @return {Promise} no data returned - */ - async function remove (id, auth, params) { - let payload - await get(id, auth, params, {}, true) // check exist - await models.DBHelper.delete(Model, id, buildQueryByParams(params)) - if (SUB_USER_DOCUMENTS[resource] || SUB_ORG_DOCUMENTS[resource]) { - payload = _.assign({}, params) - } else { - payload = { - id - } - } - try { - await publishMessage('remove', resource, payload) - } catch (err) { - logger.logFullError(err) +/** + * delete child of record with delay between each item deleted + * @param model the child model to delete + * @param id the user id to delete + * @param params the params for child + * @param resourceName the es recource name + */ +async function deleteChild (model, id, params, resourceName) { + const query = {} + query[params[0]] = id + const result = await dbHelper.find(model, query) + + if (result && result.length > 0) { + let index = 0 + while (index < result.length) { + // initialize params + const record = result[index] + const esParams = {} + params.forEach(attr => { esParams[attr] = record[attr] }) + + // remove from db + dbHelper.remove(model, record.id) + deleteRecordFromEs(record.id, esParams, resourceName) + + // sleep for configured time + await sleep(config.CASCADE_PAUSE_MS) + index++ } } - - return { - create, search, patch, get, remove - } } module.exports = { - getServiceMethods + getResource, + patchRecordInEs, + deleteRecordFromEs, + searchRecordInEs, + createRecordInEs, + getRecordInEs, + deleteChild } diff --git a/src/models/Achievement.js b/src/models/Achievement.js index f31684d..87dc1bb 100644 --- a/src/models/Achievement.js +++ b/src/models/Achievement.js @@ -1,19 +1,42 @@ -const { RecordObject } = require('./BaseObject') - /** * Achievement model */ -class Achievement extends RecordObject { - constructor () { - super() - this.achievementsProviderId = null - this.name = null - this.uri = null - this.certifierId = null - this.certifiedDate = null - this.userId = null +const { DataTypes } = require('sequelize') + +module.exports = (sequelize) => { + const Achievement = sequelize.define('Achievement', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + name: { + type: DataTypes.STRING + }, + uri: { + type: DataTypes.STRING + }, + certifierId: { + type: DataTypes.STRING + }, + certifiedDate: { + type: DataTypes.DATE + } + }, + { + timestamps: true, + updatedAt: 'updated', + createdAt: 'created' + }) + Achievement.associate = (models) => { + Achievement.belongsTo(models.User, { foreignKey: 'userId', type: DataTypes.UUID }) + Achievement.belongsTo(models.AchievementsProvider, { foreignKey: 'achievementsProviderId', type: DataTypes.UUID }) } + return Achievement } - -Achievement.tableName = 'Achievement' -module.exports = Achievement diff --git a/src/models/AchievementsProvider.js b/src/models/AchievementsProvider.js index ab1db52..bb05b04 100644 --- a/src/models/AchievementsProvider.js +++ b/src/models/AchievementsProvider.js @@ -1,17 +1,32 @@ -const { RecordObject } = require('./BaseObject') - /** * AchievementsProvider model */ -class AchievementsProvider extends RecordObject { - constructor () { - super() - this.name = null +const { DataTypes } = require('sequelize') + +module.exports = (sequelize) => { + const AchievementsProvider = sequelize.define('AchievementsProvider', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + name: { + type: DataTypes.STRING + } + }, + { + timestamps: true, + updatedAt: 'updated', + createdAt: 'created' + }) + AchievementsProvider.associate = (models) => { + AchievementsProvider.hasMany(models.Achievement, { foreignKey: 'achievementsProviderId', type: DataTypes.UUID }) } + return AchievementsProvider } - -AchievementsProvider.tableName = 'AchievementsProvider' -AchievementsProvider.additionalSql = [ - 'CREATE INDEX ON AchievementsProvider (name)' -] -module.exports = AchievementsProvider diff --git a/src/models/Attribute.js b/src/models/Attribute.js index c41b9ff..6d7965f 100644 --- a/src/models/Attribute.js +++ b/src/models/Attribute.js @@ -1,18 +1,33 @@ -const { RecordObject } = require('./BaseObject') - /** * Attribute model */ -class Attribute extends RecordObject { - constructor () { - super() - this.attributeGroupId = null - this.name = null +const { DataTypes } = require('sequelize') + +module.exports = (sequelize) => { + const Attribute = sequelize.define('Attribute', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + name: { + type: DataTypes.STRING + } + }, + { + timestamps: true, + updatedAt: 'updated', + createdAt: 'created' + }) + Attribute.associate = (models) => { + Attribute.hasMany(models.UsersAttribute, { foreignKey: 'attributeId', type: DataTypes.UUID }) + Attribute.belongsTo(models.AttributeGroup, { foreignKey: 'attributeGroupId', type: DataTypes.UUID }) } + return Attribute } - -Attribute.tableName = 'Attribute' -Attribute.additionalSql = [ - 'CREATE INDEX ON Attribute (name)' -] -module.exports = Attribute diff --git a/src/models/AttributeGroup.js b/src/models/AttributeGroup.js index dbd1272..e24cee0 100644 --- a/src/models/AttributeGroup.js +++ b/src/models/AttributeGroup.js @@ -1,18 +1,33 @@ -const { RecordObject } = require('./BaseObject') - /** * AttributeGroup model */ -class AttributeGroup extends RecordObject { - constructor () { - super() - this.organizationId = null - this.name = null +const { DataTypes } = require('sequelize') + +module.exports = (sequelize) => { + const AttributeGroup = sequelize.define('AttributeGroup', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + name: { + type: DataTypes.STRING + } + }, + { + timestamps: true, + updatedAt: 'updated', + createdAt: 'created' + }) + AttributeGroup.associate = (models) => { + AttributeGroup.belongsTo(models.Organization, { foreignKey: 'organizationId', type: DataTypes.UUID }) + AttributeGroup.hasMany(models.Attribute, { foreignKey: 'attributeGroupId', type: DataTypes.UUID }) } + return AttributeGroup } - -AttributeGroup.tableName = 'AttributeGroup' -AttributeGroup.additionalSql = [ - 'CREATE INDEX ON AttributeGroup (name)' -] -module.exports = AttributeGroup diff --git a/src/models/BaseObject.js b/src/models/BaseObject.js deleted file mode 100644 index 64a1a1f..0000000 --- a/src/models/BaseObject.js +++ /dev/null @@ -1,48 +0,0 @@ -const _ = require('lodash') - -/** - * Base object - */ -class BaseObject { - constructor () { - this.id = null - } - - /** - * to json object - * @returns {*} - */ - toJSON () { - return _.omit(this) - } - - /** - * set value from obj - * @param obj the object value - */ - from (obj) { - const keys = _.keys(this) - _.each(keys, key => { - this[key] = obj[key] - }) - return this - } -} - -/** - * base object with created and updated etc . - */ -class RecordObject extends BaseObject { - constructor () { - super() - this.created = null - this.updated = null - this.createdBy = null - this.updatedBy = null - } -} - -module.exports = { - BaseObject, - RecordObject -} diff --git a/src/models/DBHelper.js b/src/models/DBHelper.js deleted file mode 100644 index 7862c4d..0000000 --- a/src/models/DBHelper.js +++ /dev/null @@ -1,191 +0,0 @@ -const { readerToJson, writeValueAsIon } = require('../common/helper') - -const QLDB = require('amazon-qldb-driver-nodejs') -const config = require('config') -const _ = require('lodash') -const logger = require('../common/logger') -const uuid = require('uuid') -const errors = require('../common/errors') - -/** - * the database instance - */ -const qldbInstance = new QLDB.PooledQldbDriver(config.DATABASE, { - accessKeyId: config.AWS_ACCESS_KEY_ID, - secretAccessKey: config.AWS_SECRET_ACCESS_KEY, - region: config.AWS_REGION -}) - -/** - * Database helper - */ -class DBHelper { - /** - * get db connection session - * @returns {Promise} - */ - static async getSession () { - return qldbInstance.getSession() - } - - /** - * get model by id - * @param Model the model - * @param id the id - * @param attributes the attributes - * @returns {Promise<{}>} - */ - static async get (Model, id, attributes) { - const session = await this.getSession() - try { - const tn = Model.tableName - const sql = `SELECT ${_.isNil(attributes) || _.isEmpty(attributes) ? '*' : attributes.join(',')} FROM ${tn} WHERE id = '${id}'` - const result = await session.executeStatement(sql, []) - if (result.getResultList().length <= 0) { - throw errors.newEntityNotFoundError(`cannot find ${tn} where id = ${id}`) - } - return new Model().from(readerToJson(result.getResultList()[0])) - } finally { - session.close() - } - } - - /** - * delete item by id - * @param model the model - * @param id the id - * @param queries the db queries - * @returns {Promise} - */ - static async delete (model, id, queries = []) { - const session = await this.getSession() - try { - const tn = model.tableName - if (id) { - queries.push(`id = '${id}'`) - } - if (queries.length <= 0) { - throw errors.newBadRequestError('delete rows without queries is not allowed') - } - const sql = `DELETE FROM ${tn} where ${queries.join(' AND ')}` - logger.debug(sql) - await session.executeStatement(sql, []) - } finally { - session.close() - } - } - - /** - * find items by queries - * @param Model the model - * @param queries the sql queries - * @returns {Promise<{}[]>} - */ - static async find (Model, queries) { - const session = await this.getSession() - try { - const tn = Model.tableName - let sql = `SELECT * FROM ${tn}` - if (queries.length > 0 && queries[0].toLowerCase().indexOf('select') === 0) { - sql = queries.shift() - } - const where = queries.length <= 0 ? '' : ('WHERE ' + queries.join(' AND ')) - sql = `${sql} ${where}` - logger.debug(sql) - const results = await session.executeStatement(sql, []) - return results.getResultList().map(r => new Model().from(readerToJson(r))) - } finally { - session.close() - } - } - - /** - * save/insert item to database - * @param model the model - * @param entity the entity - * @param create is create - * @returns {Promise} - */ - static async save (model, entity, create) { - const session = await this.getSession() - try { - const tn = model.tableName - const documentsWriter = QLDB.createQldbWriter() - if (entity.id && !create) { - const statement = `UPDATE ${tn} AS p SET p = ? WHERE p.id = '${entity.id}'` - writeValueAsIon(entity.toJSON(), documentsWriter) - await session.executeStatement(statement, [documentsWriter]) - } else { - entity.id = entity.id || uuid.v4() - const statement = `INSERT INTO ${tn} ?` - writeValueAsIon(entity.toJSON(), documentsWriter) - await session.executeStatement(statement, [documentsWriter]) - } - } finally { - session.close() - } - return entity - } - - /** - * create table if not exist - * @param model the model - * @returns {Promise} - */ - static async createTable (model) { - const session = await this.getSession() - try { - const tables = await session.getTableNames() - const tn = model.tableName - if (!_.includes(tables, tn)) { - await session.executeStatement(`create table ${tn}`, []) - logger.info(`table ${tn} created ...`) - const additional = [`create index on ${tn} (id)`].concat(model.additionalSql || []) - for (let i = 0; i < additional.length; i++) { - logger.debug(additional[i]) - await session.executeStatement(additional[i]) - } - } else { - logger.info('exists, skip create table ' + tn) - } - } finally { - session.close() - } - } - - /** - * clear table - * @param model the model - * @returns {Promise} - */ - static async clear (model) { - const session = await qldbInstance.getSession() - try { - const transaction = await session.startTransaction() - await transaction.executeInline('DELETE FROM ' + model.tableName, []) - await transaction.commit() - logger.info(`${model.tableName} clean`) - } finally { - session.close() - } - } - - /** - * drop table - * @param model the model - * @returns {Promise} - */ - static async drop (model) { - const session = await qldbInstance.getSession() - try { - const transaction = await session.startTransaction() - await transaction.executeInline('drop table ' + model.tableName, []) - await transaction.commit() - logger.info(`table ${model.tableName} dropped`) - } finally { - session.close() - } - } -} - -module.exports = DBHelper diff --git a/src/models/ExternalProfile.js b/src/models/ExternalProfile.js index a847499..7129449 100644 --- a/src/models/ExternalProfile.js +++ b/src/models/ExternalProfile.js @@ -1,19 +1,39 @@ -const { RecordObject } = require('./BaseObject') - /** * ExternalProfile model */ -class ExternalProfile extends RecordObject { - constructor () { - super() - this.userId = null - this.organizationId = null - this.externalId = null - this.uri = null - this.isInactive = null +const { DataTypes } = require('sequelize') + +module.exports = (sequelize) => { + const ExternalProfile = sequelize.define('ExternalProfile', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + externalId: { + type: DataTypes.STRING + }, + uri: { + type: DataTypes.STRING + }, + isInactive: { + type: DataTypes.BOOLEAN + } + }, + { + timestamps: true, + updatedAt: 'updated', + createdAt: 'created' + }) + ExternalProfile.associate = (models) => { + ExternalProfile.belongsTo(models.User, { foreignKey: 'userId', type: DataTypes.UUID }) + ExternalProfile.belongsTo(models.Organization, { foreignKey: 'organizationId', type: DataTypes.UUID }) } + return ExternalProfile } - -ExternalProfile.tableName = 'ExternalProfile' - -module.exports = ExternalProfile diff --git a/src/models/Organization.js b/src/models/Organization.js index a8692a5..91fd1e4 100644 --- a/src/models/Organization.js +++ b/src/models/Organization.js @@ -1,17 +1,34 @@ -const { RecordObject } = require('./BaseObject') - /** * Organization model */ -class Organization extends RecordObject { - constructor () { - super() - this.name = null +const { DataTypes } = require('sequelize') + +module.exports = (sequelize) => { + const Organization = sequelize.define('Organization', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + name: { + type: DataTypes.STRING + } + }, + { + timestamps: true, + updatedAt: 'updated', + createdAt: 'created' + }) + Organization.associate = (models) => { + Organization.hasMany(models.ExternalProfile, { foreignKey: 'organizationId', type: DataTypes.UUID }) + Organization.hasMany(models.AttributeGroup, { foreignKey: 'organizationId', type: DataTypes.UUID }) + Organization.hasMany(models.OrganizationSkillsProvider, { foreignKey: 'organizationId', type: DataTypes.UUID }) } + return Organization } - -Organization.tableName = 'Organization' -Organization.additionalSql = [ - 'CREATE INDEX ON Organization (name)' -] -module.exports = Organization diff --git a/src/models/OrganizationSkillsProvider.js b/src/models/OrganizationSkillsProvider.js index afab78e..0756fb2 100644 --- a/src/models/OrganizationSkillsProvider.js +++ b/src/models/OrganizationSkillsProvider.js @@ -1,16 +1,30 @@ -const { RecordObject } = require('./BaseObject') - /** * OrganizationSkillsProvider model */ -class OrganizationSkillsProvider extends RecordObject { - constructor () { - super() - this.organizationId = null - this.skillProviderId = null +const { DataTypes } = require('sequelize') + +module.exports = (sequelize) => { + const OrganizationSkillsProvider = sequelize.define('OrganizationSkillsProvider', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + } + }, + { + timestamps: true, + updatedAt: 'updated', + createdAt: 'created' + }) + OrganizationSkillsProvider.associate = (models) => { + OrganizationSkillsProvider.belongsTo(models.SkillsProvider, { foreignKey: 'skillProviderId', type: DataTypes.UUID }) + OrganizationSkillsProvider.belongsTo(models.Organization, { foreignKey: 'organizationId', type: DataTypes.UUID }) } + return OrganizationSkillsProvider } - -OrganizationSkillsProvider.tableName = 'OrganizationSkillsProvider' - -module.exports = OrganizationSkillsProvider diff --git a/src/models/Role.js b/src/models/Role.js index 8f01963..5011742 100644 --- a/src/models/Role.js +++ b/src/models/Role.js @@ -1,17 +1,32 @@ -const { RecordObject } = require('./BaseObject') - /** * Role model */ -class Role extends RecordObject { - constructor () { - super() - this.name = null +const { DataTypes } = require('sequelize') + +module.exports = (sequelize) => { + const Role = sequelize.define('Role', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + name: { + type: DataTypes.STRING + } + }, + { + timestamps: true, + updatedAt: 'updated', + createdAt: 'created' + }) + Role.associate = (models) => { + Role.hasMany(models.UsersRole, { foreignKey: 'roleId', type: DataTypes.UUID }) } + return Role } - -Role.tableName = 'Role' -Role.additionalSql = [ - 'CREATE INDEX ON Role (name)' -] -module.exports = Role diff --git a/src/models/Skill.js b/src/models/Skill.js index fd5bfe7..355f161 100644 --- a/src/models/Skill.js +++ b/src/models/Skill.js @@ -1,17 +1,36 @@ -const { RecordObject } = require('./BaseObject') +const { DataTypes } = require('sequelize') -/** - * Skill model - */ -class Skill extends RecordObject { - constructor () { - super() - this.skillProviderId = null - this.name = null - this.externalId = null - this.uri = null +module.exports = (sequelize) => { + const Skill = sequelize.define('Skill', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + name: { + type: DataTypes.STRING + }, + externalId: { + type: DataTypes.STRING + }, + uri: { + type: DataTypes.STRING + } + }, + { + timestamps: true, + updatedAt: 'updated', + createdAt: 'created' + }) + Skill.associate = (models) => { + Skill.belongsTo(models.SkillsProvider, { foreignKey: 'skillProviderId', type: DataTypes.UUID }) + Skill.hasMany(models.UsersSkill, { foreignKey: 'skillId', type: DataTypes.UUID }) } + return Skill } - -Skill.tableName = 'Skill' -module.exports = Skill diff --git a/src/models/SkillsProvider.js b/src/models/SkillsProvider.js index f37617d..f9aa226 100644 --- a/src/models/SkillsProvider.js +++ b/src/models/SkillsProvider.js @@ -1,14 +1,30 @@ -const { RecordObject } = require('./BaseObject') +const { DataTypes } = require('sequelize') -/** - * SkillsProvider model - */ -class SkillsProvider extends RecordObject { - constructor () { - super() - this.name = null +module.exports = (sequelize) => { + const SkillsProvider = sequelize.define('SkillsProvider', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + name: { + type: DataTypes.STRING + } + }, + { + timestamps: true, + updatedAt: 'updated', + createdAt: 'created' + }) + SkillsProvider.associate = (models) => { + SkillsProvider.hasMany(models.Skill, { foreignKey: 'skillProviderId', type: DataTypes.UUID }) + SkillsProvider.hasMany(models.OrganizationSkillsProvider, { foreignKey: 'skillProviderId', type: DataTypes.UUID }) } + return SkillsProvider } - -SkillsProvider.tableName = 'SkillsProvider' -module.exports = SkillsProvider diff --git a/src/models/User.js b/src/models/User.js index f089ccb..8e5d9da 100644 --- a/src/models/User.js +++ b/src/models/User.js @@ -1,23 +1,42 @@ -const { RecordObject } = require('./BaseObject') - /** * User model */ -class User extends RecordObject { - constructor () { - super() - this.handle = null - this.firstName = null - this.lastName = null +const { DataTypes } = require('sequelize') + +module.exports = (sequelize) => { + const User = sequelize.define('User', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + handle: { + type: DataTypes.STRING + }, + firstName: { + type: DataTypes.STRING + }, + lastName: { + type: DataTypes.STRING + } + }, + { + timestamps: true, + updatedAt: 'updated', + createdAt: 'created' + }) + User.associate = (models) => { + User.hasMany(models.UsersSkill, { foreignKey: 'userId', type: DataTypes.UUID }) + User.hasMany(models.Achievement, { foreignKey: 'userId', type: DataTypes.UUID }) + User.hasMany(models.UsersAttribute, { foreignKey: 'userId', type: DataTypes.UUID }) + User.hasMany(models.ExternalProfile, { foreignKey: 'userId', type: DataTypes.UUID }) + User.hasMany(models.UsersRole, { foreignKey: 'userId', type: DataTypes.UUID }) } + return User } - -/** - * User is keywords in database, so use `DUser` - * @type {string} - */ -User.tableName = 'DUser' -User.additionalSql = [ - 'CREATE INDEX ON DUser (handle)' -] -module.exports = User diff --git a/src/models/UserAttribute.js b/src/models/UserAttribute.js index 0d7dc0b..194fbd5 100644 --- a/src/models/UserAttribute.js +++ b/src/models/UserAttribute.js @@ -1,16 +1,33 @@ -const { RecordObject } = require('./BaseObject') - /** * UsersAttribute skill model */ -class UsersAttribute extends RecordObject { - constructor () { - super() - this.attributeId = null - this.value = null - this.userId = null +const { DataTypes } = require('sequelize') + +module.exports = (sequelize) => { + const UsersAttribute = sequelize.define('UsersAttribute', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + value: { + type: DataTypes.STRING + } + }, + { + timestamps: true, + updatedAt: 'updated', + createdAt: 'created' + }) + UsersAttribute.associate = (models) => { + UsersAttribute.belongsTo(models.User, { foreignKey: 'userId', type: DataTypes.UUID }) + UsersAttribute.belongsTo(models.Attribute, { foreignKey: 'attributeId', type: DataTypes.UUID }) } + return UsersAttribute } - -UsersAttribute.tableName = 'UsersAttribute' -module.exports = UsersAttribute diff --git a/src/models/UsersRole.js b/src/models/UsersRole.js index 4e43824..d94a85f 100644 --- a/src/models/UsersRole.js +++ b/src/models/UsersRole.js @@ -1,16 +1,30 @@ -const { RecordObject } = require('./BaseObject') - /** - * UserRole model + * UsersRole model */ -class UsersRole extends RecordObject { - constructor () { - super() - this.userId = null - this.roleId = null +const { DataTypes } = require('sequelize') + +module.exports = (sequelize) => { + const UsersRole = sequelize.define('UsersRole', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + } + }, + { + timestamps: true, + updatedAt: 'updated', + createdAt: 'created' + }) + UsersRole.associate = (models) => { + UsersRole.belongsTo(models.User, { foreignKey: 'userId', type: DataTypes.UUID }) + UsersRole.belongsTo(models.Role, { foreignKey: 'roleId', type: DataTypes.UUID }) } + return UsersRole } - -UsersRole.tableName = 'UserRole' - -module.exports = UsersRole diff --git a/src/models/UsersSkill.js b/src/models/UsersSkill.js index 6407d94..11250de 100644 --- a/src/models/UsersSkill.js +++ b/src/models/UsersSkill.js @@ -1,18 +1,36 @@ -const { RecordObject } = require('./BaseObject') +const { DataTypes } = require('sequelize') -/** - * users skill model - */ -class UsersSkill extends RecordObject { - constructor () { - super() - this.skillId = null - this.metricValue = null - this.certifierId = null - this.certifiedDate = null - this.userId = null +module.exports = (sequelize) => { + const UsersSkill = sequelize.define('UsersSkill', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 + }, + createdBy: { + type: DataTypes.STRING + }, + updatedBy: { + type: DataTypes.STRING + }, + metricValue: { + type: DataTypes.STRING + }, + certifierId: { + type: DataTypes.STRING + }, + certifiedDate: { + type: DataTypes.DATE + } + }, + { + timestamps: true, + updatedAt: 'updated', + createdAt: 'created' + }) + UsersSkill.associate = (models) => { + UsersSkill.belongsTo(models.User, { foreignKey: 'userId', type: DataTypes.UUID }) + UsersSkill.belongsTo(models.Skill, { foreignKey: 'skillId', type: DataTypes.UUID }) } + return UsersSkill } - -UsersSkill.tableName = 'UsersSkill' -module.exports = UsersSkill diff --git a/src/models/index.js b/src/models/index.js index 8eecacc..4bf6b21 100755 --- a/src/models/index.js +++ b/src/models/index.js @@ -1,61 +1,43 @@ /** * the model index */ - -const User = require('./User') -const Role = require('./Role') -const SkillsProvider = require('./SkillsProvider') -const Skill = require('./Skill') -const Organization = require('./Organization') -const UsersRole = require('./UsersRole') -const UsersSkill = require('./UsersSkill') -const ExternalProfile = require('./ExternalProfile') -const AchievementsProvider = require('./AchievementsProvider') -const Achievement = require('./Achievement') -const AttributeGroup = require('./AttributeGroup') -const Attribute = require('./Attribute') -const UserAttribute = require('./UserAttribute') -const OrganizationSkillsProvider = require('./OrganizationSkillsProvider') +const { Sequelize } = require('sequelize') +const config = require('config') +const fs = require('fs') +const path = require('path') const logger = require('../common/logger') -const consts = require('../consts') -const DBHelper = require('./DBHelper') +/** + * the database instance + */ +const sequelize = new Sequelize( + `postgresql://${config.DB_USERNAME}:${config.DB_PASSWORD}@${config.DB_HOST}:${config.DB_PORT}/${config.DB_NAME}` +) + +const db = {} + +fs + .readdirSync(__dirname) + .filter(file => (file.indexOf('.') !== 0) && (file !== 'index.js')) + .forEach((file) => { + const model = require(path.join(__dirname, file))(sequelize) + db[model.name] = model + }) + +Object.keys(db).forEach((modelName) => { + if ('associate' in db[modelName]) { + db[modelName].associate(db) + } +}) + +module.exports = sequelize -module.exports = { - User, - Role, - SkillsProvider, - Organization, - Skill, - UsersRole, - UsersSkill, - Achievement, - ExternalProfile, - AchievementsProvider, - AttributeGroup, - Attribute, - UserAttribute, - OrganizationSkillsProvider, - consts, - DBHelper -} /** * create table */ module.exports.init = async () => { logger.info('connect to database, check/create tables ...') - await DBHelper.createTable(User) - await DBHelper.createTable(Role) - await DBHelper.createTable(SkillsProvider) - await DBHelper.createTable(Skill) - await DBHelper.createTable(Organization) - await DBHelper.createTable(UsersRole) - await DBHelper.createTable(UsersSkill) - await DBHelper.createTable(ExternalProfile) - await DBHelper.createTable(AchievementsProvider) - await DBHelper.createTable(Achievement) - await DBHelper.createTable(AttributeGroup) - await DBHelper.createTable(Attribute) - await DBHelper.createTable(UserAttribute) - await DBHelper.createTable(OrganizationSkillsProvider) + // authenticate db + await sequelize.authenticate() + logger.info(`Connected to db ${config.DB_NAME} successfully`) } diff --git a/src/modules/achievement/service.js b/src/modules/achievement/service.js index 02109fa..e27a65e 100644 --- a/src/modules/achievement/service.js +++ b/src/modules/achievement/service.js @@ -1,13 +1,41 @@ /** * the achievement services */ +const joi = require('@hapi/joi') +const _ = require('lodash') -const joi = require('@hapi/joi').extend(require('@hapi/joi-date')) -const models = require('../../models/index') +const errors = require('../../common/errors') const helper = require('../../common/helper') -const methods = helper.getServiceMethods( - models.Achievement, - { // create request body joi schema +const dbHelper = require('../../common/db-helper') +const serviceHelper = require('../../common/service-helper') +const sequelize = require('../../models/index') + +const User = sequelize.models.User +const AchievementsProvider = sequelize.models.AchievementsProvider +const Achievement = sequelize.models.Achievement +const resource = serviceHelper.getResource('Achievement') +const uniqueFields = [['userId', 'achievementsProviderId']] + +/** + * create entity + * @param entity the request device entity + * @param auth the auth information + * @return {Promise} the created device + */ +async function create (entity, auth) { + await dbHelper.get(User, entity.userId) + await dbHelper.get(AchievementsProvider, entity.achievementsProviderId) + + await dbHelper.makeSureUnique(Achievement, entity, uniqueFields) + + const result = await dbHelper.create(Achievement, entity, auth) + await serviceHelper.createRecordInEs(resource, result) + + return result +} + +create.schema = { + entity: { userId: joi.string().required(), achievementsProviderId: joi.string().required(), name: joi.string().required(), @@ -15,7 +43,32 @@ const methods = helper.getServiceMethods( certifierId: joi.string().allow(''), certifiedDate: joi.date().format('iso') }, - { // patch request body joi schema + auth: joi.object() +} + +/** + * patch device by id + * @param id the device id + * @param entity the request device entity + * @param auth the auth object + * @param params the query params + * @return {Promise} the updated device + */ +async function patch (id, entity, auth, params) { + await dbHelper.get(User, entity.userId) + await dbHelper.get(AchievementsProvider, entity.achievementsProviderId) + + await dbHelper.makeSureUnique(Achievement, entity, uniqueFields, params) + + const newEntity = await dbHelper.update(Achievement, id, entity, auth, params) + await serviceHelper.patchRecordInEs(resource, newEntity) + + return newEntity +} + +patch.schema = { + id: joi.string(), + entity: { userId: joi.string().required(), achievementsProviderId: joi.string().required(), name: joi.string(), @@ -23,23 +76,92 @@ const methods = helper.getServiceMethods( certifierId: joi.string().allow(''), certifiedDate: joi.date().format('iso') }, - { // search request query joi schema + auth: joi.object(), + params: joi.object() +} + +/** + * get device by id + * @param id the device id + * @param auth the auth obj + * @param params the path parameters + * @param query the query parameters + * @param fromDb Should we bypass Elasticsearch for the record and fetch from db instead? + * @return {Promise} the db device + */ +async function get (id, auth, params, query = {}, fromDb = false) { + const trueParams = _.assign(params, query) + if (!fromDb) { + const esResult = await serviceHelper.getRecordInEs(resource, id, trueParams, auth) + if (esResult) { + return esResult + } + } + + const recordObj = await dbHelper.get(Achievement, id, params) + if (!recordObj) { + throw errors.newEntityNotFoundError(`cannot find ${Achievement.name} where ${_.map(trueParams, (v, k) => `${k}:${v}`).join(', ')}`) + } + + helper.permissionCheck(auth, recordObj) + return recordObj +} + +/** + * search devices by query + * @param query the search query + * @param auth the auth object + * @return {Promise} the results + */ +async function search (query, auth) { + // get from elasticsearch, if that fails get from db + // and response headers ('X-Total', 'X-Page', etc.) are not set in case of db return + const esResult = await serviceHelper.searchRecordInEs(resource, query, auth) + if (esResult) { + return esResult + } + + // add query for associations + if (query.achievementsProviderName) { + query['$AchievementsProvider.name$'] = query.achievementsProviderName + delete query.achievementsProviderName + } + + const items = await dbHelper.find(Achievement, query, auth, [{ + model: AchievementsProvider, + as: 'AchievementsProvider', + attributes: [] + }]) + + return { fromDb: true, result: items, total: items.length } +} + +search.schema = { + query: { + page: joi.id(), + perPage: joi.pageSize(), userId: joi.string().required(), achievementsProviderName: joi.string() }, - async (query) => { // build search query by request - const dbQueries = ['SELECT * FROM AchievementsProvider, Achievement', - `Achievement.userId = '${query.userId}'`, - 'Achievement.achievementsProviderId = AchievementsProvider.id'] - // filter by achievements provider name - if (query.achievementsProviderName) { - dbQueries.push(`AchievementsProvider.name like '%${query.achievementsProviderName}%'`) - } - return dbQueries - }, - [['userId', 'achievementsProviderId']] // unique fields -) + auth: joi.object() +} + +/** + * remove entity by id + * @param id the entity id + * @param auth the auth object + * @param params the query params + * @return {Promise} no data returned + */ +async function remove (id, auth, params) { + await dbHelper.remove(Achievement, id, params) + await serviceHelper.deleteRecordFromEs(id, params, resource) +} module.exports = { - ...methods + create, + search, + patch, + get, + remove } diff --git a/src/modules/achievementsProvider/service.js b/src/modules/achievementsProvider/service.js index bd6246c..03e1b3e 100644 --- a/src/modules/achievementsProvider/service.js +++ b/src/modules/achievementsProvider/service.js @@ -3,30 +3,141 @@ */ const joi = require('@hapi/joi') -const models = require('../../models/index') +const _ = require('lodash') + +const errors = require('../../common/errors') const helper = require('../../common/helper') -const methods = helper.getServiceMethods( - models.AchievementsProvider, - { // create request body joi schema +const dbHelper = require('../../common/db-helper') +const serviceHelper = require('../../common/service-helper') +const sequelize = require('../../models/index') + +const AchievementsProvider = sequelize.models.AchievementsProvider +const Achievement = sequelize.models.Achievement +const resource = serviceHelper.getResource('AchievementsProvider') +const uniqueFields = [['name']] + +/** + * create entity + * @param entity the request device entity + * @param auth the auth information + * @return {Promise} the created device + */ +async function create (entity, auth) { + await dbHelper.makeSureUnique(AchievementsProvider, entity, uniqueFields) + + const result = await dbHelper.create(AchievementsProvider, entity, auth) + await serviceHelper.createRecordInEs(resource, result) + + return result +} + +create.schema = { + entity: { name: joi.string().required() }, - { // patch request body joi schema - name: joi.string() - }, - { // search request query joi schema + auth: joi.object() +} + +/** + * patch device by id + * @param id the device id + * @param entity the request device entity + * @param auth the auth object + * @param params the query params + * @return {Promise} the updated device + */ +async function patch (id, entity, auth, params) { + await dbHelper.makeSureUnique(AchievementsProvider, entity, uniqueFields) + + const newEntity = await dbHelper.update(AchievementsProvider, id, entity, auth) + await serviceHelper.patchRecordInEs(resource, newEntity) + + return newEntity +} + +patch.schema = { + id: joi.string(), + entity: { name: joi.string() }, - async (query) => { // build search query by request - const dbQueries = [] - // filter by provider name - if (query.name) { - dbQueries.push(`name like '%${query.name}%'`) + auth: joi.object(), + params: joi.object() +} + +/** + * get device by id + * @param id the device id + * @param auth the auth obj + * @param params the path parameters + * @param query the query parameters + * @param fromDb Should we bypass Elasticsearch for the record and fetch from db instead? + * @return {Promise} the db device + */ +async function get (id, auth, params, query = {}, fromDb = false) { + const trueParams = _.assign(params, query) + if (!fromDb) { + const esResult = await serviceHelper.getRecordInEs(resource, id, trueParams, auth) + if (esResult) { + return esResult } - return dbQueries + } + + const recordObj = await dbHelper.get(AchievementsProvider, id) + if (!recordObj) { + throw errors.newEntityNotFoundError(`cannot find ${AchievementsProvider.name} where ${_.map(trueParams, (v, k) => `${k}:${v}`).join(', ')}`) + } + + helper.permissionCheck(auth, recordObj) + return recordObj +} + +/** + * search devices by query + * @param query the search query + * @param auth the auth object + * @return {Promise} the results + */ +async function search (query, auth) { + // get from elasticsearch, if that fails get from db + // and response headers ('X-Total', 'X-Page', etc.) are not set in case of db return + const esResult = await serviceHelper.searchRecordInEs(resource, query, auth) + if (esResult) { + return esResult + } + + const items = await dbHelper.find(AchievementsProvider, query, auth) + return { fromDb: true, result: items, total: items.length } +} + +search.schema = { + query: { + page: joi.id(), + perPage: joi.pageSize(), + name: joi.string() }, - [['name']] // unique fields -) + auth: joi.object() +} + +/** + * remove entity by id + * @param id the entity id + * @param auth the auth object + * @param params the query params + * @return {Promise} no data returned + */ +async function remove (id, auth, params) { + const existing = await dbHelper.find(Achievement, { achievementsProviderId: id }) + if (existing.length > 0) { + throw errors.deleteConflictError(`Please delete ${Achievement.name} with ids ${existing.map(o => o.id)}`) + } + await dbHelper.remove(AchievementsProvider, id) + await serviceHelper.deleteRecordFromEs(id, params, resource) +} module.exports = { - ...methods + create, + search, + patch, + get, + remove } diff --git a/src/modules/attribute/service.js b/src/modules/attribute/service.js index 7a607a2..f6672e3 100644 --- a/src/modules/attribute/service.js +++ b/src/modules/attribute/service.js @@ -1,39 +1,149 @@ /** - * the attribute group services + * the attribute services */ const joi = require('@hapi/joi') -const models = require('../../models/index') +const _ = require('lodash') + +const errors = require('../../common/errors') const helper = require('../../common/helper') -const methods = helper.getServiceMethods( - models.Attribute, - { // create request body joi schema +const dbHelper = require('../../common/db-helper') +const serviceHelper = require('../../common/service-helper') +const sequelize = require('../../models/index') + +const Attribute = sequelize.models.Attribute +const AttributeGroup = sequelize.models.AttributeGroup +const UsersAttribute = sequelize.models.UsersAttribute +const resource = serviceHelper.getResource('Attribute') +const uniqueFields = [['name', 'attributeGroupId']] + +/** + * create entity + * @param entity the request device entity + * @param auth the auth information + * @return {Promise} the created device + */ +async function create (entity, auth) { + await dbHelper.get(AttributeGroup, entity.attributeGroupId) + await dbHelper.makeSureUnique(Attribute, entity, uniqueFields) + + const result = await dbHelper.create(Attribute, entity, auth) + await serviceHelper.createRecordInEs(resource, result) + return result +} + +create.schema = { + entity: { name: joi.string().required(), attributeGroupId: joi.string().required() }, - { // patch request body joi schema + auth: joi.object() +} + +/** + * patch device by id + * @param id the device id + * @param entity the request device entity + * @param auth the auth object + * @param params the query params + * @return {Promise} the updated device + */ +async function patch (id, entity, auth, params) { + if (entity.attributeGroupId) { + await dbHelper.get(AttributeGroup, entity.attributeGroupId) + } + await dbHelper.makeSureUnique(Attribute, entity, uniqueFields) + + const newEntity = await dbHelper.update(Attribute, id, entity, auth) + await serviceHelper.patchRecordInEs(resource, newEntity) + return newEntity +} + +patch.schema = { + id: joi.string(), + entity: { name: joi.string(), attributeGroupId: joi.string() }, - { // search request query joi schema + auth: joi.object(), + params: joi.object() +} + +/** + * get device by id + * @param id the device id + * @param auth the auth obj + * @param params the path parameters + * @param query the query parameters + * @param fromDb Should we bypass Elasticsearch for the record and fetch from db instead? + * @return {Promise} the db device + */ +async function get (id, auth, params, query = {}, fromDb = false) { + const trueParams = _.assign(params, query) + if (!fromDb) { + const esResult = await serviceHelper.getRecordInEs(resource, id, trueParams, auth) + if (esResult) { + return esResult + } + } + + const recordObj = await dbHelper.get(Attribute, id) + if (!recordObj) { + throw errors.newEntityNotFoundError(`cannot find ${Attribute.name} where ${_.map(trueParams, (v, k) => `${k}:${v}`).join(', ')}`) + } + + helper.permissionCheck(auth, recordObj) + return recordObj +} + +/** + * search devices by query + * @param query the search query + * @param auth the auth object + * @return {Promise} the results + */ +async function search (query, auth) { + // get from elasticsearch, if that fails get from db + // and response headers ('X-Total', 'X-Page', etc.) are not set in case of db return + const esResult = await serviceHelper.searchRecordInEs(resource, query, auth) + if (esResult) { + return esResult + } + + const items = await dbHelper.find(Attribute, query, auth) + return { fromDb: true, result: items, total: items.length } +} + +search.schema = { + query: { + page: joi.id(), + perPage: joi.pageSize(), name: joi.string(), attributeGroupId: joi.string() }, - async (query) => { // build search query by request - const dbQueries = [] - // filter by provider name - if (query.name) { - dbQueries.push(`name like '%${query.name}%'`) - } - // filter by attribute group id - if (query.attributeGroupId) { - dbQueries.push(`attributeGroupId = '${query.attributeGroupId}'`) - } - return dbQueries - }, - [['name', 'attributeGroupId']] // unique fields -) + auth: joi.object() +} + +/** + * remove entity by id + * @param id the entity id + * @param auth the auth object + * @param params the query params + * @return {Promise} no data returned + */ +async function remove (id, auth, params) { + const existing = await dbHelper.find(UsersAttribute, { attributeId: id }) + if (existing.length > 0) { + throw errors.deleteConflictError(`Please delete ${UsersAttribute.name} with ids ${existing.map(o => o.id)}`) + } + await dbHelper.remove(Attribute, id) + await serviceHelper.deleteRecordFromEs(id, params, resource) +} module.exports = { - ...methods + create, + search, + patch, + get, + remove } diff --git a/src/modules/attributeGroup/service.js b/src/modules/attributeGroup/service.js index b9f14c7..19dbe23 100644 --- a/src/modules/attributeGroup/service.js +++ b/src/modules/attributeGroup/service.js @@ -1,38 +1,149 @@ /** - * the attribute group provider services + * the attribute group services */ const joi = require('@hapi/joi') -const models = require('../../models/index') +const _ = require('lodash') + +const errors = require('../../common/errors') const helper = require('../../common/helper') -const methods = helper.getServiceMethods( - models.AttributeGroup, - { // create request body joi schema +const dbHelper = require('../../common/db-helper') +const serviceHelper = require('../../common/service-helper') +const sequelize = require('../../models/index') + +const AttributeGroup = sequelize.models.AttributeGroup +const Organization = sequelize.models.Organization +const Attribute = sequelize.models.Attribute +const resource = serviceHelper.getResource('AttributeGroup') +const uniqueFields = [['name']] + +/** + * create entity + * @param entity the request device entity + * @param auth the auth information + * @return {Promise} the created device + */ +async function create (entity, auth) { + await dbHelper.get(Organization, entity.organizationId) + await dbHelper.makeSureUnique(AttributeGroup, entity, uniqueFields) + + const result = await dbHelper.create(AttributeGroup, entity, auth) + await serviceHelper.createRecordInEs(resource, result) + return result +} + +create.schema = { + entity: { name: joi.string().required(), organizationId: joi.string().required() }, - { // patch request body joi schema + auth: joi.object() +} + +/** + * patch device by id + * @param id the device id + * @param entity the request device entity + * @param auth the auth object + * @param params the query params + * @return {Promise} the updated device + */ +async function patch (id, entity, auth, params) { + if (entity.organizationId) { + await dbHelper.get(Organization, entity.organizationId) + } + await dbHelper.makeSureUnique(AttributeGroup, entity, uniqueFields) + + const newEntity = await dbHelper.update(AttributeGroup, id, entity, auth) + await serviceHelper.patchRecordInEs(resource, newEntity) + return newEntity +} + +patch.schema = { + id: joi.string(), + entity: { name: joi.string(), organizationId: joi.string() }, - { // search request query joi schema + auth: joi.object(), + params: joi.object() +} + +/** + * get device by id + * @param id the device id + * @param auth the auth obj + * @param params the path parameters + * @param query the query parameters + * @param fromDb Should we bypass Elasticsearch for the record and fetch from db instead? + * @return {Promise} the db device + */ +async function get (id, auth, params, query = {}, fromDb = false) { + const trueParams = _.assign(params, query) + if (!fromDb) { + const esResult = await serviceHelper.getRecordInEs(resource, id, trueParams, auth) + if (esResult) { + return esResult + } + } + + const recordObj = await dbHelper.get(AttributeGroup, id) + if (!recordObj) { + throw errors.newEntityNotFoundError(`cannot find ${AttributeGroup.name} where ${_.map(trueParams, (v, k) => `${k}:${v}`).join(', ')}`) + } + + helper.permissionCheck(auth, recordObj) + return recordObj +} + +/** + * search devices by query + * @param query the search query + * @param auth the auth object + * @return {Promise} the results + */ +async function search (query, auth) { + // get from elasticsearch, if that fails get from db + // and response headers ('X-Total', 'X-Page', etc.) are not set in case of db return + const esResult = await serviceHelper.searchRecordInEs(resource, query, auth) + if (esResult) { + return esResult + } + + const items = await dbHelper.find(AttributeGroup, query, auth) + return { fromDb: true, result: items, total: items.length } +} + +search.schema = { + query: { + page: joi.id(), + perPage: joi.pageSize(), name: joi.string(), organizationId: joi.string() }, - async (query) => { // build search query by request - const dbQueries = [] - // filter by provider name - if (query.name) { - dbQueries.push(`name = '${query.name}'`) - } - if (query.organizationId) { - dbQueries.push(`organizationId = '${query.organizationId}'`) - } - return dbQueries - }, - [['name']] // unique fields -) + auth: joi.object() +} + +/** + * remove entity by id + * @param id the entity id + * @param auth the auth object + * @param params the query params + * @return {Promise} no data returned + */ +async function remove (id, auth, params) { + const existing = await dbHelper.find(Attribute, { attributeGroupId: id }) + if (existing.length > 0) { + throw errors.deleteConflictError(`Please delete ${Attribute.name} with ids ${existing.map(o => o.id)}`) + } + await dbHelper.remove(AttributeGroup, id) + await serviceHelper.deleteRecordFromEs(id, params, resource) +} module.exports = { - ...methods + create, + search, + patch, + get, + remove } diff --git a/src/modules/externalProfile/service.js b/src/modules/externalProfile/service.js index 5116710..a1efa11 100644 --- a/src/modules/externalProfile/service.js +++ b/src/modules/externalProfile/service.js @@ -1,52 +1,170 @@ + /** * the external profile services */ const joi = require('@hapi/joi') -const models = require('../../models/index') +const _ = require('lodash') + +const errors = require('../../common/errors') const helper = require('../../common/helper') -const methods = helper.getServiceMethods( - models.ExternalProfile, - { // create request body joi schema +const dbHelper = require('../../common/db-helper') +const serviceHelper = require('../../common/service-helper') +const sequelize = require('../../models/index') + +const User = sequelize.models.User +const Organization = sequelize.models.Organization +const ExternalProfile = sequelize.models.ExternalProfile +const resource = serviceHelper.getResource('ExternalProfile') +const uniqueFields = [['userId', 'organizationId']] + +/** + * create entity + * @param entity the request device entity + * @param auth the auth information + * @return {Promise} the created device + */ +async function create (entity, auth) { + await dbHelper.get(Organization, entity.organizationId) + await dbHelper.get(User, entity.userId) + await dbHelper.makeSureUnique(ExternalProfile, entity, uniqueFields) + + const result = await dbHelper.create(ExternalProfile, entity, auth) + await serviceHelper.createRecordInEs(resource, result) + + return result +} + +create.schema = { + entity: { userId: joi.string().required(), organizationId: joi.string().required(), externalId: joi.string().required(), uri: joi.string(), isInactive: joi.boolean().default(false) }, - { // patch request body joi schema + auth: joi.object() +} + +/** + * patch device by id + * @param id the device id + * @param entity the request device entity + * @param auth the auth object + * @param params the query params + * @return {Promise} the updated device + */ +async function patch (id, entity, auth, params) { + await dbHelper.get(Organization, entity.organizationId) + await dbHelper.get(User, entity.userId) + + await dbHelper.makeSureUnique(ExternalProfile, entity, uniqueFields, params) + + const newEntity = await dbHelper.update(ExternalProfile, id, entity, auth, params) + await serviceHelper.patchRecordInEs(resource, newEntity) + + return newEntity +} + +patch.schema = { + id: joi.string(), + entity: { userId: joi.string().required(), organizationId: joi.string().required(), externalId: joi.string(), uri: joi.string(), isInactive: joi.boolean() }, - { // search request query joi schema + auth: joi.object(), + params: joi.object() +} + +/** + * get device by id + * @param id the device id + * @param auth the auth obj + * @param params the path parameters + * @param query the query parameters + * @param fromDb Should we bypass Elasticsearch for the record and fetch from db instead? + * @return {Promise} the db device + */ +async function get (id, auth, params, query = {}, fromDb = false) { + const trueParams = _.assign(params, query) + if (!fromDb) { + const esResult = await serviceHelper.getRecordInEs(resource, id, trueParams, auth) + if (esResult) { + return esResult + } + } + + const recordObj = await dbHelper.get(ExternalProfile, id, params) + if (!recordObj) { + throw errors.newEntityNotFoundError(`cannot find ${ExternalProfile.name} where ${_.map(trueParams, (v, k) => `${k}:${v}`).join(', ')}`) + } + + helper.permissionCheck(auth, recordObj) + return recordObj +} + +/** + * search devices by query + * @param query the search query + * @param auth the auth object + * @return {Promise} the results + */ +async function search (query, auth) { + // make sure id exists + await dbHelper.get(User, query.userId) + + // get from elasticsearch, if that fails get from db + // and response headers ('X-Total', 'X-Page', etc.) are not set in case of db return + const esResult = await serviceHelper.searchRecordInEs(resource, query, auth) + if (esResult) { + return esResult + } + + // add query for associations + if (query.organizationName) { + query['$Organization.name$'] = query.organizationName + delete query.organizationName + } + const items = await dbHelper.find(ExternalProfile, query, auth, [{ + model: Organization, + as: 'Organization', + attributes: [] + }]) + + return { fromDb: true, result: items, total: items.length } +} + +search.schema = { + query: { + page: joi.id(), + perPage: joi.pageSize(), userId: joi.string().required(), externalId: joi.string(), organizationName: joi.string(), isInactive: joi.boolean() }, - async (query) => { // build search query by request - const dbQueries = ['SELECT * FROM Organization, ExternalProfile', - `ExternalProfile.userId = '${query.userId}'`, - 'ExternalProfile.organizationId = Organization.id'] - // filter by organization name - if (query.organizationName) { - dbQueries.push(`Organization.name like '%${query.organizationName}%'`) - } - if (query.externalId) { - dbQueries.push(`ExternalProfile.externalId = '${query.externalId}'`) - } - if (query.isInactive) { - dbQueries.push(`ExternalProfile.isInactive = '${query.isInactive}'`) - } + auth: joi.object() +} - return dbQueries - }, - [['userId', 'organizationId']] // unique fields -) +/** + * remove entity by id + * @param id the entity id + * @param auth the auth object + * @param params the query params + * @return {Promise} no data returned + */ +async function remove (id, auth, params) { + await dbHelper.remove(ExternalProfile, id, params) + await serviceHelper.deleteRecordFromEs(id, params, resource) +} module.exports = { - ...methods + create, + search, + patch, + get, + remove } diff --git a/src/modules/health/service.js b/src/modules/health/service.js index d1cf1fe..e75cc14 100644 --- a/src/modules/health/service.js +++ b/src/modules/health/service.js @@ -1,14 +1,12 @@ const errors = require('../../common/errors') -const models = require('../../models') +const sequelize = require('../../models/index') async function get () { - // Check QLDB Connection by retrieving a session + // Check DB Connection by authenticating try { - const session = await models.DBHelper.getSession() - - session.close() + await sequelize.authenticate() } catch (e) { - throw errors.serviceUnavailableError(`QLDB is unavailable, ${e.message}`) + throw errors.serviceUnavailableError(`DB is unavailable, ${e.message}`) } return { checksRun: 1 } diff --git a/src/modules/organization/service.js b/src/modules/organization/service.js index 191214d..920b48e 100644 --- a/src/modules/organization/service.js +++ b/src/modules/organization/service.js @@ -3,21 +3,156 @@ */ const joi = require('@hapi/joi') -const models = require('../../models/index') +const _ = require('lodash') + +const errors = require('../../common/errors') const helper = require('../../common/helper') -const methods = helper.getServiceMethods( - models.Organization, - { name: joi.string().required() }, - { name: joi.string() }, - { name: joi.string() }, - async query => { - const dbQueries = [] - if (query.name) { - dbQueries.push(`name like '%${query.name}%'`) +const dbHelper = require('../../common/db-helper') +const serviceHelper = require('../../common/service-helper') +const sequelize = require('../../models/index') + +const OrganizationSkillsProvider = sequelize.models.OrganizationSkillsProvider +const ExternalProfile = sequelize.models.ExternalProfile +const AttributeGroup = sequelize.models.AttributeGroup +const Organization = sequelize.models.Organization +const resource = serviceHelper.getResource('Organization') +const uniqueFields = [['name']] + +/** + * create entity + * @param entity the request device entity + * @param auth the auth information + * @return {Promise} the created device + */ +async function create (entity, auth) { + await dbHelper.makeSureUnique(Organization, entity, uniqueFields) + const result = await dbHelper.create(Organization, entity, auth) + await serviceHelper.createRecordInEs(resource, result) + + return result +} + +create.schema = { + entity: { + name: joi.string().required() + }, + auth: joi.object() +} + +/** + * patch device by id + * @param id the device id + * @param entity the request device entity + * @param auth the auth object + * @param params the query params + * @return {Promise} the updated device + */ +async function patch (id, entity, auth, params) { + await dbHelper.makeSureUnique(Organization, entity, uniqueFields) + const newEntity = await dbHelper.update(Organization, id, entity, auth) + await serviceHelper.patchRecordInEs(resource, newEntity) + + return newEntity +} + +patch.schema = { + id: joi.string(), + entity: { + name: joi.string() + }, + auth: joi.object(), + params: joi.object() +} + +/** + * get device by id + * @param id the device id + * @param auth the auth obj + * @param params the path parameters + * @param query the query parameters + * @param fromDb Should we bypass Elasticsearch for the record and fetch from db instead? + * @return {Promise} the db device + */ +async function get (id, auth, params, query = {}, fromDb = false) { + const trueParams = _.assign(params, query) + if (!fromDb) { + const esResult = await serviceHelper.getRecordInEs(resource, id, trueParams, auth) + if (esResult) { + return esResult } - return dbQueries - }, [['name']]) + } + + const recordObj = await dbHelper.get(Organization, id) + if (!recordObj) { + throw errors.newEntityNotFoundError(`cannot find ${Organization.name} where ${_.map(trueParams, (v, k) => `${k}:${v}`).join(', ')}`) + } + + helper.permissionCheck(auth, recordObj) + return recordObj +} + +/** + * search devices by query + * @param query the search query + * @param auth the auth object + * @return {Promise} the results + */ +async function search (query, auth) { + // get from elasticsearch, if that fails get from db + // and response headers ('X-Total', 'X-Page', etc.) are not set in case of db return + const esResult = await serviceHelper.searchRecordInEs(resource, query, auth) + if (esResult) { + return esResult + } + + const items = await dbHelper.find(Organization, query, auth) + return { fromDb: true, result: items, total: items.length } +} + +search.schema = { + query: { + page: joi.id(), + perPage: joi.pageSize(), + name: joi.string() + }, + auth: joi.object() +} + +/** + * remove entity by id + * @param id the entity id + * @param auth the auth object + * @param params the query params + * @return {Promise} no data returned + */ +async function remove (id, auth, params) { + let existing = await dbHelper.find(AttributeGroup, { organizationId: id }) + if (existing.length > 0) { + throw errors.deleteConflictError(`Please delete ${AttributeGroup.name} with ids ${existing.map(o => o.id)}`) + } + existing = await dbHelper.find(ExternalProfile, { organizationId: id }) + if (existing.length > 0) { + throw errors.deleteConflictError(`Please delete ${ExternalProfile.name} with ids ${existing.map(o => o.id)}`) + } + + beginCascadeDelete(id, params) +} + +/** + * begin the cascade delete + * @param {*} id the organization id to delete + * @param {*} id the path params + */ +async function beginCascadeDelete (id, params) { + await serviceHelper.deleteChild(OrganizationSkillsProvider, id, ['organizationId', 'skillProviderId'], 'OrganizationSkillsProvider') + await dbHelper.remove(Organization, id) + await serviceHelper.deleteRecordFromEs(id, params, resource) +} module.exports = { - ...methods + create, + search, + patch, + get, + remove } diff --git a/src/modules/organizationSkillsProvider/service.js b/src/modules/organizationSkillsProvider/service.js index 2a64c68..2bf1175 100644 --- a/src/modules/organizationSkillsProvider/service.js +++ b/src/modules/organizationSkillsProvider/service.js @@ -3,24 +3,153 @@ */ const joi = require('@hapi/joi') -const models = require('../../models/index') +const _ = require('lodash') + +const errors = require('../../common/errors') const helper = require('../../common/helper') -const methods = helper.getServiceMethods( - models.OrganizationSkillsProvider, - { +const dbHelper = require('../../common/db-helper') +const serviceHelper = require('../../common/service-helper') +const sequelize = require('../../models/index') + +const SkillsProvider = sequelize.models.SkillsProvider +const Organization = sequelize.models.Organization +const OrganizationSkillsProvider = sequelize.models.OrganizationSkillsProvider +const resource = serviceHelper.getResource('OrganizationSkillsProvider') +const uniqueFields = [['organizationId', 'skillProviderId']] + +/** + * create entity + * @param entity the request device entity + * @param auth the auth information + * @return {Promise} the created device + */ +async function create (entity, auth) { + await dbHelper.get(Organization, entity.organizationId) + await dbHelper.get(SkillsProvider, entity.skillProviderId) + await dbHelper.makeSureUnique(OrganizationSkillsProvider, entity, uniqueFields) + + const result = await dbHelper.create(OrganizationSkillsProvider, entity, auth) + await serviceHelper.createRecordInEs(resource, result) + + return result +} + +create.schema = { + entity: { organizationId: joi.string().required(), skillProviderId: joi.string().required() }, - { + auth: joi.object() +} + +/** + * patch device by id + * @param id the device id + * @param entity the request device entity + * @param auth the auth object + * @param params the query params + * @return {Promise} the updated device + */ +async function patch (id, entity, auth, params) { + if (entity.organizationId) { + await dbHelper.get(Organization, entity.organizationId) + } + if (entity.skillProviderId) { + await dbHelper.get(SkillsProvider, entity.skillProviderId) + } + + await dbHelper.makeSureUnique(OrganizationSkillsProvider, entity, uniqueFields, params) + + const newEntity = await dbHelper.update(OrganizationSkillsProvider, id, entity, auth, params) + await serviceHelper.patchRecordInEs(resource, newEntity) + + return newEntity +} + +patch.schema = { + id: joi.string(), + entity: { organizationId: joi.string(), skillProviderId: joi.string() }, - { + auth: joi.object(), + params: joi.object() +} + +/** + * get device by id + * @param id the device id + * @param auth the auth obj + * @param params the path parameters + * @param query the query parameters + * @param fromDb Should we bypass Elasticsearch for the record and fetch from db instead? + * @return {Promise} the db device + */ +async function get (id, auth, params, query = {}, fromDb = false) { + const trueParams = _.assign(params, query) + if (!fromDb) { + const esResult = await serviceHelper.getRecordInEs(resource, id, trueParams, auth) + if (esResult) { + return esResult + } + } + + const recordObj = await dbHelper.get(OrganizationSkillsProvider, id, params) + if (!recordObj) { + throw errors.newEntityNotFoundError(`cannot find ${OrganizationSkillsProvider.name} where ${_.map(trueParams, (v, k) => `${k}:${v}`).join(', ')}`) + } + + helper.permissionCheck(auth, recordObj) + return recordObj +} + +/** + * search devices by query + * @param query the search query + * @param auth the auth object + * @return {Promise} the results + */ +async function search (query, auth) { + // make sure id exists + await dbHelper.get(Organization, query.organizationId) + + // get from elasticsearch, if that fails get from db + // and response headers ('X-Total', 'X-Page', etc.) are not set in case of db return + const esResult = await serviceHelper.searchRecordInEs(resource, query, auth) + if (esResult) { + return esResult + } + + const items = await dbHelper.find(OrganizationSkillsProvider, query, auth) + + return { fromDb: true, result: items, total: items.length } +} + +search.schema = { + query: { + page: joi.id(), + perPage: joi.pageSize(), organizationId: joi.string().required() }, - async (query) => [`organizationId = '${query.organizationId}'`], - [['organizationId', 'skillProviderId']]) + auth: joi.object() +} + +/** + * remove entity by id + * @param id the entity id + * @param auth the auth object + * @param params the query params + * @return {Promise} no data returned + */ +async function remove (id, auth, params) { + await dbHelper.remove(OrganizationSkillsProvider, id, params) + await serviceHelper.deleteRecordFromEs(id, params, resource) +} module.exports = { - ...methods + create, + search, + patch, + get, + remove } diff --git a/src/modules/role/service.js b/src/modules/role/service.js index b8a34f1..1c11623 100644 --- a/src/modules/role/service.js +++ b/src/modules/role/service.js @@ -3,21 +3,141 @@ */ const joi = require('@hapi/joi') -const models = require('../../models/index') +const _ = require('lodash') + +const errors = require('../../common/errors') const helper = require('../../common/helper') -const methods = helper.getServiceMethods( - models.Role, - { name: joi.string().required() }, - { name: joi.string() }, - { name: joi.string() }, - async query => { - const dbQueries = [] - if (query.name) { - dbQueries.push(`name like '%${query.name}%'`) +const dbHelper = require('../../common/db-helper') +const serviceHelper = require('../../common/service-helper') +const sequelize = require('../../models/index') + +const Role = sequelize.models.Role +const UsersRole = sequelize.models.UsersRole +const resource = serviceHelper.getResource('Role') +const uniqueFields = [['name']] + +/** + * create entity + * @param entity the request device entity + * @param auth the auth information + * @return {Promise} the created device + */ +async function create (entity, auth) { + await dbHelper.makeSureUnique(Role, entity, uniqueFields) + + const result = await dbHelper.create(Role, entity, auth) + await serviceHelper.createRecordInEs(resource, result) + + return result +} + +create.schema = { + entity: { + name: joi.string().required() + }, + auth: joi.object() +} + +/** + * patch device by id + * @param id the device id + * @param entity the request device entity + * @param auth the auth object + * @param params the query params + * @return {Promise} the updated device + */ +async function patch (id, entity, auth, params) { + await dbHelper.makeSureUnique(Role, entity, uniqueFields) + + const newEntity = await dbHelper.update(Role, id, entity, auth) + await serviceHelper.patchRecordInEs(resource, newEntity) + + return newEntity +} + +patch.schema = { + id: joi.string(), + entity: { + name: joi.string() + }, + auth: joi.object(), + params: joi.object() +} + +/** + * get device by id + * @param id the device id + * @param auth the auth obj + * @param params the path parameters + * @param query the query parameters + * @param fromDb Should we bypass Elasticsearch for the record and fetch from db instead? + * @return {Promise} the db device + */ +async function get (id, auth, params, query = {}, fromDb = false) { + const trueParams = _.assign(params, query) + if (!fromDb) { + const esResult = await serviceHelper.getRecordInEs(resource, id, trueParams, auth) + if (esResult) { + return esResult } - return dbQueries - }, [['name']]) + } + + const recordObj = await dbHelper.get(Role, id) + if (!recordObj) { + throw errors.newEntityNotFoundError(`cannot find ${Role.name} where ${_.map(trueParams, (v, k) => `${k}:${v}`).join(', ')}`) + } + + helper.permissionCheck(auth, recordObj) + return recordObj +} + +/** + * search devices by query + * @param query the search query + * @param auth the auth object + * @return {Promise} the results + */ +async function search (query, auth) { + // get from elasticsearch, if that fails get from db + // and response headers ('X-Total', 'X-Page', etc.) are not set in case of db return + const esResult = await serviceHelper.searchRecordInEs(resource, query, auth) + if (esResult) { + return esResult + } + + const items = await dbHelper.find(Role, query, auth) + return { fromDb: true, result: items, total: items.length } +} + +search.schema = { + query: { + page: joi.id(), + perPage: joi.pageSize(), + name: joi.string() + }, + auth: joi.object() +} + +/** + * remove entity by id + * @param id the entity id + * @param auth the auth object + * @param params the query params + * @return {Promise} no data returned + */ +async function remove (id, auth, params) { + const existing = await dbHelper.find(UsersRole, { roleId: id }) + if (existing.length > 0) { + throw errors.deleteConflictError(`Please delete ${UsersRole.name} with ids ${existing.map(o => o.id)}`) + } + await dbHelper.remove(Role, id) + await serviceHelper.deleteRecordFromEs(id, params, resource) +} module.exports = { - ...methods + create, + search, + patch, + get, + remove } diff --git a/src/modules/skill/service.js b/src/modules/skill/service.js index a869e88..02ad1c0 100644 --- a/src/modules/skill/service.js +++ b/src/modules/skill/service.js @@ -3,43 +3,154 @@ */ const joi = require('@hapi/joi') -const models = require('../../models/index') +const _ = require('lodash') + +const errors = require('../../common/errors') const helper = require('../../common/helper') -const methods = helper.getServiceMethods( - models.Skill, - { +const dbHelper = require('../../common/db-helper') +const serviceHelper = require('../../common/service-helper') +const sequelize = require('../../models/index') + +const Skill = sequelize.models.Skill +const SkillsProvider = sequelize.models.SkillsProvider +const UsersSkill = sequelize.models.UsersSkill +const resource = serviceHelper.getResource('Skill') +const uniqueFields = [['skillProviderId', 'externalId', 'name']] + +/** + * create entity + * @param entity the request device entity + * @param auth the auth information + * @return {Promise} the created device + */ +async function create (entity, auth) { + await dbHelper.get(SkillsProvider, entity.skillProviderId) + await dbHelper.makeSureUnique(Skill, entity, uniqueFields) + + const result = await dbHelper.create(Skill, entity, auth) + await serviceHelper.createRecordInEs(resource, result) + + return result +} + +create.schema = { + entity: { skillProviderId: joi.string().required(), name: joi.string().required(), uri: joi.string(), externalId: joi.string() }, - { + auth: joi.object() +} + +/** + * patch device by id + * @param id the device id + * @param entity the request device entity + * @param auth the auth object + * @param params the query params + * @return {Promise} the updated device + */ +async function patch (id, entity, auth, params) { + if (entity.skillProviderId) { + await dbHelper.get(SkillsProvider, entity.skillProviderId) + } + await dbHelper.makeSureUnique(Skill, entity, uniqueFields) + + const newEntity = await dbHelper.update(Skill, id, entity, auth) + await serviceHelper.patchRecordInEs(resource, newEntity) + + return newEntity +} + +patch.schema = { + id: joi.string(), + entity: { skillProviderId: joi.string(), name: joi.string(), uri: joi.string(), externalId: joi.string() }, - { + auth: joi.object(), + params: joi.object() +} + +/** + * get device by id + * @param id the device id + * @param auth the auth obj + * @param params the path parameters + * @param query the query parameters + * @param fromDb Should we bypass Elasticsearch for the record and fetch from db instead? + * @return {Promise} the db device + */ +async function get (id, auth, params, query = {}, fromDb = false) { + const trueParams = _.assign(params, query) + if (!fromDb) { + const esResult = await serviceHelper.getRecordInEs(resource, id, trueParams, auth) + if (esResult) { + return esResult + } + } + + const recordObj = await dbHelper.get(Skill, id) + if (!recordObj) { + throw errors.newEntityNotFoundError(`cannot find ${Skill.name} where ${_.map(trueParams, (v, k) => `${k}:${v}`).join(', ')}`) + } + + helper.permissionCheck(auth, recordObj) + return recordObj +} + +/** + * search devices by query + * @param query the search query + * @param auth the auth object + * @return {Promise} the results + */ +async function search (query, auth) { + // get from elasticsearch, if that fails get from db + // and response headers ('X-Total', 'X-Page', etc.) are not set in case of db return + const esResult = await serviceHelper.searchRecordInEs(resource, query, auth) + if (esResult) { + return esResult + } + + const items = await dbHelper.find(Skill, query, auth) + return { fromDb: true, result: items, total: items.length } +} + +search.schema = { + query: { + page: joi.id(), + perPage: joi.pageSize(), skillProviderId: joi.string(), name: joi.string(), externalId: joi.string() }, - async query => { - const dbQueries = [] - if (query.externalId) { - dbQueries.push(`externalId like '%${query.externalId}%'`) - } - if (query.skillProviderId) { - dbQueries.push(`skillProviderId like '%${query.skillProviderId}%'`) - } - if (query.name) { - dbQueries.push(`name = '${query.skillProviderId}'`) - } - return dbQueries - }, - [['skillProviderId', 'externalId', 'name']] -) + auth: joi.object() +} + +/** + * remove entity by id + * @param id the entity id + * @param auth the auth object + * @param params the query params + * @return {Promise} no data returned + */ +async function remove (id, auth, params) { + const existing = await dbHelper.find(UsersSkill, { skillId: id }) + if (existing.length > 0) { + throw errors.deleteConflictError(`Please delete ${UsersSkill.name} with ids ${existing.map(o => o.id)}`) + } + await dbHelper.remove(Skill, id) + await serviceHelper.deleteRecordFromEs(id, params, resource) +} module.exports = { - ...methods + create, + search, + patch, + get, + remove } diff --git a/src/modules/skillsProvider/service.js b/src/modules/skillsProvider/service.js index 7dd9f55..9456f5d 100644 --- a/src/modules/skillsProvider/service.js +++ b/src/modules/skillsProvider/service.js @@ -3,21 +3,140 @@ */ const joi = require('@hapi/joi') -const models = require('../../models/index') +const _ = require('lodash') + +const errors = require('../../common/errors') const helper = require('../../common/helper') -const methods = helper.getServiceMethods( - models.SkillsProvider, - { name: joi.string().required() }, - { name: joi.string() }, - { name: joi.string() }, - async query => { - const dbQueries = [] - if (query.name) { - dbQueries.push(`name like '%${query.name}%'`) +const dbHelper = require('../../common/db-helper') +const serviceHelper = require('../../common/service-helper') +const sequelize = require('../../models/index') + +const SkillsProvider = sequelize.models.SkillsProvider +const Skill = sequelize.models.Skill +const OrganizationSkillsProvider = sequelize.models.OrganizationSkillsProvider +const resource = serviceHelper.getResource('SkillsProvider') + +/** + * create entity + * @param entity the request device entity + * @param auth the auth information + * @return {Promise} the created device + */ +async function create (entity, auth) { + const result = await dbHelper.create(SkillsProvider, entity, auth) + await serviceHelper.createRecordInEs(resource, result) + return result +} + +create.schema = { + entity: { + name: joi.string().required() + }, + auth: joi.object() +} + +/** + * patch device by id + * @param id the device id + * @param entity the request device entity + * @param auth the auth object + * @param params the query params + * @return {Promise} the updated device + */ +async function patch (id, entity, auth, params) { + const newEntity = await dbHelper.update(SkillsProvider, id, entity, auth) + await serviceHelper.patchRecordInEs(resource, newEntity) + return newEntity +} + +patch.schema = { + id: joi.string(), + entity: { + name: joi.string() + }, + auth: joi.object(), + params: joi.object() +} + +/** + * get device by id + * @param id the device id + * @param auth the auth obj + * @param params the path parameters + * @param query the query parameters + * @param fromDb Should we bypass Elasticsearch for the record and fetch from db instead? + * @return {Promise} the db device + */ +async function get (id, auth, params, query = {}, fromDb = false) { + const trueParams = _.assign(params, query) + if (!fromDb) { + const esResult = await serviceHelper.getRecordInEs(resource, id, trueParams, auth) + if (esResult) { + return esResult } - return dbQueries - }) + } + + const recordObj = await dbHelper.get(SkillsProvider, id) + if (!recordObj) { + throw errors.newEntityNotFoundError(`cannot find ${SkillsProvider.name} where ${_.map(trueParams, (v, k) => `${k}:${v}`).join(', ')}`) + } + + helper.permissionCheck(auth, recordObj) + return recordObj +} + +/** + * search devices by query + * @param query the search query + * @param auth the auth object + * @return {Promise} the results + */ +async function search (query, auth) { + // get from elasticsearch, if that fails get from db + // and response headers ('X-Total', 'X-Page', etc.) are not set in case of db return + const esResult = await serviceHelper.searchRecordInEs(resource, query, auth) + if (esResult) { + return esResult + } + + const items = await dbHelper.find(SkillsProvider, query, auth) + return { fromDb: true, result: items, total: items.length } +} + +search.schema = { + query: { + page: joi.id(), + perPage: joi.pageSize(), + name: joi.string() + }, + auth: joi.object() +} + +/** + * remove entity by id + * @param id the entity id + * @param auth the auth object + * @param params the query params + * @return {Promise} no data returned + */ +async function remove (id, auth, params) { + let existing = await dbHelper.find(Skill, { skillProviderId: id }) + if (existing.length > 0) { + throw errors.deleteConflictError(`Please delete ${Skill.name} with ids ${existing.map(o => o.id)}`) + } + existing = await dbHelper.find(OrganizationSkillsProvider, { skillProviderId: id }) + if (existing.length > 0) { + throw errors.deleteConflictError(`Please delete ${OrganizationSkillsProvider.name} with ids ${existing.map(o => o.id)}`) + } + + await dbHelper.remove(SkillsProvider, id) + await serviceHelper.deleteRecordFromEs(id, params, resource) +} module.exports = { - ...methods + create, + search, + patch, + get, + remove } diff --git a/src/modules/user/service.js b/src/modules/user/service.js index 7593d50..bd5031f 100644 --- a/src/modules/user/service.js +++ b/src/modules/user/service.js @@ -3,42 +3,176 @@ */ const joi = require('@hapi/joi') -const models = require('../../models/index') +const _ = require('lodash') + +const errors = require('../../common/errors') const helper = require('../../common/helper') -const methods = helper.getServiceMethods( - models.User, - { +const dbHelper = require('../../common/db-helper') +const serviceHelper = require('../../common/service-helper') +const sequelize = require('../../models/index') + +const User = sequelize.models.User +const Achievement = sequelize.models.Achievement +const ExternalProfile = sequelize.models.ExternalProfile +const UsersAttribute = sequelize.models.UsersAttribute +const UsersRole = sequelize.models.UsersRole +const UsersSkill = sequelize.models.UsersSkill + +const resource = serviceHelper.getResource('User') +const uniqueFields = [['handle']] + +/** + * create entity + * @param entity the request device entity + * @param auth the auth information + * @return {Promise} the created device + */ +async function create (entity, auth) { + await dbHelper.makeSureUnique(User, entity, uniqueFields) + + const result = await dbHelper.create(User, entity, auth) + await serviceHelper.createRecordInEs(resource, result) + + return result +} + +create.schema = { + entity: { handle: joi.string().required(), firstName: joi.string().required(), lastName: joi.string().required() }, - { + auth: joi.object() +} + +/** + * patch device by id + * @param id the device id + * @param entity the request device entity + * @param auth the auth object + * @param params the query params + * @return {Promise} the updated device + */ +async function patch (id, entity, auth, params) { + await dbHelper.makeSureUnique(User, entity, uniqueFields) + + const newEntity = await dbHelper.update(User, id, entity, auth) + await serviceHelper.patchRecordInEs(resource, newEntity) + + return newEntity +} + +patch.schema = { + id: joi.string(), + entity: { handle: joi.string(), firstName: joi.string(), lastName: joi.string() }, - { + auth: joi.object(), + params: joi.object() +} + +/** + * get device by id + * @param id the device id + * @param auth the auth obj + * @param params the path parameters + * @param query the query parameters + * @param fromDb Should we bypass Elasticsearch for the record and fetch from db instead? + * @return {Promise} the db device + */ +async function get (id, auth, params, query = {}, fromDb = false) { + const trueParams = _.assign(params, query) + if (!fromDb) { + const esResult = await serviceHelper.getRecordInEs(resource, id, trueParams, auth) + if (esResult) { + return esResult + } + } + + const recordObj = await dbHelper.get(User, id) + if (!recordObj) { + throw errors.newEntityNotFoundError(`cannot find ${User.name} where ${_.map(trueParams, (v, k) => `${k}:${v}`).join(', ')}`) + } + + helper.permissionCheck(auth, recordObj) + return recordObj +} + +/** + * search devices by query + * @param query the search query + * @param auth the auth object + * @return {Promise} the results + */ +async function search (query, auth) { + // get from elasticsearch, if that fails get from db + // and response headers ('X-Total', 'X-Page', etc.) are not set in case of db return + const esResult = await serviceHelper.searchRecordInEs(resource, query, auth) + if (esResult) { + return esResult + } + + delete query.enrich + // add query for associations + const include = [ + { + where: {}, + model: UsersRole, + attributes: [] + } + ] + if (query.roleId) { + include[0].where.roleId = query.roleId + delete query.roleId + } + + const items = await dbHelper.find(User, query, auth, include) + return { fromDb: true, result: items, total: items.length } +} + +search.schema = { + query: { + page: joi.id(), + perPage: joi.pageSize(), handle: joi.string(), roleId: joi.string(), - enrich: joi.boolean(), - 'externalProfile.externalId': joi.string(), - 'externalProfile.organizationId': joi.string() + enrich: joi.boolean() }, - async query => { - let prefix = 'select * from DUser' - const dbQueries = [] - if (query.handle) { - dbQueries.push(`DUser.handle like '%${query.handle}%'`) - } - if (query.roleId) { - dbQueries.push(`Role.id = '${query.roleId}'`) - prefix = 'select * from Role, UserRole, DUser' - dbQueries.push('UserRole.userId = DUser.id') - dbQueries.push('UserRole.roleId = Role.id') - } - return [prefix].concat(dbQueries) - }, [['handle']]) + auth: joi.object() +} + +/** + * remove entity by id + * @param id the entity id + * @param auth the auth object + * @param params the path params + * @return {Promise} no data returned + */ +async function remove (id, auth, params) { + beginCascadeDelete(id, params) +} + +/** + * begin the cascade delete + * @param {*} id the user id to delete + * @param params the path params + */ +async function beginCascadeDelete (id, params) { + await serviceHelper.deleteChild(Achievement, id, ['userId', 'achievementsProviderId'], 'Achievement') + await serviceHelper.deleteChild(ExternalProfile, id, ['userId', 'organizationId'], 'ExternalProfile') + await serviceHelper.deleteChild(UsersAttribute, id, ['userId', 'attributeId'], 'UsersAttribute') + await serviceHelper.deleteChild(UsersRole, id, ['userId', 'roleId'], 'UsersRole') + await serviceHelper.deleteChild(UsersSkill, id, ['userId', 'skillId'], 'UsersSkill') + await dbHelper.remove(User, id) + await serviceHelper.deleteRecordFromEs(id, params, resource) +} module.exports = { - ...methods + create, + search, + patch, + get, + remove } diff --git a/src/modules/usersAttribute/service.js b/src/modules/usersAttribute/service.js index 141cbac..6abbf81 100644 --- a/src/modules/usersAttribute/service.js +++ b/src/modules/usersAttribute/service.js @@ -1,50 +1,182 @@ /** * the user attribute services */ - const joi = require('@hapi/joi') -const models = require('../../models/index') +const _ = require('lodash') + +const errors = require('../../common/errors') const helper = require('../../common/helper') -const methods = helper.getServiceMethods( - models.UserAttribute, - { // create request body joi schema +const dbHelper = require('../../common/db-helper') +const serviceHelper = require('../../common/service-helper') +const sequelize = require('../../models/index') + +const AttributeGroup = sequelize.models.AttributeGroup +const Attribute = sequelize.models.Attribute +const User = sequelize.models.User +const UsersAttribute = sequelize.models.UsersAttribute +const resource = serviceHelper.getResource('UsersAttribute') +const uniqueFields = [['userId', 'attributeId']] + +/** + * create entity + * @param entity the request device entity + * @param auth the auth information + * @return {Promise} the created device + */ +async function create (entity, auth) { + await dbHelper.get(Attribute, entity.attributeId) + await dbHelper.get(User, entity.userId) + await dbHelper.makeSureUnique(UsersAttribute, entity, uniqueFields) + + const result = await dbHelper.create(UsersAttribute, entity, auth) + await serviceHelper.createRecordInEs(resource, result) + + return result +} + +create.schema = { + entity: { userId: joi.string().required(), attributeId: joi.string().required(), value: joi.string().required() }, - { // patch request body joi schema - userId: joi.string().required(), - attributeId: joi.string().required(), + auth: joi.object() +} + +/** + * patch device by id + * @param id the device id + * @param entity the request device entity + * @param auth the auth object + * @param params the query params + * @return {Promise} the updated device + */ +async function patch (id, entity, auth, params) { + if (entity.attributeId) { + await dbHelper.get(Attribute, entity.attributeId) + } + if (entity.userId) { + await dbHelper.get(User, entity.userId) + } + + await dbHelper.makeSureUnique(UsersAttribute, entity, uniqueFields, params) + + const newEntity = await dbHelper.update(UsersAttribute, id, entity, auth, params) + await serviceHelper.patchRecordInEs(resource, newEntity) + + return newEntity +} + +patch.schema = { + id: joi.string(), + entity: { + userId: joi.string(), + attributeId: joi.string(), value: joi.string() }, - { // search request query joi schema + auth: joi.object(), + params: joi.object() +} + +/** + * get device by id + * @param id the device id + * @param auth the auth obj + * @param params the path parameters + * @param query the query parameters + * @param fromDb Should we bypass Elasticsearch for the record and fetch from db instead? + * @return {Promise} the db device + */ +async function get (id, auth, params, query = {}, fromDb = false) { + const trueParams = _.assign(params, query) + if (!fromDb) { + const esResult = await serviceHelper.getRecordInEs(resource, id, trueParams, auth) + if (esResult) { + return esResult + } + } + + const recordObj = await dbHelper.get(UsersAttribute, id, params) + if (!recordObj) { + throw errors.newEntityNotFoundError(`cannot find ${UsersAttribute.name} where ${_.map(trueParams, (v, k) => `${k}:${v}`).join(', ')}`) + } + + helper.permissionCheck(auth, recordObj) + return recordObj +} + +/** + * search devices by query + * @param query the search query + * @param auth the auth object + * @return {Promise} the results + */ +async function search (query, auth) { + // make sure id exists + await dbHelper.get(User, query.userId) + + // get from elasticsearch, if that fails get from db + // and response headers ('X-Total', 'X-Page', etc.) are not set in case of db return + const esResult = await serviceHelper.searchRecordInEs(resource, query, auth) + if (esResult) { + return esResult + } + + // add query for associations + if (query.attributeName) { + query['$Attribute.name$'] = query.attributeName + delete query.attributeName + } + if (query.attributeGroupName) { + query['$Attribute.AttributeGroup.name$'] = query.attributeGroupName + delete query.attributeGroupName + } + if (query.attributeGroupId) { + query['$Attribute.AttributeGroup.id$'] = query.attributeGroupId + delete query.attributeGroupId + } + const items = await dbHelper.find(UsersAttribute, query, auth, [{ + model: Attribute, + as: 'Attribute', + attributes: [], + include: [{ + model: AttributeGroup, + as: 'AttributeGroup', + attributes: [] + }] + }]) + + return { fromDb: true, result: items, total: items.length } +} + +search.schema = { + query: { + page: joi.id(), + perPage: joi.pageSize(), userId: joi.string().required(), attributeName: joi.string(), attributeGroupName: joi.string(), attributeGroupId: joi.string() }, - async (query) => { // build search query by request - const dbQueries = ['SELECT * FROM Attribute, AttributeGroup, UserAttribute', - `UserAttribute.userId = '${query.userId}'`, - 'UserAttribute.attributeId=Attribute.id', - 'Attribute.attributeGroupId = AttributeGroup.id'] - // filter by attribute name - if (query.attributeName) { - dbQueries.push(`Attribute.name like '%${query.attributeName}%'`) - } - // filter by attribute group name - if (query.attributeGroupName) { - dbQueries.push(`AttributeGroup.name like '%${query.attributeGroupName}%'`) - } - // filter by attribute group id - if (query.attributeGroupId) { - dbQueries.push(`AttributeGroup.id = '${query.attributeGroupId}'`) - } - return dbQueries - }, - [['userId', 'attributeId']] // unique fields -) + auth: joi.object() +} + +/** + * remove entity by id + * @param id the entity id + * @param auth the auth object + * @param params the query params + * @return {Promise} no data returned + */ +async function remove (id, auth, params) { + await dbHelper.remove(UsersAttribute, id, params) + await serviceHelper.deleteRecordFromEs(id, params, resource) +} module.exports = { - ...methods + create, + search, + patch, + get, + remove } diff --git a/src/modules/usersRole/service.js b/src/modules/usersRole/service.js index 743250a..84a1675 100644 --- a/src/modules/usersRole/service.js +++ b/src/modules/usersRole/service.js @@ -1,26 +1,121 @@ /** - * the skill services + * the user role services */ const joi = require('@hapi/joi') -const models = require('../../models/index') +const _ = require('lodash') + +const errors = require('../../common/errors') const helper = require('../../common/helper') -const methods = helper.getServiceMethods( - models.UsersRole, - { +const dbHelper = require('../../common/db-helper') +const serviceHelper = require('../../common/service-helper') +const sequelize = require('../../models/index') + +const Role = sequelize.models.Role +const User = sequelize.models.User +const UsersRole = sequelize.models.UsersRole +const resource = serviceHelper.getResource('UsersRole') +const uniqueFields = [['userId', 'roleId']] + +/** + * create entity + * @param entity the request device entity + * @param auth the auth information + * @return {Promise} the created device + */ +async function create (entity, auth) { + await dbHelper.get(Role, entity.roleId) + await dbHelper.get(User, entity.userId) + await dbHelper.makeSureUnique(UsersRole, entity, uniqueFields) + + const result = await dbHelper.create(UsersRole, entity, auth) + await serviceHelper.createRecordInEs(resource, result) + + return result +} + +create.schema = { + entity: { userId: joi.string().required(), roleId: joi.string().required() }, - { - userId: joi.string(), - roleId: joi.string() - }, - { + auth: joi.object() +} + +/** + * get device by id + * @param id the device id + * @param auth the auth obj + * @param params the path parameters + * @param query the query parameters + * @param fromDb Should we bypass Elasticsearch for the record and fetch from db instead? + * @return {Promise} the db device + */ +async function get (id, auth, params, query = {}, fromDb = false) { + const trueParams = _.assign(params, query) + if (!fromDb) { + const esResult = await serviceHelper.getRecordInEs(resource, id, trueParams, auth) + if (esResult) { + return esResult + } + } + + const recordObj = await dbHelper.get(UsersRole, id, params) + if (!recordObj) { + throw errors.newEntityNotFoundError(`cannot find ${UsersRole.name} where ${_.map(trueParams, (v, k) => `${k}:${v}`).join(', ')}`) + } + + helper.permissionCheck(auth, recordObj) + return recordObj +} + +/** + * search devices by query + * @param query the search query + * @param auth the auth object + * @return {Promise} the results + */ +async function search (query, auth) { + // make sure id exists + await dbHelper.get(User, query.userId) + + // get from elasticsearch, if that fails get from db + // and response headers ('X-Total', 'X-Page', etc.) are not set in case of db return + const esResult = await serviceHelper.searchRecordInEs(resource, query, auth) + if (esResult) { + return esResult + } + + // add query for associations + const items = await dbHelper.find(UsersRole, query, auth) + + return { fromDb: true, result: items, total: items.length } +} + +search.schema = { + query: { + page: joi.id(), + perPage: joi.pageSize(), userId: joi.string().required() }, - async (query) => [`userId = '${query.userId}'`], - [['userId', 'roleId']]) + auth: joi.object() +} + +/** + * remove entity by id + * @param id the entity id + * @param auth the auth object + * @param params the query params + * @return {Promise} no data returned + */ +async function remove (id, auth, params) { + await dbHelper.remove(UsersRole, id, params) + await serviceHelper.deleteRecordFromEs(id, params, resource) +} module.exports = { - ...methods + create, + search, + get, + remove } diff --git a/src/modules/usersSkill/service.js b/src/modules/usersSkill/service.js index 21f113d..686e77a 100644 --- a/src/modules/usersSkill/service.js +++ b/src/modules/usersSkill/service.js @@ -1,38 +1,170 @@ /** * the users skill services */ +const joi = require('@hapi/joi') +const _ = require('lodash') -const joi = require('@hapi/joi').extend(require('@hapi/joi-date')) -const models = require('../../models/index') +const errors = require('../../common/errors') const helper = require('../../common/helper') -const methods = helper.getServiceMethods( - models.UsersSkill, - { +const dbHelper = require('../../common/db-helper') +const serviceHelper = require('../../common/service-helper') +const sequelize = require('../../models/index') + +const Skill = sequelize.models.Skill +const User = sequelize.models.User +const UsersSkill = sequelize.models.UsersSkill +const resource = serviceHelper.getResource('UsersSkill') +const uniqueFields = [['userId', 'skillId']] + +/** + * create entity + * @param entity the request device entity + * @param auth the auth information + * @return {Promise} the created device + */ +async function create (entity, auth) { + await dbHelper.get(Skill, entity.skillId) + await dbHelper.get(User, entity.userId) + await dbHelper.makeSureUnique(UsersSkill, entity, uniqueFields) + + const result = await dbHelper.create(UsersSkill, entity, auth) + await serviceHelper.createRecordInEs(resource, result) + + return result +} + +create.schema = { + entity: { userId: joi.string().required(), skillId: joi.string().required(), metricValue: joi.string(), certifierId: joi.string(), certifiedDate: joi.date().format('iso') }, - { + auth: joi.object() +} + +/** + * patch device by id + * @param id the device id + * @param entity the request device entity + * @param auth the auth object + * @param params the query params + * @return {Promise} the updated device + */ +async function patch (id, entity, auth, params) { + if (entity.skillId) { + await dbHelper.get(Skill, entity.skillId) + } + if (entity.userId) { + await dbHelper.get(User, entity.userId) + } + + await dbHelper.makeSureUnique(UsersSkill, entity, uniqueFields, params) + + const newEntity = await dbHelper.update(UsersSkill, id, entity, auth, params) + await serviceHelper.patchRecordInEs(resource, newEntity) + + return newEntity +} + +patch.schema = { + id: joi.string(), + entity: { userId: joi.string(), skillId: joi.string(), metricValue: joi.string(), certifierId: joi.string(), certifiedDate: joi.date() }, - { + auth: joi.object(), + params: joi.object() +} + +/** + * get device by id + * @param id the device id + * @param auth the auth obj + * @param params the path parameters + * @param query the query parameters + * @param fromDb Should we bypass Elasticsearch for the record and fetch from db instead? + * @return {Promise} the db device + */ +async function get (id, auth, params, query = {}, fromDb = false) { + const trueParams = _.assign(params, query) + if (!fromDb) { + const esResult = await serviceHelper.getRecordInEs(resource, id, trueParams, auth) + if (esResult) { + return esResult + } + } + + const recordObj = await dbHelper.get(UsersSkill, id, params) + if (!recordObj) { + throw errors.newEntityNotFoundError(`cannot find ${UsersSkill.name} where ${_.map(trueParams, (v, k) => `${k}:${v}`).join(', ')}`) + } + + helper.permissionCheck(auth, recordObj) + return recordObj +} + +/** + * search devices by query + * @param query the search query + * @param auth the auth object + * @return {Promise} the results + */ +async function search (query, auth) { + // make sure id exists + await dbHelper.get(User, query.userId) + + // get from elasticsearch, if that fails get from db + // and response headers ('X-Total', 'X-Page', etc.) are not set in case of db return + const esResult = await serviceHelper.searchRecordInEs(resource, query, auth) + if (esResult) { + return esResult + } + + // add query for associations + if (query.skillName) { + query['$Skill.name$'] = query.skillName + delete query.skillName + } + const items = await dbHelper.find(UsersSkill, query, auth, [{ + model: Skill, + as: 'Skill', + attributes: [] + }]) + + return { fromDb: true, result: items, total: items.length } +} + +search.schema = { + query: { + page: joi.id(), + perPage: joi.pageSize(), userId: joi.string().required(), skillName: joi.string() }, - async (query) => { - const dbQueries = ['SELECT * FROM Skill, UsersSkill', `UsersSkill.userId = '${query.userId}'`, 'UsersSkill.skillId = Skill.id'] - if (query.skillName) { - dbQueries.push(`Skill.name like '%${query.skillName}%'`) - } - return dbQueries - }, [['userId', 'skillId']]) + auth: joi.object() +} + +/** + * remove entity by id + * @param id the entity id + * @param auth the auth object + * @param params the query params + * @return {Promise} no data returned + */ +async function remove (id, auth, params) { + await dbHelper.remove(UsersSkill, id, params) + await serviceHelper.deleteRecordFromEs(id, params, resource) +} module.exports = { - ...methods + create, + search, + patch, + get, + remove } diff --git a/verification_postman_collection.postman_collection b/verification_postman_collection.postman_collection new file mode 100644 index 0000000..1b60b9e --- /dev/null +++ b/verification_postman_collection.postman_collection @@ -0,0 +1,3331 @@ +{ + "info": { + "_postman_id": "b7b965d0-0675-4f38-a957-d5e43d5fc2cf", + "name": "UBahn_API", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "users", + "item": [ + { + "name": "{{HOST}}/users", + "event": [ + { + "listen": "test", + "script": { + "id": "51bca1aa-3271-4464-88b4-64e6d3581dea", + "exec": [ + "var rsp = pm.response.json();", + "if(rsp.id) pm.environment.set(\"userId\", rsp.id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"handle\":\"handle_01\",\n \"firstName\": \"test\",\n \"lastName\": \"user\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/users", + "host": [ + "{{HOST}}" + ], + "path": [ + "users" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/:id", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}" + ], + "query": [ + { + "key": "enrich", + "value": "true", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/:id", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"handle\":\"handle_013\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/users/{{userId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/:id", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/users?roleId=8607ddb3-abf6-4512-a618-c60d4771174b&enrich=true&handle=tc-Admin", + "host": [ + "{{HOST}}" + ], + "path": [ + "users" + ], + "query": [ + { + "key": "page", + "value": "1", + "disabled": true + }, + { + "key": "perPage", + "value": "1", + "disabled": true + }, + { + "key": "roleId", + "value": "8607ddb3-abf6-4512-a618-c60d4771174b" + }, + { + "key": "enrich", + "value": "true" + }, + { + "key": "handle", + "value": "tc-Admin" + } + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/users", + "host": [ + "{{HOST}}" + ], + "path": [ + "users" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/:id", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/users/{{userId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "roles", + "item": [ + { + "name": "{{HOST}}/roles", + "event": [ + { + "listen": "test", + "script": { + "id": "2277ab37-1890-4df2-a6cb-66debd11d049", + "exec": [ + "var rsp = pm.response.json();", + "if(rsp.id) pm.environment.set(\"roleId\", rsp.id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\":\"Admin2\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/roles", + "host": [ + "{{HOST}}" + ], + "path": [ + "roles" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/roles/:id", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{HOST}}/roles/{{roleId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "roles", + "{{roleId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/roles/:id", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\":\"Admin02\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/roles/{{roleId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "roles", + "{{roleId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/roles/:id", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "url": { + "raw": "{{HOST}}/roles/{{roleId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "roles", + "{{roleId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/roles", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/roles?name=Admin02", + "host": [ + "{{HOST}}" + ], + "path": [ + "roles" + ], + "query": [ + { + "key": "name", + "value": "Admin02" + } + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/roles", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/roles", + "host": [ + "{{HOST}}" + ], + "path": [ + "roles" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/roles/:id", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/roles/{{roleId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "roles", + "{{roleId}}" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "usersRoles", + "item": [ + { + "name": "{{HOST}}/users/:userId/roles", + "event": [ + { + "listen": "test", + "script": { + "id": "1f1c7d28-fcd9-49d7-8106-bbef22020061", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"roleId\":\"{{roleId}}\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/users/{{userId}}/roles", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "roles" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/:userId/roles/:roleId", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}/roles/{{roleId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "roles", + "{{roleId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/:userId/roles/:roleId", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}/roles/{{roleId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "roles", + "{{roleId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/:userId/roles", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}/roles", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "roles" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/:userId/roles", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}/roles", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "roles" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/:userId/roles/:roleId", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/users/{{userId}}/roles/{{roleId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "roles", + "{{roleId}}" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "organizations", + "item": [ + { + "name": "{{HOST}}/organizations", + "event": [ + { + "listen": "test", + "script": { + "id": "73ba304a-6675-412d-8b82-f3b3f0f4c9a3", + "exec": [ + "var rsp = pm.response.json();", + "if(rsp.id) pm.environment.set(\"organizationId\", rsp.id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\":\"organization_01\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/organizations", + "host": [ + "{{HOST}}" + ], + "path": [ + "organizations" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/organizations/:id", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{HOST}}/organizations/{{organizationId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "organizations", + "{{organizationId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/organizations/:id", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\":\"organization_update\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/organizations/{{organizationId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "organizations", + "{{organizationId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/organizations/:id", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "url": { + "raw": "{{HOST}}/organizations/{{organizationId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "organizations", + "{{organizationId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/organizations", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/organizations", + "host": [ + "{{HOST}}" + ], + "path": [ + "organizations" + ], + "query": [ + { + "key": "name", + "value": "organization_update", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/organizations", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/organizations", + "host": [ + "{{HOST}}" + ], + "path": [ + "organizations" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/organizations/:id", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/organizations/{{organizationId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "organizations", + "{{organizationId}}" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "skillsProviders", + "item": [ + { + "name": "{{HOST}}/skillsProviders", + "event": [ + { + "listen": "test", + "script": { + "id": "d3210feb-694f-41fc-a0ae-a9537fa367a5", + "exec": [ + "var rsp = pm.response.json();", + "if(rsp.id) pm.environment.set(\"skillsProviderId\", rsp.id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\":\"skillsProviders_01\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/skillsProviders", + "host": [ + "{{HOST}}" + ], + "path": [ + "skillsProviders" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/skillsProviders/:id", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text", + "disabled": true + } + ], + "url": { + "raw": "{{HOST}}/skillsProviders/{{skillsProviderId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "skillsProviders", + "{{skillsProviderId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/skillsProviders/:id", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\":\"skillsProviders_update\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/skillsProviders/{{skillsProviderId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "skillsProviders", + "{{skillsProviderId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/skillsProviders/:id", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "url": { + "raw": "{{HOST}}/skillsProviders/{{skillsProviderId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "skillsProviders", + "{{skillsProviderId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/skillsProviders", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/skillsProviders?page=1", + "host": [ + "{{HOST}}" + ], + "path": [ + "skillsProviders" + ], + "query": [ + { + "key": "name", + "value": "skillsProviders_update", + "disabled": true + }, + { + "key": "perPage", + "value": "2", + "disabled": true + }, + { + "key": "page", + "value": "1" + } + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/skillsProviders", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/skillsProviders", + "host": [ + "{{HOST}}" + ], + "path": [ + "skillsProviders" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/skillsProviders/:id", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/skillsProviders/{{skillsProviderId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "skillsProviders", + "{{skillsProviderId}}" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "skills", + "item": [ + { + "name": "{{HOST}}/skills", + "event": [ + { + "listen": "test", + "script": { + "id": "185c55f5-186d-4e63-8534-bf00151ca5e2", + "exec": [ + "var rsp = pm.response.json();", + "if(rsp.id) pm.environment.set(\"skillId\", rsp.id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"skillProviderId\":\"{{skillsProviderId}}\",\n\t\"name\":\"jump\",\n\t\"uri\":\"http://www.google.com\",\n\t\"externalId\":\"externalId33\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/skills", + "host": [ + "{{HOST}}" + ], + "path": [ + "skills" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/skills/:id", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{HOST}}/skills/{{skillId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "skills", + "{{skillId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/skills/:id", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\":\"skill_name_update\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/skills/{{skillId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "skills", + "{{skillId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/skills/:id", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "url": { + "raw": "{{HOST}}/skills/{{skillId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "skills", + "{{skillId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/skills", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/skills?perPage=2&page=1", + "host": [ + "{{HOST}}" + ], + "path": [ + "skills" + ], + "query": [ + { + "key": "perPage", + "value": "2" + }, + { + "key": "page", + "value": "1" + }, + { + "key": "externalId", + "value": "externalId", + "disabled": true + }, + { + "key": "skillProviderId", + "value": "7637ae1a-3b7c-44eb-a5ed-10ea02f1885d", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/skills", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/skills", + "host": [ + "{{HOST}}" + ], + "path": [ + "skills" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/skills/:id", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/skills/{{skillId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "skills", + "{{skillId}}" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "usersSkills", + "item": [ + { + "name": "{{HOST}}/users/:userId/skills", + "event": [ + { + "listen": "test", + "script": { + "id": "ab13a2e6-3fa6-4936-b463-ab243d42e668", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"skillId\":\"{{skillId}}\",\n\t\"metricValue\":\"3L\",\n\t\"certifierId\":\"certifier_id2\",\n\t\"certifiedDate\":\"2020-05-04T07:36:28.036Z\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/users/{{userId}}/skills", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "skills" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/{{userId}}/skills/:skillId", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}/skills/{{skillId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "skills", + "{{skillId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/{{userId}}/skills/:skillId", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"metricValue\":\"5.5L\",\n \"skillId\": \"1f4c9be7-4d56-450b-88f0-1a2000735656\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/users/{{userId}}/skills/{{skillId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "skills", + "{{skillId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/{{userId}}/skills/:skillId", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}/skills/{{skillId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "skills", + "{{skillId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/{{userId}}/skills", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}/skills?skillName=jump", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "skills" + ], + "query": [ + { + "key": "skillName", + "value": "jump" + } + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/{{userId}}/skills", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}/skills", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "skills" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/{{userId}}/skills/:skillId", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/users/{{userId}}/skills/{{skillId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "skills", + "{{skillId}}" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "externalProfiles", + "item": [ + { + "name": "{{HOST}}/users/:userId/externalProfiles", + "event": [ + { + "listen": "test", + "script": { + "id": "e0d10acb-a16b-4987-a5ce-e5c8b6c188bc", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"organizationId\":\"{{organizationId}}\",\n\t\"uri\":\"http://uri.com/uri\",\n \"externalId\": \"someid\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/users/{{userId}}/externalProfiles", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "externalProfiles" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/{{userId}}/externalProfiles/:organizationId", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}/externalProfiles/{{organizationId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "externalProfiles", + "{{organizationId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/{{userId}}/externalProfiles/:organizationId", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"uri\":\"http://www.new.com/new-uri\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/users/{{userId}}/externalProfiles/{{organizationId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "externalProfiles", + "{{organizationId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/{{userId}}/externalProfiles/:organizationId", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}/externalProfiles/{{organizationId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "externalProfiles", + "{{organizationId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/{{userId}}/externalProfiles", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}/externalProfiles?organizationName=organization_01", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "externalProfiles" + ], + "query": [ + { + "key": "organizationName", + "value": "organization_01" + } + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/{{userId}}/externalProfiles", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}/externalProfiles", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "externalProfiles" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/{{userId}}/externalProfiles/:organizationId", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/users/{{userId}}/externalProfiles/{{organizationId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "externalProfiles", + "{{organizationId}}" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "achievementsProviders", + "item": [ + { + "name": "{{HOST}}/achievementsProviders", + "event": [ + { + "listen": "test", + "script": { + "id": "c5f29da9-6c98-4205-8b82-19764d5dd777", + "exec": [ + "var rsp = pm.response.json();", + "if(rsp.id) pm.environment.set(\"achievementsProviderId\", rsp.id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\":\"achievementsProviders_02\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/achievementsProviders", + "host": [ + "{{HOST}}" + ], + "path": [ + "achievementsProviders" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/achievementsProviders/:id", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{HOST}}/achievementsProviders/{{achievementsProviderId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "achievementsProviders", + "{{achievementsProviderId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/achievementsProviders/:id", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\":\"achievementsProviders_update\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/achievementsProviders/{{achievementsProviderId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "achievementsProviders", + "{{achievementsProviderId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/achievementsProviders/:id", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "url": { + "raw": "{{HOST}}/achievementsProviders/{{achievementsProviderId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "achievementsProviders", + "{{achievementsProviderId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/achievementsProviders", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/achievementsProviders?name=achievementsProviders_update", + "host": [ + "{{HOST}}" + ], + "path": [ + "achievementsProviders" + ], + "query": [ + { + "key": "name", + "value": "achievementsProviders_update" + } + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/achievementsProviders", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/achievementsProviders", + "host": [ + "{{HOST}}" + ], + "path": [ + "achievementsProviders" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/achievementsProviders/:id", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/achievementsProviders/{{achievementsProviderId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "achievementsProviders", + "{{achievementsProviderId}}" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "achievements", + "item": [ + { + "name": "{{HOST}}/users/:userId/achievements", + "event": [ + { + "listen": "test", + "script": { + "id": "42f6a569-8340-4327-ae21-cd43a553a951", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"achievementsProviderId\":\"{{achievementsProviderId}}\",\n\t\"name\":\"achievement-name-3\",\n\t\"uri\":\"http://www.google.com/xx\",\n\t\"certifierId\":\"certifierId\",\n\t\"certifiedDate\":\"2020-05-04T07:36:28.036Z\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/users/{{userId}}/achievements", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "achievements" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/{{userId}}/achievements/:achievementsProviderId", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}/achievements/{{achievementsProviderId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "achievements", + "{{achievementsProviderId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/{{userId}}/achievements/:achievementsProviderId", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t \"name\": \"name_update\",\n\t \"uri\": \"uri_new\",\n\t \"certifierId\": \"string\",\n\t \"certifiedDate\": \"2020-05-13T06:33:54.708Z\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/users/{{userId}}/achievements/{{achievementsProviderId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "achievements", + "{{achievementsProviderId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/{{userId}}/achievements/:achievementsProviderId", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}/achievements/{{achievementsProviderId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "achievements", + "{{achievementsProviderId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/:userId/achievements", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}/achievements?userId={{userId}}&achievementsProviderName=achievementsProviders_02", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "achievements" + ], + "query": [ + { + "key": "userId", + "value": "{{userId}}" + }, + { + "key": "achievementsProviderName", + "value": "achievementsProviders_02" + } + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/:userId/achievements", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}/externalProfiles", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "externalProfiles" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/{{userId}}/achievements/:achievementsProviderId", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/users/{{userId}}/achievements/{{achievementsProviderId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "achievements", + "{{achievementsProviderId}}" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "attributeGroups", + "item": [ + { + "name": "{{HOST}}/attributeGroups", + "event": [ + { + "listen": "test", + "script": { + "id": "b025da55-3e62-411c-ae0d-df776b943fb8", + "exec": [ + "var rsp = pm.response.json();", + "if (rsp.id) pm.environment.set(\"attributeGroupId\", rsp.id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"organizationId\":\"{{organizationId}}\",\n\t\"name\":\"attributeGroup_01\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/attributeGroups", + "host": [ + "{{HOST}}" + ], + "path": [ + "attributeGroups" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/attributeGroups/:id", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{HOST}}/attributeGroups/{{attributeGroupId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "attributeGroups", + "{{attributeGroupId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/attributeGroups/:id", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\":\"group 03\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/attributeGroups/{{attributeGroupId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "attributeGroups", + "{{attributeGroupId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/attributeGroups/:id", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "url": { + "raw": "{{HOST}}/attributeGroups/{{attributeGroupId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "attributeGroups", + "{{attributeGroupId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/attributeGroups", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/attributeGroups?name=attributeGroup_01&organizationId={{organizationId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "attributeGroups" + ], + "query": [ + { + "key": "name", + "value": "attributeGroup_01" + }, + { + "key": "organizationId", + "value": "{{organizationId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/attributeGroups", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/attributeGroups", + "host": [ + "{{HOST}}" + ], + "path": [ + "attributeGroups" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/attributeGroups/:id", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/attributeGroups/{{attributeGroupId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "attributeGroups", + "{{attributeGroupId}}" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "attributes", + "item": [ + { + "name": "{{HOST}}/attributes", + "event": [ + { + "listen": "test", + "script": { + "id": "16db5594-d846-43de-a4ba-01465b606cae", + "exec": [ + "var rsp = pm.response.json();", + "if (rsp.id) pm.environment.set(\"attributeId\", rsp.id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"attributeGroupId\":\"{{attributeGroupId}}\",\n\t\"name\":\"attribute_02\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/attributes", + "host": [ + "{{HOST}}" + ], + "path": [ + "attributes" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/attributes/:id", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{HOST}}/attributes/{{attributeId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "attributes", + "{{attributeId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/attributes/:id", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\":\"attr-04\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/attributes/{{attributeId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "attributes", + "{{attributeId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/attributes/:id", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "url": { + "raw": "{{HOST}}/attributes/{{attributeId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "attributes", + "{{attributeId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/attributes", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/attributes?name=attr-04&attributeGroupId={{attributeGroupId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "attributes" + ], + "query": [ + { + "key": "name", + "value": "attr-04" + }, + { + "key": "attributeGroupId", + "value": "{{attributeGroupId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/attributes", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/attributes", + "host": [ + "{{HOST}}" + ], + "path": [ + "attributes" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/attributes/:id", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/attributes/{{attributeId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "attributes", + "{{attributeId}}" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "userAttributes", + "item": [ + { + "name": "{{HOST}}/users/:userId/attributes", + "event": [ + { + "listen": "test", + "script": { + "id": "b85b6859-954c-4f07-9221-fb9349ae2656", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"attributeId\":\"{{attributeId}}\",\n\t\"value\":\"1.23\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/users/{{userId}}/attributes", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "attributes" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/{{userId}}/attributes/:attributeId", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}/attributes/{{attributeId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "attributes", + "{{attributeId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/{{userId}}/attributes/:attributeId", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"value\":\"2.56\"\n}", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/users/{{userId}}/attributes/{{attributeId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "attributes", + "{{attributeId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/{{userId}}/attributes/:attributeId", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}/attributes/{{attributeId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "attributes", + "{{attributeId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/:userId/attributes", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}/attributes?attributeGroupId={{attributeGroupId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "attributes" + ], + "query": [ + { + "key": "attributeName", + "value": "attr-04", + "disabled": true + }, + { + "key": "attributeGroupName", + "value": "group 03", + "disabled": true + }, + { + "key": "attributeGroupId", + "value": "{{attributeGroupId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/:userId/attributes", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{HOST}}/users/{{userId}}/attributes", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "attributes" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/users/{{userId}}/attributes/:attributeId", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": {} + } + }, + "url": { + "raw": "{{HOST}}/users/{{userId}}/attributes/{{attributeId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "users", + "{{userId}}", + "attributes", + "{{attributeId}}" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "organizationSkillsProvider", + "item": [ + { + "name": "{{HOST}}/organizations/{{organizationId}}/skillProviders", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/organizations/{{organizationId}}/skillProviders", + "host": [ + "{{HOST}}" + ], + "path": [ + "organizations", + "{{organizationId}}", + "skillProviders" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/organizations/{{organizationId}}/skillProviders", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/organizations/{{organizationId}}/skillProviders", + "host": [ + "{{HOST}}" + ], + "path": [ + "organizations", + "{{organizationId}}", + "skillProviders" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/organizations/{{organizationId}}/skillProviders", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"skillProviderId\": \"{{skillsProviderId}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{HOST}}/organizations/{{organizationId}}/skillProviders", + "host": [ + "{{HOST}}" + ], + "path": [ + "organizations", + "{{organizationId}}", + "skillProviders" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/organizations/{{organizationId}}/skillProviders", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/organizations/{{organizationId}}/skillProviders/{{skillsProviderId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "organizations", + "{{organizationId}}", + "skillProviders", + "{{skillsProviderId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/organizations/{{organizationId}}/skillProviders", + "request": { + "method": "HEAD", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/organizations/{{organizationId}}/skillProviders/{{skillsProviderId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "organizations", + "{{organizationId}}", + "skillProviders", + "{{skillsProviderId}}" + ] + } + }, + "response": [] + }, + { + "name": "{{HOST}}/organizations/{{organizationId}}/skillProviders", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{HOST}}/organizations/{{organizationId}}/skillProviders/{{skillsProviderId}}", + "host": [ + "{{HOST}}" + ], + "path": [ + "organizations", + "{{organizationId}}", + "skillProviders", + "{{skillsProviderId}}" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + } + ], + "protocolProfileBehavior": {} +} \ No newline at end of file diff --git a/verification_postman_env.postman_environment b/verification_postman_env.postman_environment new file mode 100644 index 0000000..86d7add --- /dev/null +++ b/verification_postman_env.postman_environment @@ -0,0 +1,59 @@ +{ + "id": "abfb453c-6133-41f9-84a0-0670cb6e8b85", + "name": "UBahn_ENV", + "values": [ + { + "key": "HOST", + "value": "http://127.0.0.1:3002/api/1.0", + "enabled": true + }, + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiQ29waWxvdCIsIkFkbWluIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6InRjLUFkbWluIiwiZXhwIjoxNjg1NTcxNDYwLCJ1c2VySWQiOiIyMzE2Njc2OCIsImlhdCI6MTU4NTU3MDg2MCwiZW1haWwiOiJ0Yy1BZG1pbkBnbWFpbC5jb20iLCJqdGkiOiIwZjFlZjFkMy0yYjMzLTQ5MDAtYmI0My00OGYyMjg1Zjk2MzAifQ.D_TtClF4xkuSPSWoUYvkWigUWVFhH5UuF7Eci4S1_xg", + "enabled": true + }, + { + "key": "userId", + "value": "55f6fa1c-fc38-4b74-83ad-278babd7efd2", + "enabled": true + }, + { + "key": "roleId", + "value": "", + "enabled": true + }, + { + "key": "skillsProviderId", + "value": "7637ae1a-3b7c-44eb-a5ed-10ea02f1885d", + "enabled": true + }, + { + "key": "skillId", + "value": "e72e432f-6ef4-4f19-962b-eb59b805b317", + "enabled": true + }, + { + "key": "organizationId", + "value": "36ed815b-3da1-49f1-a043-aaed0a4e81ad", + "enabled": true + }, + { + "key": "achievementsProviderId", + "value": "", + "enabled": true + }, + { + "key": "attributeGroupId", + "value": "", + "enabled": true + }, + { + "key": "attributeId", + "value": "", + "enabled": true + } + ], + "_postman_variable_scope": "environment", + "_postman_exported_at": "2020-11-24T08:56:01.977Z", + "_postman_exported_using": "Postman/7.36.0" +}