From 296ba5acf36ed7a1a3019a2aba0ace9088e5f076 Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Wed, 26 Aug 2020 15:01:34 +0930 Subject: [PATCH 01/48] Updated build so that integration tests can fail the build Added rule to not deploy dependabot pull requests --- .circleci/config.yml | 10 +++- ...se.travis.yml => docker-compose.mocha.yml} | 3 +- .../docker-compose.newman.yml | 47 +++++++++++++++++++ .../triggerIntegrationTests.sh | 2 +- test/postman/runNewmanTests.js | 7 ++- 5 files changed, 62 insertions(+), 7 deletions(-) rename support/integration-testing/{docker-compose.travis.yml => docker-compose.mocha.yml} (83%) create mode 100644 support/integration-testing/docker-compose.newman.yml diff --git a/.circleci/config.yml b/.circleci/config.yml index db60c28afb..7a8d4f2b57 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -37,8 +37,8 @@ jobs: at: /home/circleci/project - run: export NVM_DIR=/opt/circleci/.nvm && source /opt/circleci/.nvm/nvm.sh && nvm install 12.16.0 && nvm use 12.16.0 - run: export NVM_DIR=/opt/circleci/.nvm && source /opt/circleci/.nvm/nvm.sh && nvm use 12.16.0 && node_modules/.bin/tsc -p tsconfig-codecov.json - - run: (cd support/integration-testing && chmod +x triggerIntegrationTests.sh && ./triggerIntegrationTests.sh) - #- run: nyc report --reporter=text-lcov > coverage.lcov;codecov -t $CODECOV_TOKEN; rm coverage.lcov + - run: (cd support/integration-testing && docker-compose -f docker-compose.newman.yml up --abort-on-container-exit --exit-code-from redboxportal && docker-compose -f docker-compose.mocha.yml up --abort-on-container-exit --exit-code-from redboxportal) + - run: export NVM_DIR=/opt/circleci/.nvm && source /opt/circleci/.nvm/nvm.sh && nvm use 12.16.0 && npm i -g codecov && codecov -t $CODECOV_TOKEN - run: export NVM_DIR=/opt/circleci/.nvm && source /opt/circleci/.nvm/nvm.sh && nvm use 12.16.0 && node_modules/.bin/tsc deploy: docker: @@ -104,6 +104,9 @@ workflows: - deploy: requires: - test + filters: + branches: + ignore: /^dependabot.*/ - generate-docs: requires: - test @@ -119,4 +122,7 @@ workflows: - deploy-bcryotjs: requires: - test + filters: + branches: + ignore: /^dependabot.*/ \ No newline at end of file diff --git a/support/integration-testing/docker-compose.travis.yml b/support/integration-testing/docker-compose.mocha.yml similarity index 83% rename from support/integration-testing/docker-compose.travis.yml rename to support/integration-testing/docker-compose.mocha.yml index 8ceee445e3..8b16d8c9fc 100644 --- a/support/integration-testing/docker-compose.travis.yml +++ b/support/integration-testing/docker-compose.mocha.yml @@ -24,8 +24,7 @@ services: main: aliases: - rbportal - entrypoint: /bin/bash -c "mkdir -p /attachments/staging; cd /opt/redbox-portal/support/integration-testing; chmod +x *.sh; ./travisRunIntegrationTest.sh" - # entrypoint: /bin/bash -c "cd /opt/redbox-portal; node app.js;" + entrypoint: /bin/bash -c "cd /opt/redbox-portal && npm run test" mongodb: image: mvertes/alpine-mongo:latest networks: diff --git a/support/integration-testing/docker-compose.newman.yml b/support/integration-testing/docker-compose.newman.yml new file mode 100644 index 0000000000..10cac96ac3 --- /dev/null +++ b/support/integration-testing/docker-compose.newman.yml @@ -0,0 +1,47 @@ +version: '3.1' +networks: + main: +services: + redboxportal: + image: qcifengineering/redbox-portal:latest + ports: + - "1500:1500" + volumes: + - "../../:/opt/redbox-portal" + - "./.tmp/attachments:/attachments" + expose: + - "1500" + environment: + - NODE_ENV=docker + - PORT=1500 + - sails_redbox__apiKey=c8e844fc-8550-497f-b970-7900ec8741ca + - sails_record__baseUrl_redbox=http://redbox:9000/redbox + - sails_record__baseUrl_mint=http://203.101.226.160/mint + # - sails_log__level=verbose + - sails_auth__default__local__default__token=d077835a-696b-4728-85cf-3ffd57152b1e + - sails_security__csrf=false + networks: + main: + aliases: + - rbportal + entrypoint: /bin/bash -c "cd /opt/redbox-portal && npm i nyc -g && nyc --exclude-after-remap=false --reporter=lcov text npm run api-test " + mongodb: + image: mvertes/alpine-mongo:latest + networks: + main: + aliases: + - mongodb + ports: + - "27017:27017" + redbox: + image: qcifengineering/redbox:2.x + expose: + - "9000" + environment: + - RB_API_KEY=c8e844fc-8550-497f-b970-7900ec8741ca + networks: + main: + aliases: + - redbox + ports: + - "9000:9000" diff --git a/support/integration-testing/triggerIntegrationTests.sh b/support/integration-testing/triggerIntegrationTests.sh index c3072841d4..8dada0de18 100755 --- a/support/integration-testing/triggerIntegrationTests.sh +++ b/support/integration-testing/triggerIntegrationTests.sh @@ -1,5 +1,5 @@ #! /bin/bash -docker-compose -f docker-compose.travis.yml up --abort-on-container-exit --exit-code-from redboxportal; + if [ -f failedTests ]; then echo "Detected failed tests" diff --git a/test/postman/runNewmanTests.js b/test/postman/runNewmanTests.js index 1b3ad746b0..a7748574eb 100644 --- a/test/postman/runNewmanTests.js +++ b/test/postman/runNewmanTests.js @@ -32,12 +32,15 @@ sails.lift({ collection: require('./test-collection.json'), reporters: 'cli', environment: require('./local.environment.json'), - }, function(err) { + }, function(err, summary) {d + const runError = err || summary.run.error || summary.run.failures.length; if (err) { console.log(err); process.exit(1); } - process.exit(0); + if(runError) { + process.exit(1); + } }); From 944335fe25891cf12a98e773b32d269a88e21e41 Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Wed, 26 Aug 2020 15:31:04 +0930 Subject: [PATCH 02/48] Fixed newman test run command --- support/integration-testing/docker-compose.newman.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support/integration-testing/docker-compose.newman.yml b/support/integration-testing/docker-compose.newman.yml index 10cac96ac3..b146595f71 100644 --- a/support/integration-testing/docker-compose.newman.yml +++ b/support/integration-testing/docker-compose.newman.yml @@ -24,7 +24,7 @@ services: main: aliases: - rbportal - entrypoint: /bin/bash -c "cd /opt/redbox-portal && npm i nyc -g && nyc --exclude-after-remap=false --reporter=lcov text npm run api-test " + entrypoint: /bin/bash -c "cd /opt/redbox-portal && npm i nyc -g && nyc --exclude-after-remap=false --reporter=lcov npm run api-test " mongodb: image: mvertes/alpine-mongo:latest networks: From 47c70141df9f887a4550ba2e7b6a0d76b89448e6 Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Wed, 26 Aug 2020 15:52:15 +0930 Subject: [PATCH 03/48] Fixed newman test run command --- support/integration-testing/docker-compose.newman.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support/integration-testing/docker-compose.newman.yml b/support/integration-testing/docker-compose.newman.yml index b146595f71..3277bdce16 100644 --- a/support/integration-testing/docker-compose.newman.yml +++ b/support/integration-testing/docker-compose.newman.yml @@ -24,7 +24,7 @@ services: main: aliases: - rbportal - entrypoint: /bin/bash -c "cd /opt/redbox-portal && npm i nyc -g && nyc --exclude-after-remap=false --reporter=lcov npm run api-test " + entrypoint: /bin/bash -c "cd /opt/redbox-portal && npm i nyc -g && npm i newman && nyc --exclude-after-remap=false --reporter=lcov npm run api-test " mongodb: image: mvertes/alpine-mongo:latest networks: From c286a8f2bddb6c50f80b55bed3ac52c4be651bb8 Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Wed, 26 Aug 2020 16:23:03 +0930 Subject: [PATCH 04/48] Fixed syntax error --- test/postman/runNewmanTests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/postman/runNewmanTests.js b/test/postman/runNewmanTests.js index a7748574eb..12147d774a 100644 --- a/test/postman/runNewmanTests.js +++ b/test/postman/runNewmanTests.js @@ -32,7 +32,7 @@ sails.lift({ collection: require('./test-collection.json'), reporters: 'cli', environment: require('./local.environment.json'), - }, function(err, summary) {d + }, function(err, summary) { const runError = err || summary.run.error || summary.run.failures.length; if (err) { console.log(err); From bc6ba14809d2a2db13faf354d30e14eef65718d5 Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Wed, 26 Aug 2020 17:38:53 +0930 Subject: [PATCH 05/48] Fixed update researcher password integration test Added circleci badge to readme --- README.md | 2 +- test/postman/test-collection.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 606cab90d1..3ee8035d3c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ReDBox Logo -[![Build Status](https://travis-ci.org/redbox-mint/redbox-portal.svg?branch=master)](https://travis-ci.org/redbox-mint/redbox-portal) +[![Build Status](https://circleci.com/gh/redbox-mint/redbox-portal.svg?style=svg)](https://circleci.com/gh/redbox-mint/redbox-portal) [![codecov](https://codecov.io/gh/redbox-mint/redbox-portal/branch/master/graph/badge.svg)](https://codecov.io/gh/redbox-mint/redbox-portal) ReDBox is an open source Research Data Management platform that assists researchers and institutions to plan, create and publish their research data assets. diff --git a/test/postman/test-collection.json b/test/postman/test-collection.json index 0d03febbca..3d7be89e43 100644 --- a/test/postman/test-collection.json +++ b/test/postman/test-collection.json @@ -1232,7 +1232,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"{{researcherUsername}}\",\n \"details\": {\n \"name\": \"A dummy researcher\",\n \"email\": \"{{researcherEmail}}\",\n \"password\": \"`Qw1234567\",\n \"roles\": [\n \"Researcher\",\n \"Guest\"\n ]\n }\n}" + "raw": "{\n \"username\": \"{{researcherUsername}}\",\n \"details\": {\n \"name\": \"A dummy researcher\",\n \"email\": \"{{researcherEmail}}\",\n \"password\": \"`{[researcherPassword]}\",\n \"roles\": [\n \"Researcher\",\n \"Guest\"\n ]\n }\n}" }, "url": { "raw": "{{host}}/default/rdmp/admin/users/newUser", From 3915b6bbde7a767d6c36e9770580cc367fd925a5 Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Wed, 26 Aug 2020 18:23:18 +0930 Subject: [PATCH 06/48] Reverting --- support/integration-testing/docker-compose.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/support/integration-testing/docker-compose.yml b/support/integration-testing/docker-compose.yml index ac82896246..37a78c36e3 100644 --- a/support/integration-testing/docker-compose.yml +++ b/support/integration-testing/docker-compose.yml @@ -8,19 +8,23 @@ services: - "1500:1500" volumes: - "../../:/opt/redbox-portal" + - "./.tmp/attachments:/attachments" expose: - "1500" environment: - NODE_ENV=docker - PORT=1500 - - sails_redbox__apiKey=AFakeAPIKey + - sails_redbox__apiKey=c8e844fc-8550-497f-b970-7900ec8741ca - sails_record__baseUrl_redbox=http://redbox:9000/redbox - sails_record__baseUrl_mint=http://203.101.226.160/mint + # - sails_log__level=verbose + - sails_auth__default__local__default__token=d077835a-696b-4728-85cf-3ffd57152b1e + - sails_security__csrf=false networks: main: aliases: - rbportal - entrypoint: /bin/bash -c "npm i nyc -g;mkdir -p /attachments/staging/; cd /opt/redbox-portal && nyc npm run api-test && npm run test" + entrypoint: /bin/bash -c "cd /opt/redbox-portal && node app.js" mongodb: image: mvertes/alpine-mongo:latest networks: From c3e5269d964659b6a7362aea7be666ebffca111f Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Wed, 26 Aug 2020 18:24:33 +0930 Subject: [PATCH 07/48] Fixed update researcher password integration test Added circleci badge to readme --- test/postman/test-collection.json | 84 +++++++++++++++---------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/test/postman/test-collection.json b/test/postman/test-collection.json index 3d7be89e43..2bf9671507 100644 --- a/test/postman/test-collection.json +++ b/test/postman/test-collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "61367569-7f2c-4ec8-ba06-ac7afb020329", + "_postman_id": "62b4f908-0978-4e8d-ba0b-5d06e9908eb8", "name": "Redbox Portal API - With tests", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -14,7 +14,7 @@ { "listen": "test", "script": { - "id": "a7cccca2-40e3-4780-b69f-d8fb57d8b067", + "id": "ca5095f8-0e14-42d9-915b-239780a9cff1", "exec": [ "", "pm.test(\"Status code is 201\", function () {", @@ -37,7 +37,7 @@ { "listen": "prerequest", "script": { - "id": "0057715c-837d-4308-9274-3752ee6986cd", + "id": "dfe55471-c89f-4615-b738-64948ff1890c", "exec": [ "" ], @@ -87,7 +87,7 @@ { "listen": "test", "script": { - "id": "04ecc9e4-69a6-480b-a2f0-3a269c8f3408", + "id": "4bd728dc-3328-4005-81d7-c5341831ea37", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -140,7 +140,7 @@ { "listen": "test", "script": { - "id": "9cae682d-b648-49f2-b7dc-1689d87fcf2b", + "id": "47977f8c-5914-41a8-90b4-7031ee68a435", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -195,7 +195,7 @@ { "listen": "test", "script": { - "id": "d97d721b-3488-403f-81da-49a0684b7313", + "id": "98c67002-0202-4d5e-92e9-468cca58687f", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -251,7 +251,7 @@ { "listen": "test", "script": { - "id": "3368e251-9dd9-4782-a310-e7ac3fdedb31", + "id": "c9ae57ec-f8ac-463e-a87f-22441d35db82", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -304,7 +304,7 @@ { "listen": "test", "script": { - "id": "29152b69-19fc-4735-b8d6-3acdbcfd383d", + "id": "2d1d1477-8861-44d1-ae56-a1bb8542bcf9", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -361,7 +361,7 @@ { "listen": "test", "script": { - "id": "7f8b6393-977e-49db-93f1-892a530a8315", + "id": "4587ff92-23d6-4701-a891-55287681f09a", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -423,7 +423,7 @@ { "listen": "test", "script": { - "id": "b6be2f11-f046-4b6a-8bc3-93bab408fb36", + "id": "5da12ebf-2ba3-4a30-8490-ffed3910ab35", "exec": [ "postman.setGlobalVariable(\"cookie\",postman.getResponseHeader(\"set-cookie\") );", "", @@ -518,7 +518,7 @@ { "listen": "test", "script": { - "id": "61193731-32b4-4097-ac1b-fe1711f82c0e", + "id": "8663b578-ff18-4545-9e43-0f6926b2bf52", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -606,7 +606,7 @@ { "listen": "test", "script": { - "id": "0cd3e72e-c513-446e-9790-dc2cc524dafc", + "id": "d0e457d1-e6b2-46c6-aac6-b6795963b9d7", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -683,7 +683,7 @@ { "listen": "test", "script": { - "id": "6bf87f5b-d8f9-4c02-b9a5-e3ac161d9622", + "id": "a80f2565-b2cd-4d9e-bf61-0c8554dab274", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -760,7 +760,7 @@ { "listen": "test", "script": { - "id": "4ab2e920-b51d-4048-b631-0ebbb7fee412", + "id": "a0d8a57d-6dda-481d-a04a-5e94771c9ee0", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -849,7 +849,7 @@ { "listen": "test", "script": { - "id": "2452e6c3-dd8c-40c3-8877-ead9d8a5d2ae", + "id": "c3452a4e-97f5-4f45-9839-05602b45e876", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -939,7 +939,7 @@ { "listen": "test", "script": { - "id": "64b01d73-5453-4416-af90-a253388a6be2", + "id": "0e343046-1cb1-4dc0-a9fa-ba9ff1070482", "exec": [ "// \"contributor_ci\": {", " // \"text_full_name\": \"Alberto Zweinstein\",", @@ -1054,7 +1054,7 @@ { "listen": "test", "script": { - "id": "b51b9514-e2de-43ca-bb21-842d265ba49c", + "id": "ada7d6f4-1304-45a1-acb9-f444747c45cf", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1152,7 +1152,7 @@ { "listen": "test", "script": { - "id": "a8bd9335-4829-4458-94ed-63ccafefd728", + "id": "e1744b03-522c-46f4-a07d-a91f662d0db0", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1256,7 +1256,7 @@ { "listen": "test", "script": { - "id": "7f5fd0ef-a302-476f-af81-17891dfaddb1", + "id": "21724d77-4e9f-41ae-a20b-8f6f070a5c57", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1362,7 +1362,7 @@ { "listen": "test", "script": { - "id": "a42e408f-c12b-4ba6-82e7-418626b9fdf1", + "id": "098acda1-ad95-40bb-85c0-dba00f7efce0", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1465,7 +1465,7 @@ { "listen": "test", "script": { - "id": "d4515295-eb0b-4e9b-969b-82991c5eaf18", + "id": "ea1b5cbb-8835-49bf-98e4-7e2b9e13911d", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1545,7 +1545,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"userid\": \"{{researcherUserId}}\",\n \"details\": {\n \"name\": \"A dummy researcher\",\n \"email\": \"{{researcherEmail}}\",\n \"password\": \"`Qw7654321\",\n \"roles\": [\n \"Researcher\",\n \"Guest\"\n ]\n }\n}" + "raw": "{\n \"userid\": \"{{researcherUserId}}\",\n \"details\": {\n \"name\": \"A dummy researcher\",\n \"email\": \"{{researcherEmail}}\",\n \"password\": \"{{reseacherPassword}}\",\n \"roles\": [\n \"Researcher\",\n \"Guest\"\n ]\n }\n}" }, "url": { "raw": "{{host}}/default/rdmp/admin/users/update", @@ -1569,7 +1569,7 @@ { "listen": "test", "script": { - "id": "c946448a-c620-43e8-b51e-6158140855d9", + "id": "ab56c882-2ade-4308-a7ae-96c043e86ece", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1673,7 +1673,7 @@ { "listen": "test", "script": { - "id": "c724b9e1-6a32-4b3b-8aeb-d6b8923d1edc", + "id": "41224895-8efa-4fa1-b756-71c4a5dfa380", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1854,7 +1854,7 @@ { "listen": "test", "script": { - "id": "495fdc88-6ee3-4aea-a2cd-dd60ad157673", + "id": "0a41b1f6-6b69-45d9-9905-b5f14ae52752", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1957,7 +1957,7 @@ { "listen": "test", "script": { - "id": "07ff4a6f-5e00-4e95-938d-97ccb4ded98d", + "id": "68e79fbf-0d3a-4a55-bf92-c4d975cded59", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2079,7 +2079,7 @@ { "listen": "test", "script": { - "id": "153e2e7e-0814-4ea5-91d8-b17b6487d444", + "id": "9a89e62c-7140-4056-a9a0-6f277294bc32", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -2173,7 +2173,7 @@ { "listen": "test", "script": { - "id": "45332e35-f23d-41c9-ae11-d81826782b4a", + "id": "ba1a6cf1-e970-4041-85da-5d59a23a6a66", "exec": [ "", "pm.test(\"Status code is 201\", function () {", @@ -2275,7 +2275,7 @@ { "listen": "test", "script": { - "id": "2500f122-e353-4b7a-ba5d-1ab7b70b1d93", + "id": "56e774a8-4e00-46f6-9b89-c60f4354dd38", "exec": [ "pm.test(\"Status code is 204\", function () {", " pm.response.to.have.status(204);", @@ -2369,7 +2369,7 @@ { "listen": "test", "script": { - "id": "4212586c-f877-41a9-9a35-aa01b9fb637c", + "id": "79d2a0a8-f221-4f88-a911-b8f308ed840f", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2461,7 +2461,7 @@ { "listen": "test", "script": { - "id": "970864f5-3d96-4820-9d9b-fe196e0673c2", + "id": "1a921de3-6836-4987-a155-d780f688aa3d", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2497,7 +2497,7 @@ { "listen": "test", "script": { - "id": "2f8567df-f5f2-40d4-8588-a04d8edcf812", + "id": "40e1ff1d-bc20-465c-98f3-6ac7f8e8b08b", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2588,7 +2588,7 @@ { "listen": "test", "script": { - "id": "5083443b-cd83-4d47-a541-6df35ccb6b3a", + "id": "22f320bb-6b77-4628-906a-327dcb3c5ffa", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2680,7 +2680,7 @@ { "listen": "test", "script": { - "id": "3e335064-3323-4820-bd75-62ce3c2ddd99", + "id": "f9831932-0294-4f9a-9f4a-4e11909b4ee4", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2772,7 +2772,7 @@ { "listen": "test", "script": { - "id": "6282414b-3183-496f-81ca-6f912cfd42fa", + "id": "40211284-ad8b-4d7b-a40b-a9d8cefeebb7", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2872,7 +2872,7 @@ { "listen": "test", "script": { - "id": "33d9a5a2-9338-47c4-a39d-20a7f99f30b6", + "id": "6afa5172-d59f-4fd3-8f13-efd244678b86", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -2970,7 +2970,7 @@ { "listen": "test", "script": { - "id": "04d5df05-a7a7-4d68-aa6d-2f6652145b7b", + "id": "c49daf1e-6dfd-4aac-9b99-0a24338413d9", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -3141,7 +3141,7 @@ { "listen": "test", "script": { - "id": "59a974a3-0b39-4ada-b04a-5d263bf42bee", + "id": "503ec593-335f-4e1e-82c3-6ab02ff81682", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3217,7 +3217,7 @@ { "listen": "test", "script": { - "id": "efe97111-eeca-48d5-a3b6-93812751cef2", + "id": "12a1cf86-99af-4a39-8d8e-40cfe55da2f3", "exec": [ "pm.test(\"Status code is 403\", function () {", " pm.response.to.have.status(403);", @@ -3286,7 +3286,7 @@ { "listen": "test", "script": { - "id": "f10a38c8-ec72-4807-986f-2a83972651f6", + "id": "26bcddb0-2663-40fa-90b8-8f65dbbdcb2b", "exec": [ "postman.setGlobalVariable(\"cookie\",postman.getResponseHeader(\"set-cookie\") );", "", @@ -3385,7 +3385,7 @@ { "listen": "test", "script": { - "id": "c659a30f-486a-4143-9bdb-746715a67309", + "id": "f5b84bb3-ccf8-44ae-891d-2c833c5f4420", "exec": [ "postman.setGlobalVariable(\"cookie\",postman.getResponseHeader(\"set-cookie\") );", "", @@ -3481,7 +3481,7 @@ { "listen": "test", "script": { - "id": "50f38480-59bf-4a5e-8fa5-7ff89657d526", + "id": "c7db8ee9-aea7-492e-89f1-c6e5b1f528d0", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", From 7c706813071b829aed833f01f9b574c7aae385a6 Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Wed, 26 Aug 2020 19:36:25 +0930 Subject: [PATCH 08/48] Fixed typo in reset password param. Fixed script exit when successful --- test/postman/runNewmanTests.js | 1 + test/postman/test-collection.json | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/postman/runNewmanTests.js b/test/postman/runNewmanTests.js index 12147d774a..59306c04d9 100644 --- a/test/postman/runNewmanTests.js +++ b/test/postman/runNewmanTests.js @@ -41,6 +41,7 @@ sails.lift({ if(runError) { process.exit(1); } + process.exit(0); }); diff --git a/test/postman/test-collection.json b/test/postman/test-collection.json index 2bf9671507..1df53b8787 100644 --- a/test/postman/test-collection.json +++ b/test/postman/test-collection.json @@ -1128,7 +1128,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"{{researcherUsername}}\",\n \"details\": {\n \"name\": \"A dummy researcher\",\n \"email\": \"{{researcherEmail}}\",\n \"password\": \"{{researcherPassword}}\",\n \"roles\": [\n \"Researcher\",\n \"Guest\"\n ]\n }\n}" + "raw": "{\n \"username\": \"{{researcherUsername}}\",\n \"details\": {\n \"name\": \"A dummy researcher\",\n \"email\": \"{{researcherEmail}}\",\n \"password\": \"!7654321wQ\",\n \"roles\": [\n \"Researcher\",\n \"Guest\"\n ]\n }\n}" }, "url": { "raw": "{{host}}/default/rdmp/admin/users/newUser", @@ -1467,6 +1467,7 @@ "script": { "id": "ea1b5cbb-8835-49bf-98e4-7e2b9e13911d", "exec": [ + "", "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", "});", @@ -1545,7 +1546,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"userid\": \"{{researcherUserId}}\",\n \"details\": {\n \"name\": \"A dummy researcher\",\n \"email\": \"{{researcherEmail}}\",\n \"password\": \"{{reseacherPassword}}\",\n \"roles\": [\n \"Researcher\",\n \"Guest\"\n ]\n }\n}" + "raw": "{\n \"userid\": \"{{researcherUserId}}\",\n \"details\": {\n \"name\": \"A dummy researcher\",\n \"email\": \"{{researcherEmail}}\",\n \"password\": \"{{researcherPassword}}\",\n \"roles\": [\n \"Researcher\",\n \"Guest\"\n ]\n }\n}" }, "url": { "raw": "{{host}}/default/rdmp/admin/users/update", From 79f84d53f464be66e191cb849c4020a5ed3939d9 Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Thu, 27 Aug 2020 09:49:20 +0930 Subject: [PATCH 09/48] Upgraded packages inlcuding sails to 1.2.5 --- package-lock.json | 561 +++++++++++++++++++++++++--------------------- package.json | 10 +- 2 files changed, 310 insertions(+), 261 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0d032bdeb1..7f7fe103ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -396,9 +396,9 @@ "dev": true }, "@types/jquery": { - "version": "3.3.38", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.38.tgz", - "integrity": "sha512-nkDvmx7x/6kDM5guu/YpXkGZ/Xj/IwGiLDdKM99YA5Vag7pjGyTJ8BNUh/6hxEn/sEu5DKtyRgnONJ7EmOoKrA==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.1.tgz", + "integrity": "sha512-Tyctjh56U7eX2b9udu3wG853ASYP0uagChJcQJXLUXEU6C/JiW5qt5dl8ao01VRj1i5pgXPAf8f1mq4+FDLRQg==", "dev": true, "requires": { "@types/sizzle": "*" @@ -2862,9 +2862,9 @@ "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==" }, "ejs-cli": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ejs-cli/-/ejs-cli-2.2.0.tgz", - "integrity": "sha512-siC0o+kz6zM5CUNbm08FLj6LeyVoIOAccc1Ze3uOBAYpK7leP5WJpJPLCMBVtivSkH5BZaqPU8XpgP0ffjVteA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ejs-cli/-/ejs-cli-2.2.1.tgz", + "integrity": "sha512-rLCGrRtTRKVJgZpv5HrVXnhJdi8EJSslQki/0WZqSC52m1njdcQCvsm5fiP/IKErPB1j8Sf9FzFm6hIwdpC4Tw==", "dev": true, "requires": { "async": "^3.2.0", @@ -2875,12 +2875,6 @@ "optimist": "^0.6.1" }, "dependencies": { - "async": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", - "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", - "dev": true - }, "colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", @@ -5275,11 +5269,19 @@ "integrity": "sha1-2WyScyB28HJxG2sQ/X1PZa2O4j0=" }, "i18n-2": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/i18n-2/-/i18n-2-0.6.3.tgz", - "integrity": "sha1-V6xhhePqR8/+mTzXpcFLQN82Szk=", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/i18n-2/-/i18n-2-0.7.3.tgz", + "integrity": "sha512-NiC0dd+VAVGq/hWsK19XCTwfx7Xr0KPtldQ11/9DHY8Ic4++bbgRhjCvRD1C/K09V7UZpwgVhQuzPPom9XVrOQ==", "requires": { - "sprintf": "^0.1.5" + "debug": "^3.1.0", + "sprintf-js": "^1.1.1" + }, + "dependencies": { + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" + } } }, "i18next": { @@ -6781,11 +6783,6 @@ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" }, - "lodash.isundefined": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", - "integrity": "sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g=" - }, "lodash.mergewith": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", @@ -6822,38 +6819,62 @@ "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==" }, "log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", "dev": true, "requires": { - "chalk": "^2.4.2" + "chalk": "^4.0.0" }, "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" } }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" } } } @@ -6929,6 +6950,21 @@ "version": "3.10.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + }, + "switchback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/switchback/-/switchback-2.0.0.tgz", + "integrity": "sha1-KifZAzPe8wWnUh3MHjL2qOOtcgU=", + "requires": { + "lodash": "~2.4.1" + }, + "dependencies": { + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" + } + } } } }, @@ -7410,18 +7446,11 @@ } }, "merge-defaults": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/merge-defaults/-/merge-defaults-0.2.1.tgz", - "integrity": "sha1-3UIkjrlrtqUVIXJDIccv+Vg93oA=", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/merge-defaults/-/merge-defaults-0.2.2.tgz", + "integrity": "sha512-rKkxPFgGDZfmen0IN8BKRsGEbFU3PdO0RhR1GjOk+BLJF7+LAIhs5bUG3s26FkbB5bfIn9il25KkntRGdqHQ3A==", "requires": { - "lodash": "~2.4.1" - }, - "dependencies": { - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" - } + "@sailshq/lodash": "^3.10.2" } }, "merge-descriptors": { @@ -7667,23 +7696,23 @@ } }, "mocha": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.1.1.tgz", - "integrity": "sha512-p7FuGlYH8t7gaiodlFreseLxEmxTgvyG9RgPHODFPySNhwUehu8NIb0vdSt3WFckSneswZ0Un5typYcWElk7HQ==", + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.1.2.tgz", + "integrity": "sha512-I8FRAcuACNMLQn3lS4qeWLxXqLvGf6r2CaLstDpZmMUUSmvW6Cnm1AuHxgbc7ctZVRcfwspCRbDHymPsi3dkJw==", "dev": true, "requires": { "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.3.1", - "debug": "3.2.6", + "chokidar": "3.4.2", + "debug": "4.1.1", "diff": "4.0.2", - "escape-string-regexp": "1.0.5", - "find-up": "4.1.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", "glob": "7.1.6", "growl": "1.10.5", "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "3.0.0", + "js-yaml": "3.14.0", + "log-symbols": "4.0.0", "minimatch": "3.0.4", "ms": "2.1.2", "object.assign": "4.1.0", @@ -7711,22 +7740,6 @@ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, - "chokidar": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", - "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.3.0" - } - }, "cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -7739,9 +7752,9 @@ } }, "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { "ms": "^2.1.1" @@ -7753,6 +7766,22 @@ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -7765,14 +7794,23 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "p-locate": "^5.0.0" } }, "ms": { @@ -7781,28 +7819,22 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "p-try": "^2.0.0" } }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "readdirp": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", - "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==", + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "requires": { - "picomatch": "^2.0.7" + "p-limit": "^3.0.2" } }, "string-width": { @@ -7886,6 +7918,40 @@ "requires": { "locate-path": "^3.0.0" } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true } } }, @@ -8115,12 +8181,6 @@ "xmlbuilder": "15.1.1" }, "dependencies": { - "async": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", - "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", - "dev": true - }, "colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", @@ -8651,11 +8711,6 @@ "wrappy": "1" } }, - "open": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/open/-/open-0.0.5.tgz", - "integrity": "sha1-QsPhjslUZra/DcQvOilFw/DK2Pw=" - }, "opencollective-postinstall": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", @@ -9976,14 +10031,24 @@ } }, "request-promise": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.5.tgz", - "integrity": "sha512-ZgnepCykFdmpq86fKGwqntyTiUrHycALuGggpyCZwMvGaZWgxW6yagT0FHkgo5LzYvOaCNvxYwWYIjevSH1EDg==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.6.tgz", + "integrity": "sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ==", "requires": { "bluebird": "^3.5.0", - "request-promise-core": "1.1.3", + "request-promise-core": "1.1.4", "stealthy-require": "^1.1.1", "tough-cookie": "^2.3.3" + }, + "dependencies": { + "request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "requires": { + "lodash": "^4.17.19" + } + } } }, "request-promise-core": { @@ -10215,9 +10280,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sails": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/sails/-/sails-1.1.0.tgz", - "integrity": "sha512-YnkgmgOCYmcKqHsoZUPtIKDXj83DFAd8CwxCsThDXej0fH/t7yFBITiNOnj1JjnA5k7G9aIjWwbh6NYn/dM8Ng==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/sails/-/sails-1.2.5.tgz", + "integrity": "sha512-yha2zMB3rZwjlAsbdHAe6iA714YZ7piRbBWiJgf8h6mxVzLM22nhk7OMWn5Iugzg9a1tycpTMY0Hlow+WOSs7Q==", "requires": { "@sailshq/lodash": "^3.10.2", "async": "2.5.0", @@ -10234,37 +10299,42 @@ "ejs": "2.5.7", "express": "4.16.2", "express-session": "1.15.6", - "flaverr": "^1.9.0", + "flaverr": "^1.10.0", "glob": "7.1.2", - "i18n-2": "0.6.3", + "i18n-2": "0.7.3", "include-all": "^4.0.0", "machine": "^15.2.2", "machine-as-action": "^10.3.0-0", - "machinepack-process": "^2.0.2", - "machinepack-redis": "^1.1.1", - "merge-defaults": "0.2.1", + "machinepack-process": "^4.0.1", + "machinepack-redis": "^2.0.2", + "merge-defaults": "0.2.2", "merge-dictionaries": "1.0.0", - "minimist": "0.0.10", + "minimist": "1.2.5", "parley": "^3.3.4", "parseurl": "1.3.2", "path-to-regexp": "1.5.3", "pluralize": "1.2.1", "prompt": "0.2.14", - "rc": "1.2.2", + "rc": "1.2.8", "router": "1.3.2", "rttc": "^10.0.0-0", - "sails-generate": "^1.16.0-0", - "sails-stringfile": "0.3.2", + "sails-generate": "^2.0.0", + "sails-stringfile": "^0.3.3", "semver": "4.3.6", "serve-favicon": "2.4.5", "serve-static": "1.13.1", "skipper": "^0.9.0-0", - "sort-route-addresses": "^0.0.1", + "sort-route-addresses": "^0.0.3", "uid-safe": "2.1.5", "vary": "1.1.2", "whelk": "^6.0.1" }, "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, "async": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", @@ -10278,16 +10348,23 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" }, - "deep-extend": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", - "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=" - }, "ejs": { "version": "2.5.7", "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.7.tgz", "integrity": "sha1-zIcsFoiArjxxiXYv1f/ACJbJUYo=" }, + "fs-extra": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", + "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" + } + }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -10313,51 +10390,24 @@ "rttc": "^10.0.0-3" } }, - "machinepack-process": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/machinepack-process/-/machinepack-process-2.0.2.tgz", - "integrity": "sha1-SMaZT+n8YBXBNcszJKb614486zk=", - "requires": { - "lodash.isfunction": "3.0.8", - "lodash.isobject": "3.0.2", - "lodash.isstring": "4.0.1", - "lodash.isundefined": "3.0.1", - "machine": "~12.1.0", - "machinepack-json": "~2.0.0", - "open": "0.0.5" + "machinepack-redis": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/machinepack-redis/-/machinepack-redis-2.0.5.tgz", + "integrity": "sha512-K+5j93jaeFKKhtGc0VDVaW/42luxbVnN/XueLfXdJhFam+dMm+06iNzVC0xexZwx+MRfnpWiMOT2TncC+Vi07g==", + "requires": { + "@sailshq/lodash": "^3.10.2", + "async": "2.0.1", + "flaverr": "^1.9.2", + "machine": "^15.2.2", + "redis": "2.8.0" }, "dependencies": { - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" - }, - "machine": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/machine/-/machine-12.1.1.tgz", - "integrity": "sha512-fohf/zxGNvZL69JfbZI/rf660cLnC2tU3tSz8BHGrl+5c7C/82Zypy2fpT2FKPh1q56zfAaCqyI5hSROqBF90g==", - "requires": { - "convert-to-ecmascript-compatible-varname": "0.1.4", - "debug": "3.1.0", - "lodash": "3.10.1", - "object-hash": "0.3.0", - "rttc": "~9.3.0", - "switchback": "2.0.0" - } - }, - "rttc": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/rttc/-/rttc-9.3.4.tgz", - "integrity": "sha1-vABXU7c80WrFANkURta5kyBhctc=", + "async": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.0.1.tgz", + "integrity": "sha1-twnMAoCpw28J9FNr6CPIOKkEniU=", "requires": { - "lodash": "3.8.0" - }, - "dependencies": { - "lodash": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.8.0.tgz", - "integrity": "sha1-N265i9zZOCqTZcM8TLglDeEyW5E=" - } + "lodash": "^4.8.0" } } } @@ -10370,27 +10420,19 @@ "@sailshq/lodash": "^3.10.2" } }, - "minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" + "parasails": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/parasails/-/parasails-0.9.2.tgz", + "integrity": "sha512-LmCj4ZYPefyLWl00WcP1mTpoCLDEsy1BpTUfXFypUbKFnSGu0Z1KKRkCwxEsPb6OwaixoK2VTvgvP83ZO5E52Q==" }, - "rc": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.2.tgz", - "integrity": "sha1-2M6ctX6NZNnHut2YdsfDTL48cHc=", + "redis": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz", + "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==", "requires": { - "deep-extend": "~0.4.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - } + "double-ended-queue": "^2.1.0-0", + "redis-commands": "^1.2.0", + "redis-parser": "^2.6.0" } }, "rttc": { @@ -10400,6 +10442,51 @@ "requires": { "@sailshq/lodash": "^3.10.2" } + }, + "sails-generate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sails-generate/-/sails-generate-2.0.0.tgz", + "integrity": "sha512-DK5ssXUpP/FFgWoWsvljGEeaAxF/mQuqCKua661EI1TfvIXgCdnVlAYd6d138ofIWb17g43ko1YwscGTn59onw==", + "requires": { + "@sailshq/lodash": "^3.10.3", + "async": "2.0.1", + "chalk": "1.1.3", + "cross-spawn": "4.0.2", + "flaverr": "^1.0.0", + "fs-extra": "0.30.0", + "machinepack-process": "^4.0.0", + "parasails": "^0.9.2", + "read": "1.0.7", + "reportback": "^2.0.1", + "sails.io.js-dist": "^1.0.0" + }, + "dependencies": { + "async": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.0.1.tgz", + "integrity": "sha1-twnMAoCpw28J9FNr6CPIOKkEniU=", + "requires": { + "lodash": "^4.8.0" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + } + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" } } }, @@ -11117,8 +11204,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "optional": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "async": { "version": "1.5.2", @@ -12784,22 +12870,14 @@ "integrity": "sha1-pCdO6zL6dl2lp6OxcSYXzjsUQUk=" }, "grunt-legacy-log": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-1.0.0.tgz", - "integrity": "sha1-+4bxgJhHvAfcR4Q/ns1srLYt8tU=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-1.0.2.tgz", + "integrity": "sha512-WdedTJ/6zCXnI/coaouzqvkI19uwqbcPkdsXiDRKJyB5rOUlOxnCnTVbpeUdEckKVir2uHF3rDBYppj2p6N3+g==", "requires": { "colors": "~1.1.2", "grunt-legacy-log-utils": "~1.0.0", "hooker": "~0.2.3", - "lodash": "~3.10.1", - "underscore.string": "~3.2.3" - }, - "dependencies": { - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" - } + "lodash": "~4.17.5" } }, "grunt-legacy-log-utils": { @@ -13192,8 +13270,7 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "jsesc": { "version": "1.3.0", @@ -13294,9 +13371,9 @@ } }, "lodash": { - "version": "4.17.5", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, "lodash._baseassign": { "version": "3.2.0", @@ -13459,23 +13536,16 @@ } }, "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - } + "minimist": "^1.2.5" } }, "mocha": { @@ -13491,7 +13561,6 @@ "glob": "7.0.5", "json3": "3.3.2", "lodash.create": "3.1.1", - "mkdirp": "0.5.1", "supports-color": "3.1.2" }, "dependencies": { @@ -14280,8 +14349,7 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, "type-check": { "version": "0.3.2", @@ -14610,19 +14678,12 @@ } }, "sails-stringfile": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/sails-stringfile/-/sails-stringfile-0.3.2.tgz", - "integrity": "sha1-2k42Zqj5z9Ph80a/uBFqMD4cML0=", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/sails-stringfile/-/sails-stringfile-0.3.3.tgz", + "integrity": "sha512-m61lSEURCpKf2T7Df9lkG2eWBPGFKrhJZi8OF3TMQe7HGWyUpYdwKhV6rFsky1gY6g4ecvTZTAqwHXOE1AtaCA==", "requires": { - "colors": "*", - "lodash": "~2.4.1" - }, - "dependencies": { - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" - } + "@sailshq/lodash": "^3.10.2", + "colors": "*" } }, "sails.io.js-dist": { @@ -15444,18 +15505,11 @@ } }, "sort-route-addresses": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/sort-route-addresses/-/sort-route-addresses-0.0.1.tgz", - "integrity": "sha1-I6h9KDETsS7h/ttM9DryErtW2rs=", + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/sort-route-addresses/-/sort-route-addresses-0.0.3.tgz", + "integrity": "sha512-FK9GJty+MN4X5ml665lcgJe5y0zjF2wgnNVWS1yVnPFuCODCtMJx8B1rFN5NRwDaCbDGjc45OKkusqrx2GFL4g==", "requires": { - "lodash": "^3.10.1" - }, - "dependencies": { - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" - } + "@sailshq/lodash": "^3.10.2" } }, "sorted-array-functions": { @@ -15584,11 +15638,6 @@ "extend-shallow": "^3.0.0" } }, - "sprintf": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/sprintf/-/sprintf-0.1.5.tgz", - "integrity": "sha1-j4PjmpMXwaUCy324BQ5Rxnn27c8=" - }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", diff --git a/package.json b/package.json index a2fd722190..f88e7cadd8 100644 --- a/package.json +++ b/package.json @@ -46,10 +46,10 @@ "passport-openidconnect": "0.0.2", "rc": "1.2.8", "redux": "4.0.1", - "request-promise": "^4.2.0", + "request-promise": "^4.2.6", "rxjs": "6.5.3", "rxjs-compat": "6.6.2", - "sails": "1.1.0", + "sails": "^1.2.5", "sails-hook-autoreload": "^1.1.0", "sails-hook-grunt": "^3.1.0", "sails-hook-orm": "^3.0.1", @@ -82,20 +82,20 @@ "license": "GPL2", "devDependencies": { "@compodoc/compodoc": "^1.1.6", - "@types/jquery": "^3.3.2", + "@types/jquery": "^3.5.1", "@types/leaflet": "^1.5.17", "@types/leaflet-draw": "^1.0.3", "@types/lodash-es": "^4.14.5", "bower": "^1.8.0", "chai": "^4.1.0", - "ejs-cli": "^2.0.1", + "ejs-cli": "^2.2.1", "grunt": "^1.3.0", "grunt-contrib-cssmin": "^3.0.0", "grunt-contrib-uglify": "^5.0.0", "grunt-sass": "3.1.0", "grunt-shell": "^3.0.1", "istanbul": "^0.4.5", - "mocha": "^8.1.1", + "mocha": "^8.1.2", "newman": "^5.1.2", "node-sass": "4.14.1", "supertest": "^4.0.2", From f2086d541b8284cad1f8416e5eccd9e3eb464a67 Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Thu, 27 Aug 2020 12:39:13 +0930 Subject: [PATCH 10/48] Upgraded all packages to their latest versions --- package-lock.json | 2158 ++++++++++++++++++++++----------------------- package.json | 64 +- 2 files changed, 1099 insertions(+), 1123 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7f7fe103ab..4c125f99e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -384,6 +384,11 @@ } } }, + "@transloadit/prettier-bytes": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@transloadit/prettier-bytes/-/prettier-bytes-0.0.7.tgz", + "integrity": "sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==" + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", @@ -449,106 +454,113 @@ "dev": true }, "@uppy/companion-client": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@uppy/companion-client/-/companion-client-0.27.3.tgz", - "integrity": "sha512-yB3TwF8RU5kIOuBz1s0gUIj0as0b8dFLoFqqavQVpyx/K+awhrde0YkSvHMDAobqOhSQ67tZF2H5aBoc9+nuow==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@uppy/companion-client/-/companion-client-1.5.3.tgz", + "integrity": "sha512-VN1JssHZiOjleJcoTyubTvGua3R6P9ENkqKlCt9dPKabGn9diNQ4EZebZlAvUD5hz+zBuzvNyAwgC7xbqZlyMw==", "requires": { + "@uppy/utils": "^3.2.2", "namespace-emitter": "^2.0.1" } }, "@uppy/core": { - "version": "0.29.1", - "resolved": "https://registry.npmjs.org/@uppy/core/-/core-0.29.1.tgz", - "integrity": "sha512-k+DCWDD5SRCWqaASPDcFfIWsoPgxKCFihqngd7sXvvBGTlGmNrGEWAsmI4vH5HsWUp5pLomcZyHrvKWcGyfISQ==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@uppy/core/-/core-1.13.1.tgz", + "integrity": "sha512-HaWHUGDgcjya0NljV6dvEkVjGm+/WtrUEw+Zo7JA3Ah3VfgIUS/f7/uX+INsw+I4++TzcKSxr9fjTf+IzXSwJA==", "requires": { - "@uppy/store-default": "0.27.1", - "@uppy/utils": "0.29.1", + "@transloadit/prettier-bytes": "0.0.7", + "@uppy/store-default": "^1.2.3", + "@uppy/utils": "^3.2.2", "cuid": "^2.1.1", "lodash.throttle": "^4.1.1", "mime-match": "^1.0.2", "namespace-emitter": "^2.0.1", - "preact": "^8.2.9", - "prettier-bytes": "^1.0.4" + "preact": "8.2.9" } }, "@uppy/dashboard": { - "version": "0.29.1", - "resolved": "https://registry.npmjs.org/@uppy/dashboard/-/dashboard-0.29.1.tgz", - "integrity": "sha512-0HVj/fVevOed/Oi2RLIRlgSU2ifiE4ArwnUVS27tATTiSW1XKzvWu0wh2i4kIv4T4EHZfMn+N8pmleXty89azQ==", - "requires": { - "@uppy/informer": "0.29.1", - "@uppy/provider-views": "0.29.1", - "@uppy/status-bar": "0.29.1", - "@uppy/thumbnail-generator": "0.29.1", - "@uppy/utils": "0.29.1", + "version": "1.12.5", + "resolved": "https://registry.npmjs.org/@uppy/dashboard/-/dashboard-1.12.5.tgz", + "integrity": "sha512-PPaJrOLF/TH/7BK6qvcvZPWku84MHqrpi0fX/lpS2CaA0n+0xg6B9WwzqBWwHfgKR+F+Eki7G7aswKtg5ubHQA==", + "requires": { + "@transloadit/prettier-bytes": "0.0.7", + "@uppy/informer": "^1.5.10", + "@uppy/provider-views": "^1.7.4", + "@uppy/status-bar": "^1.7.5", + "@uppy/thumbnail-generator": "^1.6.6", + "@uppy/utils": "^3.2.2", "classnames": "^2.2.6", - "drag-drop": "2.13.3", + "cuid": "^2.1.1", + "is-shallow-equal": "^1.0.1", + "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1", - "preact": "^8.2.9", + "memoize-one": "^5.0.4", + "preact": "8.2.9", "preact-css-transition-group": "^1.3.0", - "prettier-bytes": "^1.0.4", "resize-observer-polyfill": "^1.5.0" } }, "@uppy/informer": { - "version": "0.29.1", - "resolved": "https://registry.npmjs.org/@uppy/informer/-/informer-0.29.1.tgz", - "integrity": "sha512-8TWCf7jurIIkF1kF14W0S2iXpw9ZhzX3LENYDGrXxN33x2wlOz+j1YQIVM+tIloF9Om0ljlZgllR0vhwheUQ0Q==", + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/@uppy/informer/-/informer-1.5.10.tgz", + "integrity": "sha512-hNYM0CTT9ZK2Owq3jGpQJrxkFTEo8sGvNgWnMMTUTBGQStNZLLJrP+gWbHJJeScx94PUB3TGiZBqyKIZit+W1Q==", "requires": { - "@uppy/utils": "0.29.1", - "preact": "^8.2.9" + "@uppy/utils": "^3.2.2", + "preact": "8.2.9" } }, "@uppy/provider-views": { - "version": "0.29.1", - "resolved": "https://registry.npmjs.org/@uppy/provider-views/-/provider-views-0.29.1.tgz", - "integrity": "sha512-ya161BrI+UYl96kW4u+9Rv3m5VJcms5jf8GDGIOwr81Nx9tjNH87QeTVPockN+H5PHvvdOC9rsS9SbOdqEhHZQ==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@uppy/provider-views/-/provider-views-1.7.4.tgz", + "integrity": "sha512-eWZEz/wuFdWSkBYNFEZsQ9voMsZKrQAR3WnN2S6pGZF4Peqjv7Ubiz6GB47Dko1lFPj1SpC7GLbm9/qvnyyUFw==", "requires": { - "@uppy/utils": "0.29.1", + "@uppy/utils": "^3.2.2", "classnames": "^2.2.6", - "preact": "^8.2.9" + "preact": "8.2.9" } }, "@uppy/status-bar": { - "version": "0.29.1", - "resolved": "https://registry.npmjs.org/@uppy/status-bar/-/status-bar-0.29.1.tgz", - "integrity": "sha512-MSdeEiPgApii4kUerfPbG4j/jqdKXkREoNaw0APmfQgzlR6V6tyiZW9f0l9GFAG8JH14K9IEV83Z98O5BOTH3g==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@uppy/status-bar/-/status-bar-1.7.5.tgz", + "integrity": "sha512-xsN/ag1EcfOnvTLKNNCDImgRaJMqbqdeK9/WKxRz5jamZgpY3J5UJwsoC/VeU86f2bsLOyB6tsDV6LemqlSMNw==", "requires": { - "@uppy/utils": "0.29.1", + "@transloadit/prettier-bytes": "0.0.7", + "@uppy/utils": "^3.2.2", "classnames": "^2.2.6", "lodash.throttle": "^4.1.1", - "preact": "^8.2.9", - "prettier-bytes": "^1.0.4" + "preact": "8.2.9" } }, "@uppy/store-default": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@uppy/store-default/-/store-default-0.27.1.tgz", - "integrity": "sha512-y+L1kUUZqV4QforOkl/prd3hOErgFWUvFx+tqRbAMgccsxYk8PapvUe42FZPa7Z92iIqbOohlJe8ZsIj4+2tzg==" + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@uppy/store-default/-/store-default-1.2.3.tgz", + "integrity": "sha512-9DFq4ccIqfy8mBOO5Mj52X26JsplY3hydSooM+64NKMZ9ccs6LpSm7j8WVbvvNFnSrd7SgP/53vFwo953KCKgg==" }, "@uppy/thumbnail-generator": { - "version": "0.29.1", - "resolved": "https://registry.npmjs.org/@uppy/thumbnail-generator/-/thumbnail-generator-0.29.1.tgz", - "integrity": "sha512-hysD8luN8NOI2OnlO18XJzNM2e+Adi846OAuUedn7XarjennOcCJCsdZi91UlzVvlO9u8/W6qYNXD3UHUvTRuQ==", + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@uppy/thumbnail-generator/-/thumbnail-generator-1.6.6.tgz", + "integrity": "sha512-5wg1ETyzCwZiYlubDBrRKM3pB1qcmrY/zl4ITHAfNqQ3aO0ZO2XO+MHpaC9zpG5DQow46XxlWIW8sT6HLrI7Gg==", "requires": { - "@uppy/utils": "0.29.1" + "@uppy/utils": "^3.2.2", + "exifr": "^5.0.2", + "math-log2": "^1.0.1" } }, "@uppy/tus": { - "version": "0.29.1", - "resolved": "https://registry.npmjs.org/@uppy/tus/-/tus-0.29.1.tgz", - "integrity": "sha512-m6Tc1cP/07bABhjfNixJSipBAkr2ldugi+h5xSvfnCCQ8rsxZ0NxufTvuQ/2Q0s4DWPUTulvW+TbTcTrsi9u9w==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@uppy/tus/-/tus-1.7.5.tgz", + "integrity": "sha512-aW8k8k5hUHL8SOq1tTvhoDWPXBsebaZ4dXDxeUSua1axjS1iA9F7C6RRlrVLxJgRPAPIJfW2zv3QtoJAbnhpFA==", "requires": { - "@uppy/companion-client": "0.27.3", - "@uppy/utils": "0.29.1", - "tus-js-client": "^1.5.1" + "@uppy/companion-client": "^1.5.3", + "@uppy/utils": "^3.2.2", + "tus-js-client": "^2.1.1" } }, "@uppy/utils": { - "version": "0.29.1", - "resolved": "https://registry.npmjs.org/@uppy/utils/-/utils-0.29.1.tgz", - "integrity": "sha512-pYINvzaEKnMcW6BJ3JiBP0v1wOFMo1FMt1JtafjWJtZ5n8UUhrBRmZsEoh5gPvymwhOQ+87Mlk2erV7L8QLyIg==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@uppy/utils/-/utils-3.2.2.tgz", + "integrity": "sha512-ze3NrG23MbYR29Oj6ik6iGZIpXxmhZK3VYwevfqJbzTknF/yh8BWLRWNyPoyOflNpsmQbxo/Y2hmqTnUC3wEPg==", "requires": { + "abortcontroller-polyfill": "^1.4.0", "lodash.throttle": "^4.1.1" } }, @@ -570,6 +582,11 @@ "event-target-shim": "^5.0.0" } }, + "abortcontroller-polyfill": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.5.0.tgz", + "integrity": "sha512-O6Xk757Jb4o0LMzMOMdWvxpHWrQzruYBaUruFaIOfAQRnWFxfdXYobw12jrVHGtoXk6WiiyYzc0QWN9aL62HQA==" + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -986,6 +1003,11 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" + }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", @@ -1047,11 +1069,18 @@ "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==" }, "axios": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", - "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz", + "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==", "requires": { - "follow-redirects": "1.5.10" + "follow-redirects": "^1.10.0" + }, + "dependencies": { + "follow-redirects": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", + "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" + } } }, "babel-runtime": { @@ -1243,11 +1272,6 @@ "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" }, - "blob-to-buffer": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/blob-to-buffer/-/blob-to-buffer-1.2.8.tgz", - "integrity": "sha512-re0AIxakF504MgeMtIyJkVcZ8T5aUxtp/QmTMlmjyb3P44E1BEv5x3LATBGApWAJATyXHtkXRD+gWTmeyYLiQA==" - }, "block-stream": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", @@ -1545,6 +1569,11 @@ "xmlbuilder": "^9.0.4" }, "dependencies": { + "ejs": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", + "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==" + }, "fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -2504,6 +2533,24 @@ "uuid": "^3.3.2" }, "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, "jsonld": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/jsonld/-/jsonld-1.8.1.tgz", @@ -2779,16 +2826,6 @@ "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=" }, - "drag-drop": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/drag-drop/-/drag-drop-2.13.3.tgz", - "integrity": "sha512-g+qp+ssi6+v9Qnyyco0dfyA9sYZYYDddGbc0STdMHc9hfyHeYzqGs4v18jFksgHDtYBWf+ocnUkO6jF7MbFudg==", - "requires": { - "blob-to-buffer": "^1.0.2", - "flatten": "^1.0.2", - "run-parallel": "^1.0.0" - } - }, "duplexer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", @@ -2857,9 +2894,12 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "ejs": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz", - "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==" + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.5.tgz", + "integrity": "sha512-dldq3ZfFtgVTJMLjOe+/3sROTzALlL9E34V4/sDtUd/KlBSS0s6U1/+WPE1B4sj9CXHJpL1M6rhNJnc9Wbal9w==", + "requires": { + "jake": "^10.6.1" + } }, "ejs-cli": { "version": "2.2.1", @@ -2916,35 +2956,32 @@ } }, "engine.io": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.1.5.tgz", - "integrity": "sha512-D06ivJkYxyRrcEe0bTpNnBQNgP9d3xog+qZlLbui8EsMr/DouQpf5o9FzJnWYHEYE0YsFHllUv2R1dkgYZXHcA==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.3.2.tgz", + "integrity": "sha512-AsaA9KG7cWPXWHp5FvHdDWY3AMWeZ8x+2pUVLcn71qE5AtAzgGbxuclOytygskw8XGmiQafTmnI9Bix3uihu2w==", "requires": { "accepts": "~1.3.4", "base64id": "1.0.0", "cookie": "0.3.1", "debug": "~3.1.0", "engine.io-parser": "~2.1.0", - "uws": "~9.14.0", - "ws": "~3.3.1" + "ws": "~6.1.0" }, "dependencies": { "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", + "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" + "async-limiter": "~1.0.0" } } } }, "engine.io-client": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.1.6.tgz", - "integrity": "sha512-hnuHsFluXnsKOndS4Hv6SvUrgdYx1pk2NqfaDMW+GWdgfU3+/V25Cj7I8a0x92idSpa5PIhJRKxPvp9mnoLsfg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.3.2.tgz", + "integrity": "sha512-y0CPINnhMvPuwtqXfsGuWE8BB66+B6wTtCofQDRecMQPYX3MYUZXFNKDhdrSe3EVjgOu4V3rxdeqN/Tr91IgbQ==", "requires": { "component-emitter": "1.2.1", "component-inherit": "0.0.3", @@ -2954,7 +2991,7 @@ "indexof": "0.0.1", "parseqs": "0.0.5", "parseuri": "0.0.5", - "ws": "~3.3.1", + "ws": "~6.1.0", "xmlhttprequest-ssl": "~1.5.4", "yeast": "0.1.2" }, @@ -2965,13 +3002,11 @@ "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" }, "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", + "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" + "async-limiter": "~1.0.0" } } } @@ -3270,6 +3305,11 @@ } } }, + "exifr": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/exifr/-/exifr-5.0.3.tgz", + "integrity": "sha512-vC90Lop0cBj5k9V18qHV6lzf/O1lWOlcHXn9r0Ueh4bTZnr+HV4QKFXPSR1v87sq6BBTFAgXrF/ZfeWBAwBcFQ==" + }, "exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -3731,6 +3771,14 @@ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "optional": true }, + "filelist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.1.tgz", + "integrity": "sha512-8zSK6Nu0DQIC08mUC46sWGXi+q3GGpKydAG36k+JDba6VRpkevvOWUW5a/PhShij4+vHT9M+ghgG7eM+a9JDUQ==", + "requires": { + "minimatch": "^3.0.4" + } + }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", @@ -3842,11 +3890,6 @@ "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", "dev": true }, - "flatten": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz", - "integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==" - }, "flaverr": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/flaverr/-/flaverr-1.10.0.tgz", @@ -3993,22 +4036,29 @@ "dev": true }, "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" }, "dependencies": { "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", + "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "^4.1.6", + "universalify": "^1.0.0" } + }, + "universalify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==" } } }, @@ -5758,6 +5808,11 @@ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==" }, + "is-shallow-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-shallow-equal/-/is-shallow-equal-1.0.1.tgz", + "integrity": "sha512-lq5RvK+85Hs5J3p4oA4256M1FEffzmI533ikeDHvJd42nouRRx5wBzt36JuviiGe5dIPyHON/d0/Up+PBo6XkQ==" + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -5979,6 +6034,47 @@ "iterate-iterator": "^1.0.1" } }, + "jake": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", + "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", + "requires": { + "async": "0.9.x", + "chalk": "^2.4.2", + "filelist": "^1.0.1", + "minimatch": "^3.0.4" + }, + "dependencies": { + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "jmespath": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", @@ -6722,6 +6818,11 @@ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + }, "lodash.escaperegexp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", @@ -7122,15 +7223,15 @@ } }, "machinepack-redis": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/machinepack-redis/-/machinepack-redis-1.3.0.tgz", - "integrity": "sha1-eXMRUKJs8rCwCw63V3/yaOW/dbg=", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/machinepack-redis/-/machinepack-redis-2.0.5.tgz", + "integrity": "sha512-K+5j93jaeFKKhtGc0VDVaW/42luxbVnN/XueLfXdJhFam+dMm+06iNzVC0xexZwx+MRfnpWiMOT2TncC+Vi07g==", "requires": { "@sailshq/lodash": "^3.10.2", "async": "2.0.1", - "flaverr": "^1.1.1", - "machine": "^13.0.0-11", - "redis": "2.6.3" + "flaverr": "^1.9.2", + "machine": "^15.2.2", + "redis": "2.8.0" }, "dependencies": { "async": { @@ -7141,55 +7242,24 @@ "lodash": "^4.8.0" } }, - "include-all": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/include-all/-/include-all-1.0.8.tgz", - "integrity": "sha1-6LuEsFcniiLPlEMZA32XAMGKQ3k=", - "requires": { - "lodash": "3.10.1" - }, - "dependencies": { - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" - } - } - }, "machine": { - "version": "13.0.0-24", - "resolved": "https://registry.npmjs.org/machine/-/machine-13.0.0-24.tgz", - "integrity": "sha512-M4jMQbHlAgPklsGdCxP6udDgeOEABlYxwSV0oybcgt4bZ5hz0CLIIpJUtBNtpDNe29K9u6qFHQrGAAIkEiNa7w==", + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/machine/-/machine-15.2.2.tgz", + "integrity": "sha512-gXA/U4bjMyQd2QPw8i+AxzXEDkQBImQVE2P7mmTmXPcfszT+NJc5Me0I1Tn6Fj8zsO5EsmsFxD8Xdia751ik/w==", "requires": { "@sailshq/lodash": "^3.10.2", - "convert-to-ecmascript-compatible-varname": "0.1.4", - "debug": "3.1.0", - "include-all": "^1.0.5", - "rttc": "^9.8.1", - "switchback": "^2.0.1" + "anchor": "^1.2.0", + "flaverr": "^1.7.0", + "parley": "^3.8.0", + "rttc": "^10.0.0-3" } }, "rttc": { - "version": "9.8.2", - "resolved": "https://registry.npmjs.org/rttc/-/rttc-9.8.2.tgz", - "integrity": "sha1-IzfSHUE/SjT/+IF3+V6uft+9Jr8=", - "requires": { - "lodash": "3.10.1" - }, - "dependencies": { - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" - } - } - }, - "switchback": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/switchback/-/switchback-2.0.5.tgz", - "integrity": "sha512-w9gnsTxR5geOKt45QUryhDP9KTLcOAqje9usR2VQ2ng8DfhaF+mkIcArxioMP/p6Z/ecKE58i2/B0DDlMJK1jw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rttc/-/rttc-10.0.1.tgz", + "integrity": "sha512-wBsGNVaZ8K1qG0n5jxQ7dnOpvpewyQHGIjbMFYx8D16+51MM+FwkZwDPgH4GtnaTSzrNvrJriXFyvDi7OTZQ0A==", "requires": { - "@sailshq/lodash": "^3.10.3" + "@sailshq/lodash": "^3.10.2" } } } @@ -7213,53 +7283,32 @@ } }, "machinepack-urls": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/machinepack-urls/-/machinepack-urls-3.1.1.tgz", - "integrity": "sha1-1fswMs9KATXicoU1Bvawxm3plqo=", + "version": "6.0.2-0", + "resolved": "https://registry.npmjs.org/machinepack-urls/-/machinepack-urls-6.0.2-0.tgz", + "integrity": "sha512-777UDtPvgDG2XxekkQnjQi6tHgg3uepbjWZFw82isxyMThhsNdrwzaZd9hkupxcECrThw5OuPEsL963ya+SA3w==", "requires": { - "machine": "^4.0.0" + "@sailshq/lodash": "^3.10.2", + "machine": "^15.0.0-2" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" - }, "machine": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/machine/-/machine-4.1.1.tgz", - "integrity": "sha1-7y7KudSqwtvDl4UCl4o25x/ln9c=", + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/machine/-/machine-15.2.2.tgz", + "integrity": "sha512-gXA/U4bjMyQd2QPw8i+AxzXEDkQBImQVE2P7mmTmXPcfszT+NJc5Me0I1Tn6Fj8zsO5EsmsFxD8Xdia751ik/w==", "requires": { - "convert-to-ecmascript-compatible-varname": "^0.1.0", - "debug": "^2.1.1", - "lodash": "~2.4.1", - "object-hash": "~0.3.0", - "rttc": "^1.0.2", - "switchback": "^1.1.3" + "@sailshq/lodash": "^3.10.2", + "anchor": "^1.2.0", + "flaverr": "^1.7.0", + "parley": "^3.8.0", + "rttc": "^10.0.0-3" } }, "rttc": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/rttc/-/rttc-1.0.2.tgz", - "integrity": "sha1-TTZCjpUoQrJ0P6cC5PVhoi9kje8=", - "requires": { - "lodash": "~2.4.1" - } - }, - "switchback": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/switchback/-/switchback-1.1.3.tgz", - "integrity": "sha1-EscBCTSNailvc5upEO64U/i25jE=", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rttc/-/rttc-10.0.1.tgz", + "integrity": "sha512-wBsGNVaZ8K1qG0n5jxQ7dnOpvpewyQHGIjbMFYx8D16+51MM+FwkZwDPgH4GtnaTSzrNvrJriXFyvDi7OTZQ0A==", "requires": { - "lodash": "~2.4.1" + "@sailshq/lodash": "^3.10.2" } } } @@ -7372,6 +7421,11 @@ "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", "dev": true }, + "math-log2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/math-log2/-/math-log2-1.0.1.tgz", + "integrity": "sha1-+4lBvl9evol55xjmJzsXjlhpRWU=" + }, "math-random": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", @@ -7421,6 +7475,11 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, + "memoize-one": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz", + "integrity": "sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==" + }, "memory-pager": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", @@ -8232,9 +8291,9 @@ "dev": true }, "ng2-completer": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/ng2-completer/-/ng2-completer-2.0.8.tgz", - "integrity": "sha512-WzxJ4u3vAHsfBUaFCloEBoirPZrnDabtWEKyDok7dtjhS1ZvcbwQ4asdXuDO0hZ0T1QC66U/PwLhKfkG501hVg==" + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ng2-completer/-/ng2-completer-9.0.1.tgz", + "integrity": "sha512-zEKehHdCK8E/k4Y0HepprGdYBHr2AOsaq4QpeqoCyUElOOC5M3qi3ZEHV1VF54I7heBQktswwXe5UyWduJ0Xeg==" }, "ng2-datetime": { "version": "1.4.0", @@ -8264,12 +8323,11 @@ "integrity": "sha512-sSHCgWfJ+Lui/u+0msF3oyCgvdkhxDbkCS6Q8uiJquzOimkJBvX6hl5aSSA7DR1XbMpdM8r7phjcF63sF4rkKg==" }, "node-cache": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-4.2.1.tgz", - "integrity": "sha512-BOb67bWg2dTyax5kdef5WfU3X8xu4wPg+zHzkvls0Q/QpYycIFRLEEIdAx9Wma43DxG6Qzn4illdZoYseKWa4A==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", "requires": { - "clone": "2.x", - "lodash": "^4.17.15" + "clone": "2.x" } }, "node-fetch": { @@ -9466,9 +9524,9 @@ } }, "preact": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/preact/-/preact-8.5.3.tgz", - "integrity": "sha512-O3kKP+1YdgqHOFsZF2a9JVdtqD+RPzCQc3rP+Ualf7V6rmRDchZ9MJbiGTT7LuyqFKZqlHSOyO/oMFmI2lVTsw==" + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/preact/-/preact-8.2.9.tgz", + "integrity": "sha512-ThuGXBmJS3VsT+jIP+eQufD3L8pRw/PY3FoCys6O9Pu6aF12Pn9zAJDX99TfwRAFOCEKm/P0lwiPTbqKMJp0fA==" }, "preact-css-transition-group": { "version": "1.3.0", @@ -9485,11 +9543,6 @@ "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" }, - "prettier-bytes": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prettier-bytes/-/prettier-bytes-1.0.4.tgz", - "integrity": "sha1-mUsCqkb2mcULYle1+qp/4lV+YtY=" - }, "pretty-bytes": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-3.0.1.tgz", @@ -9617,9 +9670,9 @@ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, "querystringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", - "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" }, "quote-stream": { "version": "1.0.2", @@ -9902,13 +9955,13 @@ } }, "redis": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/redis/-/redis-2.6.3.tgz", - "integrity": "sha1-hDBbklU8ah8Jx8R8MLEazn27etQ=", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz", + "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==", "requires": { "double-ended-queue": "^2.1.0-0", "redis-commands": "^1.2.0", - "redis-parser": "^2.0.0" + "redis-parser": "^2.6.0" } }, "redis-commands": { @@ -9922,9 +9975,9 @@ "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=" }, "redux": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.1.tgz", - "integrity": "sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz", + "integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==", "requires": { "loose-envify": "^1.4.0", "symbol-observable": "^1.2.0" @@ -10238,20 +10291,15 @@ } } }, - "run-parallel": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", - "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==" - }, "rx": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/rx/-/rx-2.5.3.tgz", "integrity": "sha1-Ia3H2A8CACr1Da6X/Z2/JIdV9WY=" }, "rxjs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", - "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.2.tgz", + "integrity": "sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==", "requires": { "tslib": "^1.9.0" } @@ -11055,31 +11103,29 @@ } }, "sails-hook-grunt": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/sails-hook-grunt/-/sails-hook-grunt-3.1.1.tgz", - "integrity": "sha512-JD6e+orxbmY+PH2rCEwQ/bK8lnJkyqfi86OKHvYubyIInTEuUu+iOWrBwZhaqLmK96euK/22GOSkuPQ6X1CWdQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sails-hook-grunt/-/sails-hook-grunt-4.0.1.tgz", + "integrity": "sha512-y6oL7XxXl1rpSEeGHTXaZKd18FhZL7IX14gh/e4jOT8Nf8L7jLylC16qdn95g0dTO/FJftyTS4Jbl8hGn6CU4Q==", "requires": { "@sailshq/grunt-contrib-uglify": "^3.2.1", "@sailshq/lodash": "^3.10.2", - "babel-core": "6.26.0", + "babel-core": "6.26.3", "babel-polyfill": "6.26.0", - "babel-preset-env": "1.6.1", + "babel-preset-env": "1.7.0", "chalk": "1.1.3", - "grunt": "1.0.1", + "grunt": "1.0.4", "grunt-babel": "7.0.0", "grunt-cli": "1.2.0", "grunt-contrib-clean": "1.0.0", - "grunt-contrib-coffee": "1.0.0", "grunt-contrib-concat": "1.0.1", "grunt-contrib-copy": "1.0.0", - "grunt-contrib-cssmin": "1.0.1", - "grunt-contrib-jst": "1.0.0", + "grunt-contrib-cssmin": "2.2.1", "grunt-contrib-less": "1.3.0", - "grunt-contrib-watch": "1.0.0", + "grunt-contrib-watch": "1.1.0", "grunt-hash": "0.5.0", "grunt-sails-linker": "^0.10.1", - "grunt-sync": "0.6.2", - "include-all": "1.0.8" + "grunt-sync": "0.8.1", + "include-all": "^4.0.3" }, "dependencies": { "@sailshq/grunt-contrib-uglify": { @@ -11094,9 +11140,9 @@ } }, "@sailshq/lodash": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@sailshq/lodash/-/lodash-3.10.2.tgz", - "integrity": "sha1-FWfUc0U2TCwuIHe8ETSHsd/mIVQ=" + "version": "3.10.3", + "resolved": "https://registry.npmjs.org/@sailshq/lodash/-/lodash-3.10.3.tgz", + "integrity": "sha512-XTF5BtsTSiSpTnfqrCGS5Q8FvSHWCywA0oRxFAZo8E1a8k1MMFUvk3VlRk3q/SusEYwy7gvVdyt9vvNlTa2VuA==" }, "abbrev": { "version": "1.1.1", @@ -11104,9 +11150,9 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "acorn": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.4.1.tgz", - "integrity": "sha512-XLmq3H/BVvW6/GbxKryGxWORz1ebilSsUDlyC27bXhWGWAZWkGwS6FLHjOlwFXNFoWFQEO/Df4u0YYd0K3BQgQ==" + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" }, "acorn-jsx": { "version": "3.0.1", @@ -11124,30 +11170,25 @@ } }, "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", + "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "ajv-keywords": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=" - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", + "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==" }, "ansi-escapes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz", - "integrity": "sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" }, "ansi-regex": { "version": "2.1.1", @@ -11165,6 +11206,13 @@ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "requires": { "sprintf-js": "~1.0.2" + }, + "dependencies": { + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + } } }, "array-find-index": { @@ -11172,24 +11220,6 @@ "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" - }, "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -11197,14 +11227,19 @@ "optional": true }, "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "optional": true, + "requires": { + "safer-buffer": "~2.1.0" + } }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "optional": true }, "async": { "version": "1.5.2", @@ -11224,9 +11259,9 @@ "optional": true }, "aws4": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "optional": true }, "babel-code-frame": { @@ -11240,9 +11275,9 @@ } }, "babel-core": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz", - "integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=", + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", "requires": { "babel-code-frame": "^6.26.0", "babel-generator": "^6.26.0", @@ -11254,15 +11289,15 @@ "babel-traverse": "^6.26.0", "babel-types": "^6.26.0", "babylon": "^6.18.0", - "convert-source-map": "^1.5.0", - "debug": "^2.6.8", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", "json5": "^0.5.1", "lodash": "^4.17.4", "minimatch": "^3.0.4", "path-is-absolute": "^1.0.1", - "private": "^0.1.7", + "private": "^0.1.8", "slash": "^1.0.0", - "source-map": "^0.5.6" + "source-map": "^0.5.7" }, "dependencies": { "source-map": { @@ -11567,9 +11602,9 @@ } }, "babel-plugin-transform-es2015-modules-commonjs": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz", - "integrity": "sha1-DYOUApt9xqvhqX7xgeAHWN0uXYo=", + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", + "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", "requires": { "babel-plugin-transform-strict-mode": "^6.24.1", "babel-runtime": "^6.26.0", @@ -11717,9 +11752,9 @@ } }, "babel-preset-env": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.6.1.tgz", - "integrity": "sha512-W6VIyA6Ch9ePMI7VptNn2wBM6dbG0eSz25HEiL40nQXCsXGTGZSTZu1Iap+cj3Q0S5a7T9+529l/5Bkvd+afNA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.7.0.tgz", + "integrity": "sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg==", "requires": { "babel-plugin-check-es2015-constants": "^6.22.0", "babel-plugin-syntax-trailing-function-commas": "^6.22.0", @@ -11748,7 +11783,7 @@ "babel-plugin-transform-es2015-unicode-regex": "^6.22.0", "babel-plugin-transform-exponentiation-operator": "^6.22.0", "babel-plugin-transform-regenerator": "^6.22.0", - "browserslist": "^2.1.2", + "browserslist": "^3.2.6", "invariant": "^2.2.2", "semver": "^5.3.0" } @@ -11826,62 +11861,23 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "optional": true, "requires": { "tweetnacl": "^0.14.3" } }, - "body-parser": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.14.2.tgz", - "integrity": "sha1-EBXLH+LEQ4WCWVgdtTMy+NDPUPk=", - "requires": { - "bytes": "2.2.0", - "content-type": "~1.0.1", - "debug": "~2.2.0", - "depd": "~1.1.0", - "http-errors": "~1.3.1", - "iconv-lite": "0.4.13", - "on-finished": "~2.3.0", - "qs": "5.2.0", - "raw-body": "~2.1.5", - "type-is": "~1.6.10" - }, - "dependencies": { - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "requires": { - "ms": "0.7.1" - } - }, - "iconv-lite": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", - "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=" - }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" - }, - "qs": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz", - "integrity": "sha1-qfMRQq9GjLcrJbMBNrokVoNJFr4=" - } - } - }, - "boom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", - "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", - "optional": true, + "body": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", + "integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=", "requires": { - "hoek": "4.x.x" + "continuable-cache": "^0.3.1", + "error": "^7.0.0", + "raw-body": "~1.1.0", + "safe-json-parse": "~1.0.1" } }, "brace-expansion": { @@ -11907,23 +11903,23 @@ } }, "browserslist": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", - "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.8.tgz", + "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==", "requires": { - "caniuse-lite": "^1.0.30000792", - "electron-to-chromium": "^1.3.30" + "caniuse-lite": "^1.0.30000844", + "electron-to-chromium": "^1.3.47" } }, - "builtin-modules": { + "buffer-from": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, "bytes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.2.0.tgz", - "integrity": "sha1-/TVGSkA/b5EXwt42Cez/nK4ABYg=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=" }, "caller-path": { "version": "0.1.0", @@ -11953,9 +11949,9 @@ } }, "caniuse-lite": { - "version": "1.0.30000810", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000810.tgz", - "integrity": "sha512-/0Q00Oie9C72P8zQHtFvzmkrMC3oOFUnMWjCy5F2+BE8lzICm91hQPhh0+XIsAFPKOe2Dh3pKgbRmU3EKxfldA==" + "version": "1.0.30000969", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000969.tgz", + "integrity": "sha512-Kus0yxkoAJgVc0bax7S4gLSlFifCa7MnSZL9p9VuS/HIKEL4seaqh28KIQAAO50cD/rJ5CiJkJFapkdDAlhFxQ==" }, "caseless": { "version": "0.12.0", @@ -11986,29 +11982,17 @@ "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==" }, "clean-css": { - "version": "3.4.28", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.28.tgz", - "integrity": "sha1-vxlF6C/ICPVWlebd6uwBQA79A/8=", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.11.tgz", + "integrity": "sha1-Ls3xRaujj1R0DybO/Q/z4D4SXWo=", "requires": { - "commander": "2.8.x", - "source-map": "0.4.x" + "source-map": "0.5.x" }, "dependencies": { - "commander": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", - "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", - "requires": { - "graceful-readlink": ">= 1.0.0" - } - }, "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "requires": { - "amdefine": ">=0.0.4" - } + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" } } }, @@ -12030,17 +12014,17 @@ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, - "coffee-script": { + "coffeescript": { "version": "1.10.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.10.0.tgz", - "integrity": "sha1-EpOLz5vhlI+gBvkuDEyegXBRCMA=" + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.10.0.tgz", + "integrity": "sha1-56qDAZF+9iGzXYo580jc3R234z4=" }, "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "requires": { - "color-name": "^1.1.1" + "color-name": "1.1.3" } }, "color-name": { @@ -12054,9 +12038,9 @@ "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=" }, "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "optional": true, "requires": { "delayed-stream": "~1.0.0" @@ -12073,29 +12057,33 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", - "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "requires": { + "buffer-from": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^2.2.2", "typedarray": "^0.0.6" } }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "continuable-cache": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", + "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=" }, "convert-source-map": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", - "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "requires": { + "safe-buffer": "~5.1.1" + } }, "core-js": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", - "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=" + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.7.tgz", + "integrity": "sha512-ydmsQxDVH7lDpYoirXft8S83ddKKfdsrsmWhtyj7xafXVLbLhKOyfD7kAi2ueFfeP7m9rNavjW59O3hLLzzC5A==" }, "core-util-is": { "version": "1.0.2", @@ -12112,26 +12100,6 @@ "which": "^1.2.9" } }, - "cryptiles": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", - "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", - "optional": true, - "requires": { - "boom": "5.x.x" - }, - "dependencies": { - "boom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", - "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", - "optional": true, - "requires": { - "hoek": "4.x.x" - } - } - } - }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -12144,6 +12112,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "optional": true, "requires": { "assert-plus": "^1.0.0" } @@ -12175,31 +12144,12 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" - } - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "optional": true }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, "detect-indent": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", @@ -12221,23 +12171,25 @@ "esutils": "^2.0.2" } }, - "ecc-jsbn": { + "duplexer": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "optional": true, "requires": { - "jsbn": "~0.1.0" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, "electron-to-chromium": { - "version": "1.3.34", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.34.tgz", - "integrity": "sha1-2TSY9AORuwwWpgPYJBuZUUBBV+0=" + "version": "1.3.135", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.135.tgz", + "integrity": "sha512-xXLNstRdVsisPF3pL3H9TVZo2XkMILfqtD6RiWIUmDK2sFX1Bjwqmd8LBp0Kuo2FgKO63JXPoEVGm8WyYdwP0Q==" }, "errno": { "version": "0.1.7", @@ -12248,10 +12200,19 @@ "prr": "~1.0.1" } }, + "error": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/error/-/error-7.0.2.tgz", + "integrity": "sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI=", + "requires": { + "string-template": "~0.2.1", + "xtend": "~4.0.0" + } + }, "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "requires": { "is-arrayish": "^0.2.1" } @@ -12305,46 +12266,57 @@ "text-table": "~0.2.0" }, "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "requires": { "color-convert": "^1.9.0" } }, "chalk": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", - "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "requires": { - "ansi-styles": "^3.2.0", + "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", - "supports-color": "^5.2.0" + "supports-color": "^5.3.0" } }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, - "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==" + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" }, "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -12354,14 +12326,15 @@ "path-is-absolute": "^1.0.0" } }, - "js-yaml": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", - "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, "strip-ansi": { "version": "4.0.0", @@ -12372,9 +12345,9 @@ } }, "supports-color": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", - "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "requires": { "has-flag": "^3.0.0" } @@ -12382,43 +12355,42 @@ } }, "eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", "requires": { "esrecurse": "^4.1.0", "estraverse": "^4.1.1" } }, "espree": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.3.tgz", - "integrity": "sha512-Zy3tAJDORxQZLl2baguiRU1syPERAIg0L+JB2MWorORgTu/CplzvxS9WWA7Xh4+Q+eOQihNs/1o1Xep8cvCxWQ==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", "requires": { - "acorn": "^5.4.0", + "acorn": "^5.5.0", "acorn-jsx": "^3.0.0" } }, "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", - "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "requires": { "estraverse": "^4.0.0" } }, "esrecurse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", - "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "requires": { - "estraverse": "^4.1.0", - "object-assign": "^4.0.1" + "estraverse": "^4.1.0" } }, "estraverse": { @@ -12441,10 +12413,16 @@ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=" }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "optional": true + }, "external-editor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.1.0.tgz", - "integrity": "sha512-E44iT5QVOUJBKij4IIV3uvxuNlbKS38Tw1HiupxEIHPv9qtC2PrDYohbXV5U+1jnfIXttny8gUhj+oZvflFlzA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", "requires": { "chardet": "^0.4.0", "iconv-lite": "^0.4.17", @@ -12458,9 +12436,9 @@ "optional": true }, "fast-deep-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", - "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" }, "fast-json-stable-stringify": { "version": "2.0.0", @@ -12535,13 +12513,13 @@ } }, "flat-cache": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", - "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", "requires": { "circular-json": "^0.3.1", - "del": "^2.0.2", "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", "write": "^0.2.1" } }, @@ -12552,16 +12530,26 @@ "optional": true }, "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "optional": true, "requires": { "asynckit": "^0.4.0", - "combined-stream": "1.0.6", + "combined-stream": "^1.0.6", "mime-types": "^2.1.12" } }, + "fs-extra": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", + "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -12573,9 +12561,9 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" }, "gaze": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.2.tgz", - "integrity": "sha1-hHIkZ3rbiHDWeSV+0ziP22HkAQU=", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", "requires": { "globule": "^1.0.0" } @@ -12594,6 +12582,7 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "optional": true, "requires": { "assert-plus": "^1.0.0" } @@ -12616,33 +12605,20 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==" }, - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, "globule": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz", - "integrity": "sha1-HcScaCLdnoovoAuiopUAboZkvQk=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", + "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", "requires": { "glob": "~7.1.1", - "lodash": "~4.17.4", + "lodash": "~4.17.10", "minimatch": "~3.0.2" }, "dependencies": { "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -12655,21 +12631,26 @@ } }, "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" }, "graceful-readlink": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=" + }, "grunt": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.0.1.tgz", - "integrity": "sha1-6HeHZOlEsY8yuw8QuQeEdcnftWs=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.0.4.tgz", + "integrity": "sha512-PYsMOrOC+MsdGEkFVwMaMyc6Ob7pKmq+deg1Sjr+vvMWp35sztfwKE7qoN51V+UEtHsyNuMcGdgMLFkBHvMxHQ==", "requires": { - "coffee-script": "~1.10.0", + "coffeescript": "~1.10.0", "dateformat": "~1.0.12", "eventemitter2": "~0.4.13", "exit": "~0.1.1", @@ -12677,14 +12658,15 @@ "glob": "~7.0.0", "grunt-cli": "~1.2.0", "grunt-known-options": "~1.1.0", - "grunt-legacy-log": "~1.0.0", - "grunt-legacy-util": "~1.0.0", + "grunt-legacy-log": "~2.0.0", + "grunt-legacy-util": "~1.1.1", "iconv-lite": "~0.4.13", - "js-yaml": "~3.5.2", - "minimatch": "~3.0.0", + "js-yaml": "~3.13.0", + "minimatch": "~3.0.2", + "mkdirp": "~0.5.1", "nopt": "~3.0.6", "path-is-absolute": "~1.0.0", - "rimraf": "~2.2.8" + "rimraf": "~2.6.2" } }, "grunt-babel": { @@ -12701,6 +12683,13 @@ "grunt-known-options": "~1.1.0", "nopt": "~3.0.6", "resolve": "~1.1.0" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" + } } }, "grunt-contrib-clean": { @@ -12710,73 +12699,6 @@ "requires": { "async": "^1.5.2", "rimraf": "^2.5.1" - }, - "dependencies": { - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "requires": { - "glob": "^7.0.5" - } - } - } - }, - "grunt-contrib-coffee": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-coffee/-/grunt-contrib-coffee-1.0.0.tgz", - "integrity": "sha1-2u6wSVTxTihovMm6bq+RBf3C2kw=", - "requires": { - "chalk": "~1.0.0", - "coffee-script": "~1.10.0", - "lodash": "~4.3.0", - "uri-path": "~1.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz", - "integrity": "sha1-QchHGUZGN15qGl0Qw8oFTvn8mA0=" - }, - "chalk": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.0.0.tgz", - "integrity": "sha1-s89O0P9Tl8mcdbj2edsvUoMfltw=", - "requires": { - "ansi-styles": "^2.0.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^1.0.3", - "strip-ansi": "^2.0.1", - "supports-color": "^1.3.0" - } - }, - "has-ansi": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-1.0.3.tgz", - "integrity": "sha1-wLWxYV2eOCsP9nFp2We0JeSMpTg=", - "requires": { - "ansi-regex": "^1.1.0", - "get-stdin": "^4.0.1" - } - }, - "lodash": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", - "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=" - }, - "strip-ansi": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz", - "integrity": "sha1-32LBqpTtLxFOHQ8h/R1QSCt5pg4=", - "requires": { - "ansi-regex": "^1.0.0" - } - }, - "supports-color": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.3.1.tgz", - "integrity": "sha1-FXWN8J2P87SswwdTn6vicJXhBC0=" - } } }, "grunt-contrib-concat": { @@ -12805,28 +12727,41 @@ } }, "grunt-contrib-cssmin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/grunt-contrib-cssmin/-/grunt-contrib-cssmin-1.0.1.tgz", - "integrity": "sha1-9tRSRMyH79zFIfaRjq/ZIe/YyNo=", - "requires": { - "chalk": "^1.0.0", - "clean-css": "~3.4.2", - "maxmin": "^1.1.0" - } - }, - "grunt-contrib-jst": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-jst/-/grunt-contrib-jst-1.0.0.tgz", - "integrity": "sha1-uOcDWuO2JYdYC9bYPI8MSEEGOHQ=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/grunt-contrib-cssmin/-/grunt-contrib-cssmin-2.2.1.tgz", + "integrity": "sha512-IXNomhQ5ekVZbDbj/ik5YccoD9khU6LT2fDXqO1+/Txjq8cp0tQKjVS8i8EAbHOrSDkL7/UD6A7b+xj98gqh9w==", "requires": { "chalk": "^1.0.0", - "lodash": "^2.4.1" + "clean-css": "~4.1.1", + "maxmin": "^2.1.0" }, "dependencies": { - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" + "gzip-size": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-3.0.0.tgz", + "integrity": "sha1-VGGI6b3DN/Zzdy+BZgRks4nc5SA=", + "requires": { + "duplexer": "^0.1.1" + } + }, + "maxmin": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-2.1.0.tgz", + "integrity": "sha1-TTsiCQPZXu5+t6x/qGTnLcCaMWY=", + "requires": { + "chalk": "^1.0.0", + "figures": "^1.0.1", + "gzip-size": "^3.0.0", + "pretty-bytes": "^3.0.0" + } + }, + "pretty-bytes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-3.0.1.tgz", + "integrity": "sha1-J9AAjXeAY6C0gRuzXHnxvV1fvM8=", + "requires": { + "number-is-nan": "^1.0.0" + } } } }, @@ -12842,20 +12777,23 @@ } }, "grunt-contrib-watch": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.0.0.tgz", - "integrity": "sha1-hKGnodar0m7VaEE0lscxM+mQAY8=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz", + "integrity": "sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg==", "requires": { - "async": "^1.5.0", - "gaze": "^1.0.0", - "lodash": "^3.10.1", - "tiny-lr": "^0.2.1" + "async": "^2.6.0", + "gaze": "^1.1.0", + "lodash": "^4.17.10", + "tiny-lr": "^1.1.1" }, "dependencies": { - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "requires": { + "lodash": "^4.17.11" + } } } }, @@ -12865,56 +12803,70 @@ "integrity": "sha1-mHgdeZ90spU4aS9Yxh1QZIwb0p4=" }, "grunt-known-options": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.0.tgz", - "integrity": "sha1-pCdO6zL6dl2lp6OxcSYXzjsUQUk=" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.1.tgz", + "integrity": "sha512-cHwsLqoighpu7TuYj5RonnEuxGVFnztcUqTqp5rXFGYL4OuPFofwC4Ycg7n9fYwvK6F5WbYgeVOwph9Crs2fsQ==" }, "grunt-legacy-log": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-1.0.2.tgz", - "integrity": "sha512-WdedTJ/6zCXnI/coaouzqvkI19uwqbcPkdsXiDRKJyB5rOUlOxnCnTVbpeUdEckKVir2uHF3rDBYppj2p6N3+g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-2.0.0.tgz", + "integrity": "sha512-1m3+5QvDYfR1ltr8hjiaiNjddxGdQWcH0rw1iKKiQnF0+xtgTazirSTGu68RchPyh1OBng1bBUjLmX8q9NpoCw==", "requires": { "colors": "~1.1.2", - "grunt-legacy-log-utils": "~1.0.0", + "grunt-legacy-log-utils": "~2.0.0", "hooker": "~0.2.3", "lodash": "~4.17.5" } }, "grunt-legacy-log-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-1.0.0.tgz", - "integrity": "sha1-p7ji0Ps1taUPSvmG/BEnSevJbz0=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.0.1.tgz", + "integrity": "sha512-o7uHyO/J+i2tXG8r2bZNlVk20vlIFJ9IEYyHMCQGfWYru8Jv3wTqKZzvV30YW9rWEjq0eP3cflQ1qWojIe9VFA==", "requires": { - "chalk": "~1.1.1", - "lodash": "~4.3.0" + "chalk": "~2.4.1", + "lodash": "~4.17.10" }, "dependencies": { - "lodash": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", - "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=" + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } } } }, "grunt-legacy-util": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.0.0.tgz", - "integrity": "sha1-OGqnjcbtUJhsKxiVcmWxtIq7m4Y=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.1.1.tgz", + "integrity": "sha512-9zyA29w/fBe6BIfjGENndwoe1Uy31BIXxTH3s8mga0Z5Bz2Sp4UCjkeyv2tI449ymkx3x26B+46FV4fXEddl5A==", "requires": { "async": "~1.5.2", "exit": "~0.1.1", "getobject": "~0.1.0", "hooker": "~0.2.3", - "lodash": "~4.3.0", - "underscore.string": "~3.2.3", - "which": "~1.2.1" - }, - "dependencies": { - "lodash": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", - "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=" - } + "lodash": "~4.17.10", + "underscore.string": "~3.3.4", + "which": "~1.3.0" } }, "grunt-sails-linker": { @@ -12923,14 +12875,28 @@ "integrity": "sha1-DSz1RzwDuuu2zmwd4eWBY9OsjQY=" }, "grunt-sync": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/grunt-sync/-/grunt-sync-0.6.2.tgz", - "integrity": "sha1-2ay2W0IF0Be9ZGLkn+wtkHGs5Hs=", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/grunt-sync/-/grunt-sync-0.8.1.tgz", + "integrity": "sha512-xoOOgip7LcrwSUbyu27IbWZefjL7M0UNN5V7b0U90REZf1IpDytPWVLNh5dbb/IJUQng3UFyHCUCWPwPDMzipw==", "requires": { - "glob": "^7.0.5", - "lodash": "^4.14.2", - "md5-file": "^2.0.3", - "promised-io": "0.3.5" + "fs-extra": "6.0.1", + "glob": "7.0.5", + "md5-file": "2.0.3" + }, + "dependencies": { + "glob": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.5.tgz", + "integrity": "sha1-tCAqaQmbu00pKnwblbZoK2fr3JU=", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } } }, "gzip-size": { @@ -12949,12 +12915,12 @@ "optional": true }, "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "optional": true, "requires": { - "ajv": "^5.1.0", + "ajv": "^6.5.5", "har-schema": "^2.0.0" } }, @@ -12971,24 +12937,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, - "hawk": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", - "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", - "optional": true, - "requires": { - "boom": "4.x.x", - "cryptiles": "3.x.x", - "hoek": "4.x.x", - "sntp": "2.x.x" - } - }, - "hoek": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", - "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==", - "optional": true - }, "home-or-tmp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", @@ -13004,23 +12952,14 @@ "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=" }, "hosted-git-info": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", - "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==" - }, - "http-errors": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", - "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", - "requires": { - "inherits": "~2.0.1", - "statuses": "1" - } + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" }, "http-parser-js": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", - "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=" + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.0.tgz", + "integrity": "sha512-cZdEF7r4gfRIq7ezX9J0T+kQmJNOub71dWbgAXVHDct80TKP4MCETtZQ31xyv38UwgzkWPYF/Xc0ge55dW9Z9w==" }, "http-signature": { "version": "1.2.0", @@ -13034,14 +12973,17 @@ } }, "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } }, "ignore": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz", - "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==" + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" }, "image-size": { "version": "0.4.0", @@ -13055,18 +12997,12 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "include-all": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/include-all/-/include-all-1.0.8.tgz", - "integrity": "sha1-6LuEsFcniiLPlEMZA32XAMGKQ3k=", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/include-all/-/include-all-4.0.3.tgz", + "integrity": "sha1-ZfBujxGJSxp7XsH8l+azOS98+nU=", "requires": { - "lodash": "3.10.1" - }, - "dependencies": { - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" - } + "@sailshq/lodash": "^3.10.2", + "merge-dictionaries": "^0.0.3" } }, "indent-string": { @@ -13118,21 +13054,21 @@ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "requires": { "color-convert": "^1.9.0" } }, "chalk": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", - "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "requires": { - "ansi-styles": "^3.2.0", + "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", - "supports-color": "^5.2.0" + "supports-color": "^5.3.0" } }, "figures": { @@ -13152,9 +13088,9 @@ } }, "supports-color": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", - "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "requires": { "has-flag": "^3.0.0" } @@ -13162,9 +13098,9 @@ } }, "invariant": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.3.tgz", - "integrity": "sha512-7Z5PPegwDTyjbaeCnV0efcyS6vdKAU51kpEmS7QFib3P4822l8ICYyMn7qvJnc+WzLoDsuI9gPMKbJ8pCu8XtA==", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "requires": { "loose-envify": "^1.0.0" } @@ -13174,14 +13110,6 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "requires": { - "builtin-modules": "^1.0.0" - } - }, "is-finite": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", @@ -13195,27 +13123,6 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=" - }, - "is-path-in-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", - "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", - "requires": { - "is-path-inside": "^1.0.0" - } - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "requires": { - "path-is-inside": "^1.0.1" - } - }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", @@ -13259,18 +13166,19 @@ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" }, "js-yaml": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.5.5.tgz", - "integrity": "sha1-A3fDgBfKvHMisNH7zSWkkWQfL74=", + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "requires": { - "argparse": "^1.0.2", - "esprima": "^2.6.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true }, "jsesc": { "version": "1.3.0", @@ -13284,9 +13192,9 @@ "optional": true }, "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -13309,6 +13217,14 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -13354,9 +13270,9 @@ } }, "livereload-js": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.3.0.tgz", - "integrity": "sha512-j1R0/FeGa64Y+NmqfZhyoVRzcFlOZ8sNlKzHjh4VvLULFACZhn68XrX5DFg2FhMvSMJmROuFxRSa560ECWKBMg==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz", + "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==" }, "load-json-file": { "version": "1.1.0", @@ -13371,9 +13287,9 @@ } }, "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, "lodash._baseassign": { "version": "3.2.0", @@ -13435,11 +13351,11 @@ } }, "loose-envify": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", - "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "requires": { - "js-tokens": "^3.0.0" + "js-tokens": "^3.0.0 || ^4.0.0" } }, "loud-rejection": { @@ -13452,9 +13368,9 @@ } }, "lru-cache": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", - "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -13477,14 +13393,9 @@ } }, "md5-file": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-2.0.7.tgz", - "integrity": "sha1-MH94vQTMsFTkZ+xmHPpamv3J8hA=" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-2.0.3.tgz", + "integrity": "sha1-SgULUuQLVHfQmUO/n9fx/4oonNE=" }, "meow": { "version": "3.7.0", @@ -13503,6 +13414,14 @@ "trim-newlines": "^1.0.0" } }, + "merge-dictionaries": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/merge-dictionaries/-/merge-dictionaries-0.0.3.tgz", + "integrity": "sha1-xN5NWNuyXkwoI6owy44VOQaet1c=", + "requires": { + "@sailshq/lodash": "^3.10.2" + } + }, "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -13510,16 +13429,18 @@ "optional": true }, "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "optional": true }, "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "optional": true, "requires": { - "mime-db": "~1.33.0" + "mime-db": "1.40.0" } }, "mimic-fn": { @@ -13536,16 +13457,23 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { - "minimist": "^1.2.5" + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } } }, "mocha": { @@ -13559,8 +13487,10 @@ "diff": "1.4.0", "escape-string-regexp": "1.0.5", "glob": "7.0.5", + "growl": "1.9.2", "json3": "3.3.2", "lodash.create": "3.1.1", + "mkdirp": "0.5.1", "supports-color": "3.1.2" }, "dependencies": { @@ -13637,12 +13567,12 @@ } }, "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "requires": { "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", + "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" } @@ -13653,9 +13583,9 @@ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "optional": true }, "object-assign": { @@ -13663,14 +13593,6 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -13723,11 +13645,6 @@ "error-ex": "^1.2.0" } }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" - }, "path-exists": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", @@ -13746,6 +13663,11 @@ "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" }, + "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==" + }, "path-type": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", @@ -13810,9 +13732,9 @@ "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "progress": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", - "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" }, "promise": { "version": "7.3.1", @@ -13823,11 +13745,6 @@ "asap": "~2.0.3" } }, - "promised-io": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/promised-io/-/promised-io-0.3.5.tgz", - "integrity": "sha1-StIXuzZYvKrplGsXqGaOzYUeE1Y=" - }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -13839,37 +13756,35 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", "optional": true }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", - "optional": true + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "raw-body": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", - "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", + "integrity": "sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=", "requires": { - "bytes": "2.4.0", - "iconv-lite": "0.4.13", - "unpipe": "1.0.0" + "bytes": "1", + "string_decoder": "0.10" }, "dependencies": { - "bytes": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", - "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" - }, - "iconv-lite": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", - "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=" + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" } } }, @@ -13893,16 +13808,16 @@ } }, "readable-stream": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", - "integrity": "sha512-vuYxeWYM+fde14+rajzqgeohAI7YoJcHE7kXDAc4Nk0EbuKnJfqtY9YtRkLo/tqkuF7MsBQRhPnPeyjYITp3ZQ==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", + "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, @@ -13916,9 +13831,9 @@ } }, "regenerate": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", - "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==" }, "regenerator-runtime": { "version": "0.11.1", @@ -13974,33 +13889,31 @@ } }, "request": { - "version": "2.83.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", - "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "optional": true, "requires": { "aws-sign2": "~0.7.0", - "aws4": "^1.6.0", + "aws4": "^1.8.0", "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.1", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~2.3.1", - "har-validator": "~5.0.3", - "hawk": "~6.0.2", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.17", - "oauth-sign": "~0.8.2", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", "performance-now": "^2.1.0", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1", - "stringstream": "~0.0.5", - "tough-cookie": "~2.3.3", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", "tunnel-agent": "^0.6.0", - "uuid": "^3.1.0" + "uuid": "^3.3.2" } }, "require-uncached": { @@ -14013,9 +13926,12 @@ } }, "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", + "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", + "requires": { + "path-parse": "^1.0.6" + } }, "resolve-from": { "version": "1.0.1", @@ -14032,9 +13948,27 @@ } }, "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } }, "run-async": { "version": "2.3.0", @@ -14058,14 +13992,24 @@ } }, "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-json-parse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", + "integrity": "sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" }, "shebang-command": { "version": "1.2.0", @@ -14098,15 +14042,6 @@ "is-fullwidth-code-point": "^2.0.0" } }, - "sntp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", - "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", - "optional": true, - "requires": { - "hoek": "4.x.x" - } - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -14128,32 +14063,59 @@ } }, "spdx-correct": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", - "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", "requires": { - "spdx-license-ids": "^1.0.2" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==" + }, "spdx-expression-parse": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", - "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=" + "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==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } }, "spdx-license-ids": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", - "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=" + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", + "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==" }, "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "optional": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=" }, "string-width": { "version": "2.1.1", @@ -14180,9 +14142,9 @@ } }, "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" } @@ -14222,12 +14184,12 @@ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" }, "table": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", - "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", + "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", "requires": { - "ajv": "^5.2.3", - "ajv-keywords": "^2.1.0", + "ajv": "^6.0.1", + "ajv-keywords": "^3.0.0", "chalk": "^2.1.0", "lodash": "^4.17.4", "slice-ansi": "1.0.0", @@ -14235,27 +14197,27 @@ }, "dependencies": { "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "requires": { "color-convert": "^1.9.0" } }, "chalk": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", - "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "requires": { - "ansi-styles": "^3.2.0", + "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", - "supports-color": "^5.2.0" + "supports-color": "^5.3.0" } }, "supports-color": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", - "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "requires": { "has-flag": "^3.0.0" } @@ -14273,35 +14235,30 @@ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "tiny-lr": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-0.2.1.tgz", - "integrity": "sha1-s/26gC5dVqM8L28QeUsy5Hescp0=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", + "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", "requires": { - "body-parser": "~1.14.0", - "debug": "~2.2.0", + "body": "^5.1.0", + "debug": "^3.1.0", "faye-websocket": "~0.10.0", - "livereload-js": "^2.2.0", - "parseurl": "~1.3.0", - "qs": "~5.1.0" + "livereload-js": "^2.3.0", + "object-assign": "^4.1.0", + "qs": "^6.4.0" }, "dependencies": { "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "requires": { - "ms": "0.7.1" + "ms": "^2.1.1" } }, "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" - }, - "qs": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-5.1.0.tgz", - "integrity": "sha1-TZMuXH6kEcynajEtOaYGIA/VDNk=" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" } } }, @@ -14319,12 +14276,21 @@ "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" }, "tough-cookie": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", - "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "optional": true, "requires": { + "psl": "^1.1.24", "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "optional": true + } } }, "trim-newlines": { @@ -14349,7 +14315,8 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true }, "type-check": { "version": "0.3.2", @@ -14359,15 +14326,6 @@ "prelude-ls": "~1.1.2" } }, - "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.18" - } - }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -14383,14 +14341,26 @@ } }, "underscore.string": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.2.3.tgz", - "integrity": "sha1-gGmSYzZl1eX8tNsfs6hi62jp5to=" + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.5.tgz", + "integrity": "sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg==", + "requires": { + "sprintf-js": "^1.0.3", + "util-deprecate": "^1.0.2" + } }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } }, "uri-path": { "version": "1.0.0", @@ -14403,18 +14373,18 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", "optional": true }, "validate-npm-package-license": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", - "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "requires": { - "spdx-correct": "~1.0.0", - "spdx-expression-parse": "~1.0.0" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, "verror": { @@ -14437,10 +14407,15 @@ "websocket-extensions": ">=0.1.1" } }, + "websocket-extensions": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==" + }, "which": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", - "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "requires": { "isexe": "^2.0.0" } @@ -14463,6 +14438,11 @@ "mkdirp": "^0.5.1" } }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", @@ -14519,18 +14499,18 @@ } }, "sails-hook-sockets": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/sails-hook-sockets/-/sails-hook-sockets-1.5.5.tgz", - "integrity": "sha512-HS5D+yyQw00ByEUjIeEsExG7AdU3eFh+4xq5vCTZTz81aBmXalJ/vNJmDwmT4IPAoL7+vhimH4xuNXeSyQKPow==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sails-hook-sockets/-/sails-hook-sockets-2.0.0.tgz", + "integrity": "sha512-Y/HG2iYD8n2ljUYdtKrcu756SAhs0qI9SX9pfO6oWOHbS/OWQYh7I0iMGmbMX+qo67OVDnLdwMP4brIHt9kuLg==", "requires": { "@sailshq/lodash": "^3.10.2", "async": "2.0.1", "flaverr": "^1.0.0", - "machinepack-redis": "^1.1.1", - "machinepack-urls": "^3.1.1", + "machinepack-redis": "^2.0.3", + "machinepack-urls": "^6.0.2-0", "proxy-addr": "1.1.5", "semver": "4.3.6", - "socket.io": "2.0.3", + "socket.io": "2.2.0", "uid2": "0.0.3" }, "dependencies": { @@ -15419,25 +15399,30 @@ } }, "socket.io": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.0.3.tgz", - "integrity": "sha1-Q1nwaiSTOua9CHeYr3jGgOrjReM=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.2.0.tgz", + "integrity": "sha512-wxXrIuZ8AILcn+f1B4ez4hJTPG24iNgxBBDaJfT6MsyOhVYiTXWexGoPkd87ktJG8kQEcL/NBvRi64+9k4Kc0w==", "requires": { - "debug": "~2.6.6", - "engine.io": "~3.1.0", - "object-assign": "~4.1.1", + "debug": "~4.1.0", + "engine.io": "~3.3.1", + "has-binary2": "~1.0.2", "socket.io-adapter": "~1.1.0", - "socket.io-client": "~2.0.2", - "socket.io-parser": "~3.1.1" + "socket.io-client": "2.2.0", + "socket.io-parser": "~3.3.0" }, "dependencies": { "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -15447,22 +15432,23 @@ "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==" }, "socket.io-client": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.0.4.tgz", - "integrity": "sha1-CRilUkBtxeVAs4Dc2Xr8SmQzL44=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.2.0.tgz", + "integrity": "sha512-56ZrkTDbdTLmBIyfFYesgOxsjcLnwAKoN4CiPyTVkMQj3zTUh0QAx3GbvIvLpFEOvQWu92yyWICxB0u7wkVbYA==", "requires": { "backo2": "1.0.2", "base64-arraybuffer": "0.1.5", "component-bind": "1.0.0", "component-emitter": "1.2.1", - "debug": "~2.6.4", - "engine.io-client": "~3.1.0", + "debug": "~3.1.0", + "engine.io-client": "~3.3.1", + "has-binary2": "~1.0.2", "has-cors": "1.1.0", "indexof": "0.0.1", "object-component": "0.0.3", "parseqs": "0.0.5", "parseuri": "0.0.5", - "socket.io-parser": "~3.1.1", + "socket.io-parser": "~3.3.0", "to-array": "0.1.4" }, "dependencies": { @@ -15470,25 +15456,16 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } } } }, "socket.io-parser": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.3.tgz", - "integrity": "sha512-g0a2HPqLguqAczs3dMECuA1RgoGFPyvDqcbaDEdCWY9g59kdUAz3YRmaJBNKXflrHNwB7Q12Gkf/0CZXfdHR7g==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz", + "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==", "requires": { "component-emitter": "1.2.1", "debug": "~3.1.0", - "has-binary2": "~1.0.2", "isarray": "2.0.1" }, "dependencies": { @@ -15887,12 +15864,6 @@ "safe-buffer": "~5.1.0" } }, - "stringstream": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", - "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==", - "optional": true - }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -16018,9 +15989,9 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, "systemjs": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/systemjs/-/systemjs-6.0.0.tgz", - "integrity": "sha512-Dc7TBShsS8baQKZBsh+bOZOHPS82V7IQWQ6IxR+HCWz4+jN2WSQjj+LIS2KbX0tdB2QAG/isSexgFFk40Z+UYg==" + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/systemjs/-/systemjs-6.5.0.tgz", + "integrity": "sha512-B+NzKJD1srC/URfNVBdDExAUAsAVXpVQxZxX54AtqU0xiK9imkqurQu3qi6JdyA2GBAw2ssjolYIa7kh+xY1uw==" }, "tar": { "version": "4.4.13", @@ -16275,13 +16246,12 @@ } }, "tus-js-client": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/tus-js-client/-/tus-js-client-1.8.0.tgz", - "integrity": "sha512-qPX3TywqzxocTxUZtcS8X7Aik72SVMa0jKi4hWyfvRV+s9raVzzYGaP4MoJGaF0yOgm2+b6jXaVEHogxcJ8LGw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tus-js-client/-/tus-js-client-2.1.1.tgz", + "integrity": "sha512-ILpgHlR0nfKxmlkXfrZ2z61upkHEXhADOGbGyvXSPjp7bn1NhU50p/Mu59q577Xirayr9vlW4tmoFqUrHKcWeQ==", "requires": { "buffer-from": "^0.1.1", "combine-errors": "^3.0.3", - "extend": "^3.0.2", "js-base64": "^2.4.9", "lodash.throttle": "^4.1.1", "proper-lockfile": "^2.0.1", @@ -16344,17 +16314,23 @@ "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==" }, "typescript-require": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/typescript-require/-/typescript-require-0.2.10.tgz", - "integrity": "sha1-jI7iqnXzUwtWC4ScKSfNNpfrpo4=", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/typescript-require/-/typescript-require-0.3.0.tgz", + "integrity": "sha512-mWHox/6LkZFeQyC0eXQFGbc8XZCZqSulyi5KUNfbJJIpFJnYBnJO9RELlGt9rFlCtfauxou+JcOMqeisBws92A==", "requires": { - "typescript": "^1.5.3" + "@types/node": "^14.0.23", + "typescript": "^3.9.6" }, "dependencies": { + "@types/node": { + "version": "14.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.0.tgz", + "integrity": "sha512-mikldZQitV94akrc4sCcSjtJfsTKt4p+e/s0AGscVA6XArQ9kFclP+ZiYUMnq987rc6QlYxXv/EivqlfSLxpKA==" + }, "typescript": { - "version": "1.8.10", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-1.8.10.tgz", - "integrity": "sha1-tHXW4N/wv1DyluXKbvn7tccyDx4=" + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==" } } }, @@ -16395,11 +16371,6 @@ "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz", "integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=" }, - "ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" - }, "unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", @@ -16673,12 +16644,6 @@ } } }, - "uws": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/uws/-/uws-9.14.0.tgz", - "integrity": "sha512-HNMztPP5A1sKuVFmdZ6BPVpBQd5bUjNC8EFMFiICK+oho/OQsAJy5hnIx4btMHiOk8j04f/DbIlqnEZ9d72dqg==", - "optional": true - }, "v8flags": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz", @@ -16880,7 +16845,8 @@ "websocket-extensions": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true }, "whatwg-encoding": { "version": "1.0.5", @@ -17498,9 +17464,19 @@ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" }, "zone.js": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.9.1.tgz", - "integrity": "sha512-GkPiJL8jifSrKReKaTZ5jkhrMEgXbXYC+IPo1iquBjayRa0q86w3Dipjn8b415jpitMExe9lV8iTsv8tk3DGag==" + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.1.tgz", + "integrity": "sha512-KcZawpmVgS+3U2rzKTM6fLKaCX1QDv3//NxiSOOsqpQY/r5hl+xpYikPwY93Sp7CAB+J5mZJpb/YubxEYLGK5g==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==" + } + } } } } diff --git a/package.json b/package.json index f88e7cadd8..96c4fab73f 100644 --- a/package.json +++ b/package.json @@ -5,65 +5,65 @@ "keywords": [], "dependencies": { "@sailshq/upgrade": "^1.0.9", - "@uppy/core": "^0.29.0", - "@uppy/dashboard": "^0.29.0", - "@uppy/tus": "^0.29.0", + "@uppy/core": "^1.13.1", + "@uppy/dashboard": "^1.12.5", + "@uppy/tus": "^1.7.5", "async": "3.2.0", - "axios": "^0.19.0", + "axios": "^0.20.0", "bcrypt": "5.0.0", - "bootstrap-sass": "^3.3.7", - "calcyte": "^1.0.2", + "bootstrap-sass": "^3.4.1", + "calcyte": "^1.0.6", "chokidar": "~3.4.2", "connect-mongo": "3.2.0", - "core-js": "^3.0.1", - "datacrate": "^1.0.4", + "core-js": "^3.6.5", + "datacrate": "^1.0.12", "dotenv": "^8.2.0", - "ejs": "2.6.1", + "ejs": "3.1.5", "flat": "^5.0.2", "font-awesome-sass": "4.7.0", - "fs-extra": "^7.0.0", - "grunt-ts": "^6.0.0-beta.19", + "fs-extra": "^9.0.1", + "grunt-ts": "^6.0.0-beta.22", "har-validator": "5.1.5", "i18next": "19.7.0", - "i18next-node-fs-backend": "^2.1.1", + "i18next-node-fs-backend": "^2.1.3", "include-all": "^4.0.3", - "leaflet": "^1.3.1", - "leaflet-draw": "^1.0.2", + "leaflet": "^1.6.0", + "leaflet-draw": "^1.0.4", "lodash": "^4.17.20", - "lodash-es": "^4.17.5", + "lodash-es": "^4.17.15", "lucene-escape-query": "^1.0.1", "moment": "^2.27.0", - "ng2-completer": "2.0.8", - "ng2-datetime": "^1.3.2", + "ng2-completer": "9.0.1", + "ng2-datetime": "^1.4.0", "ngx-bootstrap": "^5.6.1", - "node-cache": "^4.1.1", - "node-schedule": "^1.3.0", + "node-cache": "^5.1.2", + "node-schedule": "^1.3.2", "numeral": "^2.0.6", - "passport": "^0.4.0", + "passport": "^0.4.1", "passport-http-bearer": "^1.0.1", "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", "passport-openidconnect": "0.0.2", "rc": "1.2.8", - "redux": "4.0.1", + "redux": "4.0.5", "request-promise": "^4.2.6", - "rxjs": "6.5.3", + "rxjs": "6.6.2", "rxjs-compat": "6.6.2", "sails": "^1.2.5", "sails-hook-autoreload": "^1.1.0", - "sails-hook-grunt": "^3.1.0", + "sails-hook-grunt": "^4.0.1", "sails-hook-orm": "^3.0.1", - "sails-hook-sockets": "^1.5.2", - "sails-mongo": "^1.0.0", + "sails-hook-sockets": "^2.0.0", + "sails-mongo": "^1.2.0", "skipper-gridfs": "^1.0.2", - "systemjs": "6.0.0", + "systemjs": "6.5.0", "ts-node": "^9.0.0", "ts-smart-logger": "0.1.0", "tus-node-server": "^0.3.2", "typescript": "4.0", - "typescript-require": "~0.2.9-1", + "typescript-require": "~0.3.0", "url-pattern": "^1.0.3", - "zone.js": "^0.9.0" + "zone.js": "^0.11.1" }, "scripts": { "debug": "node debug app.js", @@ -81,13 +81,13 @@ "author": "QCIF Engineering", "license": "GPL2", "devDependencies": { - "@compodoc/compodoc": "^1.1.6", + "@compodoc/compodoc": "^1.1.11", "@types/jquery": "^3.5.1", "@types/leaflet": "^1.5.17", "@types/leaflet-draw": "^1.0.3", - "@types/lodash-es": "^4.14.5", - "bower": "^1.8.0", - "chai": "^4.1.0", + "@types/lodash-es": "^4.17.3", + "bower": "^1.8.8", + "chai": "^4.2.0", "ejs-cli": "^2.2.1", "grunt": "^1.3.0", "grunt-contrib-cssmin": "^3.0.0", From 3de3698757b74442eca0dc11f94773a48dcf60d4 Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Thu, 27 Aug 2020 16:36:59 +0930 Subject: [PATCH 11/48] Updated CacheService to support new node-cache library --- typescript/api/services/CacheService.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/typescript/api/services/CacheService.ts b/typescript/api/services/CacheService.ts index b10583c41d..1995835236 100644 --- a/typescript/api/services/CacheService.ts +++ b/typescript/api/services/CacheService.ts @@ -51,7 +51,7 @@ export module Services { } public get(name): Observable { - const cacheGet = Observable.bindNodeCallback(this.cache.get)(name); + const cacheGet = Observable.of(this.cache.get(name)); return cacheGet.flatMap(data => { if (data) { return Observable.of(data); @@ -74,6 +74,8 @@ export module Services { }); } }); + + } public set(name, data, expiry=sails.config.custom_cache.cacheExpiry) { From 9a835456d2a80b07a4f47b221cc3d93282470353 Mon Sep 17 00:00:00 2001 From: Shilo B Date: Mon, 31 Aug 2020 15:39:42 +1000 Subject: [PATCH 12/48] Preparing services for storage plugin support. --- typescript/api/services/ConfigService.ts | 23 +++++++++++++++++++---- typescript/api/services/RecordsService.ts | 5 +++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/typescript/api/services/ConfigService.ts b/typescript/api/services/ConfigService.ts index 76c7fd7507..02bf3fc0db 100644 --- a/typescript/api/services/ConfigService.ts +++ b/typescript/api/services/ConfigService.ts @@ -51,7 +51,13 @@ export module Services { } public mergeHookConfig(hookName:string, configMap:any=sails.config, config_dirs: string[] = ["form-config", "config"], dontMergeFields:any[] = ["fields"]) { - const hook_root_dir = `${sails.config.appPath}/node_modules/${hookName}`; + var hook_root_dir = `${sails.config.appPath}/node_modules/${hookName}`; + var appPath = sails.config.appPath; + // check if the app path was launched from the hook directory, e.g. when launching tests. + if (!fs.pathExistsSync(hook_root_dir) && _.endsWith(sails.config.appPath, hookName)) { + hook_root_dir = sails.config.appPath; + appPath = appPath.substring(0, appPath.lastIndexOf(`/node_modules/${hookName}`)); + } const hook_log_header = hookName; let origDontMerge = _.clone(dontMergeFields); const concatArrsFn = function (objValue, srcValue, key) { @@ -116,15 +122,15 @@ export module Services { } // check if the core exists when API definitions are present ... if (fs.pathExistsSync(`${hook_root_dir}/api`) && !fs.pathExistsSync(`${hook_root_dir}/api/core`)) { - sails.log.verbose(`${hook_log_header}::Adding Symlink to API core...`); + sails.log.verbose(`${hook_log_header}::Adding Symlink to API core... ${hook_root_dir}/api/core -> ${appPath}/api/core`); // create core services symlink if not present - fs.ensureSymlinkSync(`${sails.config.appPath}/api/core`, `${hook_root_dir}/api/core`); + fs.ensureSymlinkSync(`${appPath}/api/core`, `${hook_root_dir}/api/core`); } sails.log.verbose(`${hook_log_header}::Adding custom API elements...`); let apiDirs = ["services", "controllers"]; _.each(apiDirs, (apiType) => { const files = this.walkDirSync(`${hook_root_dir}/api/${apiType}`, []); - sails.log.verbose(`${hook_log_header}::Processing:`); + sails.log.verbose(`${hook_log_header}::Processing '${apiType}':`); sails.log.verbose(JSON.stringify(files)); if (!_.isEmpty(files)) { _.each(files, (file) => { @@ -135,6 +141,15 @@ export module Services { }); } }); + // for models, we need to copy them over to `api/models`... + const modelFiles = this.walkDirSync(`${hook_root_dir}/api/models`, []); + if (!_.isEmpty(modelFiles)) { + _.each(modelFiles, (modelFile) => { + const dest = `${appPath}/api/models/${basename(modelFile)}`; + sails.log.verbose(`Copying ${modelFile} to ${dest}`) + fs.copySync(modelFile, `${appPath}/api/models/${dest}`) + }); + } sails.log.verbose(`${hook_log_header}::Adding custom API elements...completed.`); sails.log.verbose(`${hookName}::Merge complete.`); } diff --git a/typescript/api/services/RecordsService.ts b/typescript/api/services/RecordsService.ts index 8b0c356f75..ced9ba98b5 100644 --- a/typescript/api/services/RecordsService.ts +++ b/typescript/api/services/RecordsService.ts @@ -103,6 +103,11 @@ export module Services { } public async checkRedboxRunning(): Promise { + // check if a valid storage plugin is loaded.... + if (!_.isEmpty(sails.config.storage)) { + sails.log.info("ReDBox storage plugin is active!"); + return true; + } let retries = 1000; for(let i =0; i< retries; i++) { try { From 4a2888a6b987671c81d52cd60191dfd2d4c74bfc Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Wed, 2 Sep 2020 11:44:37 +0930 Subject: [PATCH 13/48] Added interfaces for RecordsService and SearchService. Completed refactor of Controllers to take advantage of typed services --- package-lock.json | 144 +++-- package.json | 2 +- .../api/controllers/RecordController.ts | 515 +++++++++++------- .../webservice/RecordController.ts | 57 +- typescript/api/core/RecordsService.ts | 29 + typescript/api/core/SearchService.ts | 8 + typescript/api/services/RecordsService.ts | 86 +-- 7 files changed, 495 insertions(+), 346 deletions(-) create mode 100644 typescript/api/core/RecordsService.ts create mode 100644 typescript/api/core/SearchService.ts diff --git a/package-lock.json b/package-lock.json index 4c125f99e5..92a32d0598 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2444,9 +2444,9 @@ } }, "csv-parse": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.10.1.tgz", - "integrity": "sha512-gdDJVchi0oSLIcYXz1H/VSgLE6htHDqJyFsRU/vTkQgmVOZ3S0IR2LXnNbWUYG7VD76dYVwdfBLyx8AX9+An8A==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.12.0.tgz", + "integrity": "sha512-wPQl3H79vWLPI8cgKFcQXl0NBgYYEqVnT1i6/So7OjMpsI540oD7p93r3w6fDSyPvwkTepG05F69/7AViX2lXg==", "dev": true }, "cuid": { @@ -3259,9 +3259,9 @@ "dev": true }, "eventemitter3": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", - "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "dev": true }, "events": { @@ -3648,9 +3648,9 @@ "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" }, "faker": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/faker/-/faker-4.1.0.tgz", - "integrity": "sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/faker/-/faker-5.1.0.tgz", + "integrity": "sha512-RrWKFSSA/aNLP0g3o2WW1Zez7/MnMr7xkiZmoCfAGZmdkDQZ6l2KtuXHN5XjdvpRjDl8+3vf+Rrtl06Z352+Mw==", "dev": true }, "falafel": { @@ -8213,9 +8213,9 @@ "dev": true }, "newman": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/newman/-/newman-5.1.2.tgz", - "integrity": "sha512-4lojj/MCxK51IJdk1Gfh4yaiy+Hzp89NUUSBRJb8nr+4cYGW+Df8FU7MVk8nt2Fz2J40Lj02Nj6B9Lh3WpPZrg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/newman/-/newman-5.2.0.tgz", + "integrity": "sha512-VzvKHhdPM7QvuwQfnJMj50hHLf27AE9hCAbMgbP5aXIQ12eKhrL3if1U0OWcB+BvSLdww9nKvMDwvuF72/ipFw==", "dev": true, "requires": { "async": "3.2.0", @@ -8223,19 +8223,20 @@ "cli-progress": "3.8.2", "cli-table3": "0.6.0", "colors": "1.4.0", - "commander": "5.1.0", - "csv-parse": "4.10.1", - "eventemitter3": "4.0.4", + "commander": "6.1.0", + "csv-parse": "4.12.0", + "eventemitter3": "4.0.7", "filesize": "6.1.0", - "lodash": "4.17.19", + "lodash": "4.17.20", "mkdirp": "1.0.4", - "postman-collection": "3.6.4", + "postman-collection": "3.6.6", "postman-collection-transformer": "3.3.3", - "postman-request": "2.88.1-postman.23", - "postman-runtime": "7.26.2", + "postman-request": "2.88.1-postman.24", + "postman-runtime": "7.26.5", "pretty-ms": "7.0.0", "semver": "7.3.2", "serialised-error": "1.1.3", + "tough-cookie": "3.0.1", "word-wrap": "1.2.3", "xmlbuilder": "15.1.1" }, @@ -8247,9 +8248,9 @@ "dev": true }, "commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.1.0.tgz", + "integrity": "sha512-wl7PNrYWd2y5mp1OK/LhTlv8Ff4kQJQRXXAvF+uU/TPNiVJUxZLRYGj/B0y/lPGAVcSbJqH2Za/cvHmrPMC8mA==", "dev": true }, "filesize": { @@ -8258,12 +8259,6 @@ "integrity": "sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg==", "dev": true }, - "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", - "dev": true - }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -8276,6 +8271,17 @@ "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "dev": true }, + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "dev": true, + "requires": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, "xmlbuilder": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", @@ -9286,22 +9292,22 @@ } }, "postman-collection": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-3.6.4.tgz", - "integrity": "sha512-chnMai59BIPLk/QEGrqn5o+38whjeL+LFn40fW0pUZxxFbyhSLjt315nZ9NAYSKxkR9UZ4elZX0PHt6jCZqyeA==", + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-3.6.6.tgz", + "integrity": "sha512-fm9AGKHbL2coSzD5nw+F07JrX7jzqu2doGIXevPPrwlpTZyTM6yagEdENeO/Na8rSUrI1+tKPj+TgAFiLvtF4w==", "dev": true, "requires": { "escape-html": "1.0.3", - "faker": "4.1.0", + "faker": "5.1.0", "file-type": "3.9.0", "http-reasons": "0.1.0", "iconv-lite": "0.6.2", "liquid-json": "0.3.1", - "lodash": "4.17.19", - "marked": "1.1.0", + "lodash": "4.17.20", + "marked": "1.1.1", "mime-format": "2.0.0", "mime-types": "2.1.27", - "postman-url-encoder": "2.1.2", + "postman-url-encoder": "2.1.3", "sanitize-html": "1.20.1", "semver": "7.3.2", "uuid": "3.4.0" @@ -9316,16 +9322,10 @@ "safer-buffer": ">= 2.1.2 < 3.0.0" } }, - "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", - "dev": true - }, "marked": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-1.1.0.tgz", - "integrity": "sha512-EkE7RW6KcXfMHy2PA7Jg0YJE1l8UPEZE8k45tylzmZM30/r1M1MUXWQfJlrSbsTeh7m/XTwHbWUENvAJZpp1YA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.1.1.tgz", + "integrity": "sha512-mJzT8D2yPxoPh7h0UXkB+dBj4FykPJ2OIfxAWeIHrvoHDkFxukV/29QxoFQoPM6RLEwhIFdJpmKBlqVM3s2ZIw==", "dev": true }, "semver": { @@ -9377,9 +9377,9 @@ } }, "postman-request": { - "version": "2.88.1-postman.23", - "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.23.tgz", - "integrity": "sha512-ftqsjGCGKjk23c+gy85aw1Ubs1MIsULhkZ5D9IMuKP8jiGJXW7avNk9jMVfFcyONayvVllfyJugPHZydbt1baA==", + "version": "2.88.1-postman.24", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.24.tgz", + "integrity": "sha512-afW2QxA9YCSaMUBFGRWvxnyjN4SqgXC5HqKJ0DFNfbx4ZW6AsBCFXeb5NAFgCH3kZ/og0XhUSDV+imjWwahLLg==", "dev": true, "requires": { "@postman/form-data": "~3.1.0", @@ -9420,26 +9420,26 @@ } }, "postman-runtime": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/postman-runtime/-/postman-runtime-7.26.2.tgz", - "integrity": "sha512-0KJ2oRbEhxYNq+n1d58+7zTq/46ASVW/j6J0gvQMX7vjaLX9BE4wwt1m04it326psAQdMkRd8hWDLKaxTIgk/Q==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/postman-runtime/-/postman-runtime-7.26.5.tgz", + "integrity": "sha512-5sY4iWjG4GeGQ5o4QAJkAt79jmcZhOrs3XSbtCLrs4NqoZpTBx5WDQr6h1Dy4Y8Q0X/9NMCYqF/c5pnsqUyDCQ==", "dev": true, "requires": { "async": "2.6.3", - "aws4": "1.10.0", - "eventemitter3": "4.0.4", + "aws4": "1.10.1", + "eventemitter3": "4.0.7", "handlebars": "4.7.6", "http-reasons": "0.1.0", "httpntlm": "1.7.6", "inherits": "2.0.4", "js-sha512": "0.8.0", - "lodash": "4.17.19", + "lodash": "4.17.20", "node-oauth1": "1.3.0", "performance-now": "2.1.0", - "postman-collection": "3.6.4", - "postman-request": "2.88.1-postman.23", - "postman-sandbox": "3.5.7", - "postman-url-encoder": "2.1.2", + "postman-collection": "3.6.6", + "postman-request": "2.88.1-postman.24", + "postman-sandbox": "3.5.9", + "postman-url-encoder": "2.1.3", "resolve-from": "5.0.0", "serialised-error": "1.1.3", "tough-cookie": "3.0.1", @@ -9455,10 +9455,10 @@ "lodash": "^4.17.14" } }, - "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "aws4": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", + "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==", "dev": true }, "resolve-from": { @@ -9481,25 +9481,19 @@ } }, "postman-sandbox": { - "version": "3.5.7", - "resolved": "https://registry.npmjs.org/postman-sandbox/-/postman-sandbox-3.5.7.tgz", - "integrity": "sha512-+fUyy4uQ4GnYu+UB4zasLVZEosoxlYlqzndaP1iJoF09nMy/G9n5xPkveICC1++Q1Ydfv521bWxsPIujX4qtBg==", + "version": "3.5.9", + "resolved": "https://registry.npmjs.org/postman-sandbox/-/postman-sandbox-3.5.9.tgz", + "integrity": "sha512-B9mREFulQuYOa9+B7rklb94d9iZ6EYyhsUvdIfxphGUByimb6mOhumWV0sGbrtxVTsCAtTpN/68Shm7NCjrZ0A==", "dev": true, "requires": { "inherits": "2.0.4", - "lodash": "4.17.19", + "lodash": "4.17.20", "teleport-javascript": "1.0.0", "tough-cookie": "3.0.1", "uuid": "3.4.0", "uvm": "1.7.9" }, "dependencies": { - "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", - "dev": true - }, "tough-cookie": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", @@ -9514,12 +9508,12 @@ } }, "postman-url-encoder": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/postman-url-encoder/-/postman-url-encoder-2.1.2.tgz", - "integrity": "sha512-/ECLaAQPVOlYyrWj5wzj2S0Ke+kXAXCDzvmixit4mBlNy2R8VaHUdNh6M7kRDT0td7bDRhQc+PCHzNME0+7VNA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/postman-url-encoder/-/postman-url-encoder-2.1.3.tgz", + "integrity": "sha512-CwQjnoxaugCGeOyzVeZ4k1cNQ6iS8OBCzuWzcf4kLStKeRp0MwmLKYv25frynmDpugUUimq/d+FZCq6GtIX9Ag==", "dev": true, "requires": { - "postman-collection": "^3.6.3", + "postman-collection": "^3.6.4", "punycode": "^2.1.1" } }, diff --git a/package.json b/package.json index 96c4fab73f..15776bb3d0 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "grunt-shell": "^3.0.1", "istanbul": "^0.4.5", "mocha": "^8.1.2", - "newman": "^5.1.2", + "newman": "^5.2.0", "node-sass": "4.14.1", "supertest": "^4.0.2", "uglify-es": "^3.3.10" diff --git a/typescript/api/controllers/RecordController.ts b/typescript/api/controllers/RecordController.ts index cfd2dce9e9..2bbcf04d53 100644 --- a/typescript/api/controllers/RecordController.ts +++ b/typescript/api/controllers/RecordController.ts @@ -20,18 +20,23 @@ // declare var module; declare var sails; -import { Observable } from 'rxjs/Rx'; +import { + Observable +} from 'rxjs/Rx'; import moment = require('moment'); import * as tus from 'tus-node-server'; import * as fs from 'fs'; import * as url from 'url'; declare var _; -declare var FormsService, RecordsService, WorkflowStepsService, BrandingService, RecordTypesService, TranslationService, User, UsersService, EmailService, RolesService; +declare var FormsService, WorkflowStepsService, BrandingService, RecordTypesService, TranslationService, User, UsersService, EmailService, RolesService; /** * Package that contains all Controllers. */ import controller = require('../core/CoreController.js'); +import RecordsService from '../core/RecordsService.js'; +import SearchService from '../core/SearchService.js'; +import DatastreamService from '../core/DatastreamService.js'; export module Controllers { /** * Responsible for all things related to a Record, includings Forms, etc. @@ -40,6 +45,21 @@ export module Controllers { */ export class Record extends controller.Controllers.Core.Controller { + RecordsService: RecordsService = sails.services.recordsservice; + SearchService: SearchService = sails.services.recordsservice; + DatastreamService: DatastreamService = sails.services.recordsservice; + + constructor() { + super(); + let datastreamServiceName = sails.config.record.datastreamService; + if (datastreamServiceName != undefined) { + this.DatastreamService = sails.services[datastreamServiceName]; + } + + } + + + /** * Exported methods, accessible from internet. */ @@ -71,19 +91,20 @@ export module Controllers { ************************************************************************************************** */ - public bootstrap() { - } + public bootstrap() {} public getMeta(req, res) { const brand = BrandingService.getBrand(req.session.branding); const oid = req.param('oid') ? req.param('oid') : ''; - var obs = RecordsService.getMeta(oid); + var obs = Observable.fromPromise(this.RecordsService.getMeta(oid)); return obs.subscribe(record => { this.hasViewAccess(brand, req.user, record).subscribe(hasViewAccess => { if (hasViewAccess) { return res.json(record.metadata); } else { - return res.json({ status: "Access Denied" }); + return res.json({ + status: "Access Denied" + }); } }); @@ -104,10 +125,16 @@ export module Controllers { appSelector = form['customAngularApp']['appSelector']; appName = form['customAngularApp']['appName']; } - return this.sendView(req, res, 'record/edit', { oid: oid, rdmp: rdmp, recordType: recordType, appSelector: appSelector, appName: appName }); + return this.sendView(req, res, 'record/edit', { + oid: oid, + rdmp: rdmp, + recordType: recordType, + appSelector: appSelector, + appName: appName + }); }); } else { - RecordsService.getMeta(oid).flatMap(record => { + Observable.fromPromise(this.RecordsService.getMeta(oid)).flatMap(record => { const formName = record.metaMetadata.form; return FormsService.getFormByName(formName, true); }).subscribe(form => { @@ -116,24 +143,36 @@ export module Controllers { appSelector = form['customAngularApp']['appSelector']; appName = form['customAngularApp']['appName']; } - return this.sendView(req, res, 'record/edit', { oid: oid, rdmp: rdmp, recordType: recordType, appSelector: appSelector, appName: appName }); + return this.sendView(req, res, 'record/edit', { + oid: oid, + rdmp: rdmp, + recordType: recordType, + appSelector: appSelector, + appName: appName + }); }, error => { - return this.sendView(req, res, 'record/edit', { oid: oid, rdmp: rdmp, recordType: recordType, appSelector: appSelector, appName: appName }); + return this.sendView(req, res, 'record/edit', { + oid: oid, + rdmp: rdmp, + recordType: recordType, + appSelector: appSelector, + appName: appName + }); }); } } - protected hasEditAccess(brand, user, currentRec) { + protected hasEditAccess(brand, user, currentRec): Observable < boolean > { sails.log.verbose("Current Record: "); sails.log.verbose(currentRec); - return Observable.of(RecordsService.hasEditAccess(brand, user, user.roles, currentRec)); + return Observable.of(this.RecordsService.hasEditAccess(brand, user, user.roles, currentRec)); } protected hasViewAccess(brand, user, currentRec) { sails.log.verbose("Current Record: "); sails.log.verbose(currentRec); - return Observable.of(RecordsService.hasViewAccess(brand, user, user.roles, currentRec)); + return Observable.of(this.RecordsService.hasViewAccess(brand, user, user.roles, currentRec)); } public getTransferResponsibilityConfig(req, res) { @@ -199,9 +238,9 @@ export module Controllers { // First: check if this user has edit access to this record, we don't want Gremlins sneaking in on us // Not trusting what was posted, retrieving from DB... this.getRecord(rec.oid).subscribe(recObj => { - if (RecordsService.hasEditAccess(brand, user, user.roles, recObj)) { + if (this.RecordsService.hasEditAccess(brand, user, user.roles, recObj)) { let recType = rec.metadata.metaMetadata.type; - let relatedRecords = RecordsService.getRelatedRecords(rec.oid, brand); + let relatedRecords = this.RecordsService.getRelatedRecords(rec.oid, brand); relatedRecords.then(relatedRecords => { @@ -212,7 +251,9 @@ export module Controllers { //If there are no relationships, the record isn't related to any others so manually inject the info needed to have this record processed if (relationships.indexOf(recType) == -1) { relationships.push(recType); - relatedObjects[recType] = [{ redboxOid: rec.oid }]; + relatedObjects[recType] = [{ + redboxOid: rec.oid + }]; } let relationshipCount = 0; _.each(relationships, relationship => { @@ -236,7 +277,7 @@ export module Controllers { sails.log.verbose(`Updating record ${oid}`); sails.log.verbose(JSON.stringify(record)); - RecordsService.updateMeta(brand, oid, record).subscribe(response => { + Observable.fromPromise(this.RecordsService.updateMeta(brand, oid, record)).subscribe(response => { relationshipObjectCount++; if (response && response.code == "200") { if (oid == rec.oid) { @@ -253,7 +294,10 @@ export module Controllers { if (relationshipCount == relationships.length && relationshipObjectCount == relationshipObjects.length) { - completeRecordSet.push({ success: true, record: record }); + completeRecordSet.push({ + success: true, + record: record + }); if (completeRecordSet.length == records.length) { if (hasError) { return this.ajaxFail(req, res, null, completeRecordSet); @@ -272,7 +316,11 @@ export module Controllers { sails.log.error(`Failed to update authorization:`); sails.log.error(response); hasError = true; - completeRecordSet.push({ success: false, error: response, record: record }); + completeRecordSet.push({ + success: false, + error: response, + record: record + }); if (completeRecordSet.length == records.length) { if (hasError) { return this.ajaxFail(req, res, null, completeRecordSet); @@ -285,7 +333,11 @@ export module Controllers { sails.log.error("Error updating auth:"); sails.log.error(error); hasError = true; - completeRecordSet.push({ success: false, error: error.message, record: record }); + completeRecordSet.push({ + success: false, + error: error.message, + record: record + }); if (completeRecordSet.length == records.length) { if (hasError) { return this.ajaxFail(req, res, null, completeRecordSet); @@ -304,7 +356,11 @@ export module Controllers { } else { const errorMsg = `Attempted to transfer responsibilities, but user: '${user.username}' has no access to record: ${rec.oid}`; sails.log.error(errorMsg); - completeRecordSet.push({ success: false, error: errorMsg, record: rec }); + completeRecordSet.push({ + success: false, + error: errorMsg, + record: rec + }); // send response in case failures occur in the last entry of the array if (completeRecordSet.length == records.length) { if (hasError) { @@ -343,7 +399,7 @@ export module Controllers { }); } else { // defaults to retrive the form of the current workflow state... - obs = RecordsService.getMeta(oid).flatMap(currentRec => { + obs = Observable.fromPromise(this.RecordsService.getMeta(oid)).flatMap(currentRec => { if (_.isEmpty(currentRec)) { return Observable.throw(new Error(`Error, empty metadata for OID: ${oid}`)); } @@ -395,7 +451,9 @@ export module Controllers { if (!_.isEmpty(form)) { this.ajaxOk(req, res, null, form); } else { - this.ajaxFail(req, res, null, { message: `Failed to get form with name:${name}` }); + this.ajaxFail(req, res, null, { + message: `Failed to get form with name:${name}` + }); } }, error => { sails.log.error("Error getting form definition:"); @@ -410,12 +468,22 @@ export module Controllers { } public create(req, res) { + + this.createInternal(req,res).then(result => {}); + } + + private async createInternal(req,res) { const brand = BrandingService.getBrand(req.session.branding); const metadata = req.body; - let record: any = { metaMetadata: {} }; + let record: any = { + metaMetadata: {} + }; var recType = req.param('recordType'); const targetStep = req.param('targetStep'); - record.authorization = { view: [req.user.username], edit: [req.user.username] }; + record.authorization = { + view: [req.user.username], + edit: [req.user.username] + }; record.metaMetadata.brandId = brand.id; record.metaMetadata.createdBy = req.user.username; record.metaMetadata.createdOn = moment().format(); @@ -424,86 +492,86 @@ export module Controllers { record.metaMetadata.type = recType; record.metadata = metadata; - RecordTypesService.get(brand, recType).subscribe(recordType => { - if(recordType.packageName) { + let recordType = await RecordTypesService.get(brand, recType).toPromise(); + + if (recordType.packageName) { record.metaMetadata.packageName = recordType.packageName; } - let wfStepObs = WorkflowStepsService.getFirst(recordType); + let wfStep = await WorkflowStepsService.getFirst(recordType).toPromise(); if (targetStep) { - wfStepObs = WorkflowStepsService.get(recType, targetStep); + wfStep = await WorkflowStepsService.get(recType, targetStep).toPromise(); } - wfStepObs.subscribe(wfStep => { - RecordsService.updateWorkflowStep(record, wfStep); + try{ + this.RecordsService.updateWorkflowStep(record, wfStep); return this.createRecord(record, brand, recordType, req, res); - }, error => { + }catch (error) { this.ajaxFail(req, res, `Failed to save record: ${error}`); - }); - }); + } + } - private createRecord(record, brand, recordType, req, res) { + private async createRecord(record, brand, recordType, req, res) { const user = req.user; let formDef = null; let oid = null; const fieldsToCheck = ['location', 'uploadUrl']; - FormsService.getFormByName(record.metaMetadata.form, true) - .flatMap((form) => { - formDef = form; - record.metaMetadata.attachmentFields = form.attachmentFields; - return Observable.fromPromise(RecordsService.create(brand, record, recordType, user)); - }) - .flatMap(response => { - if (response && response.code == "200") { - response.success = true; - oid = response.oid; - if (!_.isEmpty(record.metaMetadata.attachmentFields)) { - // check if we have any pending-oid elements - _.each(record.metaMetadata.attachmentFields, (attFieldName) => { - _.each(_.get(record.metadata, attFieldName), (attFieldEntry, attFieldIdx) => { - if (!_.isEmpty(attFieldEntry)) { - _.each(fieldsToCheck, (fldName) => { - const fldVal = _.get(attFieldEntry, fldName); - if (!_.isEmpty(fldVal)) { - _.set(record.metadata, `${attFieldName}[${attFieldIdx}].${fldName}`, _.replace(fldVal, 'pending-oid', oid)); - } - }); - } + let form = await FormsService.getFormByName(record.metaMetadata.form, true).toPromise(); + + formDef = form; + record.metaMetadata.attachmentFields = form.attachmentFields; + let response = await this.RecordsService.create(brand, record, recordType, user); + + let updateResponse = response; + if (response && response.code == "200") { + response.success = true; + oid = response.oid; + if (!_.isEmpty(record.metaMetadata.attachmentFields)) { + // check if we have any pending-oid elements + _.each(record.metaMetadata.attachmentFields, (attFieldName) => { + _.each(_.get(record.metadata, attFieldName), (attFieldEntry, attFieldIdx) => { + if (!_.isEmpty(attFieldEntry)) { + _.each(fieldsToCheck, (fldName) => { + const fldVal = _.get(attFieldEntry, fldName); + if (!_.isEmpty(fldVal)) { + _.set(record.metadata, `${attFieldName}[${attFieldIdx}].${fldName}`, _.replace(fldVal, 'pending-oid', oid)); + } + }); + } + }); }); - }); - // update the metadata ... - return RecordsService.updateMeta(brand, oid, record, user, false, false); + // update the metadata ... + let updateResponse = await this.RecordsService.updateMeta(brand, oid, record, user, false, false); + } else { + // no need for update... return the creation response + let updateResponse = response; + } } else { - // no need for update... return the creation response - return Observable.of(response); + sails.log.error(`Failed to save record:`); + sails.log.error(JSON.stringify(response)); + // return the rsponse instead of throwing an exception + let updateResponse = response; } - } else { - sails.log.error(`Failed to save record:`); - sails.log.error(JSON.stringify(response)); - // return the rsponse instead of throwing an exception - return Observable.of(response); - } - }) - .subscribe(response => { - // handle datastream update - if (response && response.code == "200") { - if (!_.isEmpty(record.metaMetadata.attachmentFields)) { - // we emtpy the data locations in cloned record so we can reuse the same `this.updateDataStream` method call - const emptyDatastreamRecord = _.cloneDeep(record); - _.each(record.metaMetadata.attachmentFields, (attFieldName:any) => { - _.set(emptyDatastreamRecord.metadata, attFieldName, []); - }); - // update the datastreams in RB, this is a terminal call - return this.updateDataStream(oid, emptyDatastreamRecord, record.metadata, response, req, res); + try{ + // handle datastream update + if (updateResponse && updateResponse.code == "200") { + if (!_.isEmpty(record.metaMetadata.attachmentFields)) { + // we emtpy the data locations in cloned record so we can reuse the same `this.updateDataStream` method call + const emptyDatastreamRecord = _.cloneDeep(record); + _.each(record.metaMetadata.attachmentFields, (attFieldName: any) => { + _.set(emptyDatastreamRecord.metadata, attFieldName, []); + }); + // update the datastreams in RB, this is a terminal call + return this.updateDataStream(oid, emptyDatastreamRecord, record.metadata, response, req, res).toPromise(); + } else { + // terminate the request + this.ajaxOk(req, res, null, updateResponse); + } } else { - // terminate the request - this.ajaxOk(req, res, null, response); + this.ajaxFail(req, res, null, response); } - } else { - this.ajaxFail(req, res, null, response); + } catch(error){ + throw new Error(`Failed to save record: ${error}`); } - }, error => { - return Observable.throw(`Failed to save record: ${error}`) - }); } @@ -514,23 +582,30 @@ export module Controllers { let currentRec = null; let message = null; this.getRecord(oid).flatMap(cr => { - currentRec = cr; - return this.hasEditAccess(brand, user, currentRec); - }) + currentRec = cr; + return this.hasEditAccess(brand, user, currentRec); + }) .flatMap(hasEditAccess => { if (hasEditAccess) { - return RecordsService.delete(oid); + return Observable.fromPromise(this.RecordsService.delete(oid)); } message = TranslationService.t('edit-error-no-permissions'); return Observable.throw(new Error(TranslationService.t('edit-error-no-permissions'))); }) .subscribe(response => { if (response && response.code == "200") { - const resp = { success: true, oid: oid }; + const resp = { + success: true, + oid: oid + }; sails.log.verbose(`Successfully deleted: ${oid}`); this.ajaxOk(req, res, null, resp); } else { - this.ajaxFail(req, res, TranslationService.t('failed-delete'), { success: false, oid: oid, message: response.message }); + this.ajaxFail(req, res, TranslationService.t('failed-delete'), { + success: false, + oid: oid, + message: response.message + }); } }, error => { sails.log.error("Error deleting:"); @@ -538,14 +613,15 @@ export module Controllers { if (message == null) { message = error.message; } else - if (error.error && error.error.code == 500) { - message = TranslationService.t('missing-record'); - } + if (error.error && error.error.code == 500) { + message = TranslationService.t('missing-record'); + } this.ajaxFail(req, res, message); }); } public update(req, res) { + const brand = BrandingService.getBrand(req.session.branding); const metadata = req.body; const oid = req.param('oid'); @@ -555,10 +631,12 @@ export module Controllers { let origRecord = null; const failedAttachments = []; let recType = null; + + this.getRecord(oid).flatMap(cr => { - currentRec = cr; - return this.hasEditAccess(brand, user, currentRec); - }) + currentRec = cr; + return this.hasEditAccess(brand, user, currentRec); + }) .flatMap(hasEditAccess => { return RecordTypesService.get(brand, currentRec.metaMetadata.type) }).flatMap(recordType => { @@ -568,10 +646,11 @@ export module Controllers { } else { return Observable.of(null); } - }).flatMap(nextStep => { + }).flatMap(nextStepResp => { if (metadata.delete) { return Observable.of(currentRec); } + let nextStep:any = nextStepResp; let hasPermissionToTransition = true; if (nextStep != undefined) { if (nextStep.config != undefined) { @@ -594,7 +673,7 @@ export module Controllers { } } if (hasPermissionToTransition) { - RecordsService.updateWorkflowStep(currentRec, nextStep); + this.RecordsService.updateWorkflowStep(currentRec, nextStep); } origRecord = _.cloneDeep(currentRec); currentRec.metadata = metadata; @@ -603,7 +682,7 @@ export module Controllers { }).subscribe(record => { if (metadata.delete) { - RecordsService.delete(oid).subscribe(response => { + Observable.fromPromise(this.RecordsService.delete(oid)).subscribe(response => { if (response && response.code == "200") { response.success = true; sails.log.verbose(`Successfully deleted: ${oid}`); @@ -655,12 +734,12 @@ export module Controllers { protected updateDataStream(oid, origRecord, metadata, response, req, res) { const fileIdsAdded = []; let datastreamServiceName = sails.config.record.datastreamService; - if(datastreamServiceName == undefined) { - datastreamServiceName = "recordsservice"; - } - sails.log.verbose(`Updating datastream, using service: ${datastreamServiceName}`); - let datastreamService = sails.services[datastreamServiceName]; - return datastreamService.updateDatastream(oid, origRecord, metadata, sails.config.record.attachments.stageDir, fileIdsAdded) + if (datastreamServiceName == undefined) { + datastreamServiceName = "recordsservice"; + } + sails.log.verbose(`Updating datastream, using service: ${datastreamServiceName}`); + let datastreamService = sails.services[datastreamServiceName]; + return datastreamService.updateDatastream(oid, origRecord, metadata, sails.config.record.attachments.stageDir, fileIdsAdded) .concatMap(reqs => { if (reqs) { sails.log.verbose(`Updating data streams...`); @@ -702,19 +781,23 @@ export module Controllers { }); } - protected saveMetadata(brand, oid, currentRec, metadata, user): Observable { + protected saveMetadata(brand, oid, currentRec, metadata, user): Observable < any > { currentRec.metadata = metadata; return this.updateMetadata(brand, oid, currentRec, user); } - protected saveAuthorization(brand, oid, currentRec, authorization, user): Observable { - return this.hasEditAccess(brand, user, currentRec) - .flatMap(hasEditAccess => { + protected saveAuthorization(brand, oid, currentRec, authorization, user): Observable < any > { + let editAccessResp:Observable = this.hasEditAccess(brand, user, currentRec); + return editAccessResp + .map(hasEditAccess => { if (hasEditAccess) { currentRec.authorization = authorization; return this.updateAuthorization(brand, oid, currentRec, user); } else { - return { code: 403, message: "Not authorized to edit" }; + return { + code: 403, + message: "Not authorized to edit" + }; } }); } @@ -722,7 +805,7 @@ export module Controllers { protected getRecord(oid) { - return RecordsService.getMeta(oid).flatMap(currentRec => { + return Observable.fromPromise(this.RecordsService.getMeta(oid)).flatMap(currentRec => { if (_.isEmpty(currentRec)) { return Observable.throw(new Error(`Failed to update meta, cannot find existing record with oid: ${oid}`)); } @@ -736,16 +819,16 @@ export module Controllers { } currentRec.metaMetadata.lastSavedBy = user.username; currentRec.metaMetadata.lastSaveDate = moment().format(); - sails.log.verbose(`Calling record service...`); - sails.log.verbose(currentRec); - return RecordsService.updateMeta(brand, oid, currentRec, user); + sails.log.error(`Calling record service...`); + sails.log.error(currentRec); + return Observable.fromPromise(this.RecordsService.updateMeta(brand, oid, currentRec, user)); } protected updateAuthorization(brand, oid, currentRec, user) { if (currentRec.metaMetadata.brandId != brand.id) { return Observable.throw(new Error(`Failed to update meta, brand's don't match: ${currentRec.metaMetadata.brandId} != ${brand.id}, with oid: ${oid}`)); } - return RecordsService.updateMeta(brand, oid, currentRec, user); + return Observable.fromPromise(this.RecordsService.updateMeta(brand, oid, currentRec, user)); } public stepTo(req, res) { @@ -755,29 +838,30 @@ export module Controllers { const targetStep = req.param('targetStep'); let origRecord = null; return this.getRecord(oid).flatMap(currentRec => { - origRecord = _.cloneDeep(currentRec); - return this.hasEditAccess(brand, req.user, currentRec) - .flatMap(hasEditAccess => { - if (!hasEditAccess) { - return Observable.throw(new Error(TranslationService.t('edit-error-no-permissions'))); - } - return RecordTypesService.get(brand, origRecord.metaMetadata.type); - }) - .flatMap(recType => { - return WorkflowStepsService.get(recType, targetStep) - .flatMap(nextStep => { - currentRec.metadata = metadata; - sails.log.verbose("Current rec:"); - sails.log.verbose(currentRec); - sails.log.verbose("Next step:"); - sails.log.verbose(nextStep); - RecordsService.updateWorkflowStep(currentRec, nextStep); - return this.updateMetadata(brand, oid, currentRec, req.user); - }); - }) - }) + origRecord = _.cloneDeep(currentRec); + return this.hasEditAccess(brand, req.user, currentRec) + .flatMap(hasEditAccess => { + if (!hasEditAccess) { + return Observable.throw(new Error(TranslationService.t('edit-error-no-permissions'))); + } + return RecordTypesService.get(brand, origRecord.metaMetadata.type); + }) + .flatMap(recType => { + return WorkflowStepsService.get(recType, targetStep) + .flatMap(nextStep => { + currentRec.metadata = metadata; + sails.log.verbose("Current rec:"); + sails.log.verbose(currentRec); + sails.log.verbose("Next step:"); + sails.log.verbose(nextStep); + this.RecordsService.updateWorkflowStep(currentRec, nextStep); + return this.updateMetadata(brand, oid, currentRec, req.user); + }); + }) + }) .subscribe(response => { - return response.subscribe(response => { + let responseValue:Observable = response; + return responseValue.subscribe(response => { sails.log.error(response); if (response && response.code == "200") { response.success = true; @@ -805,7 +889,7 @@ export module Controllers { const fieldsToDelete = []; const metadata = currentRec.metadata; const metaMetadata = currentRec.metaMetadata; - _.forEach(fields, (field:any) => { + _.forEach(fields, (field: any) => { if (!_.isEmpty(field.definition.name) && !_.isUndefined(field.definition.name)) { if (_.has(metaMetadata, field.definition.name)) { field.definition.value = metaMetadata[field.definition.name]; @@ -854,11 +938,13 @@ export module Controllers { fld.definition.value = _.get(metadata, `${field.definition.name}.${fld.definition.name}`); }); } else - if (field.definition.fields) { - this.mergeFieldsSync(req, res, field.definition.fields, currentRec, workflowSteps); - } + if (field.definition.fields) { + this.mergeFieldsSync(req, res, field.definition.fields, currentRec, workflowSteps); + } + }); + _.remove(fields, (f) => { + return _.includes(fieldsToDelete, f); }); - _.remove(fields, (f) => { return _.includes(fieldsToDelete, f); }); } protected replaceCustomFields(req, res, field, metadata) { @@ -900,10 +986,10 @@ export module Controllers { } /** - * Not currently used as transfer responsibility is configured. - * Commenting out so we can reinstate it when more formal "edit permission" - * screens are implemented. - */ + * Not currently used as transfer responsibility is configured. + * Commenting out so we can reinstate it when more formal "edit permission" + * screens are implemented. + */ // public modifyEditors(req, res) { // const records = req.body.records; // var toUsername = req.body.username; @@ -974,14 +1060,20 @@ export module Controllers { const facetSearches = []; _.forEach(exactSearchNames, (exactSearch) => { - exactSearches.push({ name: exactSearch, value: req.query[`exact_${exactSearch}`] }); + exactSearches.push({ + name: exactSearch, + value: req.query[`exact_${exactSearch}`] + }); }); _.forEach(facetSearchNames, (facetSearch) => { - facetSearches.push({ name: facetSearch, value: req.query[`facet_${facetSearch}`] }); + facetSearches.push({ + name: facetSearch, + value: req.query[`facet_${facetSearch}`] + }); }); - RecordsService.searchFuzzy(type, workflow, searchString, exactSearches, facetSearches, brand, req.user, req.user.roles, sails.config.record.search.returnFields) + Observable.fromPromise(this.SearchService.searchFuzzy(type, workflow, searchString, exactSearches, facetSearches, brand, req.user, req.user.roles, sails.config.record.search.returnFields)) .subscribe(searchRes => { this.ajaxOk(req, res, null, searchRes); }, error => { @@ -1060,49 +1152,49 @@ export module Controllers { return; } return this.getRecord(oid).flatMap(currentRec => { - return this.hasEditAccess(brand, req.user, currentRec).flatMap(hasEditAccess => { - if (!hasEditAccess) { - sails.log.error("Error: edit error no permissions in do attachment."); - return Observable.throwError(new Error(TranslationService.t('edit-error-no-permissions'))); - } - if (method == 'get') { - // check if this attachId exists in the record - let found = null; - _.each(currentRec.metaMetadata.attachmentFields, (attField) => { - if (!found) { - const attFieldVal = currentRec.metadata[attField]; - found = _.find(attFieldVal, (attVal) => { - return attVal.fileId == attachId - }); - if (found) { - return false; + return this.hasEditAccess(brand, req.user, currentRec).flatMap(hasEditAccess => { + if (!hasEditAccess) { + sails.log.error("Error: edit error no permissions in do attachment."); + return Observable.throwError(new Error(TranslationService.t('edit-error-no-permissions'))); + } + if (method == 'get') { + // check if this attachId exists in the record + let found = null; + _.each(currentRec.metaMetadata.attachmentFields, (attField) => { + if (!found) { + const attFieldVal = currentRec.metadata[attField]; + found = _.find(attFieldVal, (attVal) => { + return attVal.fileId == attachId + }); + if (found) { + return false; + } } + }); + if (!found) { + sails.log.verbose("Error: Attachment not found in do attachment."); + return Observable.throwError(new Error(TranslationService.t('attachment-not-found'))) } - }); - if (!found) { - sails.log.verbose("Error: Attachment not found in do attachment."); - return Observable.throwError(new Error(TranslationService.t('attachment-not-found'))) - } - res.set('Content-Type', found.mimeType); - res.set('Content-Disposition', `attachment; filename="${found.name}"`); - sails.log.verbose(`Returning datastream observable of ${oid}: ${found.name}, attachId: ${attachId}`); - let datastreamServiceName = sails.config.record.datastreamService; - if(datastreamServiceName == undefined) { - datastreamServiceName = "recordsservice"; - } - sails.log.verbose(`Accessing datastream, using service: ${datastreamServiceName}`); - let datastreamService = sails.services[datastreamServiceName]; - return datastreamService.getDatastream(oid, attachId).flatMap((response) => { - res.end(Buffer.from(response.body), 'binary'); + res.set('Content-Type', found.mimeType); + res.set('Content-Disposition', `attachment; filename="${found.name}"`); + sails.log.verbose(`Returning datastream observable of ${oid}: ${found.name}, attachId: ${attachId}`); + let datastreamServiceName = sails.config.record.datastreamService; + if (datastreamServiceName == undefined) { + datastreamServiceName = "recordsservice"; + } + sails.log.verbose(`Accessing datastream, using service: ${datastreamServiceName}`); + let datastreamService = sails.services[datastreamServiceName]; + return datastreamService.getDatastream(oid, attachId).flatMap((response) => { + res.end(Buffer.from(response.body), 'binary'); + return Observable.of(oid); + }); + } else { + // process the upload... + this.tusServer.handle(req, res); return Observable.of(oid); - }); - } else { - // process the upload... - this.tusServer.handle(req, res); - return Observable.of(oid); - } - }); - }) + } + }); + }) .subscribe(whatever => { // ignore... }, error => { @@ -1138,18 +1230,26 @@ export module Controllers { let editUsers = authorization['edit'] let editUserResponse = []; - for(let i = 0; i< editUsers.length; i++){ + for (let i = 0; i < editUsers.length; i++) { let editUsername = editUsers[i]; let user = await UsersService.getUserWithUsername(editUsername).toPromise(); - editUserResponse.push({ username: editUsername, name: user.name, email: user.email }); + editUserResponse.push({ + username: editUsername, + name: user.name, + email: user.email + }); } let viewUsers = authorization['view'] let viewUserResponse = []; - for(let i = 0; i< viewUsers.length; i++){ + for (let i = 0; i < viewUsers.length; i++) { let viewUsername = viewUsers[i]; let user = await UsersService.getUserWithUsername(viewUsername).toPromise(); - viewUserResponse.push({ username: viewUsername, name: user.name, email: user.email }); + viewUserResponse.push({ + username: viewUsername, + name: user.name, + email: user.email + }); } let editPendingUsers = authorization['editPending']; @@ -1158,11 +1258,18 @@ export module Controllers { let editRoles = authorization['editRoles']; let viewRoles = authorization['viewRoles']; - return { edit: editUserResponse, view: viewUserResponse, editRoles: editRoles, viewRoles: viewRoles, editPending: editPendingUsers, viewPending: viewPendingUsers }; + return { + edit: editUserResponse, + view: viewUserResponse, + editRoles: editRoles, + viewRoles: viewRoles, + editPending: editPendingUsers, + viewPending: viewPendingUsers + }; } public getPermissions(req, res) { - return this.getPermissionsInternal(req,res).then(response =>{ + return this.getPermissionsInternal(req, res).then(response => { return this.ajaxOk(req, res, null, response); }); } @@ -1171,7 +1278,7 @@ export module Controllers { public getAttachments(req, res) { sails.log.verbose('getting attachments....'); const oid = req.param('oid'); - RecordsService.getAttachments(oid).subscribe((attachments:any[]) => { + Observable.fromPromise(this.RecordsService.getAttachments(oid)).subscribe((attachments: any[]) => { return this.ajaxOk(req, res, null, attachments); }); } @@ -1190,12 +1297,8 @@ export module Controllers { res.set('Content-Type', 'application/octet-stream'); res.set('Content-Disposition', `attachment; filename="${fileName}"`); sails.log.verbose(`Returning datastream observable of ${oid}: ${fileName}, datastreamId: ${datastreamId}`); - let datastreamServiceName = sails.config.record.datastreamService; - if(datastreamServiceName == undefined) { - datastreamServiceName = "recordsservice"; - } - let datastreamService = sails.services[datastreamServiceName]; - return datastreamService.getDatastream(oid, datastreamId).flatMap((response) => { + + return this.DatastreamService.getDatastream(oid, datastreamId).flatMap((response) => { res.end(Buffer.from(response.body), 'binary'); return Observable.of(oid); }); @@ -1224,4 +1327,4 @@ export module Controllers { } } -module.exports = new Controllers.Record().exports(); +module.exports = new Controllers.Record().exports(); \ No newline at end of file diff --git a/typescript/api/controllers/webservice/RecordController.ts b/typescript/api/controllers/webservice/RecordController.ts index a0971ac4f6..e82af1257e 100644 --- a/typescript/api/controllers/webservice/RecordController.ts +++ b/typescript/api/controllers/webservice/RecordController.ts @@ -27,7 +27,6 @@ declare var UsersService; declare var FormsService; declare var RecordTypesService; declare var WorkflowStepsService; -declare var RecordsService; declare var _; declare var User; /** @@ -36,6 +35,9 @@ declare var User; import {Observable} from 'rxjs/Rx'; import * as path from "path"; import controller = require('../../core/CoreController.js'); +import RecordsService from '../../core/RecordsService.js'; +import SearchService from '../../core/SearchService.js'; +import DatastreamService from '../../core/DatastreamService.js'; const UUIDGenerator = require('uuid/v4'); export module Controllers { @@ -46,6 +48,9 @@ export module Controllers { */ export class Record extends controller.Controllers.Core.Controller { + RecordsService: RecordsService = sails.services.recordsservice; + SearchService: SearchService = sails.services.recordsservice; + DatastreamService: DatastreamService = sails.services.recordsservice; /** * Exported methods, accessible from internet. */ @@ -79,7 +84,7 @@ export module Controllers { const brand = BrandingService.getBrand(req.session.branding); var oid = req.param('oid'); - RecordsService.getMeta(oid).subscribe(record => { + Observable.fromPromise(this.RecordsService.getMeta(oid)).subscribe(record => { return res.json(record["authorization"]); }); @@ -93,7 +98,7 @@ export module Controllers { var body = req.body; var users = body["users"]; var pendingUsers = body["pendingUsers"]; - RecordsService.getMeta(oid).subscribe(record => { + Observable.fromPromise(this.RecordsService.getMeta(oid)).subscribe(record => { if (users != null && users.length > 0) { record["authorization"]["edit"] = _.union(record["authorization"]["edit"], users); @@ -103,10 +108,10 @@ export module Controllers { record["authorization"]["editPending"] = _.union(record["authorization"]["editPending"], pendingUsers); } - var obs = RecordsService.updateMeta(brand, oid, record,req.user); + var obs = Observable.fromPromise(this.RecordsService.updateMeta(brand, oid, record,req.user)); obs.subscribe(result => { if (result["code"] == 200) { - RecordsService.getMeta(result["oid"]).subscribe(record => { + Observable.fromPromise(this.RecordsService.getMeta(result["oid"])).subscribe(record => { return res.json(record["authorization"]); }); } else { @@ -127,7 +132,7 @@ export module Controllers { var body = req.body; var users = body["users"]; var pendingUsers = body["pendingUsers"]; - RecordsService.getMeta(oid).subscribe(record => { + Observable.fromPromise(this.RecordsService.getMeta(oid)).subscribe(record => { if (users != null && users.length > 0) { record["authorization"]["view"] = _.union(record["authorization"]["view"], users); @@ -137,10 +142,10 @@ export module Controllers { record["authorization"]["viewPending"] = _.union(record["authorization"]["viewPending"], pendingUsers); } - var obs = RecordsService.updateMeta(brand, oid, record, req.user); + var obs = Observable.fromPromise(this.RecordsService.updateMeta(brand, oid, record, req.user)); obs.subscribe(result => { if (result["code"] == 200) { - RecordsService.getMeta(result["oid"]).subscribe(record => { + Observable.fromPromise(this.RecordsService.getMeta(result["oid"])).subscribe(record => { return res.json(record["authorization"]); }); } else { @@ -157,7 +162,7 @@ export module Controllers { var body = req.body; var users = body["users"]; var pendingUsers = body["pendingUsers"]; - RecordsService.getMeta(oid).subscribe(record => { + Observable.fromPromise(this.RecordsService.getMeta(oid)).subscribe(record => { if (users != null && users.length > 0) { record["authorization"]["edit"] = _.difference(record["authorization"]["edit"], users); @@ -167,10 +172,10 @@ export module Controllers { record["authorization"]["editPending"] = _.difference(record["authorization"]["editPending"], pendingUsers); } - var obs = RecordsService.updateMeta(brand, oid, record,req.user); + var obs = Observable.fromPromise(this.RecordsService.updateMeta(brand, oid, record,req.user)); obs.subscribe(result => { if (result["code"] == 200) { - RecordsService.getMeta(result["oid"]).subscribe(record => { + Observable.fromPromise(this.RecordsService.getMeta(result["oid"])).subscribe(record => { return res.json(record["authorization"]); }); } else { @@ -187,7 +192,7 @@ export module Controllers { var body = req.body; var users = body["users"]; var pendingUsers = body["pendingUsers"]; - RecordsService.getMeta(oid).subscribe(record => { + Observable.fromPromise(this.RecordsService.getMeta(oid)).subscribe(record => { if (users != null && users.length > 0) { record["authorization"]["view"] = _.difference(record["authorization"]["view"], users); @@ -197,10 +202,10 @@ export module Controllers { record["authorization"]["viewPending"] = _.difference(record["authorization"]["viewPending"], pendingUsers); } - var obs = RecordsService.updateMeta(brand, oid, record, req.user); + var obs = Observable.fromPromise(this.RecordsService.updateMeta(brand, oid, record, req.user)); obs.subscribe(result => { if (result["code"] == 200) { - RecordsService.getMeta(result["oid"]).subscribe(record => { + Observable.fromPromise(this.RecordsService.getMeta(result["oid"])).subscribe(record => { return res.json(record["authorization"]); }); } else { @@ -215,7 +220,7 @@ export module Controllers { const brand = BrandingService.getBrand(req.session.branding); var oid = req.param('oid'); - RecordsService.getMeta(oid).subscribe(record => { + Observable.fromPromise(this.RecordsService.getMeta(oid)).subscribe(record => { if (_.isEmpty(record)) { return Observable.throw(new Error(`Failed to get meta, cannot find existing record with oid: ${oid}`)); } @@ -233,7 +238,7 @@ export module Controllers { sails.log.debug(brand); var oid = req.param('oid'); - RecordsService.getMeta(oid).subscribe(record => { + Observable.fromPromise(this.RecordsService.getMeta(oid)).subscribe(record => { return res.json(record["metaMetadata"]); }); } @@ -243,7 +248,7 @@ export module Controllers { var oid = req.param('oid'); const shouldMerge = req.param('merge', false); - RecordsService.getMeta(oid).subscribe(record => { + Observable.fromPromise(this.RecordsService.getMeta(oid)).subscribe(record => { if (_.isEmpty(record)) { return Observable.throw(new Error(`Failed to update meta, cannot find existing record with oid: ${oid}`)); } @@ -257,7 +262,7 @@ export module Controllers { } else { record["metadata"] = req.body; } - var obs = RecordsService.updateMeta(brand, oid, record, req.user); + var obs = Observable.fromPromise(this.RecordsService.updateMeta(brand, oid, record, req.user)); obs.subscribe(result => { return res.json(result); }, error=> { @@ -275,9 +280,9 @@ export module Controllers { const brand = BrandingService.getBrand(req.session.branding); var oid = req.param('oid'); - RecordsService.getMeta(oid).subscribe(record => { + Observable.fromPromise(this.RecordsService.getMeta(oid)).subscribe(record => { record["metaMetadata"] = req.body; - var obs = RecordsService.updateMeta(brand, oid, record, req.user); + var obs = Observable.fromPromise(this.RecordsService.updateMeta(brand, oid, record, req.user)); obs.subscribe(result => { return res.json(result); }); @@ -308,7 +313,8 @@ export module Controllers { var recordTypeObservable = RecordTypesService.get(brand, recordType); recordTypeObservable.subscribe(recordTypeModel => { - + sails.log.error("recordTypeModel") + sails.log.error(recordTypeModel) if (recordTypeModel) { var metadata = body["metadata"]; var workflowStage = body["workflowStage"]; @@ -357,7 +363,10 @@ export module Controllers { }); - var obs = Observable.fromPromise(RecordsService.create(brand, request, recordTypeModel)); + sails.log.error("request is:") + sails.log.error(request) + let createPromise = this.RecordsService.create(brand, request, recordTypeModel) + var obs = Observable.fromPromise(createPromise); obs.subscribe(result => { if (result["code"] == "200") { result["code"] = 201; @@ -384,7 +393,7 @@ export module Controllers { const oid = req.param('oid'); const datastreamId = req.param('datastreamId'); sails.log.info(`getDataStream ${oid} ${datastreamId}`); - return RecordsService.getMeta(oid).flatMap(currentRec => { + return Observable.fromPromise(this.RecordsService.getMeta(oid)).flatMap(currentRec => { const fileName = req.param('fileName') ? req.param('fileName') : datastreamId; res.set('Content-Type', 'application/octet-stream'); res.set('Content-Disposition', `attachment; filename="${fileName}"`); @@ -552,7 +561,7 @@ export module Controllers { item["metadata"]= this.getDocMetadata(doc); item["dateCreated"] = doc["date_object_created"][0]; item["dateModified"] = doc["date_object_modified"][0]; - item["hasEditAccess"] = RecordsService.hasEditAccess(brand, user, roles, doc); + item["hasEditAccess"] = this.RecordsService.hasEditAccess(brand, user, roles, doc); items.push(item); } response["noItems"] = items.length; diff --git a/typescript/api/core/RecordsService.ts b/typescript/api/core/RecordsService.ts new file mode 100644 index 0000000000..ec23943991 --- /dev/null +++ b/typescript/api/core/RecordsService.ts @@ -0,0 +1,29 @@ +interface RecordsService{ + + create(brand, record, recordType, user?, triggerPreSaveTriggers?: boolean, triggerPostSaveTriggers?: boolean):Promise; + updateMeta(brand, oid, record, user?, triggerPreSaveTriggers?: boolean, triggerPostSaveTriggers?: boolean): Promise; + getMeta(oid): Promise; + createBatch(type, data, harvestIdFldName): Promise; + provideUserAccessAndRemovePendingAccess(oid, userid, pendingValue): void; + getRelatedRecords(oid, brand): Promise; + delete(oid): Promise; + updateNotificationLog(oid, record, options): Promise; + + + // Potentially we should move these and a couple of others that are purely record handling + // to a different service so that RecordsService is purely for integration with storage + triggerPreSaveTriggers(oid: string, record: any, recordType: object, mode: string, user): Promise; + triggerPostSaveTriggers(oid: string, record: any, recordType: object, mode: string, user): void; + triggerPostSaveSyncTriggers(oid: string, record: any, recordType: any, mode: string, user: object, response: any) : any; + hasEditAccess(brand, user, roles, record): boolean; + hasViewAccess(brand, user, roles, record): boolean; + appendToRecord(targetRecordOid: string, linkData: any, fieldName: string, fieldType: string, targetRecord: any): Promise + updateWorkflowStep(currentRec, nextStep): void; + getAttachments(oid: string, labelFilterStr?: string): Promise; + + + // Probably to be retired or reimplemented in a different service + checkRedboxRunning(): Promise; + +} +export default RecordsService diff --git a/typescript/api/core/SearchService.ts b/typescript/api/core/SearchService.ts new file mode 100644 index 0000000000..3ce46d34c5 --- /dev/null +++ b/typescript/api/core/SearchService.ts @@ -0,0 +1,8 @@ +interface SearchService{ + + + search(type, searchField, searchStr, returnFields): Promise; + searchFuzzy(type, workflowState, searchQuery, exactSearches, facetSearches, brand, user, roles, returnFields): Promise; + +} +export default SearchService diff --git a/typescript/api/services/RecordsService.ts b/typescript/api/services/RecordsService.ts index ced9ba98b5..dfd1e2c6c7 100644 --- a/typescript/api/services/RecordsService.ts +++ b/typescript/api/services/RecordsService.ts @@ -25,6 +25,9 @@ import * as request from "request-promise"; import * as luceneEscapeQuery from "lucene-escape-query"; import * as fs from 'fs'; import moment = require('moment'); +import RecordsService from '../core/RecordsService.js'; +import SearchService from '../core/SearchService.js'; +import { isObservable } from 'rxjs'; const util = require('util'); declare var FormsService, RolesService, UsersService, WorkflowStepsService, RecordTypesService; @@ -39,7 +42,7 @@ export module Services { * Author: Shilo Banihit * */ - export class Records extends services.Services.Core.Service implements DatastreamService { + export class Records extends services.Services.Core.Service implements DatastreamService, RecordsService, SearchService { protected _exportedMethods: any = [ 'create', @@ -47,7 +50,7 @@ export module Services { 'getMeta', 'hasEditAccess', 'hasViewAccess', - 'getOne', + // 'getOne', 'search', 'createBatch', 'provideUserAccessAndRemovePendingAccess', @@ -76,7 +79,7 @@ export module Services { // Params: // oid - record idea // labelFilterStr - set if you want to be selective in your attachments, will just run a simple `.indexOf` - public getAttachments(oid: string, labelFilterStr: string = undefined) { + public getAttachments(oid: string, labelFilterStr: string = undefined): Promise { let datastreamServiceName = sails.config.record.datastreamService; if(datastreamServiceName == undefined) { datastreamServiceName = "recordsservice"; @@ -98,7 +101,7 @@ export module Services { } } }); - return Observable.of(attachments); + return Observable.of(attachments).toPromise(); }); } @@ -180,7 +183,7 @@ export module Services { sails.log.verbose(`RecordsService::Appending to record:${targetRecordOid}`); if (_.isEmpty(targetRecord)) { sails.log.verbose(`RecordsService::Getting record metadata:${targetRecordOid}`); - targetRecord = await this.getMeta(targetRecordOid).toPromise(); + targetRecord = await this.getMeta(targetRecordOid); } const existingData = _.get(targetRecord, fieldName); if (_.isUndefined(existingData)) { @@ -193,17 +196,17 @@ export module Services { } _.set(targetRecord, fieldName, linkData); sails.log.verbose(`RecordsService::Updating record:${targetRecordOid}`); - return await this.updateMeta(null, targetRecordOid, targetRecord).toPromise(); + return await this.updateMeta(null, targetRecordOid, targetRecord); } - public delete(oid): Observable { + public delete(oid): Promise { const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.delete.url, oid); - return Observable.fromPromise(request[sails.config.record.api.delete.method](options)); + return request[sails.config.record.api.delete.method](options); } - public updateMeta(brand, oid, record, user = null, triggerPreSaveTriggers = true, triggerPostSaveTriggers = true): Observable { + public updateMeta(brand, oid, record, user = null, triggerPreSaveTriggers = true, triggerPostSaveTriggers = true): Promise { if(brand == null ) { - return Observable.from(this.updateMetaInternal(brand, oid, record, null, user, false)); + return this.updateMetaInternal(brand, oid, record, null, user, false); } else { return RecordTypesService.get(brand, record.metaMetadata.type).flatMap(async(recordType) => { @@ -240,9 +243,9 @@ export module Services { } } - public getMeta(oid) { + public getMeta(oid): Promise { const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.getMeta.url, oid); - return Observable.fromPromise(request[sails.config.record.api.getMeta.method](options)); + return request[sails.config.record.api.getMeta.method](options); } /** * Compares existing record metadata with new metadata and either removes or deletes the datastream from the record @@ -466,7 +469,7 @@ export module Services { // }); } - public createBatch(type, data, harvestIdFldName) { + public createBatch(type, data, harvestIdFldName): Promise { const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.harvest.url, null, type); data = _.map(data, dataItem => { return { harvest_id: _.get(dataItem, harvestIdFldName, ''), metadata: { metadata: dataItem, metaMetadata: { type: type } } }; @@ -474,10 +477,10 @@ export module Services { options.body = { records: data }; sails.log.verbose(`Sending data:`); sails.log.verbose(options.body); - return Observable.fromPromise(request[sails.config.record.api.harvest.method](options)); + return request[sails.config.record.api.harvest.method](options); } - public search(type, searchField, searchStr, returnFields) { + public search(type, searchField, searchStr, returnFields): Promise { const url = `${this.getSearchTypeUrl(type, searchField, searchStr)}&start=0&rows=${sails.config.record.export.maxRecords}`; sails.log.verbose(`Searching using: ${url}`); const options = this.getOptions(url); @@ -493,10 +496,10 @@ export module Services { customResp.push(customDoc); }); return Observable.of(customResp); - }); + }).toPromise(); } - public searchFuzzy(type, workflowState, searchQuery, exactSearches, facetSearches, brand, user, roles, returnFields) { + public searchFuzzy(type, workflowState, searchQuery, exactSearches, facetSearches, brand, user, roles, returnFields): Promise { const username = user.username; // const url = `${this.getSearchTypeUrl(type, searchField, searchStr)}&start=0&rows=${sails.config.record.export.maxRecords}`; let searchParam = workflowState ? ` AND workflow_stage:${workflowState} ` : ''; @@ -547,7 +550,7 @@ export module Services { }); } return Observable.of(customResp); - }); + }).toPromise(); } protected addAuthFilter(url, username, roles, brand, editAccessOnly = undefined) { @@ -569,26 +572,16 @@ export module Services { return url; } - public getOne(type) { - const url = `${this.getSearchTypeUrl(type)}&start=0&rows=1`; - sails.log.verbose(`Getting one using url: ${url}`); - const options = this.getOptions(url); - return Observable.fromPromise(request[sails.config.record.api.search.method](options)) - .flatMap(response => { - let resp: any = response; - return Observable.of(resp.response.docs); - }); - } protected getSearchTypeUrl(type, searchField = null, searchStr = null) { const searchParam = searchField ? ` AND ${searchField}:${searchStr}*` : ''; return `${sails.config.record.baseUrl.redbox}${sails.config.record.api.search.url}?q=metaMetadata_type:${type}${searchParam}&version=2.2&wt=json&sort=date_object_modified desc`; } - protected provideUserAccessAndRemovePendingAccess(oid, userid, pendingValue) { + public provideUserAccessAndRemovePendingAccess(oid, userid, pendingValue) { var metadataResponse = this.getMeta(oid); - metadataResponse.subscribe(metadata => { + Observable.fromPromise(metadataResponse).subscribe(metadata => { // remove pending edit access and add real edit access with userid var pendingEditArray = metadata['authorization']['editPending']; var editArray = metadata['authorization']['edit']; @@ -629,7 +622,7 @@ export module Services { private async getRelatedRecordsInternal(oid, recordTypeName, brand, mappingContext) { sails.log.debug("Getting related Records for oid: " + oid); - let record = await this.getMeta(oid).toPromise(); + let record = await this.getMeta(oid); let recordType = await RecordTypesService.get(brand, recordTypeName).toPromise(); @@ -675,7 +668,7 @@ export module Services { } public async getRelatedRecords(oid, brand) { - let record = await this.getMeta(oid).toPromise(); + let record = await this.getMeta(oid); let recordTypeName = record['metaMetadata']['type']; let recordType = await RecordTypesService.get(brand, recordTypeName).toPromise(); @@ -722,7 +715,7 @@ export module Services { } } - updateNotificationLog(oid, record, options) { + updateNotificationLog(oid, record, options): Promise { if (this.metTriggerCondition(oid, record, options) == "true") { sails.log.verbose(`Updating notification log for oid: ${oid}`); const logName = _.get(options, 'logName', null); @@ -756,14 +749,14 @@ export module Services { return Observable.throw(new Error('Failed to update notification log')); } return Observable.of(record); - }); + }).toPromise(); } } else { sails.log.verbose(`Notification log name: '${options.name}', for oid: ${oid}, not running, condition not met: ${options.triggerCondition}`); sails.log.verbose(JSON.stringify(record)); } // no updates or condition not met ... just return the record - return Observable.of(record); + return Observable.of(record).toPromise(); } /** @@ -820,7 +813,7 @@ export module Services { // }); } - public updateWorkflowStep(currentRec, nextStep) { + public updateWorkflowStep(currentRec, nextStep): void { if (!_.isEmpty(nextStep)) { currentRec.previousWorkflow = currentRec.workflow; currentRec.workflow = nextStep.config.workflow; @@ -855,8 +848,8 @@ export module Services { sails.log.verbose(`Triggering pre save triggers: ${preSaveUpdateHookFunctionString}`); - record = await preSaveUpdateHookFunction(oid, record, options, user).toPromise(); - + let hookResponse = preSaveUpdateHookFunction(oid, record, options, user); + record = await this.resolveHookResponse(hookResponse); } } @@ -879,7 +872,8 @@ export module Services { let options = _.get(postSaveSyncHook, "options", {}); if (_.isFunction(postSaveSyncHookFunction)) { sails.log.debug(`Triggering post-save sync trigger: ${postSaveSyncHooksFunctionString}`) - response = await postSaveSyncHookFunction(oid, record, options, user, response); + let hookResponse = postSaveSyncHookFunction(oid, record, options, user, response); + response = await this.resolveHookResponse(hookResponse); sails.log.debug(`${postSaveSyncHooksFunctionString} response now is:`); sails.log.verbose(JSON.stringify(response)); sails.log.debug(`post-save trigger ${postSaveSyncHooksFunctionString} completed for ${oid}`) @@ -893,7 +887,9 @@ export module Services { return response; } - public triggerPostSaveTriggers(oid: string, record: any, recordType: any, mode: string = 'onUpdate', user: object = undefined) { + + + public triggerPostSaveTriggers(oid: string, record: any, recordType: any, mode: string = 'onUpdate', user: object = undefined): void { sails.log.debug("Triggering post save triggers "); sails.log.debug(`hooks.${mode}.post`); sails.log.debug(recordType); @@ -918,6 +914,16 @@ export module Services { } } + private resolveHookResponse(hookResponse) { + let response = hookResponse; + if(isObservable(hookResponse)) { + response = hookResponse.toPromise(); + } else { + response = Promise.resolve(hookResponse); + } + return response; + } + } } module.exports = new Services.Records().exports(); From 849b2dce552144fca27b16af9878dae3c7ad846e Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Wed, 2 Sep 2020 16:08:27 +0930 Subject: [PATCH 14/48] Added StorageService interface and implemented RedboxJavaStorageService as the default service. --- .../api/controllers/RecordController.ts | 52 +- typescript/api/core/RecordsService.ts | 14 +- typescript/api/core/StorageService.ts | 14 + typescript/api/services/RecordsService.ts | 454 ++++++------------ .../api/services/RedboxJavaStorageService.ts | 381 +++++++++++++++ 5 files changed, 560 insertions(+), 355 deletions(-) create mode 100644 typescript/api/core/StorageService.ts create mode 100644 typescript/api/services/RedboxJavaStorageService.ts diff --git a/typescript/api/controllers/RecordController.ts b/typescript/api/controllers/RecordController.ts index 2bbcf04d53..0958bd3ab7 100644 --- a/typescript/api/controllers/RecordController.ts +++ b/typescript/api/controllers/RecordController.ts @@ -29,7 +29,7 @@ import * as fs from 'fs'; import * as url from 'url'; declare var _; -declare var FormsService, WorkflowStepsService, BrandingService, RecordTypesService, TranslationService, User, UsersService, EmailService, RolesService; +declare var FormsService, WorkflowStepsService, BrandingService, RecordsService, RecordTypesService, TranslationService, User, UsersService, EmailService, RolesService; /** * Package that contains all Controllers. */ @@ -45,15 +45,15 @@ export module Controllers { */ export class Record extends controller.Controllers.Core.Controller { - RecordsService: RecordsService = sails.services.recordsservice; - SearchService: SearchService = sails.services.recordsservice; - DatastreamService: DatastreamService = sails.services.recordsservice; + recordsService: RecordsService = RecordsService; + searchService: SearchService = RecordsService; + datastreamService: DatastreamService = RecordsService; constructor() { super(); let datastreamServiceName = sails.config.record.datastreamService; if (datastreamServiceName != undefined) { - this.DatastreamService = sails.services[datastreamServiceName]; + this.datastreamService = sails.services[datastreamServiceName]; } } @@ -96,7 +96,7 @@ export module Controllers { public getMeta(req, res) { const brand = BrandingService.getBrand(req.session.branding); const oid = req.param('oid') ? req.param('oid') : ''; - var obs = Observable.fromPromise(this.RecordsService.getMeta(oid)); + var obs = Observable.fromPromise(this.recordsService.getMeta(oid)); return obs.subscribe(record => { this.hasViewAccess(brand, req.user, record).subscribe(hasViewAccess => { if (hasViewAccess) { @@ -134,7 +134,7 @@ export module Controllers { }); }); } else { - Observable.fromPromise(this.RecordsService.getMeta(oid)).flatMap(record => { + Observable.fromPromise(this.recordsService.getMeta(oid)).flatMap(record => { const formName = record.metaMetadata.form; return FormsService.getFormByName(formName, true); }).subscribe(form => { @@ -166,13 +166,13 @@ export module Controllers { protected hasEditAccess(brand, user, currentRec): Observable < boolean > { sails.log.verbose("Current Record: "); sails.log.verbose(currentRec); - return Observable.of(this.RecordsService.hasEditAccess(brand, user, user.roles, currentRec)); + return Observable.of(this.recordsService.hasEditAccess(brand, user, user.roles, currentRec)); } protected hasViewAccess(brand, user, currentRec) { sails.log.verbose("Current Record: "); sails.log.verbose(currentRec); - return Observable.of(this.RecordsService.hasViewAccess(brand, user, user.roles, currentRec)); + return Observable.of(this.recordsService.hasViewAccess(brand, user, user.roles, currentRec)); } public getTransferResponsibilityConfig(req, res) { @@ -238,9 +238,9 @@ export module Controllers { // First: check if this user has edit access to this record, we don't want Gremlins sneaking in on us // Not trusting what was posted, retrieving from DB... this.getRecord(rec.oid).subscribe(recObj => { - if (this.RecordsService.hasEditAccess(brand, user, user.roles, recObj)) { + if (this.recordsService.hasEditAccess(brand, user, user.roles, recObj)) { let recType = rec.metadata.metaMetadata.type; - let relatedRecords = this.RecordsService.getRelatedRecords(rec.oid, brand); + let relatedRecords = this.recordsService.getRelatedRecords(rec.oid, brand); relatedRecords.then(relatedRecords => { @@ -277,7 +277,7 @@ export module Controllers { sails.log.verbose(`Updating record ${oid}`); sails.log.verbose(JSON.stringify(record)); - Observable.fromPromise(this.RecordsService.updateMeta(brand, oid, record)).subscribe(response => { + Observable.fromPromise(this.recordsService.updateMeta(brand, oid, record)).subscribe(response => { relationshipObjectCount++; if (response && response.code == "200") { if (oid == rec.oid) { @@ -399,7 +399,7 @@ export module Controllers { }); } else { // defaults to retrive the form of the current workflow state... - obs = Observable.fromPromise(this.RecordsService.getMeta(oid)).flatMap(currentRec => { + obs = Observable.fromPromise(this.recordsService.getMeta(oid)).flatMap(currentRec => { if (_.isEmpty(currentRec)) { return Observable.throw(new Error(`Error, empty metadata for OID: ${oid}`)); } @@ -502,7 +502,7 @@ export module Controllers { wfStep = await WorkflowStepsService.get(recType, targetStep).toPromise(); } try{ - this.RecordsService.updateWorkflowStep(record, wfStep); + this.recordsService.updateWorkflowStep(record, wfStep); return this.createRecord(record, brand, recordType, req, res); }catch (error) { this.ajaxFail(req, res, `Failed to save record: ${error}`); @@ -519,7 +519,7 @@ export module Controllers { formDef = form; record.metaMetadata.attachmentFields = form.attachmentFields; - let response = await this.RecordsService.create(brand, record, recordType, user); + let response = await this.recordsService.create(brand, record, recordType, user); let updateResponse = response; if (response && response.code == "200") { @@ -540,7 +540,7 @@ export module Controllers { }); }); // update the metadata ... - let updateResponse = await this.RecordsService.updateMeta(brand, oid, record, user, false, false); + let updateResponse = await this.recordsService.updateMeta(brand, oid, record, user, false, false); } else { // no need for update... return the creation response let updateResponse = response; @@ -587,7 +587,7 @@ export module Controllers { }) .flatMap(hasEditAccess => { if (hasEditAccess) { - return Observable.fromPromise(this.RecordsService.delete(oid)); + return Observable.fromPromise(this.recordsService.delete(oid)); } message = TranslationService.t('edit-error-no-permissions'); return Observable.throw(new Error(TranslationService.t('edit-error-no-permissions'))); @@ -673,7 +673,7 @@ export module Controllers { } } if (hasPermissionToTransition) { - this.RecordsService.updateWorkflowStep(currentRec, nextStep); + this.recordsService.updateWorkflowStep(currentRec, nextStep); } origRecord = _.cloneDeep(currentRec); currentRec.metadata = metadata; @@ -682,7 +682,7 @@ export module Controllers { }).subscribe(record => { if (metadata.delete) { - Observable.fromPromise(this.RecordsService.delete(oid)).subscribe(response => { + Observable.fromPromise(this.recordsService.delete(oid)).subscribe(response => { if (response && response.code == "200") { response.success = true; sails.log.verbose(`Successfully deleted: ${oid}`); @@ -805,7 +805,7 @@ export module Controllers { protected getRecord(oid) { - return Observable.fromPromise(this.RecordsService.getMeta(oid)).flatMap(currentRec => { + return Observable.fromPromise(this.recordsService.getMeta(oid)).flatMap(currentRec => { if (_.isEmpty(currentRec)) { return Observable.throw(new Error(`Failed to update meta, cannot find existing record with oid: ${oid}`)); } @@ -821,14 +821,14 @@ export module Controllers { currentRec.metaMetadata.lastSaveDate = moment().format(); sails.log.error(`Calling record service...`); sails.log.error(currentRec); - return Observable.fromPromise(this.RecordsService.updateMeta(brand, oid, currentRec, user)); + return Observable.fromPromise(this.recordsService.updateMeta(brand, oid, currentRec, user)); } protected updateAuthorization(brand, oid, currentRec, user) { if (currentRec.metaMetadata.brandId != brand.id) { return Observable.throw(new Error(`Failed to update meta, brand's don't match: ${currentRec.metaMetadata.brandId} != ${brand.id}, with oid: ${oid}`)); } - return Observable.fromPromise(this.RecordsService.updateMeta(brand, oid, currentRec, user)); + return Observable.fromPromise(this.recordsService.updateMeta(brand, oid, currentRec, user)); } public stepTo(req, res) { @@ -854,7 +854,7 @@ export module Controllers { sails.log.verbose(currentRec); sails.log.verbose("Next step:"); sails.log.verbose(nextStep); - this.RecordsService.updateWorkflowStep(currentRec, nextStep); + this.recordsService.updateWorkflowStep(currentRec, nextStep); return this.updateMetadata(brand, oid, currentRec, req.user); }); }) @@ -1073,7 +1073,7 @@ export module Controllers { }); - Observable.fromPromise(this.SearchService.searchFuzzy(type, workflow, searchString, exactSearches, facetSearches, brand, req.user, req.user.roles, sails.config.record.search.returnFields)) + Observable.fromPromise(this.searchService.searchFuzzy(type, workflow, searchString, exactSearches, facetSearches, brand, req.user, req.user.roles, sails.config.record.search.returnFields)) .subscribe(searchRes => { this.ajaxOk(req, res, null, searchRes); }, error => { @@ -1278,7 +1278,7 @@ export module Controllers { public getAttachments(req, res) { sails.log.verbose('getting attachments....'); const oid = req.param('oid'); - Observable.fromPromise(this.RecordsService.getAttachments(oid)).subscribe((attachments: any[]) => { + Observable.fromPromise(this.recordsService.getAttachments(oid)).subscribe((attachments: any[]) => { return this.ajaxOk(req, res, null, attachments); }); } @@ -1298,7 +1298,7 @@ export module Controllers { res.set('Content-Disposition', `attachment; filename="${fileName}"`); sails.log.verbose(`Returning datastream observable of ${oid}: ${fileName}, datastreamId: ${datastreamId}`); - return this.DatastreamService.getDatastream(oid, datastreamId).flatMap((response) => { + return this.datastreamService.getDatastream(oid, datastreamId).flatMap((response) => { res.end(Buffer.from(response.body), 'binary'); return Observable.of(oid); }); diff --git a/typescript/api/core/RecordsService.ts b/typescript/api/core/RecordsService.ts index ec23943991..32f79617d4 100644 --- a/typescript/api/core/RecordsService.ts +++ b/typescript/api/core/RecordsService.ts @@ -1,17 +1,7 @@ -interface RecordsService{ +import StorageService from "./StorageService"; - create(brand, record, recordType, user?, triggerPreSaveTriggers?: boolean, triggerPostSaveTriggers?: boolean):Promise; - updateMeta(brand, oid, record, user?, triggerPreSaveTriggers?: boolean, triggerPostSaveTriggers?: boolean): Promise; - getMeta(oid): Promise; - createBatch(type, data, harvestIdFldName): Promise; - provideUserAccessAndRemovePendingAccess(oid, userid, pendingValue): void; - getRelatedRecords(oid, brand): Promise; - delete(oid): Promise; - updateNotificationLog(oid, record, options): Promise; - +interface RecordsService extends StorageService { - // Potentially we should move these and a couple of others that are purely record handling - // to a different service so that RecordsService is purely for integration with storage triggerPreSaveTriggers(oid: string, record: any, recordType: object, mode: string, user): Promise; triggerPostSaveTriggers(oid: string, record: any, recordType: object, mode: string, user): void; triggerPostSaveSyncTriggers(oid: string, record: any, recordType: any, mode: string, user: object, response: any) : any; diff --git a/typescript/api/core/StorageService.ts b/typescript/api/core/StorageService.ts new file mode 100644 index 0000000000..59579903de --- /dev/null +++ b/typescript/api/core/StorageService.ts @@ -0,0 +1,14 @@ +interface StorageService{ + + create(brand, record, recordType, user?, triggerPreSaveTriggers?: boolean, triggerPostSaveTriggers?: boolean):Promise; + updateMeta(brand, oid, record, user?, triggerPreSaveTriggers?: boolean, triggerPostSaveTriggers?: boolean): Promise; + getMeta(oid): Promise; + createBatch(type, data, harvestIdFldName): Promise; + provideUserAccessAndRemovePendingAccess(oid, userid, pendingValue): void; + getRelatedRecords(oid, brand): Promise; + delete(oid): Promise; + updateNotificationLog(oid, record, options): Promise; + + +} +export default StorageService diff --git a/typescript/api/services/RecordsService.ts b/typescript/api/services/RecordsService.ts index dfd1e2c6c7..2d4d29be7a 100644 --- a/typescript/api/services/RecordsService.ts +++ b/typescript/api/services/RecordsService.ts @@ -17,20 +17,28 @@ // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import { Observable } from 'rxjs/Rx'; +import { + Observable +} from 'rxjs/Rx'; import services = require('../core/CoreService.js'); import DatastreamService from '../core/DatastreamService.js'; -import { Sails, Model } from "sails"; +import { + Sails, + Model +} from "sails"; import * as request from "request-promise"; import * as luceneEscapeQuery from "lucene-escape-query"; import * as fs from 'fs'; import moment = require('moment'); import RecordsService from '../core/RecordsService.js'; import SearchService from '../core/SearchService.js'; -import { isObservable } from 'rxjs'; +import { + isObservable +} from 'rxjs'; +import StorageService from '../core/StorageService.js'; const util = require('util'); -declare var FormsService, RolesService, UsersService, WorkflowStepsService, RecordTypesService; +declare var FormsService, RolesService, UsersService, WorkflowStepsService, RecordTypesService, RedboxJavaStorageService; declare var sails: Sails; declare var _; declare var _this; @@ -42,7 +50,27 @@ export module Services { * Author: Shilo Banihit * */ - export class Records extends services.Services.Core.Service implements DatastreamService, RecordsService, SearchService { + export class Records extends services.Services.Core.Service implements DatastreamService, RecordsService, SearchService, StorageService { + + storageService: StorageService = null; + + + constructor() { + super(); + let that = this; + sails.on('ready', function () { + that.getStorageService(); + }); + } + + getStorageService() { + if (_.isEmpty(sails.config.storage) || _.isEmpty(sails.config.storage.serviceName)) { + this.storageService = RedboxJavaStorageService; + + } else { + this.storageService = sails.services[sails.config.storage.serviceName]; + } + } protected _exportedMethods: any = [ 'create', @@ -50,7 +78,6 @@ export module Services { 'getMeta', 'hasEditAccess', 'hasViewAccess', - // 'getOne', 'search', 'createBatch', 'provideUserAccessAndRemovePendingAccess', @@ -74,100 +101,128 @@ export module Services { 'appendToRecord' ]; + + + create(brand: any, record: any, recordType: any, user ? : any, triggerPreSaveTriggers ? : boolean, triggerPostSaveTriggers ? : boolean): Promise < any > { + return this.storageService.create(brand, record, recordType, user, triggerPreSaveTriggers, triggerPostSaveTriggers); + } + updateMeta(brand: any, oid: any, record: any, user ? : any, triggerPreSaveTriggers ? : boolean, triggerPostSaveTriggers ? : boolean): Promise < any > { + return this.storageService.updateMeta(brand, oid, record, user, triggerPreSaveTriggers, triggerPostSaveTriggers); + } + getMeta(oid: any): Promise < any > { + return this.storageService.getMeta(oid); + } + createBatch(type: any, data: any, harvestIdFldName: any): Promise < any > { + return this.storageService.createBatch(type, data, harvestIdFldName); + } + provideUserAccessAndRemovePendingAccess(oid: any, userid: any, pendingValue: any): void { + this.storageService.provideUserAccessAndRemovePendingAccess(oid, userid, pendingValue); + } + getRelatedRecords(oid: any, brand: any): Promise < any > { + return this.storageService.getRelatedRecords(oid, brand); + } + delete(oid: any): Promise < any > { + return this.storageService.delete(oid); + } + updateNotificationLog(oid: any, record: any, options: any): Promise < any > { + return this.storageService.updateNotificationLog(oid, record, options); + } + // Gets attachments for this record, will use the `sails.config.record.datastreamService` if set, otherwise will use this service // // Params: // oid - record idea // labelFilterStr - set if you want to be selective in your attachments, will just run a simple `.indexOf` - public getAttachments(oid: string, labelFilterStr: string = undefined): Promise { + public getAttachments(oid: string, labelFilterStr: string = undefined): Promise < any > { let datastreamServiceName = sails.config.record.datastreamService; - if(datastreamServiceName == undefined) { + if (datastreamServiceName == undefined) { datastreamServiceName = "recordsservice"; } let datastreamService = sails.services[datastreamServiceName]; return datastreamService.listDatastreams(oid) - .flatMap(datastreams => { - let attachments = []; - _.each(datastreams['datastreams'], datastream => { - let attachment = {}; - attachment['dateUpdated'] = moment(datastream['lastModified']['$date']).format(); - attachment['label'] = datastream['label']; - attachment['contentType'] = datastream['contentType']; - if (_.isUndefined(labelFilterStr) && _.isEmpty(labelFilterStr)) { - attachments.push(attachment); - } else { - if (datastream['label'] && datastream['label'].indexOf(labelFilterStr) != -1) { + .flatMap(datastreams => { + let attachments = []; + _.each(datastreams['datastreams'], datastream => { + let attachment = {}; + attachment['dateUpdated'] = moment(datastream['lastModified']['$date']).format(); + attachment['label'] = datastream['label']; + attachment['contentType'] = datastream['contentType']; + if (_.isUndefined(labelFilterStr) && _.isEmpty(labelFilterStr)) { attachments.push(attachment); + } else { + if (datastream['label'] && datastream['label'].indexOf(labelFilterStr) != -1) { + attachments.push(attachment); + } } - } + }); + return Observable.of(attachments).toPromise(); }); - return Observable.of(attachments).toPromise(); - }); } - public async checkRedboxRunning(): Promise { + /* + * TODO: Move/remove this block once direct mongo access is implemented + */ + public async checkRedboxRunning(): Promise < any > { // check if a valid storage plugin is loaded.... if (!_.isEmpty(sails.config.storage)) { sails.log.info("ReDBox storage plugin is active!"); return true; } - let retries = 1000; - for(let i =0; i< retries; i++) { + let retries = 1000; + for (let i = 0; i < retries; i++) { try { - let response:any = await this.info(); - if(response['applicationVersion']) { - return true; + let response: any = await this.info(); + if (response['applicationVersion']) { + return true; + } + } catch (err) { + sails.log.info("ReDBox Storage hasn't started yet. Retrying...") } - } catch(err) { - sails.log.info("ReDBox Storage hasn't started yet. Retrying...") - } await this.sleep(1000); } return false; } private sleep(ms) { - return new Promise(resolve=>{ - setTimeout(resolve,ms) - }); + return new Promise(resolve => { + setTimeout(resolve, ms) + }); } - private info(): Promise { + private info(): Promise < any > { const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.info.url); return request[sails.config.record.api.info.method](options) } - public async create(brand, record, recordType = null, user = null, triggerPreSaveTriggers = true, triggerPostSaveTriggers = true) { - let packageType = recordType.packageType; - // let obs = this.createInternal(brand, record, packageType, recordType, user, triggerPreSaveTriggers); - let response = await this.createInternal(brand, record, packageType, recordType, user, triggerPreSaveTriggers); - if (triggerPostSaveTriggers) { - if (response && `${response.code}` == "200") { - response = await this.triggerPostSaveSyncTriggers(response['oid'], record, recordType, 'onCreate', user, response); - } - if (response && `${response.code}` == "200") { - response.success = true; - this.triggerPostSaveTriggers(response['oid'], record, recordType, 'onCreate', user); + protected getOptions(url, oid = null, packageType = null, isJson: boolean = true) { + if (!_.isEmpty(oid)) { + url = url.replace('$oid', oid); + } + if (!_.isEmpty(packageType)) { + url = url.replace('$packageType', packageType); + } + const opts: any = { + url: url, + headers: { + 'Authorization': `Bearer ${sails.config.redbox.apiKey}` } + }; + if (isJson == true) { + opts.json = true; + opts.headers['Content-Type'] = 'application/json; charset=utf-8'; + } else { + opts.encoding = null; } - return response; + return opts; } - private async createInternal(brand, record, packageType, recordType = null, user = null, triggerPreSaveTriggers = true) { - // TODO: validate metadata with the form... - const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.create.url, null, packageType); - let response = null; - if (triggerPreSaveTriggers) { - record = await this.triggerPreSaveTriggers(null, record, recordType, "onCreate", user); - } - options.body = record; - sails.log.verbose(util.inspect(options, { showHidden: false, depth: null })); - response = await request[sails.config.record.api.create.method](options); - sails.log.verbose(`Create internal response: `); - sails.log.verbose(JSON.stringify(response)); - return response; - } + + /** + * End of block to move/remove + */ + + /** * Sets/appends to a field in the targetRecord @@ -199,54 +254,7 @@ export module Services { return await this.updateMeta(null, targetRecordOid, targetRecord); } - public delete(oid): Promise { - const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.delete.url, oid); - return request[sails.config.record.api.delete.method](options); - } - - public updateMeta(brand, oid, record, user = null, triggerPreSaveTriggers = true, triggerPostSaveTriggers = true): Promise { - if(brand == null ) { - return this.updateMetaInternal(brand, oid, record, null, user, false); - - } else { - return RecordTypesService.get(brand, record.metaMetadata.type).flatMap(async(recordType) => { - let response = await this.updateMetaInternal(brand, oid, record, recordType, user, triggerPreSaveTriggers) - if (triggerPostSaveTriggers) { - if (response && `${response.code}` == "200") { - response = this.triggerPostSaveSyncTriggers(oid, record, recordType, 'onUpdate', user, response); - } - if (response && `${response.code}` == "200") { - response.success = true; - this.triggerPostSaveTriggers(oid, record, recordType, 'onUpdate', user); - } - } else { - response.success = true; - } - return response; - }); - } - } - - private async updateMetaInternal(brand, oid, record, recordType, user = null, triggerPreSaveTriggers = true) { - // TODO: validate metadata with the form... - const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.updateMeta.url, oid); - - if (triggerPreSaveTriggers) { - record = await this.triggerPreSaveTriggers(oid, record, recordType, "onUpdate", user); - options.body = record; - return await request[sails.config.record.api.updateMeta.method](options); - } else { - options.body = record; - sails.log.verbose(util.inspect(options, { showHidden: false, depth: null })); - return await request[sails.config.record.api.updateMeta.method](options); - } - } - - public getMeta(oid): Promise { - const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.getMeta.url, oid); - return request[sails.config.record.api.getMeta.method](options); - } /** * Compares existing record metadata with new metadata and either removes or deletes the datastream from the record */ @@ -372,22 +380,7 @@ export module Services { }); } - protected getOptions(url, oid = null, packageType = null, isJson: boolean = true) { - if (!_.isEmpty(oid)) { - url = url.replace('$oid', oid); - } - if (!_.isEmpty(packageType)) { - url = url.replace('$packageType', packageType); - } - const opts: any = { url: url, headers: { 'Authorization': `Bearer ${sails.config.redbox.apiKey}` } }; - if (isJson == true) { - opts.json = true; - opts.headers['Content-Type'] = 'application/json; charset=utf-8'; - } else { - opts.encoding = null; - } - return opts; - } + /** * Fine-grained access to the record, converted to sync. @@ -469,18 +462,9 @@ export module Services { // }); } - public createBatch(type, data, harvestIdFldName): Promise { - const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.harvest.url, null, type); - data = _.map(data, dataItem => { - return { harvest_id: _.get(dataItem, harvestIdFldName, ''), metadata: { metadata: dataItem, metaMetadata: { type: type } } }; - }); - options.body = { records: data }; - sails.log.verbose(`Sending data:`); - sails.log.verbose(options.body); - return request[sails.config.record.api.harvest.method](options); - } - public search(type, searchField, searchStr, returnFields): Promise { + + public search(type, searchField, searchStr, returnFields): Promise < any > { const url = `${this.getSearchTypeUrl(type, searchField, searchStr)}&start=0&rows=${sails.config.record.export.maxRecords}`; sails.log.verbose(`Searching using: ${url}`); const options = this.getOptions(url); @@ -499,7 +483,7 @@ export module Services { }).toPromise(); } - public searchFuzzy(type, workflowState, searchQuery, exactSearches, facetSearches, brand, user, roles, returnFields): Promise { + public searchFuzzy(type, workflowState, searchQuery, exactSearches, facetSearches, brand, user, roles, returnFields): Promise < any > { const username = user.username; // const url = `${this.getSearchTypeUrl(type, searchField, searchStr)}&start=0&rows=${sails.config.record.export.maxRecords}`; let searchParam = workflowState ? ` AND workflow_stage:${workflowState} ` : ''; @@ -521,7 +505,9 @@ export module Services { return Observable.fromPromise(request[sails.config.record.api.search.method](options)) .flatMap(resp => { let response: any = resp; - const customResp = { records: [] }; + const customResp = { + records: [] + }; _.forEach(response.response.docs, solrdoc => { const customDoc = {}; _.forEach(returnFields, retField => { @@ -546,7 +532,10 @@ export module Services { count: facet_field[j++] }); } - customResp['facets'].push({ name: facet_name, values: facetValues }); + customResp['facets'].push({ + name: facet_name, + values: facetValues + }); }); } return Observable.of(customResp); @@ -578,191 +567,20 @@ export module Services { return `${sails.config.record.baseUrl.redbox}${sails.config.record.api.search.url}?q=metaMetadata_type:${type}${searchParam}&version=2.2&wt=json&sort=date_object_modified desc`; } - public provideUserAccessAndRemovePendingAccess(oid, userid, pendingValue) { - var metadataResponse = this.getMeta(oid); - - Observable.fromPromise(metadataResponse).subscribe(metadata => { - // remove pending edit access and add real edit access with userid - var pendingEditArray = metadata['authorization']['editPending']; - var editArray = metadata['authorization']['edit']; - for (var i = 0; i < pendingEditArray.length; i++) { - if (pendingEditArray[i] == pendingValue) { - pendingEditArray = pendingEditArray.filter(e => e !== pendingValue); - editArray = editArray.filter(e => e !== userid); - editArray.push(userid); - } - } - metadata['authorization']['editPending'] = pendingEditArray; - metadata['authorization']['edit'] = editArray; - - var pendingViewArray = metadata['authorization']['viewPending']; - var viewArray = metadata['authorization']['view']; - for (var i = 0; i < pendingViewArray.length; i++) { - if (pendingViewArray[i] == pendingValue) { - pendingViewArray = pendingViewArray.filter(e => e !== pendingValue); - viewArray = viewArray.filter(e => e !== userid); - viewArray.push(userid); - } - } - metadata['authorization']['viewPending'] = pendingViewArray; - metadata['authorization']['view'] = viewArray; - - this.updateMeta(null, oid, metadata); - }, (error:any) => { - // swallow !!!! - sails.log.warn(`Failed to provide access to OID: ${oid}`); - sails.log.warn(error); - }); - } protected luceneEscape(str: string) { return luceneEscapeQuery.escape(str); } - private async getRelatedRecordsInternal(oid, recordTypeName, brand, mappingContext) { - sails.log.debug("Getting related Records for oid: " + oid); - let record = await this.getMeta(oid); - - let recordType = await RecordTypesService.get(brand, recordTypeName).toPromise(); - - let relationships = []; - let processedRelationships = []; - processedRelationships.push(recordType.name); - let relatedTo = recordType['relatedTo']; - if (_.isArray(relatedTo)) { - _.each(relatedTo, relatedObject => { - relationships.push({ - collection: relatedObject['recordType'], - foreignField: relatedObject['foreignField'], - localField: relatedObject['localField'] - }); - }); - - const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.getRecordRelationships.url, oid); - options.body = { - oid: oid, - relationships: relationships - }; - let relatedRecords = await request[sails.config.record.api.updateMeta.method](options); - - for (let i = 0; i < relationships.length; i++) { - let relationship = relationships[i]; - let collectionName = relationship['collection']; - let recordRelationships = relatedRecords[collectionName]; - - let newRelatedObjects = {}; - newRelatedObjects[collectionName] = recordRelationships; - _.merge(mappingContext, { relatedObjects: newRelatedObjects }); - if (_.indexOf(mappingContext['processedRelationships'], collectionName) < 0) { - mappingContext['processedRelationships'].push(collectionName); - for (let j = 0; j < recordRelationships.length; j++) { - let recordRelationship = recordRelationships[j]; - mappingContext = await this.getRelatedRecordsInternal(recordRelationship.redboxOid, collectionName, brand, mappingContext); - } - } - } - - } - return mappingContext; - } - - public async getRelatedRecords(oid, brand) { - let record = await this.getMeta(oid); - - let recordTypeName = record['metaMetadata']['type']; - let recordType = await RecordTypesService.get(brand, recordTypeName).toPromise(); - - let mappingContext = { 'processedRelationships': [], 'relatedObjects': {} }; - let relationships = []; - let processedRelationships = []; - processedRelationships.push(recordType.name); - let relatedTo = recordType['relatedTo']; - if (_.isArray(relatedTo)) { - _.each(relatedTo, relatedObject => { - relationships.push({ - collection: relatedObject['recordType'], - foreignField: relatedObject['foreignField'], - localField: relatedObject['localField'] - }); - }); - const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.getRecordRelationships.url, oid); - options.body = { - oid: oid, - relationships: relationships - }; - let relatedRecords = await request[sails.config.record.api.updateMeta.method](options); - - for (let i = 0; i < relationships.length; i++) { - let relationship = relationships[i]; - let collectionName = relationship['collection']; - let recordRelationships = relatedRecords[collectionName]; - - let newRelatedObjects = {}; - mappingContext['processedRelationships'].push(collectionName); - newRelatedObjects[collectionName] = recordRelationships; - _.merge(mappingContext, { relatedObjects: newRelatedObjects }); - for (let j = 0; j < recordRelationships.length; j++) { - let recordRelationship = recordRelationships[j]; - mappingContext = await this.getRelatedRecordsInternal(recordRelationship.redboxOid, collectionName, brand, mappingContext); - } - } - return mappingContext; - } else { - return {}; - } - } - updateNotificationLog(oid, record, options): Promise { - if (this.metTriggerCondition(oid, record, options) == "true") { - sails.log.verbose(`Updating notification log for oid: ${oid}`); - const logName = _.get(options, 'logName', null); - if (logName) { - let log = _.get(record, logName, null); - const entry = { date: moment().format('YYYY-MM-DDTHH:mm:ss') }; - if (log) { - log.push(entry); - } else { - log = [entry]; - } - _.set(record, logName, log); - } - const updateFlagName = _.get(options, 'flagName', null); - if (updateFlagName) { - _.set(record, updateFlagName, _.get(options, 'flagVal', null)); - } - sails.log.verbose(`======== Notification log updates =========`); - sails.log.verbose(JSON.stringify(record)); - sails.log.verbose(`======== End update =========`); - // ready to update - if (_.get(options, "saveRecord", false)) { - const updateOptions = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.updateMeta.url, oid); - updateOptions.body = record; - return Observable.fromPromise(request[sails.config.record.api.updateMeta.method](updateOptions)) - .flatMap(resp => { - let response: any = resp; - if (response && response.code != "200") { - sails.log.error(`Error updating notification log: ${oid}`); - sails.log.error(JSON.stringify(response)); - return Observable.throw(new Error('Failed to update notification log')); - } - return Observable.of(record); - }).toPromise(); - } - } else { - sails.log.verbose(`Notification log name: '${options.name}', for oid: ${oid}, not running, condition not met: ${options.triggerCondition}`); - sails.log.verbose(JSON.stringify(record)); - } - // no updates or condition not met ... just return the record - return Observable.of(record).toPromise(); - } /** - * Pre-save trigger to clear and re-assign permissions based on security config - * - */ + * Pre-save trigger to clear and re-assign permissions based on security config + * + */ public assignPermissions(oid, record, options, user) { // sails.log.verbose(`Assign Permissions executing on oid: ${oid}, using options:`); @@ -857,7 +675,7 @@ export module Services { return record; } - public async triggerPostSaveSyncTriggers(oid: string, record: any, recordType: any, mode: string = 'onUpdate', user: object = undefined, response:any = {}) { + public async triggerPostSaveSyncTriggers(oid: string, record: any, recordType: any, mode: string = 'onUpdate', user: object = undefined, response: any = {}) { sails.log.debug("Triggering post save sync triggers "); sails.log.debug(`hooks.${mode}.postSync`); sails.log.debug(recordType); @@ -887,7 +705,7 @@ export module Services { return response; } - + public triggerPostSaveTriggers(oid: string, record: any, recordType: any, mode: string = 'onUpdate', user: object = undefined): void { sails.log.debug("Triggering post save triggers "); @@ -916,14 +734,16 @@ export module Services { private resolveHookResponse(hookResponse) { let response = hookResponse; - if(isObservable(hookResponse)) { - response = hookResponse.toPromise(); + if (isObservable(hookResponse)) { + response = hookResponse.toPromise(); } else { response = Promise.resolve(hookResponse); } return response; } + + } } -module.exports = new Services.Records().exports(); +module.exports = new Services.Records().exports(); \ No newline at end of file diff --git a/typescript/api/services/RedboxJavaStorageService.ts b/typescript/api/services/RedboxJavaStorageService.ts new file mode 100644 index 0000000000..fc535c7721 --- /dev/null +++ b/typescript/api/services/RedboxJavaStorageService.ts @@ -0,0 +1,381 @@ +// Copyright (c) 2020 Queensland Cyber Infrastructure Foundation (http://www.qcif.edu.au/) +// +// GNU GENERAL PUBLIC LICENSE +// Version 2, June 1991 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +import { + Sails, + Model +} from "sails"; +import services = require('../core/CoreService.js'); +import StorageService from '../core/StorageService.js'; +import RecordsService from '../core/RecordsService.js'; +const util = require('util'); +import * as request from "request-promise"; +import { Observable } from "rxjs"; +import moment = require('moment'); +import { SequenceEqualOperator } from "rxjs/internal/operators/sequenceEqual"; + + +declare var RecordsService, RecordTypesService; +declare var sails: Sails; +declare var _; + +export module Services { + /** + * WorkflowSteps related functions... + * + * Author: Shilo Banihit + * + */ + export class RedboxJavaStorage extends services.Services.Core.Service implements StorageService { + recordsService: RecordsService = null; + + constructor() { + super(); + let that = this; + sails.on('ready', function() { + that.recordsService = RecordsService; + }); + } + + protected _exportedMethods: any = [ + 'create', + 'updateMeta', + 'getMeta', + 'createBatch', + 'provideUserAccessAndRemovePendingAccess', + 'getRelatedRecords', + 'delete', + 'updateNotificationLog' + ]; + + public async create(brand, record, recordType = null, user = null, triggerPreSaveTriggers = true, triggerPostSaveTriggers = true) { + let packageType = recordType.packageType; + let response = await this.createInternal(brand, record, packageType, recordType, user, triggerPreSaveTriggers); + if (triggerPostSaveTriggers) { + if (response && `${response.code}` == "200") { + response = await this.recordsService.triggerPostSaveSyncTriggers(response['oid'], record, recordType, 'onCreate', user, response); + } + if (response && `${response.code}` == "200") { + response.success = true; + this.recordsService.triggerPostSaveTriggers(response['oid'], record, recordType, 'onCreate', user); + } + } + return response; + } + + private async createInternal(brand, record, packageType, recordType = null, user = null, triggerPreSaveTriggers = true) { + // TODO: validate metadata with the form... + const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.create.url, null, packageType); + let response = null; + if (triggerPreSaveTriggers) { + record = await this.recordsService.triggerPreSaveTriggers(null, record, recordType, "onCreate", user); + } + options.body = record; + sails.log.verbose(util.inspect(options, { + showHidden: false, + depth: null + })); + response = await request[sails.config.record.api.create.method](options); + sails.log.verbose(`Create internal response: `); + sails.log.verbose(JSON.stringify(response)); + return response; + } + + public updateMeta(brand, oid, record, user = null, triggerPreSaveTriggers = true, triggerPostSaveTriggers = true): Promise < any > { + if (brand == null) { + return this.updateMetaInternal(brand, oid, record, null, user, false); + + } else { + return RecordTypesService.get(brand, record.metaMetadata.type).flatMap(async (recordType) => { + let response = await this.updateMetaInternal(brand, oid, record, recordType, user, triggerPreSaveTriggers) + if (triggerPostSaveTriggers) { + if (response && `${response.code}` == "200") { + response = this.recordsService.triggerPostSaveSyncTriggers(oid, record, recordType, 'onUpdate', user, response); + } + if (response && `${response.code}` == "200") { + response.success = true; + this.recordsService.triggerPostSaveTriggers(oid, record, recordType, 'onUpdate', user); + } + } else { + response.success = true; + } + return response; + }); + } + } + + + private async updateMetaInternal(brand, oid, record, recordType, user = null, triggerPreSaveTriggers = true) { + // TODO: validate metadata with the form... + const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.updateMeta.url, oid); + + if (triggerPreSaveTriggers) { + record = await this.recordsService.triggerPreSaveTriggers(oid, record, recordType, "onUpdate", user); + options.body = record; + return await request[sails.config.record.api.updateMeta.method](options); + } else { + options.body = record; + sails.log.verbose(util.inspect(options, { + showHidden: false, + depth: null + })); + return await request[sails.config.record.api.updateMeta.method](options); + } + } + + public getMeta(oid): Promise < any > { + const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.getMeta.url, oid); + return request[sails.config.record.api.getMeta.method](options); + } + + public createBatch(type, data, harvestIdFldName): Promise < any > { + const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.harvest.url, null, type); + data = _.map(data, dataItem => { + return { + harvest_id: _.get(dataItem, harvestIdFldName, ''), + metadata: { + metadata: dataItem, + metaMetadata: { + type: type + } + } + }; + }); + options.body = { + records: data + }; + sails.log.verbose(`Sending data:`); + sails.log.verbose(options.body); + return request[sails.config.record.api.harvest.method](options); + } + + public provideUserAccessAndRemovePendingAccess(oid, userid, pendingValue) { + var metadataResponse = this.getMeta(oid); + + Observable.fromPromise(metadataResponse).subscribe(metadata => { + // remove pending edit access and add real edit access with userid + var pendingEditArray = metadata['authorization']['editPending']; + var editArray = metadata['authorization']['edit']; + for (var i = 0; i < pendingEditArray.length; i++) { + if (pendingEditArray[i] == pendingValue) { + pendingEditArray = pendingEditArray.filter(e => e !== pendingValue); + editArray = editArray.filter(e => e !== userid); + editArray.push(userid); + } + } + metadata['authorization']['editPending'] = pendingEditArray; + metadata['authorization']['edit'] = editArray; + + var pendingViewArray = metadata['authorization']['viewPending']; + var viewArray = metadata['authorization']['view']; + for (var i = 0; i < pendingViewArray.length; i++) { + if (pendingViewArray[i] == pendingValue) { + pendingViewArray = pendingViewArray.filter(e => e !== pendingValue); + viewArray = viewArray.filter(e => e !== userid); + viewArray.push(userid); + } + } + metadata['authorization']['viewPending'] = pendingViewArray; + metadata['authorization']['view'] = viewArray; + + this.updateMeta(null, oid, metadata); + }, (error: any) => { + // swallow !!!! + sails.log.warn(`Failed to provide access to OID: ${oid}`); + sails.log.warn(error); + }); + + } + + public async getRelatedRecords(oid, brand) { + let record = await this.getMeta(oid); + + let recordTypeName = record['metaMetadata']['type']; + let recordType = await RecordTypesService.get(brand, recordTypeName).toPromise(); + + let mappingContext = { + 'processedRelationships': [], + 'relatedObjects': {} + }; + let relationships = []; + let processedRelationships = []; + processedRelationships.push(recordType.name); + let relatedTo = recordType['relatedTo']; + if (_.isArray(relatedTo)) { + _.each(relatedTo, relatedObject => { + relationships.push({ + collection: relatedObject['recordType'], + foreignField: relatedObject['foreignField'], + localField: relatedObject['localField'] + }); + }); + + const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.getRecordRelationships.url, oid); + options.body = { + oid: oid, + relationships: relationships + }; + let relatedRecords = await request[sails.config.record.api.updateMeta.method](options); + + for (let i = 0; i < relationships.length; i++) { + let relationship = relationships[i]; + let collectionName = relationship['collection']; + let recordRelationships = relatedRecords[collectionName]; + + let newRelatedObjects = {}; + mappingContext['processedRelationships'].push(collectionName); + newRelatedObjects[collectionName] = recordRelationships; + _.merge(mappingContext, { + relatedObjects: newRelatedObjects + }); + for (let j = 0; j < recordRelationships.length; j++) { + let recordRelationship = recordRelationships[j]; + mappingContext = await this.getRelatedRecordsInternal(recordRelationship.redboxOid, collectionName, brand, mappingContext); + } + } + + return mappingContext; + } else { + return {}; + } + } + + private async getRelatedRecordsInternal(oid, recordTypeName, brand, mappingContext) { + sails.log.debug("Getting related Records for oid: " + oid); + let record = await this.getMeta(oid); + + let recordType = await RecordTypesService.get(brand, recordTypeName).toPromise(); + + let relationships = []; + let processedRelationships = []; + processedRelationships.push(recordType.name); + let relatedTo = recordType['relatedTo']; + if (_.isArray(relatedTo)) { + _.each(relatedTo, relatedObject => { + relationships.push({ + collection: relatedObject['recordType'], + foreignField: relatedObject['foreignField'], + localField: relatedObject['localField'] + }); + }); + + const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.getRecordRelationships.url, oid); + options.body = { + oid: oid, + relationships: relationships + }; + let relatedRecords = await request[sails.config.record.api.updateMeta.method](options); + + for (let i = 0; i < relationships.length; i++) { + let relationship = relationships[i]; + let collectionName = relationship['collection']; + let recordRelationships = relatedRecords[collectionName]; + + let newRelatedObjects = {}; + newRelatedObjects[collectionName] = recordRelationships; + _.merge(mappingContext, { + relatedObjects: newRelatedObjects + }); + if (_.indexOf(mappingContext['processedRelationships'], collectionName) < 0) { + mappingContext['processedRelationships'].push(collectionName); + for (let j = 0; j < recordRelationships.length; j++) { + let recordRelationship = recordRelationships[j]; + mappingContext = await this.getRelatedRecordsInternal(recordRelationship.redboxOid, collectionName, brand, mappingContext); + } + } + } + + } + return mappingContext; + } + + public delete(oid): Promise < any > { + const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.delete.url, oid); + return request[sails.config.record.api.delete.method](options); + } + + updateNotificationLog(oid, record, options): Promise { + if (this.metTriggerCondition(oid, record, options) == "true") { + sails.log.verbose(`Updating notification log for oid: ${oid}`); + const logName = _.get(options, 'logName', null); + if (logName) { + let log = _.get(record, logName, null); + const entry = { date: moment().format('YYYY-MM-DDTHH:mm:ss') }; + if (log) { + log.push(entry); + } else { + log = [entry]; + } + _.set(record, logName, log); + } + const updateFlagName = _.get(options, 'flagName', null); + if (updateFlagName) { + _.set(record, updateFlagName, _.get(options, 'flagVal', null)); + } + sails.log.verbose(`======== Notification log updates =========`); + sails.log.verbose(JSON.stringify(record)); + sails.log.verbose(`======== End update =========`); + // ready to update + if (_.get(options, "saveRecord", false)) { + const updateOptions = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.updateMeta.url, oid); + updateOptions.body = record; + return Observable.fromPromise(request[sails.config.record.api.updateMeta.method](updateOptions)) + .flatMap(resp => { + let response: any = resp; + if (response && response.code != "200") { + sails.log.error(`Error updating notification log: ${oid}`); + sails.log.error(JSON.stringify(response)); + return Observable.throw(new Error('Failed to update notification log')); + } + return Observable.of(record); + }).toPromise(); + } + } else { + sails.log.verbose(`Notification log name: '${options.name}', for oid: ${oid}, not running, condition not met: ${options.triggerCondition}`); + sails.log.verbose(JSON.stringify(record)); + } + // no updates or condition not met ... just return the record + return Observable.of(record).toPromise(); + } + + protected getOptions(url, oid = null, packageType = null, isJson: boolean = true) { + if (!_.isEmpty(oid)) { + url = url.replace('$oid', oid); + } + if (!_.isEmpty(packageType)) { + url = url.replace('$packageType', packageType); + } + const opts: any = { + url: url, + headers: { + 'Authorization': `Bearer ${sails.config.redbox.apiKey}` + } + }; + if (isJson == true) { + opts.json = true; + opts.headers['Content-Type'] = 'application/json; charset=utf-8'; + } else { + opts.encoding = null; + } + return opts; + } + + } +} + +module.exports = new Services.RedboxJavaStorage().exports(); \ No newline at end of file From fb8b39a2813c4e957c098bedf96f2c302c998f1a Mon Sep 17 00:00:00 2001 From: Shilo B Date: Wed, 14 Oct 2020 11:04:38 +1000 Subject: [PATCH 15/48] First round of refactor: created StorageService interface to allow for pluggable storage backends. --- config/datastores.js | 5 +- config/emailnotification.js | 2 +- .../api/controllers/DashboardController.ts | 93 ++++--- .../api/controllers/ExportController.ts | 7 +- .../api/controllers/RecordController.ts | 83 +++--- .../webservice/RecordController.ts | 77 +++--- typescript/api/core/DatastreamService.ts | 4 +- .../api/core/DatastreamServiceResponse.ts | 36 +++ typescript/api/core/RecordsService.ts | 3 +- typescript/api/core/StorageService.ts | 2 + typescript/api/core/StorageServiceResponse.ts | 37 +++ typescript/api/services/ConfigService.ts | 2 +- typescript/api/services/DashboardService.ts | 155 ----------- typescript/api/services/EmailService.ts | 3 +- typescript/api/services/RecordsService.ts | 211 ++++----------- .../api/services/RedboxJavaStorageService.ts | 243 +++++++++++++++++- typescript/api/services/UsersService.ts | 4 +- 17 files changed, 504 insertions(+), 463 deletions(-) create mode 100644 typescript/api/core/DatastreamServiceResponse.ts create mode 100644 typescript/api/core/StorageServiceResponse.ts delete mode 100644 typescript/api/services/DashboardService.ts diff --git a/config/datastores.js b/config/datastores.js index cd0e984821..dee74a1fc7 100644 --- a/config/datastores.js +++ b/config/datastores.js @@ -13,6 +13,9 @@ module.exports.datastores = { mongodb: { adapter: require('sails-mongo'), url: 'mongodb://localhost:27017/redbox-portal' + }, + redboxStorage: { + adapter: require('sails-mongo'), + url: 'mongodb://mongodb:27017/redbox-storage' } - }; diff --git a/config/emailnotification.js b/config/emailnotification.js index ce38f71a3c..b6e129a909 100644 --- a/config/emailnotification.js +++ b/config/emailnotification.js @@ -3,7 +3,7 @@ module.exports.emailnotification = { send: {method: 'post', url: "/api/v1/messaging/emailnotification"} }, settings: { - enabled: true, + enabled: false, from: "noreply@redbox", templateDir: "views/emailTemplates/" }, diff --git a/typescript/api/controllers/DashboardController.ts b/typescript/api/controllers/DashboardController.ts index 8eca2fa06f..6e67eca837 100644 --- a/typescript/api/controllers/DashboardController.ts +++ b/typescript/api/controllers/DashboardController.ts @@ -60,7 +60,7 @@ export module Controllers { } - public getRecordList(req, res) { + public async getRecordList(req, res) { const brand = BrandingService.getBrand(req.session.branding); @@ -85,20 +85,18 @@ export module Controllers { const rows = req.param('rows'); const packageType = req.param('packageType'); const sort = req.param('sort'); - this.getRecords(workflowState, recordType, start,rows,user,roles,brand,editAccessOnly, packageType,sort).flatMap(results => { - return results; - }).subscribe(response => { - if (response && response.code == "200") { - response.success = true; - this.ajaxOk(req, res, null, response); - } else { - this.ajaxFail(req, res, null, response); - } - }, error => { - sails.log.error("Error updating meta:"); - sails.log.error(error); - this.ajaxFail(req, res, error.message); - }); + try { + const response = await this.getRecords(workflowState, recordType, start,rows,user,roles,brand,editAccessOnly, packageType,sort); + if (response) { + this.ajaxOk(req, res, null, response); + } else { + this.ajaxFail(req, res, null, response); + } + } catch (error) { + sails.log.error("Error updating meta:"); + sails.log.error(error); + this.ajaxFail(req, res, error.message); + } } private getDocMetadata(doc) { @@ -114,7 +112,7 @@ export module Controllers { return metadata; } - protected getRecords(workflowState, recordType, start,rows,user, roles, brand, editAccessOnly=undefined, packageType = undefined, sort=undefined) { + protected async getRecords(workflowState, recordType, start,rows,user, roles, brand, editAccessOnly=undefined, packageType = undefined, sort=undefined) { const username = user.username; if (!_.isUndefined(recordType) && !_.isEmpty(recordType)) { recordType = recordType.split(','); @@ -122,38 +120,39 @@ export module Controllers { if (!_.isUndefined(packageType) && !_.isEmpty(packageType)) { packageType = packageType.split(','); } - var response = DashboardService.getRecords(workflowState,recordType, start,rows,username,roles,brand,editAccessOnly, packageType, sort); - - return response.map(results => { - - var totalItems = results["response"]["numFound"]; - var startIndex = results["response"]["start"]; - var noItems = 10; - var pageNumber = (startIndex / noItems) + 1; - - var response = {}; - response["totalItems"] = totalItems; - response["currentPage"] = pageNumber; - response["noItems"] = noItems; - - var items = []; - var docs = results["response"]["docs"]; - - for (var i = 0; i < docs.length; i++) { - var doc = docs[i]; - var item = {}; - item["oid"] = doc["redboxOid"]; - item["title"] = doc["metadata"]["title"]; - item["metadata"]= this.getDocMetadata(doc); - item["dateCreated"] = doc["date_object_created"][0]; - item["dateModified"] = doc["date_object_modified"][0]; - item["hasEditAccess"] = RecordsService.hasEditAccess(brand, user, roles, doc); - items.push(item); - } + var results = await RecordsService.getRecords(workflowState,recordType, start,rows,username,roles,brand,editAccessOnly, packageType, sort); + if (!results.isSuccessful()) { + sails.log.verbose(`Failed to retrieve records!`); + return null; + } + + var totalItems = _.size(results.items); + var startIndex = start; + var noItems = rows; + var pageNumber = (startIndex / noItems) + 1; + + var response = {}; + response["totalItems"] = totalItems; + response["currentPage"] = pageNumber; + response["noItems"] = noItems; + + var items = []; + var docs = results.items; + + for (var i = 0; i < docs.length; i++) { + var doc = docs[i]; + var item = {}; + item["oid"] = doc["redboxOid"]; + item["title"] = doc["metadata"]["title"]; + item["metadata"]= this.getDocMetadata(doc); + item["dateCreated"] = doc["dateCreated"]; + item["dateModified"] = doc["lastSaveDate"]; + item["hasEditAccess"] = RecordsService.hasEditAccess(brand, user, roles, doc); + items.push(item); + } - response["items"] = items; - return Observable.of(response); - }); + response["items"] = items; + return response; } /** diff --git a/typescript/api/controllers/ExportController.ts b/typescript/api/controllers/ExportController.ts index 0a65b60802..8fea640388 100644 --- a/typescript/api/controllers/ExportController.ts +++ b/typescript/api/controllers/ExportController.ts @@ -52,7 +52,7 @@ export module Controllers { return this.sendView(req, res, 'export/index'); } - public downloadRecs(req, res) { + public async downloadRecs(req, res) { const brand = BrandingService.getBrand(req.session.branding); const format = req.param('format'); const recType = req.param('recType'); @@ -62,9 +62,8 @@ export module Controllers { if (format == 'csv') { res.set('Content-Type', 'text/csv'); res.set('Content-Disposition', `attachment; filename="${filename}"`); - DashboardService.exportAllPlans(req.user.username, req.user.roles, brand, format, before, after, recType).subscribe(response => { - return res.send(200, response); - }); + const response = await RecordsService.exportAllPlans(req.user.username, req.user.roles, brand, format, before, after, recType) + return res.send(200, response); } else { return res.send(500, 'Unsupported export format'); } diff --git a/typescript/api/controllers/RecordController.ts b/typescript/api/controllers/RecordController.ts index 0958bd3ab7..e2989cc39f 100644 --- a/typescript/api/controllers/RecordController.ts +++ b/typescript/api/controllers/RecordController.ts @@ -51,11 +51,14 @@ export module Controllers { constructor() { super(); - let datastreamServiceName = sails.config.record.datastreamService; - if (datastreamServiceName != undefined) { - this.datastreamService = sails.services[datastreamServiceName]; - } - + let that = this; + sails.on('ready', function () { + let datastreamServiceName = sails.config.record.datastreamService; + sails.log.verbose(`RecordController ready, using datastream service: ${datastreamServiceName}`); + if (datastreamServiceName != undefined) { + that.datastreamService = sails.services[datastreamServiceName]; + } + }); } @@ -223,6 +226,7 @@ export module Controllers { } public updateResponsibilities(req, res) { + sails.log.verbose(`updateResponsibilities() -> Starting...`); const brand = BrandingService.getBrand(req.session.branding); const user = req.user; const records = req.body.records; @@ -237,9 +241,10 @@ export module Controllers { _.forEach(records, rec => { // First: check if this user has edit access to this record, we don't want Gremlins sneaking in on us // Not trusting what was posted, retrieving from DB... + sails.log.verbose(`updateResponsibilities() -> Processing:${rec.oid}`); this.getRecord(rec.oid).subscribe(recObj => { if (this.recordsService.hasEditAccess(brand, user, user.roles, recObj)) { - let recType = rec.metadata.metaMetadata.type; + let recType = recObj.metaMetadata.type; let relatedRecords = this.recordsService.getRelatedRecords(rec.oid, brand); relatedRecords.then(relatedRecords => { @@ -279,7 +284,7 @@ export module Controllers { sails.log.verbose(JSON.stringify(record)); Observable.fromPromise(this.recordsService.updateMeta(brand, oid, record)).subscribe(response => { relationshipObjectCount++; - if (response && response.code == "200") { + if (response && response.isSuccessful()) { if (oid == rec.oid) { recordCtr++; } @@ -468,7 +473,6 @@ export module Controllers { } public create(req, res) { - this.createInternal(req,res).then(result => {}); } @@ -493,7 +497,7 @@ export module Controllers { record.metadata = metadata; let recordType = await RecordTypesService.get(brand, recType).toPromise(); - + if (recordType.packageName) { record.metaMetadata.packageName = recordType.packageName; } @@ -507,7 +511,7 @@ export module Controllers { }catch (error) { this.ajaxFail(req, res, `Failed to save record: ${error}`); } - + } private async createRecord(record, brand, recordType, req, res) { @@ -516,14 +520,13 @@ export module Controllers { let oid = null; const fieldsToCheck = ['location', 'uploadUrl']; let form = await FormsService.getFormByName(record.metaMetadata.form, true).toPromise(); - + formDef = form; record.metaMetadata.attachmentFields = form.attachmentFields; let response = await this.recordsService.create(brand, record, recordType, user); - + let updateResponse = response; - if (response && response.code == "200") { - response.success = true; + if (response && response.isSuccessful()) { oid = response.oid; if (!_.isEmpty(record.metaMetadata.attachmentFields)) { // check if we have any pending-oid elements @@ -553,7 +556,7 @@ export module Controllers { } try{ // handle datastream update - if (updateResponse && updateResponse.code == "200") { + if (updateResponse && updateResponse.isSuccessful()) { if (!_.isEmpty(record.metaMetadata.attachmentFields)) { // we emtpy the data locations in cloned record so we can reuse the same `this.updateDataStream` method call const emptyDatastreamRecord = _.cloneDeep(record); @@ -561,7 +564,7 @@ export module Controllers { _.set(emptyDatastreamRecord.metadata, attFieldName, []); }); // update the datastreams in RB, this is a terminal call - return this.updateDataStream(oid, emptyDatastreamRecord, record.metadata, response, req, res).toPromise(); + return this.updateDataStream(oid, emptyDatastreamRecord, record.metadata, response, req, res); } else { // terminate the request this.ajaxOk(req, res, null, updateResponse); @@ -593,7 +596,7 @@ export module Controllers { return Observable.throw(new Error(TranslationService.t('edit-error-no-permissions'))); }) .subscribe(response => { - if (response && response.code == "200") { + if (response && response.isSuccessful()) { const resp = { success: true, oid: oid @@ -621,7 +624,7 @@ export module Controllers { } public update(req, res) { - + const brand = BrandingService.getBrand(req.session.branding); const metadata = req.body; const oid = req.param('oid'); @@ -632,7 +635,7 @@ export module Controllers { const failedAttachments = []; let recType = null; - + this.getRecord(oid).flatMap(cr => { currentRec = cr; return this.hasEditAccess(brand, user, currentRec); @@ -683,7 +686,7 @@ export module Controllers { }).subscribe(record => { if (metadata.delete) { Observable.fromPromise(this.recordsService.delete(oid)).subscribe(response => { - if (response && response.code == "200") { + if (response && response.isSuccessful()) { response.success = true; sails.log.verbose(`Successfully deleted: ${oid}`); this.ajaxOk(req, res, null, response); @@ -710,7 +713,7 @@ export module Controllers { return this.updateMetadata(brand, oid, currentRec, user); }) .subscribe(response => { - if (response && response.code == "200") { + if (response && response.isSuccessful()) { return this.updateDataStream(oid, origRecord, metadata, response, req, res); } else { this.ajaxFail(req, res, null, response); @@ -733,13 +736,8 @@ export module Controllers { */ protected updateDataStream(oid, origRecord, metadata, response, req, res) { const fileIdsAdded = []; - let datastreamServiceName = sails.config.record.datastreamService; - if (datastreamServiceName == undefined) { - datastreamServiceName = "recordsservice"; - } - sails.log.verbose(`Updating datastream, using service: ${datastreamServiceName}`); - let datastreamService = sails.services[datastreamServiceName]; - return datastreamService.updateDatastream(oid, origRecord, metadata, sails.config.record.attachments.stageDir, fileIdsAdded) + + return this.datastreamService.updateDatastream(oid, origRecord, metadata, sails.config.record.attachments.stageDir, fileIdsAdded) .concatMap(reqs => { if (reqs) { sails.log.verbose(`Updating data streams...`); @@ -819,8 +817,8 @@ export module Controllers { } currentRec.metaMetadata.lastSavedBy = user.username; currentRec.metaMetadata.lastSaveDate = moment().format(); - sails.log.error(`Calling record service...`); - sails.log.error(currentRec); + sails.log.verbose(`Calling record service...`); + sails.log.verbose(currentRec); return Observable.fromPromise(this.recordsService.updateMeta(brand, oid, currentRec, user)); } @@ -863,7 +861,7 @@ export module Controllers { let responseValue:Observable = response; return responseValue.subscribe(response => { sails.log.error(response); - if (response && response.code == "200") { + if (response && response.isSuccessful()) { response.success = true; this.ajaxOk(req, res, null, response); } else { @@ -1151,6 +1149,7 @@ export module Controllers { this.tusServer.handle(req, res); return; } + const that = this; return this.getRecord(oid).flatMap(currentRec => { return this.hasEditAccess(brand, req.user, currentRec).flatMap(hasEditAccess => { if (!hasEditAccess) { @@ -1178,14 +1177,12 @@ export module Controllers { res.set('Content-Type', found.mimeType); res.set('Content-Disposition', `attachment; filename="${found.name}"`); sails.log.verbose(`Returning datastream observable of ${oid}: ${found.name}, attachId: ${attachId}`); - let datastreamServiceName = sails.config.record.datastreamService; - if (datastreamServiceName == undefined) { - datastreamServiceName = "recordsservice"; - } - sails.log.verbose(`Accessing datastream, using service: ${datastreamServiceName}`); - let datastreamService = sails.services[datastreamServiceName]; - return datastreamService.getDatastream(oid, attachId).flatMap((response) => { - res.end(Buffer.from(response.body), 'binary'); + return that.datastreamService.getDatastream(oid, attachId).flatMap((response) => { + if (response.readstream) { + response.readstream.pipe(res); + } else { + res.end(Buffer.from(response.body), 'binary'); + } return Observable.of(oid); }); } else { @@ -1299,7 +1296,11 @@ export module Controllers { sails.log.verbose(`Returning datastream observable of ${oid}: ${fileName}, datastreamId: ${datastreamId}`); return this.datastreamService.getDatastream(oid, datastreamId).flatMap((response) => { - res.end(Buffer.from(response.body), 'binary'); + if (response.readstream) { + response.readstream.pipe(res); + } else { + res.end(Buffer.from(response.body), 'binary'); + } return Observable.of(oid); }); } @@ -1327,4 +1328,4 @@ export module Controllers { } } -module.exports = new Controllers.Record().exports(); \ No newline at end of file +module.exports = new Controllers.Record().exports(); diff --git a/typescript/api/controllers/webservice/RecordController.ts b/typescript/api/controllers/webservice/RecordController.ts index e82af1257e..ba298a32b6 100644 --- a/typescript/api/controllers/webservice/RecordController.ts +++ b/typescript/api/controllers/webservice/RecordController.ts @@ -42,15 +42,15 @@ import DatastreamService from '../../core/DatastreamService.js'; const UUIDGenerator = require('uuid/v4'); export module Controllers { /** - * Responsible for all things related to the Dashboard + * RecordController API version * * @author Andrew Brazzatti */ export class Record extends controller.Controllers.Core.Controller { RecordsService: RecordsService = sails.services.recordsservice; - SearchService: SearchService = sails.services.recordsservice; - DatastreamService: DatastreamService = sails.services.recordsservice; + SearchService: SearchService; + DatastreamService: DatastreamService; /** * Exported methods, accessible from internet. */ @@ -70,6 +70,18 @@ export module Controllers { 'listRecords' ]; + constructor() { + super(); + let that = this; + sails.on('ready', function () { + let datastreamServiceName = sails.config.record.datastreamService; + sails.log.verbose(`RecordController Webservice ready, using datastream service: ${datastreamServiceName}`); + if (datastreamServiceName != undefined) { + that.DatastreamService = sails.services[datastreamServiceName]; + } + }); + } + /** ************************************************************************************************** **************************************** Add custom methods ************************************** @@ -110,9 +122,12 @@ export module Controllers { var obs = Observable.fromPromise(this.RecordsService.updateMeta(brand, oid, record,req.user)); obs.subscribe(result => { - if (result["code"] == 200) { - Observable.fromPromise(this.RecordsService.getMeta(result["oid"])).subscribe(record => { + if (result.isSuccessful()) { + Observable.fromPromise(this.RecordsService.getMeta(result.oid)).subscribe(record => { return res.json(record["authorization"]); + }, error=> { + sails.log.error(error); + return this.apiFail(req, res, 500, 'Failed adding an editor, check server logs.'); }); } else { return res.json(result); @@ -144,7 +159,7 @@ export module Controllers { var obs = Observable.fromPromise(this.RecordsService.updateMeta(brand, oid, record, req.user)); obs.subscribe(result => { - if (result["code"] == 200) { + if (result.isSuccessful()) { Observable.fromPromise(this.RecordsService.getMeta(result["oid"])).subscribe(record => { return res.json(record["authorization"]); }); @@ -174,7 +189,7 @@ export module Controllers { var obs = Observable.fromPromise(this.RecordsService.updateMeta(brand, oid, record,req.user)); obs.subscribe(result => { - if (result["code"] == 200) { + if (result.isSuccessful()) { Observable.fromPromise(this.RecordsService.getMeta(result["oid"])).subscribe(record => { return res.json(record["authorization"]); }); @@ -204,7 +219,7 @@ export module Controllers { var obs = Observable.fromPromise(this.RecordsService.updateMeta(brand, oid, record, req.user)); obs.subscribe(result => { - if (result["code"] == 200) { + if (result.isSuccessful()) { Observable.fromPromise(this.RecordsService.getMeta(result["oid"])).subscribe(record => { return res.json(record["authorization"]); }); @@ -313,8 +328,6 @@ export module Controllers { var recordTypeObservable = RecordTypesService.get(brand, recordType); recordTypeObservable.subscribe(recordTypeModel => { - sails.log.error("recordTypeModel") - sails.log.error(recordTypeModel) if (recordTypeModel) { var metadata = body["metadata"]; var workflowStage = body["workflowStage"]; @@ -362,18 +375,16 @@ export module Controllers { } }); - - sails.log.error("request is:") - sails.log.error(request) let createPromise = this.RecordsService.create(brand, request, recordTypeModel) var obs = Observable.fromPromise(createPromise); - obs.subscribe(result => { - if (result["code"] == "200") { - result["code"] = 201; - res.set('Location', sails.config.appUrl + BrandingService.getBrandAndPortalPath(req) + "/api/records/metadata/" + result["oid"]); + obs.subscribe(response => { + if (response.isSuccessful()) { + res.set('Location', sails.config.appUrl + BrandingService.getBrandAndPortalPath(req) + "/api/records/metadata/" + response.oid); + this.apiRespond(req, res, response, 201); + } else { + sails.log.error("Create Record failed"); + return this.apiFail(req, res, 500, "Create failed"); } - - return res.status(201).json(result); }, error=> { sails.log.error("Create Record failed", error); return this.apiFail(req, res, 500, "Create failed"); @@ -398,13 +409,12 @@ export module Controllers { res.set('Content-Type', 'application/octet-stream'); res.set('Content-Disposition', `attachment; filename="${fileName}"`); sails.log.info(`Returning datastream observable of ${oid}: ${fileName}, datastreamId: ${datastreamId}`); - let datastreamServiceName = sails.config.record.datastreamService; - if(datastreamServiceName == undefined) { - datastreamServiceName = "recordsservice"; - } - let datastreamService = sails.services[datastreamServiceName]; - return datastreamService.getDatastream(oid, datastreamId).flatMap((response) => { - res.end(Buffer.from(response.body), 'binary'); + return this.DatastreamService.getDatastream(oid, datastreamId).flatMap((response) => { + if (response.readstream) { + response.readstream.pipe(res); + } else { + res.end(Buffer.from(response.body), 'binary'); + } return Observable.of(oid); }); }).subscribe(whatever => { @@ -449,23 +459,16 @@ export module Controllers { sails.log.verbose(_.toString(fileIds)); const defaultErrorMessage = 'Error sending datastreams upstream.'; try { - let datastreamServiceName = sails.config.record.datastreamService; - if(datastreamServiceName == undefined) { - datastreamServiceName = "recordsservice"; - } - let datastreamService = sails.services[datastreamServiceName]; - - const reqs = datastreamService.addDatastreams(oid, fileIds); + const reqs = this.DatastreamService.addDatastreams(oid, fileIds); return Observable.fromPromise(reqs) .subscribe(result => { sails.log.verbose(`Done with updating streams and returning response...`); - if (_.get(result, 'value.error') || result instanceof Error) { - const message = _.get(result, 'value.message') || _.get(result, 'value.error'); - return res.status(500).json({message: message.toString('UTF-8')}); - } else { + if (result.isSuccessful()) { sails.log.verbose("Presuming success..."); _.merge(result, {fileIds: fileIds}); return res.json({message: result}); + } else { + return res.status(500).json({message: result.message}); } }, error => { return self.customErrorMessageHandlingOnUpstreamResult(error, res); diff --git a/typescript/api/core/DatastreamService.ts b/typescript/api/core/DatastreamService.ts index fd67c7f95b..b284cc6d86 100644 --- a/typescript/api/core/DatastreamService.ts +++ b/typescript/api/core/DatastreamService.ts @@ -1,6 +1,8 @@ +import DatastreamServiceResponse from './DatastreamServiceResponse'; + interface DatastreamService{ - addDatastreams(oid: string, fileIds: any[]): any; + addDatastreams(oid: string, fileIds: any[]): DatastreamServiceResponse; updateDatastream(oid: string, record, newMetadata, fileRoot, fileIdsAdded): any; removeDatastream(oid, fileId): any; addDatastream(oid, fileId): any; diff --git a/typescript/api/core/DatastreamServiceResponse.ts b/typescript/api/core/DatastreamServiceResponse.ts new file mode 100644 index 0000000000..6397e856b3 --- /dev/null +++ b/typescript/api/core/DatastreamServiceResponse.ts @@ -0,0 +1,36 @@ +// Copyright (c) 2020 Queensland Cyber Infrastructure Foundation (http://www.qcif.edu.au/) +// +// GNU GENERAL PUBLIC LICENSE +// Version 2, June 1991 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +/** + * Base class for a datastream service response + * + */ +export class DatastreamServiceResponse { + success: boolean; + message: string; + + constructor() { + + } + + public isSuccessful(): boolean { + return this.success === true; + } +} +export default DatastreamServiceResponse diff --git a/typescript/api/core/RecordsService.ts b/typescript/api/core/RecordsService.ts index 32f79617d4..71d3afb001 100644 --- a/typescript/api/core/RecordsService.ts +++ b/typescript/api/core/RecordsService.ts @@ -11,7 +11,8 @@ interface RecordsService extends StorageService { updateWorkflowStep(currentRec, nextStep): void; getAttachments(oid: string, labelFilterStr?: string): Promise; - + getRecords(workflowState, recordType, start, rows, username, roles, brand, editAccessOnly, packageType, sort) : Promise; + exportAllPlans(username, roles, brand, format, modBefore, modAfter, recType) : Promise; // Probably to be retired or reimplemented in a different service checkRedboxRunning(): Promise; diff --git a/typescript/api/core/StorageService.ts b/typescript/api/core/StorageService.ts index 59579903de..fc98866b1b 100644 --- a/typescript/api/core/StorageService.ts +++ b/typescript/api/core/StorageService.ts @@ -9,6 +9,8 @@ interface StorageService{ delete(oid): Promise; updateNotificationLog(oid, record, options): Promise; + getRecords(workflowState, recordType, start, rows, username, roles, brand, editAccessOnly, packageType, sort): Promise; + exportAllPlans(username, roles, brand, format, modBefore, modAfter, recType): Promise; } export default StorageService diff --git a/typescript/api/core/StorageServiceResponse.ts b/typescript/api/core/StorageServiceResponse.ts new file mode 100644 index 0000000000..2a53546e60 --- /dev/null +++ b/typescript/api/core/StorageServiceResponse.ts @@ -0,0 +1,37 @@ +// Copyright (c) 2020 Queensland Cyber Infrastructure Foundation (http://www.qcif.edu.au/) +// +// GNU GENERAL PUBLIC LICENSE +// Version 2, June 1991 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +/** + * Response class for StorageService methods. + */ +export class StorageServiceResponse { + success: boolean; + oid: string; + message: string; + metadata: any; + + constructor() { + + } + + public isSuccessful(): boolean { + return this.success === true; + } +} +export default StorageServiceResponse diff --git a/typescript/api/services/ConfigService.ts b/typescript/api/services/ConfigService.ts index 02bf3fc0db..eecffad00b 100644 --- a/typescript/api/services/ConfigService.ts +++ b/typescript/api/services/ConfigService.ts @@ -147,7 +147,7 @@ export module Services { _.each(modelFiles, (modelFile) => { const dest = `${appPath}/api/models/${basename(modelFile)}`; sails.log.verbose(`Copying ${modelFile} to ${dest}`) - fs.copySync(modelFile, `${appPath}/api/models/${dest}`) + fs.copySync(modelFile, dest); }); } sails.log.verbose(`${hook_log_header}::Adding custom API elements...completed.`); diff --git a/typescript/api/services/DashboardService.ts b/typescript/api/services/DashboardService.ts deleted file mode 100644 index 83b53c22bf..0000000000 --- a/typescript/api/services/DashboardService.ts +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) 2017 Queensland Cyber Infrastructure Foundation (http://www.qcif.edu.au/) -// -// GNU GENERAL PUBLIC LICENSE -// Version 2, June 1991 -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License along -// with this program; if not, write to the Free Software Foundation, Inc., -// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import { Observable } from 'rxjs/Rx'; -import services = require('../core/CoreService.js'); -import { Sails, Model } from "sails"; -import * as request from "request-promise"; - -declare var sails: Sails; -declare var _this; -declare var _; - -export module Services { - /** - * Records related functions... - * - * @author Andrew Brazzatti - * - */ - export class Dashboard extends services.Services.Core.Service { - - protected _exportedMethods: any = [ - 'getRecords', - 'exportAllPlans' - ]; - - - public getRecords(workflowState, recordType = undefined, start, rows = 10, username, roles, brand, editAccessOnly = undefined, packageType = undefined, sort=undefined) { - - var url = sails.config.record.baseUrl.redbox + sails.config.record.api.query.url + "?collection=metadataDocuments"; - url = this.addPaginationParams(url, start, rows); - if(sort) { - url = url+`&sort=${sort}` - } - - let roleNames = this.getRoleNames(roles, brand); - let andArray = []; - let permissions = { - "$or": [{ "authorization.view": username }, - { "authorization.edit": username }, - { "authorization.editRoles": { "$in": roleNames } }, - { "authorization.viewRoles": { "$in": roleNames } }] - }; - andArray.push(permissions); - if (!_.isUndefined(recordType) && !_.isEmpty(recordType)) { - let typeArray = []; - _.each(recordType, rType => { - typeArray.push({ "metaMetadata.type": rType }); - }); - let types = { "$or": typeArray }; - andArray.push(types); - } - if (!_.isUndefined(packageType) && !_.isEmpty(packageType)) { - let typeArray = []; - _.each(packageType, rType => { - typeArray.push({ "packageType": rType }); - }); - let types = { "$or": typeArray }; - andArray.push(types); - } - - let query = { - "metaMetadata.brandId": brand.id, - "$and":andArray, - }; - - if (workflowState != undefined) { - query["workflow.stage"] = workflowState; - } - - sails.log.verbose(JSON.stringify(query)); - var options = this.getOptions(url); - options['body'] = query; - - return Observable.fromPromise(request[sails.config.record.api.query.method](options)); - } - - exportAllPlans(username, roles, brand, format, modBefore, modAfter, recType) { - const dateQ = modBefore || modAfter ? ` AND date_object_modified:[${modAfter ? `${modAfter}T00:00:00Z` : '*'} TO ${modBefore ? `${modBefore}T23:59:59Z` : '*'}]` : ''; - var url = sails.config.record.baseUrl.redbox; - url = `${url}${sails.config.record.api.search.url}?q=metaMetadata_type:${recType}${dateQ}&sort=date_object_modified desc&version=2.2&wt=${format}`; - url = `${url}&start=0&rows=${sails.config.record.export.maxRecords}`; - url = this.addAuthFilter(url, username, roles, brand) - url = url + "&fq=metaMetadata_brandId:" + brand.id - var options = this.getOptions(url); - sails.log.verbose("Query URL is: " + url); - return Observable.fromPromise(request[sails.config.record.api.search.method](options)); - } - - - protected addQueryParams(url, workflowState) { - url = url + "?q=metaMetadata_type:rdmp AND workflow_stage:" + workflowState + "&sort=date_object_modified desc&version=2.2" - return url; - } - - protected addPaginationParams(url, start, rows) { - url = url + "&start=" + start + "&rows=" + rows + "&wt=json"; - return url; - } - - protected getRoleNames(roles, brand) { - var roleNames = []; - - for (var i = 0; i < roles.length; i++) { - var role = roles[i] - if (role.branding == brand.id) { - roleNames.push(roles[i].name); - } - } - - return roleNames; - } - - protected addAuthFilter(url, username, roles, brand, editAccessOnly = undefined) { - - var roleString = "" - var matched = false; - for (var i = 0; i < roles.length; i++) { - var role = roles[i] - if (role.branding == brand.id) { - if (matched) { - roleString += " OR "; - matched = false; - } - roleString += roles[i].name; - matched = true; - } - } - url = url + "&fq=authorization_edit:" + username + (editAccessOnly ? "" : (" OR authorization_view:" + username + " OR authorization_viewRoles:(" + roleString + ")")) + " OR authorization_editRoles:(" + roleString + ")"; - return url; - } - - protected getOptions(url) { - return { url: url, json: true, headers: { 'Authorization': `Bearer ${sails.config.redbox.apiKey}`, 'Content-Type': 'application/json; charset=utf-8' } }; - } - - } -} -module.exports = new Services.Dashboard().exports(); diff --git a/typescript/api/services/EmailService.ts b/typescript/api/services/EmailService.ts index b2af613a84..6a711d5837 100644 --- a/typescript/api/services/EmailService.ts +++ b/typescript/api/services/EmailService.ts @@ -73,7 +73,8 @@ export module Services { sails.log.verbose(body); var options = { url: url, json: true, body: body, headers: { 'Authorization': `Bearer ${sails.config.redbox.apiKey}`, 'Content-Type': 'application/json; charset=utf-8' } }; - var response = Observable.fromPromise(request[sails.config.emailnotification.api.send.method](options)).catch(error => Observable.of(`Error: ${error}`)); + // var response = Observable.fromPromise(request[sails.config.emailnotification.api.send.method](options)).catch(error => Observable.of(`Error: ${error}`)); + let response = Observable.of({success: false}); return response.map(result => { if (result['code'] != '200') { diff --git a/typescript/api/services/RecordsService.ts b/typescript/api/services/RecordsService.ts index 2d4d29be7a..801486d4b2 100644 --- a/typescript/api/services/RecordsService.ts +++ b/typescript/api/services/RecordsService.ts @@ -36,6 +36,7 @@ import { isObservable } from 'rxjs'; import StorageService from '../core/StorageService.js'; + const util = require('util'); declare var FormsService, RolesService, UsersService, WorkflowStepsService, RecordTypesService, RedboxJavaStorageService; @@ -50,9 +51,10 @@ export module Services { * Author: Shilo Banihit * */ - export class Records extends services.Services.Core.Service implements DatastreamService, RecordsService, SearchService, StorageService { + export class Records extends services.Services.Core.Service implements RecordsService, SearchService { storageService: StorageService = null; + datastreamService: DatastreamService = null; constructor() { @@ -60,18 +62,26 @@ export module Services { let that = this; sails.on('ready', function () { that.getStorageService(); + that.getDatastreamService(); }); } getStorageService() { if (_.isEmpty(sails.config.storage) || _.isEmpty(sails.config.storage.serviceName)) { this.storageService = RedboxJavaStorageService; - } else { this.storageService = sails.services[sails.config.storage.serviceName]; } } + getDatastreamService() { + if (_.isEmpty(sails.config.record) || _.isEmpty(sails.config.record.datastreamService)) { + this.datastreamService = RedboxJavaStorageService; + } else { + this.datastreamService = sails.services[sails.config.storage.serviceName]; + } + } + protected _exportedMethods: any = [ 'create', 'updateMeta', @@ -82,12 +92,6 @@ export module Services { 'createBatch', 'provideUserAccessAndRemovePendingAccess', 'searchFuzzy', - 'addDatastream', - 'addDatastreams', - 'removeDatastream', - 'updateDatastream', - 'getDatastream', - 'listDatastreams', 'deleteFilesFromStageDir', 'getRelatedRecords', 'delete', @@ -98,15 +102,17 @@ export module Services { 'triggerPostSaveSyncTriggers', 'checkRedboxRunning', 'getAttachments', - 'appendToRecord' + 'appendToRecord', + 'getRecords', + 'exportAllPlans' ]; - create(brand: any, record: any, recordType: any, user ? : any, triggerPreSaveTriggers ? : boolean, triggerPostSaveTriggers ? : boolean): Promise < any > { + create(brand: any, record: any, recordType: any, user ? : any, triggerPreSaveTriggers = true, triggerPostSaveTriggers = true): Promise < any > { return this.storageService.create(brand, record, recordType, user, triggerPreSaveTriggers, triggerPostSaveTriggers); } - updateMeta(brand: any, oid: any, record: any, user ? : any, triggerPreSaveTriggers ? : boolean, triggerPostSaveTriggers ? : boolean): Promise < any > { + updateMeta(brand: any, oid: any, record: any, user ? : any, triggerPreSaveTriggers = true, triggerPostSaveTriggers = true): Promise < any > { return this.storageService.updateMeta(brand, oid, record, user, triggerPreSaveTriggers, triggerPostSaveTriggers); } getMeta(oid: any): Promise < any > { @@ -128,39 +134,40 @@ export module Services { return this.storageService.updateNotificationLog(oid, record, options); } + public getRecords(workflowState, recordType = undefined, start, rows = 10, username, roles, brand, editAccessOnly = undefined, packageType = undefined, sort=undefined) : Promise { + return this.storageService.getRecords(workflowState, recordType, start, rows, username, roles, brand, editAccessOnly, packageType, sort); + } + + public exportAllPlans(username, roles, brand, format, modBefore, modAfter, recType) : Promise { + return this.storageService.exportAllPlans(username, roles, brand, format, modBefore, modAfter, recType); + } + // Gets attachments for this record, will use the `sails.config.record.datastreamService` if set, otherwise will use this service // // Params: // oid - record idea // labelFilterStr - set if you want to be selective in your attachments, will just run a simple `.indexOf` - public getAttachments(oid: string, labelFilterStr: string = undefined): Promise < any > { - let datastreamServiceName = sails.config.record.datastreamService; - if (datastreamServiceName == undefined) { - datastreamServiceName = "recordsservice"; - } - let datastreamService = sails.services[datastreamServiceName]; - return datastreamService.listDatastreams(oid) - .flatMap(datastreams => { - let attachments = []; - _.each(datastreams['datastreams'], datastream => { - let attachment = {}; - attachment['dateUpdated'] = moment(datastream['lastModified']['$date']).format(); - attachment['label'] = datastream['label']; - attachment['contentType'] = datastream['contentType']; - if (_.isUndefined(labelFilterStr) && _.isEmpty(labelFilterStr)) { - attachments.push(attachment); - } else { - if (datastream['label'] && datastream['label'].indexOf(labelFilterStr) != -1) { - attachments.push(attachment); - } - } - }); - return Observable.of(attachments).toPromise(); - }); + public async getAttachments(oid: string, labelFilterStr: string = undefined): Promise < any > { + let datastreams = await this.datastreamService.listDatastreams(oid, null); + let attachments = []; + _.each(datastreams['datastreams'], datastream => { + let attachment = {}; + attachment['dateUpdated'] = moment(datastream['lastModified']['$date']).format(); + attachment['label'] = datastream['label']; + attachment['contentType'] = datastream['contentType']; + if (_.isUndefined(labelFilterStr) && _.isEmpty(labelFilterStr)) { + attachments.push(attachment); + } else { + if (datastream['label'] && datastream['label'].indexOf(labelFilterStr) != -1) { + attachments.push(attachment); + } + } + }); + return attachments; } /* - * TODO: Move/remove this block once direct mongo access is implemented + * */ public async checkRedboxRunning(): Promise < any > { // check if a valid storage plugin is loaded.... @@ -254,134 +261,6 @@ export module Services { return await this.updateMeta(null, targetRecordOid, targetRecord); } - - /** - * Compares existing record metadata with new metadata and either removes or deletes the datastream from the record - */ - public updateDatastream(oid, record, newMetadata, fileRoot, fileIdsAdded) { - // loop thru the attachment fields and determine if we need to add or remove - return FormsService.getFormByName(record.metaMetadata.form, true).flatMap(form => { - const reqs = []; - record.metaMetadata.attachmentFields = form.attachmentFields; - _.each(form.attachmentFields, (attField) => { - const oldAttachments = record.metadata[attField]; - const newAttachments = newMetadata[attField]; - const removeIds = []; - // process removals - if (!_.isUndefined(oldAttachments) && !_.isNull(oldAttachments) && !_.isNull(newAttachments)) { - const toRemove = _.differenceBy(oldAttachments, newAttachments, 'fileId'); - _.each(toRemove, (removeAtt) => { - if (removeAtt.type == 'attachment') { - removeIds.push(removeAtt.fileId); - } - }); - } - // process additions - if (!_.isUndefined(newAttachments) && !_.isNull(newAttachments)) { - const toAdd = _.differenceBy(newAttachments, oldAttachments, 'fileId'); - _.each(toAdd, (addAtt) => { - if (addAtt.type == 'attachment') { - fileIdsAdded.push(addAtt.fileId); - } - }); - } - const req = this.addAndRemoveDatastreams(oid, fileIdsAdded, removeIds); - if (req) { - reqs.push(req); - } - }); - if (!_.isEmpty(reqs)) { - return Observable.of(reqs); - } else { - return Observable.of(null); - } - }); - } - - public removeDatastream(oid, fileId) { - const apiConfig = sails.config.record.api.removeDatastream; - const opts = this.getOptions(`${sails.config.record.baseUrl.redbox}${apiConfig.url}`, oid); - opts.url = `${opts.url}?skipReindex=true&datastreamId=${fileId}`; - return request[apiConfig.method](opts); - } - - public addDatastream(oid, fileId) { - const apiConfig = sails.config.record.api.addDatastream; - const opts = this.getOptions(`${sails.config.record.baseUrl.redbox}${apiConfig.url}`, oid); - opts.url = `${opts.url}?skipReindex=true&datastreamId=${fileId}`; - const fpath = `${sails.config.record.attachments.stageDir}/${fileId}`; - opts['formData'] = { - content: fs.createReadStream(fpath) - }; - return request[apiConfig.method](opts); - } - - public addAndRemoveDatastreams(oid, addIds: any[], removeIds: any[]) { - const apiConfig = sails.config.record.api.addAndRemoveDatastreams; - const opts = this.getOptions(`${sails.config.record.baseUrl.redbox}${apiConfig.url}`, oid); - opts.url = `${opts.url}?skipReindex=false`; - if (!_.isEmpty(removeIds)) { - const removeDataStreamIds = removeIds.join(','); - opts.url = `${opts.url}&removePayloadIds=${removeDataStreamIds}`; - } - if (!_.isEmpty(addIds)) { - const formData = {}; - _.each(addIds, fileId => { - const fpath = `${sails.config.record.attachments.stageDir}/${fileId}`; - formData[fileId] = fs.createReadStream(fpath); - }); - opts['formData'] = formData; - opts.json = false; - opts.headers['Content-Type'] = 'application/octet-stream'; - } - if (_.size(addIds) > 0 || _.size(removeIds) > 0) { - return request[apiConfig.method](opts); - } - } - - public addDatastreams(oid, fileIds: any[]) { - const apiConfig = sails.config.record.api.addDatastreams; - const opts = this.getOptions(`${sails.config.record.baseUrl.redbox}${apiConfig.url}`, oid); - opts.url = `${opts.url}?skipReindex=false&datastreamIds=${fileIds.join(',')}`; - const formData = {}; - _.each(fileIds, fileId => { - const fpath = `${sails.config.record.attachments.stageDir}/${fileId}`; - formData[fileId] = fs.createReadStream(fpath); - }); - opts['formData'] = formData; - - return request[apiConfig.method](opts); - } - - public getDatastream(oid, fileId) { - const apiConfig = sails.config.record.api.getDatastream; - const opts: any = this.getOptions(`${sails.config.record.baseUrl.redbox}${apiConfig.url}`, oid, null, false); - opts.url = `${opts.url}?datastreamId=${fileId}`; - opts.headers['Content-Type'] = 'application/octet-stream'; - opts.headers['accept'] = 'application/octet-stream'; - opts.resolveWithFullResponse = true; - opts.timeout = apiConfig.readTimeout; - sails.log.verbose(`Getting datastream using: `); - sails.log.verbose(JSON.stringify(opts)); - return Observable.fromPromise(request[apiConfig.method](opts)); - } - - public listDatastreams(oid, fileId) { - const apiConfig = sails.config.record.api.listDatastreams; - const opts: any = this.getOptions(`${sails.config.record.baseUrl.redbox}${apiConfig.url}`, oid); - - return Observable.fromPromise(request[apiConfig.method](opts)); - } - - public deleteFilesFromStageDir(stageDir, fileIds) { - _.each(fileIds, fileId => { - const path = `${stageDir}/${fileId}`; - fs.unlinkSync(path); - }); - } - - - /** * Fine-grained access to the record, converted to sync. * @@ -742,8 +621,8 @@ export module Services { return response; } - + } } -module.exports = new Services.Records().exports(); \ No newline at end of file +module.exports = new Services.Records().exports(); diff --git a/typescript/api/services/RedboxJavaStorageService.ts b/typescript/api/services/RedboxJavaStorageService.ts index fc535c7721..ba74cbdbf5 100644 --- a/typescript/api/services/RedboxJavaStorageService.ts +++ b/typescript/api/services/RedboxJavaStorageService.ts @@ -23,14 +23,16 @@ import { import services = require('../core/CoreService.js'); import StorageService from '../core/StorageService.js'; import RecordsService from '../core/RecordsService.js'; +import DatastreamService from '../core/DatastreamService.js'; const util = require('util'); import * as request from "request-promise"; import { Observable } from "rxjs"; import moment = require('moment'); import { SequenceEqualOperator } from "rxjs/internal/operators/sequenceEqual"; +import * as fs from 'fs'; -declare var RecordsService, RecordTypesService; +declare var RecordsService, RecordTypesService, FormsService; declare var sails: Sails; declare var _; @@ -41,9 +43,9 @@ export module Services { * Author: Shilo Banihit * */ - export class RedboxJavaStorage extends services.Services.Core.Service implements StorageService { + export class RedboxJavaStorage extends services.Services.Core.Service implements StorageService, DatastreamService { recordsService: RecordsService = null; - + constructor() { super(); let that = this; @@ -60,7 +62,16 @@ export module Services { 'provideUserAccessAndRemovePendingAccess', 'getRelatedRecords', 'delete', - 'updateNotificationLog' + 'updateNotificationLog', + 'getRecords', + 'exportAllPlans', + 'addDatastreams', + 'updateDatastream', + 'removeDatastream', + 'addDatastream', + 'addAndRemoveDatastreams', + 'getDatastream', + 'listDatastreams' ]; public async create(brand, record, recordType = null, user = null, triggerPreSaveTriggers = true, triggerPostSaveTriggers = true) { @@ -375,7 +386,229 @@ export module Services { return opts; } + public getRecords(workflowState, recordType = undefined, start, rows = 10, username, roles, brand, editAccessOnly = undefined, packageType = undefined, sort=undefined) { + + var url = sails.config.record.baseUrl.redbox + sails.config.record.api.query.url + "?collection=metadataDocuments"; + url = this.addPaginationParams(url, start, rows); + if(sort) { + url = url+`&sort=${sort}` + } + + let roleNames = this.getRoleNames(roles, brand); + let andArray = []; + let permissions = { + "$or": [{ "authorization.view": username }, + { "authorization.edit": username }, + { "authorization.editRoles": { "$in": roleNames } }, + { "authorization.viewRoles": { "$in": roleNames } }] + }; + andArray.push(permissions); + if (!_.isUndefined(recordType) && !_.isEmpty(recordType)) { + let typeArray = []; + _.each(recordType, rType => { + typeArray.push({ "metaMetadata.type": rType }); + }); + let types = { "$or": typeArray }; + andArray.push(types); + } + if (!_.isUndefined(packageType) && !_.isEmpty(packageType)) { + let typeArray = []; + _.each(packageType, rType => { + typeArray.push({ "packageType": rType }); + }); + let types = { "$or": typeArray }; + andArray.push(types); + } + + let query = { + "metaMetadata.brandId": brand.id, + "$and":andArray, + }; + + if (workflowState != undefined) { + query["workflow.stage"] = workflowState; + } + + sails.log.verbose(JSON.stringify(query)); + var options = this.getOptions(url); + options['body'] = query; + + return request[sails.config.record.api.query.method](options); + } + + protected addQueryParams(url, workflowState) { + url = url + "?q=metaMetadata_type:rdmp AND workflow_stage:" + workflowState + "&sort=date_object_modified desc&version=2.2" + return url; + } + + protected addPaginationParams(url, start, rows) { + url = url + "&start=" + start + "&rows=" + rows + "&wt=json"; + return url; + } + + protected getRoleNames(roles, brand) { + var roleNames = []; + + for (var i = 0; i < roles.length; i++) { + var role = roles[i] + if (role.branding == brand.id) { + roleNames.push(roles[i].name); + } + } + + return roleNames; + } + + protected addAuthFilter(url, username, roles, brand, editAccessOnly = undefined) { + + var roleString = "" + var matched = false; + for (var i = 0; i < roles.length; i++) { + var role = roles[i] + if (role.branding == brand.id) { + if (matched) { + roleString += " OR "; + matched = false; + } + roleString += roles[i].name; + matched = true; + } + } + url = url + "&fq=authorization_edit:" + username + (editAccessOnly ? "" : (" OR authorization_view:" + username + " OR authorization_viewRoles:(" + roleString + ")")) + " OR authorization_editRoles:(" + roleString + ")"; + return url; + } + + exportAllPlans(username, roles, brand, format, modBefore, modAfter, recType) { + const dateQ = modBefore || modAfter ? ` AND date_object_modified:[${modAfter ? `${modAfter}T00:00:00Z` : '*'} TO ${modBefore ? `${modBefore}T23:59:59Z` : '*'}]` : ''; + var url = sails.config.record.baseUrl.redbox; + url = `${url}${sails.config.record.api.search.url}?q=metaMetadata_type:${recType}${dateQ}&sort=date_object_modified desc&version=2.2&wt=${format}`; + url = `${url}&start=0&rows=${sails.config.record.export.maxRecords}`; + url = this.addAuthFilter(url, username, roles, brand) + url = url + "&fq=metaMetadata_brandId:" + brand.id + var options = this.getOptions(url); + sails.log.verbose("Query URL is: " + url); + return request[sails.config.record.api.search.method](options); + } + + public addDatastream(oid, fileId) { + const apiConfig = sails.config.record.api.addDatastream; + const opts = this.getOptions(`${sails.config.record.baseUrl.redbox}${apiConfig.url}`, oid); + opts.url = `${opts.url}?skipReindex=true&datastreamId=${fileId}`; + const fpath = `${sails.config.record.attachments.stageDir}/${fileId}`; + opts['formData'] = { + content: fs.createReadStream(fpath) + }; + return request[apiConfig.method](opts); + } + + public getDatastream(oid, fileId) { + const apiConfig = sails.config.record.api.getDatastream; + const opts: any = this.getOptions(`${sails.config.record.baseUrl.redbox}${apiConfig.url}`, oid, null, false); + opts.url = `${opts.url}?datastreamId=${fileId}`; + opts.headers['Content-Type'] = 'application/octet-stream'; + opts.headers['accept'] = 'application/octet-stream'; + opts.resolveWithFullResponse = true; + opts.timeout = apiConfig.readTimeout; + sails.log.verbose(`Getting datastream using: `); + sails.log.verbose(JSON.stringify(opts)); + return Observable.fromPromise(request[apiConfig.method](opts)); + } + + public addAndRemoveDatastreams(oid, addIds: any[], removeIds: any[]) { + const apiConfig = sails.config.record.api.addAndRemoveDatastreams; + const opts = this.getOptions(`${sails.config.record.baseUrl.redbox}${apiConfig.url}`, oid); + opts.url = `${opts.url}?skipReindex=false`; + if (!_.isEmpty(removeIds)) { + const removeDataStreamIds = removeIds.join(','); + opts.url = `${opts.url}&removePayloadIds=${removeDataStreamIds}`; + } + if (!_.isEmpty(addIds)) { + const formData = {}; + _.each(addIds, fileId => { + const fpath = `${sails.config.record.attachments.stageDir}/${fileId}`; + formData[fileId] = fs.createReadStream(fpath); + }); + opts['formData'] = formData; + opts.json = false; + opts.headers['Content-Type'] = 'application/octet-stream'; + } + if (_.size(addIds) > 0 || _.size(removeIds) > 0) { + return request[apiConfig.method](opts); + } + } + + public addDatastreams(oid, fileIds: any[]) { + const apiConfig = sails.config.record.api.addDatastreams; + const opts = this.getOptions(`${sails.config.record.baseUrl.redbox}${apiConfig.url}`, oid); + opts.url = `${opts.url}?skipReindex=false&datastreamIds=${fileIds.join(',')}`; + const formData = {}; + _.each(fileIds, fileId => { + const fpath = `${sails.config.record.attachments.stageDir}/${fileId}`; + formData[fileId] = fs.createReadStream(fpath); + }); + opts['formData'] = formData; + + return request[apiConfig.method](opts); + } + + /** + * Compares existing record metadata with new metadata and either removes or deletes the datastream from the record + */ + public updateDatastream(oid, record, newMetadata, fileRoot, fileIdsAdded) { + // loop thru the attachment fields and determine if we need to add or remove + return FormsService.getFormByName(record.metaMetadata.form, true).flatMap(form => { + const reqs = []; + record.metaMetadata.attachmentFields = form.attachmentFields; + _.each(form.attachmentFields, (attField) => { + const oldAttachments = record.metadata[attField]; + const newAttachments = newMetadata[attField]; + const removeIds = []; + // process removals + if (!_.isUndefined(oldAttachments) && !_.isNull(oldAttachments) && !_.isNull(newAttachments)) { + const toRemove = _.differenceBy(oldAttachments, newAttachments, 'fileId'); + _.each(toRemove, (removeAtt) => { + if (removeAtt.type == 'attachment') { + removeIds.push(removeAtt.fileId); + } + }); + } + // process additions + if (!_.isUndefined(newAttachments) && !_.isNull(newAttachments)) { + const toAdd = _.differenceBy(newAttachments, oldAttachments, 'fileId'); + _.each(toAdd, (addAtt) => { + if (addAtt.type == 'attachment') { + fileIdsAdded.push(addAtt.fileId); + } + }); + } + const req = this.addAndRemoveDatastreams(oid, fileIdsAdded, removeIds); + if (req) { + reqs.push(req); + } + }); + if (!_.isEmpty(reqs)) { + return Observable.of(reqs); + } else { + return Observable.of(null); + } + }); + } + + public removeDatastream(oid, fileId) { + const apiConfig = sails.config.record.api.removeDatastream; + const opts = this.getOptions(`${sails.config.record.baseUrl.redbox}${apiConfig.url}`, oid); + opts.url = `${opts.url}?skipReindex=true&datastreamId=${fileId}`; + return request[apiConfig.method](opts); + } + + public listDatastreams(oid, fileId) { + const apiConfig = sails.config.record.api.listDatastreams; + const opts: any = this.getOptions(`${sails.config.record.baseUrl.redbox}${apiConfig.url}`, oid); + + return Observable.fromPromise(request[apiConfig.method](opts)); + } + } } -module.exports = new Services.RedboxJavaStorage().exports(); \ No newline at end of file +module.exports = new Services.RedboxJavaStorage().exports(); diff --git a/typescript/api/services/UsersService.ts b/typescript/api/services/UsersService.ts index 46994fe8dd..982963acc4 100644 --- a/typescript/api/services/UsersService.ts +++ b/typescript/api/services/UsersService.ts @@ -327,11 +327,11 @@ export module Services { return this.getUserWithUsername(username).flatMap(user => { if (user) { - return Observable.throw(new Error('Username already exists')); + return Observable.throw(new Error(`Username already exists`)); } else { return this.findUsersWithEmail(email, null, null).flatMap(emailCheck => { if (_.size(emailCheck) > 0) { - return Observable.throw(new Error('Email already exists, it must be unique.')); + return Observable.throw(new Error(`Email already exists, it must be unique`)); } else { var newUser = { type: 'local', name: name }; if (!_.isEmpty(email)) { From 89799572cb84516e09eb5da8d36d43d1302efb89 Mon Sep 17 00:00:00 2001 From: Shilo B Date: Tue, 20 Oct 2020 12:25:42 +1000 Subject: [PATCH 16/48] Continuing backend refactor: stubbed EmailController, adjusted ExportController/RecordsService to use a readable rather than loading export results into memory. --- typescript/api/controllers/EmailController.ts | 42 ++++++++----------- .../api/controllers/ExportController.ts | 13 ++++-- typescript/api/core/RecordsService.ts | 3 +- typescript/api/services/RecordsService.ts | 3 +- 4 files changed, 30 insertions(+), 31 deletions(-) diff --git a/typescript/api/controllers/EmailController.ts b/typescript/api/controllers/EmailController.ts index 5efbef3610..fa208b36ec 100644 --- a/typescript/api/controllers/EmailController.ts +++ b/typescript/api/controllers/EmailController.ts @@ -57,30 +57,30 @@ export module Controllers { * * @param req * @param res - * + * * USAGE (ng2): var data = {}; data['data'] = 'test'; this.emailService.sendNotification('user@example.com', 'template', data, subject?, from?) .then(function (res) {console.log(`Email result: ${JSON.stringify(res)}`)}); - * - * TODO + * + * TODO * • proper email address validation * • support for multiple email addresses (trivial: make array) */ - public sendNotification(req, res) { + public async sendNotification(req, res) { if (!req.body.to){ sails.log.error("No email recipient in email notification request!"); - return; + return; } if (!req.body.template){ sails.log.error("No template specified in email notification request!"); - return; + return; } var to = req.body.to; var template = req.body.template; - + // use subject if provided, else use template default var subject; if (req.body.subject) { subject = req.body.subject; } @@ -89,26 +89,18 @@ export module Controllers { var data = {}; if (req.body.data) { data = req.body.data; } - var buildResponse = EmailService.buildFromTemplate(template, data); - - buildResponse.subscribe(buildResult => { - if (buildResult['status'] != 200) { - this.ajaxFail(req, res, buildResult['msg']); + var buildResult = await Observable.toPromise(EmailService.buildFromTemplate(template, data)); + if (buildResult.success) { + this.ajaxFail(req, res, buildResult['msg']); + } else { + var sendResult = await EmailService.sendMessage(to, buildResult['body'], subject); + if (sendResult['code'] != 200) { + this.ajaxFail(req, res, sendResult['msg']); } else { - var sendResponse = EmailService.sendMessage(to, buildResult['body'], subject); - - sendResponse.subscribe(sendResult => { - if (sendResult['code'] != 200) { - this.ajaxFail(req, res, sendResult['msg']); - } - else { - this.ajaxOk(req, res, sendResult['msg']); - } - }); + this.ajaxOk(req, res, sendResult['msg']); } - }); - + } } /** @@ -119,4 +111,4 @@ export module Controllers { } } -module.exports = new Controllers.Email().exports(); \ No newline at end of file +module.exports = new Controllers.Email().exports(); diff --git a/typescript/api/controllers/ExportController.ts b/typescript/api/controllers/ExportController.ts index 8fea640388..3ce7eb36a5 100644 --- a/typescript/api/controllers/ExportController.ts +++ b/typescript/api/controllers/ExportController.ts @@ -23,6 +23,9 @@ declare var sails; declare var _; import { Observable } from 'rxjs/Rx'; declare var RecordsService, DashboardService, BrandingService, TranslationService; +import util = require('util'); +import stream = require('stream'); +const pipeline = util.promisify(stream.pipeline); /** * Package that contains all Controllers. */ @@ -59,11 +62,13 @@ export module Controllers { const before = _.isEmpty(req.query.before) ? null : req.query.before; const after = _.isEmpty(req.query.after) ? null : req.query.after; const filename = `${TranslationService.t(`${recType}-title`)} - Exported Records.${format}`; - if (format == 'csv') { - res.set('Content-Type', 'text/csv'); + if (format == 'csv' || format == 'json') { + res.set('Content-Type', `text/${format}`); res.set('Content-Disposition', `attachment; filename="${filename}"`); - const response = await RecordsService.exportAllPlans(req.user.username, req.user.roles, brand, format, before, after, recType) - return res.send(200, response); + await pipeline( + RecordsService.exportAllPlans(req.user.username, req.user.roles, brand, format, before, after, recType), + res + ); } else { return res.send(500, 'Unsupported export format'); } diff --git a/typescript/api/core/RecordsService.ts b/typescript/api/core/RecordsService.ts index 71d3afb001..cf9f414b63 100644 --- a/typescript/api/core/RecordsService.ts +++ b/typescript/api/core/RecordsService.ts @@ -1,4 +1,5 @@ import StorageService from "./StorageService"; +import {Readable} from 'stream'; interface RecordsService extends StorageService { @@ -12,7 +13,7 @@ interface RecordsService extends StorageService { getAttachments(oid: string, labelFilterStr?: string): Promise; getRecords(workflowState, recordType, start, rows, username, roles, brand, editAccessOnly, packageType, sort) : Promise; - exportAllPlans(username, roles, brand, format, modBefore, modAfter, recType) : Promise; + exportAllPlans(username, roles, brand, format, modBefore, modAfter, recType) :Readable; // Probably to be retired or reimplemented in a different service checkRedboxRunning(): Promise; diff --git a/typescript/api/services/RecordsService.ts b/typescript/api/services/RecordsService.ts index 801486d4b2..149386210f 100644 --- a/typescript/api/services/RecordsService.ts +++ b/typescript/api/services/RecordsService.ts @@ -36,6 +36,7 @@ import { isObservable } from 'rxjs'; import StorageService from '../core/StorageService.js'; +import {Readable} from 'stream'; const util = require('util'); @@ -138,7 +139,7 @@ export module Services { return this.storageService.getRecords(workflowState, recordType, start, rows, username, roles, brand, editAccessOnly, packageType, sort); } - public exportAllPlans(username, roles, brand, format, modBefore, modAfter, recType) : Promise { + public exportAllPlans(username, roles, brand, format, modBefore, modAfter, recType) : Readable { return this.storageService.exportAllPlans(username, roles, brand, format, modBefore, modAfter, recType); } From 5724563ab1d133cd2f0ff962fa3492088afc7d84 Mon Sep 17 00:00:00 2001 From: Shilo B Date: Tue, 20 Oct 2020 15:55:45 +1000 Subject: [PATCH 17/48] More changes in backend refactor: datastream response, fixed issues. Prepared version. --- package-lock.json | 1512 +++++++++-------- package.json | 5 +- typescript/api/controllers/EmailController.ts | 28 +- .../webservice/RecordController.ts | 3 +- typescript/api/core/RecordsService.ts | 3 - typescript/api/core/StorageService.ts | 4 +- 6 files changed, 869 insertions(+), 686 deletions(-) diff --git a/package-lock.json b/package-lock.json index 92a32d0598..0ab652f3ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "redbox-portal", - "version": "1.1.1", + "version": "2.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -336,6 +336,22 @@ "safe-buffer": "^5.0.1" } }, + "@researchdatabox/sails-hook-redbox-storage-mongo": { + "version": "file:../sails-hook-redbox-storage-mongo/researchdatabox-sails-hook-redbox-storage-mongo-0.0.2.tgz", + "integrity": "sha512-7r/IqWBMsVXtm+EaCcur2/5TbvACSGLzmFQ5q81FNdiCweT0eNzvtSifOtFyPDSCrPAIUe4RIN1/ycmiOMy0NA==", + "requires": { + "json2csv": "^5.0.3", + "lodash": "^4.17.20", + "uuid": "8.3.0" + }, + "dependencies": { + "uuid": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==" + } + } + }, "@sailshq/lodash": { "version": "3.10.4", "resolved": "https://registry.npmjs.org/@sailshq/lodash/-/lodash-3.10.4.tgz", @@ -6202,6 +6218,23 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, + "json2csv": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/json2csv/-/json2csv-5.0.3.tgz", + "integrity": "sha512-e3gEZU/4fp8CVQMHlwT77RayAR7nylCzCYN7jTIbPTEqk0oTaE8GTcBudLgXrHt4ltOs9SAsbveMJT0YK/QUSg==", + "requires": { + "commander": "^6.1.0", + "jsonparse": "^1.3.1", + "lodash.get": "^4.4.2" + }, + "dependencies": { + "commander": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.1.0.tgz", + "integrity": "sha512-wl7PNrYWd2y5mp1OK/LhTlv8Ff4kQJQRXXAvF+uU/TPNiVJUxZLRYGj/B0y/lPGAVcSbJqH2Za/cvHmrPMC8mA==" + } + } + }, "json5": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.0.0.tgz", @@ -6236,6 +6269,11 @@ } } }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" + }, "jsonwebtoken": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", @@ -6829,6 +6867,11 @@ "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, "lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -11097,29 +11140,31 @@ } }, "sails-hook-grunt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/sails-hook-grunt/-/sails-hook-grunt-4.0.1.tgz", - "integrity": "sha512-y6oL7XxXl1rpSEeGHTXaZKd18FhZL7IX14gh/e4jOT8Nf8L7jLylC16qdn95g0dTO/FJftyTS4Jbl8hGn6CU4Q==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/sails-hook-grunt/-/sails-hook-grunt-3.1.1.tgz", + "integrity": "sha512-JD6e+orxbmY+PH2rCEwQ/bK8lnJkyqfi86OKHvYubyIInTEuUu+iOWrBwZhaqLmK96euK/22GOSkuPQ6X1CWdQ==", "requires": { "@sailshq/grunt-contrib-uglify": "^3.2.1", "@sailshq/lodash": "^3.10.2", - "babel-core": "6.26.3", + "babel-core": "6.26.0", "babel-polyfill": "6.26.0", - "babel-preset-env": "1.7.0", + "babel-preset-env": "1.6.1", "chalk": "1.1.3", - "grunt": "1.0.4", + "grunt": "1.0.1", "grunt-babel": "7.0.0", "grunt-cli": "1.2.0", "grunt-contrib-clean": "1.0.0", + "grunt-contrib-coffee": "1.0.0", "grunt-contrib-concat": "1.0.1", "grunt-contrib-copy": "1.0.0", - "grunt-contrib-cssmin": "2.2.1", + "grunt-contrib-cssmin": "1.0.1", + "grunt-contrib-jst": "1.0.0", "grunt-contrib-less": "1.3.0", - "grunt-contrib-watch": "1.1.0", + "grunt-contrib-watch": "1.0.0", "grunt-hash": "0.5.0", "grunt-sails-linker": "^0.10.1", - "grunt-sync": "0.8.1", - "include-all": "^4.0.3" + "grunt-sync": "0.6.2", + "include-all": "1.0.8" }, "dependencies": { "@sailshq/grunt-contrib-uglify": { @@ -11134,9 +11179,9 @@ } }, "@sailshq/lodash": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/@sailshq/lodash/-/lodash-3.10.3.tgz", - "integrity": "sha512-XTF5BtsTSiSpTnfqrCGS5Q8FvSHWCywA0oRxFAZo8E1a8k1MMFUvk3VlRk3q/SusEYwy7gvVdyt9vvNlTa2VuA==" + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@sailshq/lodash/-/lodash-3.10.2.tgz", + "integrity": "sha1-FWfUc0U2TCwuIHe8ETSHsd/mIVQ=" }, "abbrev": { "version": "1.1.1", @@ -11144,9 +11189,9 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.4.1.tgz", + "integrity": "sha512-XLmq3H/BVvW6/GbxKryGxWORz1ebilSsUDlyC27bXhWGWAZWkGwS6FLHjOlwFXNFoWFQEO/Df4u0YYd0K3BQgQ==" }, "acorn-jsx": { "version": "3.0.1", @@ -11164,25 +11209,30 @@ } }, "ajv": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", - "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "requires": { - "fast-deep-equal": "^2.0.1", + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "json-schema-traverse": "^0.3.0" } }, "ajv-keywords": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", - "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=" + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" }, "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz", + "integrity": "sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==" }, "ansi-regex": { "version": "2.1.1", @@ -11200,13 +11250,6 @@ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "requires": { "sprintf-js": "~1.0.2" - }, - "dependencies": { - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - } } }, "array-find-index": { @@ -11214,6 +11257,24 @@ "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + }, "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -11221,13 +11282,10 @@ "optional": true }, "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "optional": true, - "requires": { - "safer-buffer": "~2.1.0" - } + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "optional": true }, "assert-plus": { "version": "1.0.0", @@ -11253,9 +11311,9 @@ "optional": true }, "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", "optional": true }, "babel-code-frame": { @@ -11269,9 +11327,9 @@ } }, "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz", + "integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=", "requires": { "babel-code-frame": "^6.26.0", "babel-generator": "^6.26.0", @@ -11283,15 +11341,15 @@ "babel-traverse": "^6.26.0", "babel-types": "^6.26.0", "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", + "convert-source-map": "^1.5.0", + "debug": "^2.6.8", "json5": "^0.5.1", "lodash": "^4.17.4", "minimatch": "^3.0.4", "path-is-absolute": "^1.0.1", - "private": "^0.1.8", + "private": "^0.1.7", "slash": "^1.0.0", - "source-map": "^0.5.7" + "source-map": "^0.5.6" }, "dependencies": { "source-map": { @@ -11596,9 +11654,9 @@ } }, "babel-plugin-transform-es2015-modules-commonjs": { - "version": "6.26.2", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", - "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz", + "integrity": "sha1-DYOUApt9xqvhqX7xgeAHWN0uXYo=", "requires": { "babel-plugin-transform-strict-mode": "^6.24.1", "babel-runtime": "^6.26.0", @@ -11746,9 +11804,9 @@ } }, "babel-preset-env": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.7.0.tgz", - "integrity": "sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.6.1.tgz", + "integrity": "sha512-W6VIyA6Ch9ePMI7VptNn2wBM6dbG0eSz25HEiL40nQXCsXGTGZSTZu1Iap+cj3Q0S5a7T9+529l/5Bkvd+afNA==", "requires": { "babel-plugin-check-es2015-constants": "^6.22.0", "babel-plugin-syntax-trailing-function-commas": "^6.22.0", @@ -11777,7 +11835,7 @@ "babel-plugin-transform-es2015-unicode-regex": "^6.22.0", "babel-plugin-transform-exponentiation-operator": "^6.22.0", "babel-plugin-transform-regenerator": "^6.22.0", - "browserslist": "^3.2.6", + "browserslist": "^2.1.2", "invariant": "^2.2.2", "semver": "^5.3.0" } @@ -11855,23 +11913,63 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", "optional": true, "requires": { "tweetnacl": "^0.14.3" } }, - "body": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", - "integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=", + "body-parser": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.14.2.tgz", + "integrity": "sha1-EBXLH+LEQ4WCWVgdtTMy+NDPUPk=", + "requires": { + "bytes": "2.2.0", + "content-type": "~1.0.1", + "debug": "~2.2.0", + "depd": "~1.1.0", + "http-errors": "~1.3.1", + "iconv-lite": "0.4.13", + "on-finished": "~2.3.0", + "qs": "5.2.0", + "raw-body": "~2.1.5", + "type-is": "~1.6.10" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "iconv-lite": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=" + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + }, + "qs": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz", + "integrity": "sha1-qfMRQq9GjLcrJbMBNrokVoNJFr4=" + } + } + }, + "boom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "optional": true, "requires": { - "continuable-cache": "^0.3.1", - "error": "^7.0.0", - "raw-body": "~1.1.0", - "safe-json-parse": "~1.0.1" + "hoek": "4.x.x" } }, "brace-expansion": { @@ -11897,23 +11995,23 @@ } }, "browserslist": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.8.tgz", - "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==", + "version": "2.11.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", + "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", "requires": { - "caniuse-lite": "^1.0.30000844", - "electron-to-chromium": "^1.3.47" + "caniuse-lite": "^1.0.30000792", + "electron-to-chromium": "^1.3.30" } }, - "buffer-from": { + "builtin-modules": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" }, "bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", - "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.2.0.tgz", + "integrity": "sha1-/TVGSkA/b5EXwt42Cez/nK4ABYg=" }, "caller-path": { "version": "0.1.0", @@ -11943,9 +12041,9 @@ } }, "caniuse-lite": { - "version": "1.0.30000969", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000969.tgz", - "integrity": "sha512-Kus0yxkoAJgVc0bax7S4gLSlFifCa7MnSZL9p9VuS/HIKEL4seaqh28KIQAAO50cD/rJ5CiJkJFapkdDAlhFxQ==" + "version": "1.0.30000810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000810.tgz", + "integrity": "sha512-/0Q00Oie9C72P8zQHtFvzmkrMC3oOFUnMWjCy5F2+BE8lzICm91hQPhh0+XIsAFPKOe2Dh3pKgbRmU3EKxfldA==" }, "caseless": { "version": "0.12.0", @@ -11976,17 +12074,29 @@ "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==" }, "clean-css": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.11.tgz", - "integrity": "sha1-Ls3xRaujj1R0DybO/Q/z4D4SXWo=", + "version": "3.4.28", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.28.tgz", + "integrity": "sha1-vxlF6C/ICPVWlebd6uwBQA79A/8=", "requires": { - "source-map": "0.5.x" + "commander": "2.8.x", + "source-map": "0.4.x" }, "dependencies": { + "commander": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", + "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", + "requires": { + "graceful-readlink": ">= 1.0.0" + } + }, "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "requires": { + "amdefine": ">=0.0.4" + } } } }, @@ -12008,17 +12118,17 @@ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, - "coffeescript": { + "coffee-script": { "version": "1.10.0", - "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.10.0.tgz", - "integrity": "sha1-56qDAZF+9iGzXYo580jc3R234z4=" + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.10.0.tgz", + "integrity": "sha1-EpOLz5vhlI+gBvkuDEyegXBRCMA=" }, "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", "requires": { - "color-name": "1.1.3" + "color-name": "^1.1.1" } }, "color-name": { @@ -12032,9 +12142,9 @@ "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=" }, "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "optional": true, "requires": { "delayed-stream": "~1.0.0" @@ -12051,33 +12161,29 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", "requires": { - "buffer-from": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^2.2.2", "typedarray": "^0.0.6" } }, - "continuable-cache": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", - "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=" + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, "convert-source-map": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", - "requires": { - "safe-buffer": "~5.1.1" - } + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", + "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=" }, "core-js": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.7.tgz", - "integrity": "sha512-ydmsQxDVH7lDpYoirXft8S83ddKKfdsrsmWhtyj7xafXVLbLhKOyfD7kAi2ueFfeP7m9rNavjW59O3hLLzzC5A==" + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", + "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=" }, "core-util-is": { "version": "1.0.2", @@ -12094,6 +12200,26 @@ "which": "^1.2.9" } }, + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "optional": true, + "requires": { + "boom": "5.x.x" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "optional": true, + "requires": { + "hoek": "4.x.x" + } + } + } + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -12138,12 +12264,31 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "requires": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "optional": true }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, "detect-indent": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", @@ -12165,25 +12310,24 @@ "esutils": "^2.0.2" } }, - "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" - }, "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", "optional": true, "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "jsbn": "~0.1.0" } }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, "electron-to-chromium": { - "version": "1.3.135", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.135.tgz", - "integrity": "sha512-xXLNstRdVsisPF3pL3H9TVZo2XkMILfqtD6RiWIUmDK2sFX1Bjwqmd8LBp0Kuo2FgKO63JXPoEVGm8WyYdwP0Q==" + "version": "1.3.34", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.34.tgz", + "integrity": "sha1-2TSY9AORuwwWpgPYJBuZUUBBV+0=" }, "errno": { "version": "0.1.7", @@ -12194,19 +12338,10 @@ "prr": "~1.0.1" } }, - "error": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/error/-/error-7.0.2.tgz", - "integrity": "sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI=", - "requires": { - "string-template": "~0.2.1", - "xtend": "~4.0.0" - } - }, "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", "requires": { "is-arrayish": "^0.2.1" } @@ -12260,57 +12395,46 @@ "text-table": "~0.2.0" }, "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", "requires": { "color-convert": "^1.9.0" } }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", + "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", "requires": { - "ansi-styles": "^3.2.1", + "ansi-styles": "^3.2.0", "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "supports-color": "^5.2.0" } }, "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "requires": { - "ms": "^2.1.1" + "ms": "2.0.0" } }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==" }, "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -12320,15 +12444,14 @@ "path-is-absolute": "^1.0.0" } }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "js-yaml": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", + "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } }, "strip-ansi": { "version": "4.0.0", @@ -12339,9 +12462,9 @@ } }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", + "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", "requires": { "has-flag": "^3.0.0" } @@ -12349,42 +12472,43 @@ } }, "eslint-scope": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", - "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", "requires": { "esrecurse": "^4.1.0", "estraverse": "^4.1.1" } }, "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.3.tgz", + "integrity": "sha512-Zy3tAJDORxQZLl2baguiRU1syPERAIg0L+JB2MWorORgTu/CplzvxS9WWA7Xh4+Q+eOQihNs/1o1Xep8cvCxWQ==", "requires": { - "acorn": "^5.5.0", + "acorn": "^5.4.0", "acorn-jsx": "^3.0.0" } }, "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" }, "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", + "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", "requires": { "estraverse": "^4.0.0" } }, "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", + "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", "requires": { - "estraverse": "^4.1.0" + "estraverse": "^4.1.0", + "object-assign": "^4.0.1" } }, "estraverse": { @@ -12408,15 +12532,15 @@ "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=" }, "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", "optional": true }, "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.1.0.tgz", + "integrity": "sha512-E44iT5QVOUJBKij4IIV3uvxuNlbKS38Tw1HiupxEIHPv9qtC2PrDYohbXV5U+1jnfIXttny8gUhj+oZvflFlzA==", "requires": { "chardet": "^0.4.0", "iconv-lite": "^0.4.17", @@ -12430,9 +12554,9 @@ "optional": true }, "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=" }, "fast-json-stable-stringify": { "version": "2.0.0", @@ -12507,13 +12631,13 @@ } }, "flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", "requires": { "circular-json": "^0.3.1", + "del": "^2.0.2", "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", "write": "^0.2.1" } }, @@ -12524,26 +12648,16 @@ "optional": true }, "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "optional": true, "requires": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", + "combined-stream": "1.0.6", "mime-types": "^2.1.12" } }, - "fs-extra": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", - "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -12555,9 +12669,9 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" }, "gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.2.tgz", + "integrity": "sha1-hHIkZ3rbiHDWeSV+0ziP22HkAQU=", "requires": { "globule": "^1.0.0" } @@ -12599,20 +12713,33 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==" }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "requires": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, "globule": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", - "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz", + "integrity": "sha1-HcScaCLdnoovoAuiopUAboZkvQk=", "requires": { "glob": "~7.1.1", - "lodash": "~4.17.10", + "lodash": "~4.17.4", "minimatch": "~3.0.2" }, "dependencies": { "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -12625,9 +12752,9 @@ } }, "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, "graceful-readlink": { "version": "1.0.1", @@ -12640,11 +12767,11 @@ "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=" }, "grunt": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.0.4.tgz", - "integrity": "sha512-PYsMOrOC+MsdGEkFVwMaMyc6Ob7pKmq+deg1Sjr+vvMWp35sztfwKE7qoN51V+UEtHsyNuMcGdgMLFkBHvMxHQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.0.1.tgz", + "integrity": "sha1-6HeHZOlEsY8yuw8QuQeEdcnftWs=", "requires": { - "coffeescript": "~1.10.0", + "coffee-script": "~1.10.0", "dateformat": "~1.0.12", "eventemitter2": "~0.4.13", "exit": "~0.1.1", @@ -12652,15 +12779,14 @@ "glob": "~7.0.0", "grunt-cli": "~1.2.0", "grunt-known-options": "~1.1.0", - "grunt-legacy-log": "~2.0.0", - "grunt-legacy-util": "~1.1.1", + "grunt-legacy-log": "~1.0.0", + "grunt-legacy-util": "~1.0.0", "iconv-lite": "~0.4.13", - "js-yaml": "~3.13.0", - "minimatch": "~3.0.2", - "mkdirp": "~0.5.1", + "js-yaml": "~3.5.2", + "minimatch": "~3.0.0", "nopt": "~3.0.6", "path-is-absolute": "~1.0.0", - "rimraf": "~2.6.2" + "rimraf": "~2.2.8" } }, "grunt-babel": { @@ -12677,13 +12803,6 @@ "grunt-known-options": "~1.1.0", "nopt": "~3.0.6", "resolve": "~1.1.0" - }, - "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" - } } }, "grunt-contrib-clean": { @@ -12693,6 +12812,73 @@ "requires": { "async": "^1.5.2", "rimraf": "^2.5.1" + }, + "dependencies": { + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "requires": { + "glob": "^7.0.5" + } + } + } + }, + "grunt-contrib-coffee": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-coffee/-/grunt-contrib-coffee-1.0.0.tgz", + "integrity": "sha1-2u6wSVTxTihovMm6bq+RBf3C2kw=", + "requires": { + "chalk": "~1.0.0", + "coffee-script": "~1.10.0", + "lodash": "~4.3.0", + "uri-path": "~1.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz", + "integrity": "sha1-QchHGUZGN15qGl0Qw8oFTvn8mA0=" + }, + "chalk": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.0.0.tgz", + "integrity": "sha1-s89O0P9Tl8mcdbj2edsvUoMfltw=", + "requires": { + "ansi-styles": "^2.0.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^1.0.3", + "strip-ansi": "^2.0.1", + "supports-color": "^1.3.0" + } + }, + "has-ansi": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-1.0.3.tgz", + "integrity": "sha1-wLWxYV2eOCsP9nFp2We0JeSMpTg=", + "requires": { + "ansi-regex": "^1.1.0", + "get-stdin": "^4.0.1" + } + }, + "lodash": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", + "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=" + }, + "strip-ansi": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz", + "integrity": "sha1-32LBqpTtLxFOHQ8h/R1QSCt5pg4=", + "requires": { + "ansi-regex": "^1.0.0" + } + }, + "supports-color": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.3.1.tgz", + "integrity": "sha1-FXWN8J2P87SswwdTn6vicJXhBC0=" + } } }, "grunt-contrib-concat": { @@ -12721,41 +12907,28 @@ } }, "grunt-contrib-cssmin": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/grunt-contrib-cssmin/-/grunt-contrib-cssmin-2.2.1.tgz", - "integrity": "sha512-IXNomhQ5ekVZbDbj/ik5YccoD9khU6LT2fDXqO1+/Txjq8cp0tQKjVS8i8EAbHOrSDkL7/UD6A7b+xj98gqh9w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/grunt-contrib-cssmin/-/grunt-contrib-cssmin-1.0.1.tgz", + "integrity": "sha1-9tRSRMyH79zFIfaRjq/ZIe/YyNo=", + "requires": { + "chalk": "^1.0.0", + "clean-css": "~3.4.2", + "maxmin": "^1.1.0" + } + }, + "grunt-contrib-jst": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-jst/-/grunt-contrib-jst-1.0.0.tgz", + "integrity": "sha1-uOcDWuO2JYdYC9bYPI8MSEEGOHQ=", "requires": { "chalk": "^1.0.0", - "clean-css": "~4.1.1", - "maxmin": "^2.1.0" + "lodash": "^2.4.1" }, "dependencies": { - "gzip-size": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-3.0.0.tgz", - "integrity": "sha1-VGGI6b3DN/Zzdy+BZgRks4nc5SA=", - "requires": { - "duplexer": "^0.1.1" - } - }, - "maxmin": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-2.1.0.tgz", - "integrity": "sha1-TTsiCQPZXu5+t6x/qGTnLcCaMWY=", - "requires": { - "chalk": "^1.0.0", - "figures": "^1.0.1", - "gzip-size": "^3.0.0", - "pretty-bytes": "^3.0.0" - } - }, - "pretty-bytes": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-3.0.1.tgz", - "integrity": "sha1-J9AAjXeAY6C0gRuzXHnxvV1fvM8=", - "requires": { - "number-is-nan": "^1.0.0" - } + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" } } }, @@ -12771,23 +12944,20 @@ } }, "grunt-contrib-watch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz", - "integrity": "sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.0.0.tgz", + "integrity": "sha1-hKGnodar0m7VaEE0lscxM+mQAY8=", "requires": { - "async": "^2.6.0", - "gaze": "^1.1.0", - "lodash": "^4.17.10", - "tiny-lr": "^1.1.1" + "async": "^1.5.0", + "gaze": "^1.0.0", + "lodash": "^3.10.1", + "tiny-lr": "^0.2.1" }, "dependencies": { - "async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", - "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", - "requires": { - "lodash": "^4.17.11" - } + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" } } }, @@ -12797,70 +12967,64 @@ "integrity": "sha1-mHgdeZ90spU4aS9Yxh1QZIwb0p4=" }, "grunt-known-options": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.1.tgz", - "integrity": "sha512-cHwsLqoighpu7TuYj5RonnEuxGVFnztcUqTqp5rXFGYL4OuPFofwC4Ycg7n9fYwvK6F5WbYgeVOwph9Crs2fsQ==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.0.tgz", + "integrity": "sha1-pCdO6zL6dl2lp6OxcSYXzjsUQUk=" }, "grunt-legacy-log": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-2.0.0.tgz", - "integrity": "sha512-1m3+5QvDYfR1ltr8hjiaiNjddxGdQWcH0rw1iKKiQnF0+xtgTazirSTGu68RchPyh1OBng1bBUjLmX8q9NpoCw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-1.0.0.tgz", + "integrity": "sha1-+4bxgJhHvAfcR4Q/ns1srLYt8tU=", "requires": { "colors": "~1.1.2", - "grunt-legacy-log-utils": "~2.0.0", + "grunt-legacy-log-utils": "~1.0.0", "hooker": "~0.2.3", - "lodash": "~4.17.5" + "lodash": "~3.10.1", + "underscore.string": "~3.2.3" + }, + "dependencies": { + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + } } }, "grunt-legacy-log-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.0.1.tgz", - "integrity": "sha512-o7uHyO/J+i2tXG8r2bZNlVk20vlIFJ9IEYyHMCQGfWYru8Jv3wTqKZzvV30YW9rWEjq0eP3cflQ1qWojIe9VFA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-1.0.0.tgz", + "integrity": "sha1-p7ji0Ps1taUPSvmG/BEnSevJbz0=", "requires": { - "chalk": "~2.4.1", - "lodash": "~4.17.10" + "chalk": "~1.1.1", + "lodash": "~4.3.0" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } + "lodash": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", + "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=" } } }, "grunt-legacy-util": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.1.1.tgz", - "integrity": "sha512-9zyA29w/fBe6BIfjGENndwoe1Uy31BIXxTH3s8mga0Z5Bz2Sp4UCjkeyv2tI449ymkx3x26B+46FV4fXEddl5A==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.0.0.tgz", + "integrity": "sha1-OGqnjcbtUJhsKxiVcmWxtIq7m4Y=", "requires": { "async": "~1.5.2", "exit": "~0.1.1", "getobject": "~0.1.0", "hooker": "~0.2.3", - "lodash": "~4.17.10", - "underscore.string": "~3.3.4", - "which": "~1.3.0" + "lodash": "~4.3.0", + "underscore.string": "~3.2.3", + "which": "~1.2.1" + }, + "dependencies": { + "lodash": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", + "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=" + } } }, "grunt-sails-linker": { @@ -12869,28 +13033,14 @@ "integrity": "sha1-DSz1RzwDuuu2zmwd4eWBY9OsjQY=" }, "grunt-sync": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/grunt-sync/-/grunt-sync-0.8.1.tgz", - "integrity": "sha512-xoOOgip7LcrwSUbyu27IbWZefjL7M0UNN5V7b0U90REZf1IpDytPWVLNh5dbb/IJUQng3UFyHCUCWPwPDMzipw==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/grunt-sync/-/grunt-sync-0.6.2.tgz", + "integrity": "sha1-2ay2W0IF0Be9ZGLkn+wtkHGs5Hs=", "requires": { - "fs-extra": "6.0.1", - "glob": "7.0.5", - "md5-file": "2.0.3" - }, - "dependencies": { - "glob": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.5.tgz", - "integrity": "sha1-tCAqaQmbu00pKnwblbZoK2fr3JU=", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.2", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } + "glob": "^7.0.5", + "lodash": "^4.14.2", + "md5-file": "^2.0.3", + "promised-io": "0.3.5" } }, "gzip-size": { @@ -12909,12 +13059,12 @@ "optional": true }, "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "optional": true, "requires": { - "ajv": "^6.5.5", + "ajv": "^5.1.0", "har-schema": "^2.0.0" } }, @@ -12931,6 +13081,24 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, + "hawk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "optional": true, + "requires": { + "boom": "4.x.x", + "cryptiles": "3.x.x", + "hoek": "4.x.x", + "sntp": "2.x.x" + } + }, + "hoek": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==", + "optional": true + }, "home-or-tmp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", @@ -12946,14 +13114,23 @@ "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=" }, "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", + "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==" + }, + "http-errors": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", + "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", + "requires": { + "inherits": "~2.0.1", + "statuses": "1" + } }, "http-parser-js": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.0.tgz", - "integrity": "sha512-cZdEF7r4gfRIq7ezX9J0T+kQmJNOub71dWbgAXVHDct80TKP4MCETtZQ31xyv38UwgzkWPYF/Xc0ge55dW9Z9w==" + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", + "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=" }, "http-signature": { "version": "1.2.0", @@ -12967,17 +13144,14 @@ } }, "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" }, "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz", + "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==" }, "image-size": { "version": "0.4.0", @@ -12991,12 +13165,18 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "include-all": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/include-all/-/include-all-4.0.3.tgz", - "integrity": "sha1-ZfBujxGJSxp7XsH8l+azOS98+nU=", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/include-all/-/include-all-1.0.8.tgz", + "integrity": "sha1-6LuEsFcniiLPlEMZA32XAMGKQ3k=", "requires": { - "@sailshq/lodash": "^3.10.2", - "merge-dictionaries": "^0.0.3" + "lodash": "3.10.1" + }, + "dependencies": { + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + } } }, "indent-string": { @@ -13048,21 +13228,21 @@ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", "requires": { "color-convert": "^1.9.0" } }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", + "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", "requires": { - "ansi-styles": "^3.2.1", + "ansi-styles": "^3.2.0", "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "supports-color": "^5.2.0" } }, "figures": { @@ -13082,9 +13262,9 @@ } }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", + "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", "requires": { "has-flag": "^3.0.0" } @@ -13092,9 +13272,9 @@ } }, "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.3.tgz", + "integrity": "sha512-7Z5PPegwDTyjbaeCnV0efcyS6vdKAU51kpEmS7QFib3P4822l8ICYyMn7qvJnc+WzLoDsuI9gPMKbJ8pCu8XtA==", "requires": { "loose-envify": "^1.0.0" } @@ -13104,6 +13284,14 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "requires": { + "builtin-modules": "^1.0.0" + } + }, "is-finite": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", @@ -13117,6 +13305,27 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=" + }, + "is-path-in-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", + "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "requires": { + "path-is-inside": "^1.0.1" + } + }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", @@ -13160,12 +13369,12 @@ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" }, "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.5.5.tgz", + "integrity": "sha1-A3fDgBfKvHMisNH7zSWkkWQfL74=", "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^1.0.2", + "esprima": "^2.6.0" } }, "jsbn": { @@ -13186,9 +13395,9 @@ "optional": true }, "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -13211,14 +13420,6 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "requires": { - "graceful-fs": "^4.1.6" - } - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -13264,9 +13465,9 @@ } }, "livereload-js": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz", - "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.3.0.tgz", + "integrity": "sha512-j1R0/FeGa64Y+NmqfZhyoVRzcFlOZ8sNlKzHjh4VvLULFACZhn68XrX5DFg2FhMvSMJmROuFxRSa560ECWKBMg==" }, "load-json-file": { "version": "1.1.0", @@ -13281,9 +13482,9 @@ } }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" }, "lodash._baseassign": { "version": "3.2.0", @@ -13345,11 +13546,11 @@ } }, "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", + "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" + "js-tokens": "^3.0.0" } }, "loud-rejection": { @@ -13362,9 +13563,9 @@ } }, "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", + "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -13387,9 +13588,14 @@ } }, "md5-file": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-2.0.3.tgz", - "integrity": "sha1-SgULUuQLVHfQmUO/n9fx/4oonNE=" + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-2.0.7.tgz", + "integrity": "sha1-MH94vQTMsFTkZ+xmHPpamv3J8hA=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "meow": { "version": "3.7.0", @@ -13408,14 +13614,6 @@ "trim-newlines": "^1.0.0" } }, - "merge-dictionaries": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/merge-dictionaries/-/merge-dictionaries-0.0.3.tgz", - "integrity": "sha1-xN5NWNuyXkwoI6owy44VOQaet1c=", - "requires": { - "@sailshq/lodash": "^3.10.2" - } - }, "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -13423,18 +13621,16 @@ "optional": true }, "mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", - "optional": true + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" }, "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", - "optional": true, + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", "requires": { - "mime-db": "1.40.0" + "mime-db": "~1.33.0" } }, "mimic-fn": { @@ -13561,12 +13757,12 @@ } }, "normalize-package-data": { - "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==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", "requires": { "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", + "is-builtin-module": "^1.0.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" } @@ -13577,9 +13773,9 @@ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", "optional": true }, "object-assign": { @@ -13587,6 +13783,14 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -13639,6 +13843,11 @@ "error-ex": "^1.2.0" } }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, "path-exists": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", @@ -13657,11 +13866,6 @@ "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" }, - "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==" - }, "path-type": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", @@ -13726,9 +13930,9 @@ "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=" }, "promise": { "version": "7.3.1", @@ -13739,6 +13943,11 @@ "asap": "~2.0.3" } }, + "promised-io": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/promised-io/-/promised-io-0.3.5.tgz", + "integrity": "sha1-StIXuzZYvKrplGsXqGaOzYUeE1Y=" + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -13750,35 +13959,37 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, - "psl": { - "version": "1.1.31", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", - "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", - "optional": true - }, "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "optional": true }, "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "optional": true }, "raw-body": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", - "integrity": "sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", + "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=", "requires": { - "bytes": "1", - "string_decoder": "0.10" + "bytes": "2.4.0", + "iconv-lite": "0.4.13", + "unpipe": "1.0.0" }, "dependencies": { - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + "bytes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", + "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" + }, + "iconv-lite": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=" } } }, @@ -13802,16 +14013,16 @@ } }, "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "integrity": "sha512-vuYxeWYM+fde14+rajzqgeohAI7YoJcHE7kXDAc4Nk0EbuKnJfqtY9YtRkLo/tqkuF7MsBQRhPnPeyjYITp3ZQ==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", + "string_decoder": "~1.0.3", "util-deprecate": "~1.0.1" } }, @@ -13825,9 +14036,9 @@ } }, "regenerate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==" + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", + "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==" }, "regenerator-runtime": { "version": "0.11.1", @@ -13883,31 +14094,33 @@ } }, "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "version": "2.83.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", + "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", "optional": true, "requires": { "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", + "aws4": "^1.6.0", "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", + "combined-stream": "~1.0.5", + "extend": "~3.0.1", "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", + "form-data": "~2.3.1", + "har-validator": "~5.0.3", + "hawk": "~6.0.2", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", + "mime-types": "~2.1.17", + "oauth-sign": "~0.8.2", "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", + "qs": "~6.5.1", + "safe-buffer": "^5.1.1", + "stringstream": "~0.0.5", + "tough-cookie": "~2.3.3", "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" + "uuid": "^3.1.0" } }, "require-uncached": { @@ -13920,12 +14133,9 @@ } }, "resolve": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", - "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", - "requires": { - "path-parse": "^1.0.6" - } + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" }, "resolve-from": { "version": "1.0.1", @@ -13942,27 +14152,9 @@ } }, "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "requires": { - "glob": "^7.1.3" - }, - "dependencies": { - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=" }, "run-async": { "version": "2.3.0", @@ -13986,24 +14178,14 @@ } }, "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-json-parse": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", - "integrity": "sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" }, "shebang-command": { "version": "1.2.0", @@ -14036,6 +14218,15 @@ "is-fullwidth-code-point": "^2.0.0" } }, + "sntp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "optional": true, + "requires": { + "hoek": "4.x.x" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -14057,42 +14248,32 @@ } }, "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "spdx-license-ids": "^1.0.2" } }, - "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==" - }, "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==", - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", + "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=" }, "spdx-license-ids": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", - "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", + "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=" }, "sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", "optional": true, "requires": { "asn1": "~0.2.3", @@ -14102,14 +14283,13 @@ "ecc-jsbn": "~0.1.1", "getpass": "^0.1.1", "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" } }, - "string-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", - "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=" + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" }, "string-width": { "version": "2.1.1", @@ -14136,13 +14316,19 @@ } }, "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "requires": { "safe-buffer": "~5.1.0" } }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "optional": true + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -14178,12 +14364,12 @@ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" }, "table": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", - "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", "requires": { - "ajv": "^6.0.1", - "ajv-keywords": "^3.0.0", + "ajv": "^5.2.3", + "ajv-keywords": "^2.1.0", "chalk": "^2.1.0", "lodash": "^4.17.4", "slice-ansi": "1.0.0", @@ -14191,27 +14377,27 @@ }, "dependencies": { "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", "requires": { "color-convert": "^1.9.0" } }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", + "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", "requires": { - "ansi-styles": "^3.2.1", + "ansi-styles": "^3.2.0", "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "supports-color": "^5.2.0" } }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", + "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", "requires": { "has-flag": "^3.0.0" } @@ -14229,30 +14415,35 @@ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "tiny-lr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", - "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-0.2.1.tgz", + "integrity": "sha1-s/26gC5dVqM8L28QeUsy5Hescp0=", "requires": { - "body": "^5.1.0", - "debug": "^3.1.0", + "body-parser": "~1.14.0", + "debug": "~2.2.0", "faye-websocket": "~0.10.0", - "livereload-js": "^2.3.0", - "object-assign": "^4.1.0", - "qs": "^6.4.0" + "livereload-js": "^2.2.0", + "parseurl": "~1.3.0", + "qs": "~5.1.0" }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "requires": { - "ms": "^2.1.1" + "ms": "0.7.1" } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + }, + "qs": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-5.1.0.tgz", + "integrity": "sha1-TZMuXH6kEcynajEtOaYGIA/VDNk=" } } }, @@ -14270,21 +14461,12 @@ "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" }, "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", "optional": true, "requires": { - "psl": "^1.1.24", "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "optional": true - } } }, "trim-newlines": { @@ -14320,6 +14502,15 @@ "prelude-ls": "~1.1.2" } }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -14335,26 +14526,14 @@ } }, "underscore.string": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.5.tgz", - "integrity": "sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg==", - "requires": { - "sprintf-js": "^1.0.3", - "util-deprecate": "^1.0.2" - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.2.3.tgz", + "integrity": "sha1-gGmSYzZl1eX8tNsfs6hi62jp5to=" }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "requires": { - "punycode": "^2.1.0" - } + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, "uri-path": { "version": "1.0.0", @@ -14367,18 +14546,18 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", "optional": true }, "validate-npm-package-license": { - "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==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", + "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "spdx-correct": "~1.0.0", + "spdx-expression-parse": "~1.0.0" } }, "verror": { @@ -14407,9 +14586,9 @@ "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==" }, "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", "requires": { "isexe": "^2.0.0" } @@ -14432,11 +14611,6 @@ "mkdirp": "^0.5.1" } }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" - }, "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", diff --git a/package.json b/package.json index 15776bb3d0..79e96245d3 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,10 @@ { "name": "redbox-portal", - "version": "1.1.1", + "version": "2.0.0-alpha", "description": "ReDBox 2 Portal", "keywords": [], "dependencies": { + "@researchdatabox/sails-hook-redbox-storage-mongo": "0.0.2-alpha", "@sailshq/upgrade": "^1.0.9", "@uppy/core": "^1.13.1", "@uppy/dashboard": "^1.12.5", @@ -51,7 +52,7 @@ "rxjs-compat": "6.6.2", "sails": "^1.2.5", "sails-hook-autoreload": "^1.1.0", - "sails-hook-grunt": "^4.0.1", + "sails-hook-grunt": "3.1.1", "sails-hook-orm": "^3.0.1", "sails-hook-sockets": "^2.0.0", "sails-mongo": "^1.2.0", diff --git a/typescript/api/controllers/EmailController.ts b/typescript/api/controllers/EmailController.ts index fa208b36ec..2358c86265 100644 --- a/typescript/api/controllers/EmailController.ts +++ b/typescript/api/controllers/EmailController.ts @@ -69,7 +69,7 @@ export module Controllers { * • support for multiple email addresses (trivial: make array) */ - public async sendNotification(req, res) { + public sendNotification(req, res) { if (!req.body.to){ sails.log.error("No email recipient in email notification request!"); return; @@ -89,18 +89,26 @@ export module Controllers { var data = {}; if (req.body.data) { data = req.body.data; } - var buildResult = await Observable.toPromise(EmailService.buildFromTemplate(template, data)); - if (buildResult.success) { - this.ajaxFail(req, res, buildResult['msg']); - } else { - var sendResult = await EmailService.sendMessage(to, buildResult['body'], subject); - if (sendResult['code'] != 200) { - this.ajaxFail(req, res, sendResult['msg']); + var buildResponse = EmailService.buildFromTemplate(template, data); + + buildResponse.subscribe(buildResult => { + if (buildResult['status'] != 200) { + this.ajaxFail(req, res, buildResult['msg']); } else { - this.ajaxOk(req, res, sendResult['msg']); + var sendResponse = EmailService.sendMessage(to, buildResult['body'], subject); + + sendResponse.subscribe(sendResult => { + if (sendResult['code'] != 200) { + this.ajaxFail(req, res, sendResult['msg']); + } + else { + this.ajaxOk(req, res, sendResult['msg']); + } + }); } - } + }); + } /** diff --git a/typescript/api/controllers/webservice/RecordController.ts b/typescript/api/controllers/webservice/RecordController.ts index ba298a32b6..f629bedaec 100644 --- a/typescript/api/controllers/webservice/RecordController.ts +++ b/typescript/api/controllers/webservice/RecordController.ts @@ -38,6 +38,7 @@ import controller = require('../../core/CoreController.js'); import RecordsService from '../../core/RecordsService.js'; import SearchService from '../../core/SearchService.js'; import DatastreamService from '../../core/DatastreamService.js'; +import DatastreamServiceResponse from '../../core/DatastreamServiceResponse'; const UUIDGenerator = require('uuid/v4'); export module Controllers { @@ -461,7 +462,7 @@ export module Controllers { try { const reqs = this.DatastreamService.addDatastreams(oid, fileIds); return Observable.fromPromise(reqs) - .subscribe(result => { + .subscribe((result:DatastreamServiceResponse) => { sails.log.verbose(`Done with updating streams and returning response...`); if (result.isSuccessful()) { sails.log.verbose("Presuming success..."); diff --git a/typescript/api/core/RecordsService.ts b/typescript/api/core/RecordsService.ts index cf9f414b63..b4e321f61b 100644 --- a/typescript/api/core/RecordsService.ts +++ b/typescript/api/core/RecordsService.ts @@ -1,5 +1,4 @@ import StorageService from "./StorageService"; -import {Readable} from 'stream'; interface RecordsService extends StorageService { @@ -12,8 +11,6 @@ interface RecordsService extends StorageService { updateWorkflowStep(currentRec, nextStep): void; getAttachments(oid: string, labelFilterStr?: string): Promise; - getRecords(workflowState, recordType, start, rows, username, roles, brand, editAccessOnly, packageType, sort) : Promise; - exportAllPlans(username, roles, brand, format, modBefore, modAfter, recType) :Readable; // Probably to be retired or reimplemented in a different service checkRedboxRunning(): Promise; diff --git a/typescript/api/core/StorageService.ts b/typescript/api/core/StorageService.ts index fc98866b1b..1064f242dc 100644 --- a/typescript/api/core/StorageService.ts +++ b/typescript/api/core/StorageService.ts @@ -1,3 +1,5 @@ +import {Readable} from 'stream'; + interface StorageService{ create(brand, record, recordType, user?, triggerPreSaveTriggers?: boolean, triggerPostSaveTriggers?: boolean):Promise; @@ -10,7 +12,7 @@ interface StorageService{ updateNotificationLog(oid, record, options): Promise; getRecords(workflowState, recordType, start, rows, username, roles, brand, editAccessOnly, packageType, sort): Promise; - exportAllPlans(username, roles, brand, format, modBefore, modAfter, recType): Promise; + exportAllPlans(username, roles, brand, format, modBefore, modAfter, recType): Readable; } export default StorageService From dfa6d09b50783051ff7811ed69a322965aa436a3 Mon Sep 17 00:00:00 2001 From: Shilo B Date: Wed, 21 Oct 2020 11:49:52 +1000 Subject: [PATCH 18/48] Fixed new man tests. --- package-lock.json | 25 ++--- .../docker-compose.mocha.yml | 12 --- .../docker-compose.newman.yml | 12 --- .../integration-testing/docker-compose.yml | 12 --- test/postman/local.environment.json | 2 +- test/postman/test-collection.json | 100 +++++++++--------- 6 files changed, 64 insertions(+), 99 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0ab652f3ec..2199a35d48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "redbox-portal", - "version": "2.0.0", + "version": "2.0.0-alpha", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -317,9 +317,9 @@ "dev": true }, "@postman/form-data": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.0.tgz", - "integrity": "sha512-6x1UHKQ45Sv5yLFjqhhtyk3YGOF9677RVRQjfr32Bkt45pH8yIlqcpPxiIR4/ZEs3GFk5vl5j9ymmdLTt0HR6Q==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.1.tgz", + "integrity": "sha512-vjh8Q2a8S6UCm/KKs31XFJqEEgmbjBmpPNVV2eVav6905wyFAwaUOBGA1NPBI4ERH9MMZc6w0umFgM6WbEPMdg==", "dev": true, "requires": { "asynckit": "^0.4.0", @@ -337,8 +337,9 @@ } }, "@researchdatabox/sails-hook-redbox-storage-mongo": { - "version": "file:../sails-hook-redbox-storage-mongo/researchdatabox-sails-hook-redbox-storage-mongo-0.0.2.tgz", - "integrity": "sha512-7r/IqWBMsVXtm+EaCcur2/5TbvACSGLzmFQ5q81FNdiCweT0eNzvtSifOtFyPDSCrPAIUe4RIN1/ycmiOMy0NA==", + "version": "0.0.2-alpha", + "resolved": "https://registry.npmjs.org/@researchdatabox/sails-hook-redbox-storage-mongo/-/sails-hook-redbox-storage-mongo-0.0.2-alpha.tgz", + "integrity": "sha512-EVokF0CXqJL5LyoMW5LrxQKu7rct+v8nc/P20dVWyV8WRlQQpyjcwmfREodjcb77PGTFqbIJiBTzkqVX/QkvJQ==", "requires": { "json2csv": "^5.0.3", "lodash": "^4.17.20", @@ -9285,9 +9286,9 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, "postcss": { - "version": "7.0.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz", - "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==", + "version": "7.0.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", + "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -9450,9 +9451,9 @@ }, "dependencies": { "http-signature": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.4.tgz", - "integrity": "sha512-CbG3io8gUSIxNNSgq+XMjgpTMzAeVRipxVXjuGrDhH5M1a2kZ03w20s8FCLR1NjnnJj10KbvabvckmtQcYNb9g==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.5.tgz", + "integrity": "sha512-NwoTQYSJoFt34jSBbwzDHDofoA61NGXzu6wXh95o1Ry62EnmKjXb/nR/RknLeZ3G/uGwrlKNY2z7uPt+Cdl7Tw==", "dev": true, "requires": { "assert-plus": "^1.0.0", diff --git a/support/integration-testing/docker-compose.mocha.yml b/support/integration-testing/docker-compose.mocha.yml index 8b16d8c9fc..99dab0c945 100644 --- a/support/integration-testing/docker-compose.mocha.yml +++ b/support/integration-testing/docker-compose.mocha.yml @@ -33,15 +33,3 @@ services: - mongodb ports: - "27017:27017" - redbox: - image: qcifengineering/redbox:2.x - expose: - - "9000" - environment: - - RB_API_KEY=c8e844fc-8550-497f-b970-7900ec8741ca - networks: - main: - aliases: - - redbox - ports: - - "9000:9000" diff --git a/support/integration-testing/docker-compose.newman.yml b/support/integration-testing/docker-compose.newman.yml index 3277bdce16..db06b6fd1a 100644 --- a/support/integration-testing/docker-compose.newman.yml +++ b/support/integration-testing/docker-compose.newman.yml @@ -33,15 +33,3 @@ services: - mongodb ports: - "27017:27017" - redbox: - image: qcifengineering/redbox:2.x - expose: - - "9000" - environment: - - RB_API_KEY=c8e844fc-8550-497f-b970-7900ec8741ca - networks: - main: - aliases: - - redbox - ports: - - "9000:9000" diff --git a/support/integration-testing/docker-compose.yml b/support/integration-testing/docker-compose.yml index 37a78c36e3..fae8dbb781 100644 --- a/support/integration-testing/docker-compose.yml +++ b/support/integration-testing/docker-compose.yml @@ -33,15 +33,3 @@ services: - mongodb ports: - "27017:27017" - redbox: - image: qcifengineering/redbox:2.x - expose: - - "9000" - environment: - - RB_API_KEY=c8e844fc-8550-497f-b970-7900ec8741ca - networks: - main: - aliases: - - redbox - ports: - - "9000:9000" diff --git a/test/postman/local.environment.json b/test/postman/local.environment.json index 2097f886be..8fd0393fd5 100644 --- a/test/postman/local.environment.json +++ b/test/postman/local.environment.json @@ -81,7 +81,7 @@ }, { "key": "researcherPassword", - "value": "Qw1234567!", + "value": "!7654321wQ", "description": { "content": "", "type": "text/plain" diff --git a/test/postman/test-collection.json b/test/postman/test-collection.json index 1df53b8787..28b3276ccc 100644 --- a/test/postman/test-collection.json +++ b/test/postman/test-collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "62b4f908-0978-4e8d-ba0b-5d06e9908eb8", + "_postman_id": "7b31db0b-a4cb-4a8b-b8a5-bd99cb7ca23d", "name": "Redbox Portal API - With tests", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -14,7 +14,7 @@ { "listen": "test", "script": { - "id": "ca5095f8-0e14-42d9-915b-239780a9cff1", + "id": "c6679db8-156d-4591-ae8e-089f7c5ea8de", "exec": [ "", "pm.test(\"Status code is 201\", function () {", @@ -37,7 +37,7 @@ { "listen": "prerequest", "script": { - "id": "dfe55471-c89f-4615-b738-64948ff1890c", + "id": "6639f6d9-92b8-46be-8c8d-c7bfbaba30cd", "exec": [ "" ], @@ -87,7 +87,7 @@ { "listen": "test", "script": { - "id": "4bd728dc-3328-4005-81d7-c5341831ea37", + "id": "9d3088cb-a505-47e5-b2f9-02b126f83f29", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -140,7 +140,7 @@ { "listen": "test", "script": { - "id": "47977f8c-5914-41a8-90b4-7031ee68a435", + "id": "458bb2ca-c75e-4ee1-b48e-c7db4672f58e", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -195,7 +195,7 @@ { "listen": "test", "script": { - "id": "98c67002-0202-4d5e-92e9-468cca58687f", + "id": "67c12973-2c76-4d4e-9efa-fdea57611bd8", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -251,7 +251,7 @@ { "listen": "test", "script": { - "id": "c9ae57ec-f8ac-463e-a87f-22441d35db82", + "id": "d364ad2b-a452-4dd3-88e3-95496f0d1b2f", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -304,7 +304,7 @@ { "listen": "test", "script": { - "id": "2d1d1477-8861-44d1-ae56-a1bb8542bcf9", + "id": "d5215d9f-56e4-4b7f-8347-1ab9a4b48f88", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -361,7 +361,7 @@ { "listen": "test", "script": { - "id": "4587ff92-23d6-4701-a891-55287681f09a", + "id": "a8c30867-8985-4411-9743-5c2ac21d29ea", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -423,7 +423,7 @@ { "listen": "test", "script": { - "id": "5da12ebf-2ba3-4a30-8490-ffed3910ab35", + "id": "396e75bc-3b89-4567-a707-e7850aafa56c", "exec": [ "postman.setGlobalVariable(\"cookie\",postman.getResponseHeader(\"set-cookie\") );", "", @@ -518,7 +518,7 @@ { "listen": "test", "script": { - "id": "8663b578-ff18-4545-9e43-0f6926b2bf52", + "id": "290f42ee-a8bc-40d4-bf23-7d1fd2b1c613", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -565,7 +565,7 @@ } ], "url": { - "raw": "{{host}}/default/rdmp/listRecords?recordType=rdmp&state=draft&sort=date_object_modified:-1&start=0&rows=10", + "raw": "{{host}}/default/rdmp/listRecords?recordType=rdmp&state=draft&sort=\"lastSaveDate\":-1&start=0&rows=10", "host": [ "{{host}}" ], @@ -585,7 +585,7 @@ }, { "key": "sort", - "value": "date_object_modified:-1" + "value": "\"lastSaveDate\":-1" }, { "key": "start", @@ -606,7 +606,7 @@ { "listen": "test", "script": { - "id": "d0e457d1-e6b2-46c6-aac6-b6795963b9d7", + "id": "9d0cce74-a03f-4180-8838-f7d1e6988af1", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -683,7 +683,7 @@ { "listen": "test", "script": { - "id": "a80f2565-b2cd-4d9e-bf61-0c8554dab274", + "id": "5358479e-8fc9-4547-b72f-4ed6318ec4a6", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -760,7 +760,7 @@ { "listen": "test", "script": { - "id": "a0d8a57d-6dda-481d-a04a-5e94771c9ee0", + "id": "b32dc7cf-fe3b-4e4d-9561-6e8302da8e22", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -849,7 +849,7 @@ { "listen": "test", "script": { - "id": "c3452a4e-97f5-4f45-9839-05602b45e876", + "id": "2ce63735-1fdf-4448-9b86-dd9220cc0216", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -939,7 +939,7 @@ { "listen": "test", "script": { - "id": "0e343046-1cb1-4dc0-a9fa-ba9ff1070482", + "id": "b508583c-a365-4325-8ff0-a13f43977692", "exec": [ "// \"contributor_ci\": {", " // \"text_full_name\": \"Alberto Zweinstein\",", @@ -1028,7 +1028,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"records\": [\n {\n \"oid\": \"{{dmpOid}}\",\n \"title\": \"Andrew's Postman test\",\n \"metadata\": {\n \"metaMetadata\": {\n \"brandId\": \"{{brandId}}\",\n \"type\": \"rdmp\",\n \"createdBy\": \"admin\",\n \"form\": \"default-1.0-draft\"\n },\n \"metadata\": {\n \"title\": \"Andrew's Postman test\",\n \"dc:identifier\": \"http://purl.org/au-research/grants/nhmrc/566728\",\n \"description\": \"Movement of molecules within cells by a process known as membrane transport is critical for normal cell function and also exploited by bacteria to promote infection. The pathway that connects the import pathway to the export pathway is essential for the function of a large number of proteins, however this connecting pathway is poorly characterised. This study will define the machinery of this trafficking pathway, which will provide the ability to modulate biological processes and cytotoxicity.\",\n \"finalKeywords\": [\n \"270199\",\n \"Golgi apparatus\",\n \"endocytosis\",\n \"membrane transport\",\n \"protein trafficking\",\n \"secretion\",\n \"HIV-AIDS\",\n \"bacterial pathogen\",\n \"hormone secretion\",\n \"immune development\",\n \"lysosomal storage disorder\"\n ],\n \"contributor_ci\": {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n },\n \"contributor_data_manager\": {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n },\n \"contributors\": [\n {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n }\n ],\n \"vivo:Dataset_redbox:DataCollectionMethodology\": \"The data collection methodology\",\n \"vivo:Dataset_dc_format\": \"xls\",\n \"vivo:Dataset_dc:location_rdf:PlainLiteral\": \"eResearch Store network drive\",\n \"vivo:Dataset_dc:source_dc:location_rdf:PlainLiteral\": \"shared university network drive (e.g. G, H, etc)\",\n \"vivo:Dataset_dc:extent\": \"100GB - 2TB\",\n \"redbox:retentionPeriod_dc:date\": \"1year\",\n \"dc:rightsHolder_dc:name\": \"myUni\",\n \"dc:accessRights\": \"permission from the data manager\",\n \"authorization\": []\n },\n \"workflow\": {\n \"stage\": \"draft\",\n \"stageLabel\": \"Draft\"\n },\n \"authorization\": {\n \"viewRoles\": [\n \"Admin\",\n \"Librarians\"\n ],\n \"editRoles\": [\n \"Admin\",\n \"Librarians\"\n ],\n \"view\": [],\n \"edit\": [],\n \"viewPending\": [\n \"notAReal@email.edu.au\"\n ],\n \"editPending\": [\n \"notAReal@email.edu.au\"\n ]\n },\n \"redboxOid\": \"2c7c2dc046cc5834b0334445e7245b47\",\n \"packageType\": [\n \"rdmp\"\n ],\n \"date_object_created\": [\n \"2018-11-27T01:09:00.143Z\"\n ],\n \"date_object_modified\": [\n \"2018-11-27T01:09:00.654Z\"\n ]\n },\n \"dateCreated\": \"November 27, 2018 11:39 AM\",\n \"dateModified\": \"November 27, 2018 11:39 AM\",\n \"hasEditAccess\": true,\n \"dashboardTitle\": \"Andrew's Postman test\",\n \"selected\": true\n }\n ],\n \"role\": \"chiefInvestigator\",\n \"updateData\": {\n \"text_full_name\": \"Alberto Zweinstein\",\n \"storage_id\": \"23c3f253fa53809e2d986e69e2537de8\",\n \"email\": \"alberto.zweinstein@example.edu.au\",\n \"full_name_honorific\": \"Dr Alberto Zweinstein\",\n \"given_name\": \"Alberto\",\n \"family_name\": \"Zweinstein\",\n \"honorific\": \"Dr\",\n \"full_name_family_name_first\": \"Zweinstein, Alberto\"\n }\n}" + "raw": "{\n \"records\": [\n {\n \"oid\": \"{{dmpOid}}\"\n }\n ],\n \"role\": \"chiefInvestigator\",\n \"updateData\": {\n \"text_full_name\": \"Alberto Zweinstein\",\n \"storage_id\": \"23c3f253fa53809e2d986e69e2537de8\",\n \"email\": \"alberto.zweinstein@example.edu.au\",\n \"full_name_honorific\": \"Dr Alberto Zweinstein\",\n \"given_name\": \"Alberto\",\n \"family_name\": \"Zweinstein\",\n \"honorific\": \"Dr\",\n \"full_name_family_name_first\": \"Zweinstein, Alberto\"\n }\n}" }, "url": { "raw": "http://localhost:1500/default/rdmp/record/responsibility/update", @@ -1054,7 +1054,7 @@ { "listen": "test", "script": { - "id": "ada7d6f4-1304-45a1-acb9-f444747c45cf", + "id": "775bafd8-2df3-45d6-8fe3-ddff1cd51ace", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1128,7 +1128,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"{{researcherUsername}}\",\n \"details\": {\n \"name\": \"A dummy researcher\",\n \"email\": \"{{researcherEmail}}\",\n \"password\": \"!7654321wQ\",\n \"roles\": [\n \"Researcher\",\n \"Guest\"\n ]\n }\n}" + "raw": "{\n \"username\": \"{{researcherUsername}}\",\n \"details\": {\n \"name\": \"A dummy researcher\",\n \"email\": \"{{researcherEmail}}\",\n \"password\": \"{{researcherPassword}}\",\n \"roles\": [\n \"Researcher\",\n \"Guest\"\n ]\n }\n}" }, "url": { "raw": "{{host}}/default/rdmp/admin/users/newUser", @@ -1152,7 +1152,7 @@ { "listen": "test", "script": { - "id": "e1744b03-522c-46f4-a07d-a91f662d0db0", + "id": "2d5eeed7-9802-4b60-a6aa-18f28f4de4ab", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1232,7 +1232,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"{{researcherUsername}}\",\n \"details\": {\n \"name\": \"A dummy researcher\",\n \"email\": \"{{researcherEmail}}\",\n \"password\": \"`{[researcherPassword]}\",\n \"roles\": [\n \"Researcher\",\n \"Guest\"\n ]\n }\n}" + "raw": "{\n \"username\": \"{{researcherUsername}}\",\n \"details\": {\n \"name\": \"A dummy researcher\",\n \"email\": \"{{researcherEmail}}\",\n \"password\": \"`{{ researcherPassword }}\",\n \"roles\": [\n \"Researcher\",\n \"Guest\"\n ]\n }\n}" }, "url": { "raw": "{{host}}/default/rdmp/admin/users/newUser", @@ -1256,7 +1256,7 @@ { "listen": "test", "script": { - "id": "21724d77-4e9f-41ae-a20b-8f6f070a5c57", + "id": "0fbe7547-b626-4417-aabe-71585bfda7d2", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1362,7 +1362,7 @@ { "listen": "test", "script": { - "id": "098acda1-ad95-40bb-85c0-dba00f7efce0", + "id": "0af211c4-31ed-4b1b-b3e7-066eab92155b", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1465,7 +1465,7 @@ { "listen": "test", "script": { - "id": "ea1b5cbb-8835-49bf-98e4-7e2b9e13911d", + "id": "3327710b-6c78-4ac8-99a0-873ac4d482cf", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -1570,7 +1570,7 @@ { "listen": "test", "script": { - "id": "ab56c882-2ade-4308-a7ae-96c043e86ece", + "id": "0f3e4e50-0ab6-4f13-867c-edd92245db45", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1650,7 +1650,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"userid\": \"{{researcherUserId}}\",\n \"details\": {\n \"name\": \"A dummy researcher\",\n \"email\": \"{{researcherEmail}}\",\n \"password\": \"\",\n \"roles\": [\n \"Librarians\",\n \"Researcher\",\n \"Guest\"\n ]\n }\n}" + "raw": "{\n \"userid\": \"{{researcherUserId}}\",\n \"details\": {\n \"name\": \"A dummy researcher\",\n \"email\": \"{{researcherEmail}}\",\n \"password\": \"{{researcherPassword}}\",\n \"roles\": [\n \"Librarians\",\n \"Researcher\",\n \"Guest\"\n ]\n }\n}" }, "url": { "raw": "{{host}}/default/rdmp/admin/users/update", @@ -1674,7 +1674,7 @@ { "listen": "test", "script": { - "id": "41224895-8efa-4fa1-b756-71c4a5dfa380", + "id": "50716166-d5fd-4fb1-b437-ac1680c372bf", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1855,7 +1855,7 @@ { "listen": "test", "script": { - "id": "0a41b1f6-6b69-45d9-9905-b5f14ae52752", + "id": "910e4abb-f7ac-433e-8439-abf2d406996a", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1958,7 +1958,7 @@ { "listen": "test", "script": { - "id": "68e79fbf-0d3a-4a55-bf92-c4d975cded59", + "id": "60be7dda-ad7c-4088-a0af-c537cfa23fc0", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1976,7 +1976,7 @@ "pm.test(\"CSV contains Test record\", function () {", " var found = false;", " const head = parsedCSV.shift(),", - " title = head.indexOf('storage_id');", + " title = head.indexOf('redboxOid');", " parsedCSV.forEach(function(row) {", " if(row[title] == pm.environment.get('dmpOid')) {", " found = true;", @@ -2080,7 +2080,7 @@ { "listen": "test", "script": { - "id": "9a89e62c-7140-4056-a9a0-6f277294bc32", + "id": "ec11cf13-a76e-431c-8561-cc58736d0d8c", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -2174,7 +2174,7 @@ { "listen": "test", "script": { - "id": "ba1a6cf1-e970-4041-85da-5d59a23a6a66", + "id": "569b8fdd-0dcd-49ff-b992-79d7f5f5970f", "exec": [ "", "pm.test(\"Status code is 201\", function () {", @@ -2276,7 +2276,7 @@ { "listen": "test", "script": { - "id": "56e774a8-4e00-46f6-9b89-c60f4354dd38", + "id": "4db55ba8-1fea-4816-856d-4d9fc15f461a", "exec": [ "pm.test(\"Status code is 204\", function () {", " pm.response.to.have.status(204);", @@ -2370,7 +2370,7 @@ { "listen": "test", "script": { - "id": "79d2a0a8-f221-4f88-a911-b8f308ed840f", + "id": "a05e400d-7ba7-4189-9283-22df340db50b", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2462,7 +2462,7 @@ { "listen": "test", "script": { - "id": "1a921de3-6836-4987-a155-d780f688aa3d", + "id": "5f2ebd69-89f7-4a52-b30f-00f06cad2d5d", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2498,7 +2498,7 @@ { "listen": "test", "script": { - "id": "40e1ff1d-bc20-465c-98f3-6ac7f8e8b08b", + "id": "f3db7fa8-7fde-42eb-913c-dfe48f4c6539", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2589,7 +2589,7 @@ { "listen": "test", "script": { - "id": "22f320bb-6b77-4628-906a-327dcb3c5ffa", + "id": "7973e70c-96d2-455b-ba3d-32b97056931a", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2681,7 +2681,7 @@ { "listen": "test", "script": { - "id": "f9831932-0294-4f9a-9f4a-4e11909b4ee4", + "id": "2b75fb88-b3ab-4da1-8605-8c28758dffb9", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2773,7 +2773,7 @@ { "listen": "test", "script": { - "id": "40211284-ad8b-4d7b-a40b-a9d8cefeebb7", + "id": "23fe1da6-69cc-4675-b30b-d8e437499f9b", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2873,7 +2873,7 @@ { "listen": "test", "script": { - "id": "6afa5172-d59f-4fd3-8f13-efd244678b86", + "id": "c7986692-b565-4c4e-baed-80e951c4a347", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -2971,7 +2971,7 @@ { "listen": "test", "script": { - "id": "c49daf1e-6dfd-4aac-9b99-0a24338413d9", + "id": "dd144dff-909f-4430-9bd5-ca5b3045d255", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -3142,7 +3142,7 @@ { "listen": "test", "script": { - "id": "503ec593-335f-4e1e-82c3-6ab02ff81682", + "id": "8d577856-e300-485a-b1a2-56a8e1bcc70e", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3218,7 +3218,7 @@ { "listen": "test", "script": { - "id": "12a1cf86-99af-4a39-8d8e-40cfe55da2f3", + "id": "e8422191-0aee-4124-9d54-f1757bfe7250", "exec": [ "pm.test(\"Status code is 403\", function () {", " pm.response.to.have.status(403);", @@ -3287,7 +3287,7 @@ { "listen": "test", "script": { - "id": "26bcddb0-2663-40fa-90b8-8f65dbbdcb2b", + "id": "11e34f30-4d2d-43d0-a1a5-e96e61083b50", "exec": [ "postman.setGlobalVariable(\"cookie\",postman.getResponseHeader(\"set-cookie\") );", "", @@ -3363,7 +3363,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"researcheruser\",\n \"password\": \"notthepassword\",\n \"branding\": \"default\",\n \"portal\": \"rdmp\"\n}" + "raw": "{\n \"username\": \"{{researcherUsername}}\",\n \"password\": \"notthepassword\",\n \"branding\": \"default\",\n \"portal\": \"rdmp\"\n}" }, "url": { "raw": "http://localhost:1500/user/login_local", @@ -3386,7 +3386,7 @@ { "listen": "test", "script": { - "id": "f5b84bb3-ccf8-44ae-891d-2c833c5f4420", + "id": "903c1dca-6598-4e69-972a-05051e1bd5ac", "exec": [ "postman.setGlobalVariable(\"cookie\",postman.getResponseHeader(\"set-cookie\") );", "", @@ -3394,7 +3394,7 @@ " pm.response.to.have.status(200);", "});", "", - "pm.test(\"Check that user returned is researcheruser\", function () {", + "pm.test(\"Check that we have logged in correctly.\", function () {", " var jsonData = pm.response.json();", " var researcherUsername = pm.environment.get(\"researcherUsername\");", " pm.expect(jsonData.user.username).to.eql(researcherUsername);", @@ -3482,7 +3482,7 @@ { "listen": "test", "script": { - "id": "c7db8ee9-aea7-492e-89f1-c6e5b1f528d0", + "id": "60b3b76d-66c9-497e-86cd-2ac252e83bab", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", From 48ad7056fb48f656ec06519903eb2890c1b85c52 Mon Sep 17 00:00:00 2001 From: Shilo B Date: Tue, 27 Oct 2020 09:14:32 +1000 Subject: [PATCH 19/48] Added Attachment class. --- typescript/api/core/Attachment.ts | 36 +++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 typescript/api/core/Attachment.ts diff --git a/typescript/api/core/Attachment.ts b/typescript/api/core/Attachment.ts new file mode 100644 index 0000000000..2b8f9e646d --- /dev/null +++ b/typescript/api/core/Attachment.ts @@ -0,0 +1,36 @@ +// Copyright (c) 2020 Queensland Cyber Infrastructure Foundation (http://www.qcif.edu.au/) +// +// GNU GENERAL PUBLIC LICENSE +// Version 2, June 1991 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +/** + * Base class for information regarding an attachment, also includes payload + * + * @author Shilo Banihit + */ +export class Attachment { + dateUpdated: any; + label: string; + contentType: string; + dateCreated: any; + body: any; // bad idea to load everything in-memory + readstream: any; // strongly suggest services stream rather than load everything in-mem + + constructor() { + } +} +export default Attachment From 4e4b434a7ad34938d0ee6abeae7256d6ba74b999 Mon Sep 17 00:00:00 2001 From: Shilo B Date: Wed, 11 Nov 2020 13:41:34 +1000 Subject: [PATCH 20/48] Added support for Agenda as queuing library. --- config/agendaQueue.js | 17 ++ config/indexer.js | 3 + config/queue.js | 3 + config/storage.js | 3 + docker-compose.yml | 30 +-- package-lock.json | 76 ++++++++ package.json | 1 + .../api/controllers/RecordController.ts | 4 +- typescript/api/core/CoreService.ts | 2 + typescript/api/core/QueueService.ts | 30 +++ typescript/api/core/StorageService.ts | 4 +- typescript/api/services/AgendaQueueService.ts | 173 ++++++++++++++++++ typescript/api/services/RecordsService.ts | 94 +++++++++- 13 files changed, 402 insertions(+), 38 deletions(-) create mode 100644 config/agendaQueue.js create mode 100644 config/indexer.js create mode 100644 config/queue.js create mode 100644 config/storage.js create mode 100644 typescript/api/core/QueueService.ts create mode 100644 typescript/api/services/AgendaQueueService.ts diff --git a/config/agendaQueue.js b/config/agendaQueue.js new file mode 100644 index 0000000000..8dc0e51c0e --- /dev/null +++ b/config/agendaQueue.js @@ -0,0 +1,17 @@ +module.exports.agendaQueue = { + // options: { + // see: https://github.com/agenda/agenda#configuring-an-agenda + // } + // e.g. : + // jobs: [ + // { + // name: 'sampleJob', + // fnName: 'agendaqueueservice.sampleFunctionToDemonstrateHowToDefineAJobFunction', + // schedule: { + // method: 'every', + // intervalOrSchedule: '1 minute', + // data: 'sample log string' + // } + // } + // ] +}; diff --git a/config/indexer.js b/config/indexer.js new file mode 100644 index 0000000000..ef27150d39 --- /dev/null +++ b/config/indexer.js @@ -0,0 +1,3 @@ +module.exports.indexer = { + serviceName: 'solrindexerservice' +}; diff --git a/config/queue.js b/config/queue.js new file mode 100644 index 0000000000..377f67f69d --- /dev/null +++ b/config/queue.js @@ -0,0 +1,3 @@ +module.exports.queue = { + serviceName: 'agendaqueueservice' +} diff --git a/config/storage.js b/config/storage.js new file mode 100644 index 0000000000..8cf8e478a0 --- /dev/null +++ b/config/storage.js @@ -0,0 +1,3 @@ +module.exports.storage = { + serviceName: "mongostorageservice" +}; diff --git a/docker-compose.yml b/docker-compose.yml index 4fa1e1bb7f..162c831640 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,7 @@ services: redboxportal: build: . image: qcifengineering/redbox-portal:latest + ports: - "1500:1500" volumes: @@ -20,40 +21,13 @@ services: - sails_redbox__apiKey=c8e844fc-8550-497f-b970-7900ec8741ca - sails_record__baseUrl_redbox=http://redbox:9000/redbox - sails_record__baseUrl_mint=https://demo.redboxresearchdata.com.au/mint + - NODE_TLS_REJECT_UNAUTHORIZED=0 networks: main: aliases: - rbportal entrypoint: /bin/bash -c "cd /opt/redbox-portal && node app.js" - nginx: - image: nginx - ports: - - "8080:8080" - restart: always - volumes: - - "/mnt/data/publication:/usr/share/nginx/html" - - "/mnt/nginx.conf:/etc/nginx/conf.d" - expose: - - "8080" - - - redbox: - image: qcifengineering/redbox:2.x - expose: - - "9000" - environment: - - RB_API_KEY=c8e844fc-8550-497f-b970-7900ec8741ca - volumes: - - "/mnt/data/redbox:/opt/redbox/data" - - "/var/log/redbox:/opt/redbox/home/logs" - networks: - main: - aliases: - - redbox - ports: - - "9000:9000" - mongodb: image: mvertes/alpine-mongo:latest volumes: diff --git a/package-lock.json b/package-lock.json index 2199a35d48..499b7f47fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -672,6 +672,61 @@ "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" }, + "agenda": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/agenda/-/agenda-3.1.0.tgz", + "integrity": "sha512-UtxV/37gkjDYl0H2Lr4hPrBqOhAgtxYeGSYooSd1eyOmXlK1wFkbs77nItOykufFRv6tR6fskWP2RkyBndXYtg==", + "requires": { + "cron": "~1.8.0", + "date.js": "~0.3.3", + "debug": "~4.1.1", + "human-interval": "~1.0.0", + "moment-timezone": "~0.5.27", + "mongodb": "~3.5.0" + }, + "dependencies": { + "bl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "bson": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", + "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==" + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "mongodb": { + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.11.tgz", + "integrity": "sha512-0a9XI0BbgcUEmB+gykqiUGijUkVflR5B46ZWxTshTQB8yrQlByVSq/5968ojY6iXQ+sDojnuKHnpLBInkZq+6Q==", + "requires": { + "bl": "^2.2.1", + "bson": "^1.1.4", + "denque": "^1.4.1", + "require_optional": "^1.0.1", + "safe-buffer": "^5.1.2", + "saslprep": "^1.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "agent-base": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", @@ -2332,6 +2387,14 @@ "capture-stack-trace": "^1.0.0" } }, + "cron": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/cron/-/cron-1.8.2.tgz", + "integrity": "sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg==", + "requires": { + "moment-timezone": "^0.5.x" + } + }, "cron-parser": { "version": "2.15.0", "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-2.15.0.tgz", @@ -2587,6 +2650,14 @@ } } }, + "date.js": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/date.js/-/date.js-0.3.3.tgz", + "integrity": "sha512-HgigOS3h3k6HnW011nAb43c5xx5rBXk8P2v/WIT9Zv4koIaVXiH2BURguI78VVp+5Qc076T7OR378JViCnZtBw==", + "requires": { + "debug": "~3.1.0" + } + }, "dateformat": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", @@ -5330,6 +5401,11 @@ "debug": "^3.1.0" } }, + "human-interval": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/human-interval/-/human-interval-1.0.0.tgz", + "integrity": "sha512-SWPw3rD6/ocA0JnGePoXp5Zf5eILzsoL5vdWdLwtTuyrElyCpfQb0whIcxMdK/gAKNl2rFDGkPAbwI2KGZCvNA==" + }, "i": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/i/-/i-0.3.6.tgz", diff --git a/package.json b/package.json index 79e96245d3..5fcacd8e7a 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@uppy/core": "^1.13.1", "@uppy/dashboard": "^1.12.5", "@uppy/tus": "^1.7.5", + "agenda": "^3.1.0", "async": "3.2.0", "axios": "^0.20.0", "bcrypt": "5.0.0", diff --git a/typescript/api/controllers/RecordController.ts b/typescript/api/controllers/RecordController.ts index e2989cc39f..d3c541fb2e 100644 --- a/typescript/api/controllers/RecordController.ts +++ b/typescript/api/controllers/RecordController.ts @@ -546,15 +546,13 @@ export module Controllers { let updateResponse = await this.recordsService.updateMeta(brand, oid, record, user, false, false); } else { // no need for update... return the creation response - let updateResponse = response; } } else { sails.log.error(`Failed to save record:`); sails.log.error(JSON.stringify(response)); // return the rsponse instead of throwing an exception - let updateResponse = response; } - try{ + try { // handle datastream update if (updateResponse && updateResponse.isSuccessful()) { if (!_.isEmpty(record.metaMetadata.attachmentFields)) { diff --git a/typescript/api/core/CoreService.ts b/typescript/api/core/CoreService.ts index 45296a36fb..25a2796eb6 100644 --- a/typescript/api/core/CoreService.ts +++ b/typescript/api/core/CoreService.ts @@ -18,6 +18,8 @@ export module Services.Core { // Sails controller custom config. '_config', ]; + + protected logHeader: string; /** * Returns an RxJS Observable wrapped nice and tidy for your subscribing pleasure */ diff --git a/typescript/api/core/QueueService.ts b/typescript/api/core/QueueService.ts new file mode 100644 index 0000000000..b8687117cf --- /dev/null +++ b/typescript/api/core/QueueService.ts @@ -0,0 +1,30 @@ +// Copyright (c) 2020 Queensland Cyber Infrastructure Foundation (http://www.qcif.edu.au/) +// +// GNU GENERAL PUBLIC LICENSE +// Version 2, June 1991 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +/** + * Service interface for Queuing jobs + */ + +interface QueueService { + every(jobName: string, interval: string, data: any, options: any ); + schedule(jobName: string, schedule: string, data: any); + now(jobName: string, data: any); +} + +export default QueueService diff --git a/typescript/api/core/StorageService.ts b/typescript/api/core/StorageService.ts index 1064f242dc..578a81be0a 100644 --- a/typescript/api/core/StorageService.ts +++ b/typescript/api/core/StorageService.ts @@ -2,8 +2,8 @@ import {Readable} from 'stream'; interface StorageService{ - create(brand, record, recordType, user?, triggerPreSaveTriggers?: boolean, triggerPostSaveTriggers?: boolean):Promise; - updateMeta(brand, oid, record, user?, triggerPreSaveTriggers?: boolean, triggerPostSaveTriggers?: boolean): Promise; + create(brand, record, recordType, user?):Promise; + updateMeta(brand, oid, record, user?): Promise; getMeta(oid): Promise; createBatch(type, data, harvestIdFldName): Promise; provideUserAccessAndRemovePendingAccess(oid, userid, pendingValue): void; diff --git a/typescript/api/services/AgendaQueueService.ts b/typescript/api/services/AgendaQueueService.ts new file mode 100644 index 0000000000..b6daaf0398 --- /dev/null +++ b/typescript/api/services/AgendaQueueService.ts @@ -0,0 +1,173 @@ +// Copyright (c) 2020 Queensland Cyber Infrastructure Foundation (http://www.qcif.edu.au/) +// +// GNU GENERAL PUBLIC LICENSE +// Version 2, June 1991 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +// Copyright (c) 2020 Queensland Cyber Infrastructure Foundation (http://www.qcif.edu.au/) +// +// GNU GENERAL PUBLIC LICENSE +// Version 2, June 1991 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +import services = require('../core/CoreService.js'); +import QueueService from '../core/QueueService.js'; +import Agenda = require('agenda'); +import {Sails, Model} from "sails"; + +declare var module; +declare var sails: Sails; +declare var User: Model; +declare var _; +declare var _this; + +export module Services { + /** + * Service class for queuing using Agenda: https://github.com/agenda/agenda + * + */ + export class AgendaQueue extends services.Services.Core.Service implements QueueService { + protected _exportedMethods: any = [ + 'every', + 'schedule', + 'now', + 'sampleFunctionToDemonstrateHowToDefineAJobFunction' + ]; + + protected agenda: Agenda; + + constructor() { + super(); + let that = this; + sails.on('ready', function () { + (async () => { + await that.init(); + })(); + }); + } + + /** + * Looks `sails.config.agendaQueue` to init and fill the jobs collection + * + * @author Shilo Banihit + * @return + */ + protected async init() { + // set the options for Agenda, see: https://github.com/agenda/agenda#configuring-an-agenda + const agendaOpts = {}; + _.forOwn(sails.config.agendaQueue.options, (optionVal:any, optionName:string) => { + this.setOptionIfDefined(agendaOpts, optionName, optionVal); + }); + if (_.isEmpty(_.get(agendaOpts, 'db.address'))) { + agendaOpts['mongo'] = User.getDatastore().manager; + } + this.agenda = new Agenda(agendaOpts); + this.defineJobs(sails.config.agendaQueue.jobs); + await this.agenda.start(); + // check for in-line job schedule + _.each(sails.config.agendaQueue.jobs, (job) => { + if (!_.isEmpty(job.schedule)) { + const method = job.schedule.method; + const intervalOrSchedule = job.schedule.intervalOrSchedule; + const data = job.schedule.data; + const opts = job.schedule.opts; + if (method == 'now') { + this.now(job.name, data) + } else if (method == 'every') { + this.every(job.name, intervalOrSchedule, data, opts); + } else if (method == 'schedule') { + this.schedule(job.name, intervalOrSchedule, data); + } else { + sails.log.error(`AgendaQueue:: incorrect job schedule definition, method not found:`); + sails.log.error(JSON.stringify(job)); + } + } + }); + } + + /* + define the jobs... structure is: + [ + { + name: "jobName", + options: {}, // optional, see https://github.com/agenda/agenda#defining-job-processors + fnName: "Fully qualified path to service function name", // e.g. "AgendaQueueService.sampleFunctionToDemonstrateHowToDefineAJobFunction" + // optional, if you want to in-line schedule a job, based on https://github.com/agenda/agenda#creating-jobs + schedule: { + method: 'every', + when: '1 minute', + data: 'sample log string', + // options: optional, see: https://github.com/agenda/agenda#repeateveryinterval-options + } + } + ] + */ + public defineJobs(jobs: any[]) { + _.each(jobs, (job) => { + const serviceFn = _.get(sails.services, job.fnName); + if (_.isUndefined(serviceFn)) { + sails.log.error(`AgendaQueue:: Job name: ${job.name}'s service function not found: ${job.fnName}`); + sails.log.error(JSON.stringify(job)); + } else { + if (_.isEmpty(job.options)) { + this.agenda.define(job.name, serviceFn); + } else { + this.agenda.define(job.name, job.options, serviceFn); + } + } + }); + } + + private setOptionIfDefined(agendaOpts, optionName, optionVal) { + if (!_.isEmpty(optionVal)) { + _.set(agendaOpts, optionName, optionVal); + } + } + + public async sampleFunctionToDemonstrateHowToDefineAJobFunction(job) { + sails.log.info(`AgendaQueue:: sample function called by job: `); + sails.log.info(JSON.stringify(job)); + } + + public every(jobName: string, interval: string, data: any = undefined, options: any = undefined) { + this.agenda.every(interval, jobName, data, options); + } + + public schedule(jobName: string, schedule: string, data: any = undefined) { + this.agenda.schedule(schedule, jobName, data); + } + + public now(jobName: string, data: any = undefined) { + this.agenda.now(jobName, data); + } + } +} + +module.exports = new Services.AgendaQueue().exports(); diff --git a/typescript/api/services/RecordsService.ts b/typescript/api/services/RecordsService.ts index 149386210f..8eb28f5b75 100644 --- a/typescript/api/services/RecordsService.ts +++ b/typescript/api/services/RecordsService.ts @@ -22,6 +22,8 @@ import { } from 'rxjs/Rx'; import services = require('../core/CoreService.js'); import DatastreamService from '../core/DatastreamService.js'; +import IndexerService from '../core/Indexer'; +import {StorageServiceResponse} from '../core/StorageServiceResponse'; import { Sails, Model @@ -56,10 +58,11 @@ export module Services { storageService: StorageService = null; datastreamService: DatastreamService = null; - + indexerService:IndexerService = null; constructor() { super(); + this.logHeader = "RecordsService::"; let that = this; sails.on('ready', function () { that.getStorageService(); @@ -110,12 +113,93 @@ export module Services { - create(brand: any, record: any, recordType: any, user ? : any, triggerPreSaveTriggers = true, triggerPostSaveTriggers = true): Promise < any > { - return this.storageService.create(brand, record, recordType, user, triggerPreSaveTriggers, triggerPostSaveTriggers); + async create(brand: any, record: any, recordType: any, user ? : any, triggerPreSaveTriggers = true, triggerPostSaveTriggers = true) { + let createResponse = new StorageServiceResponse(); + const failedMessage = "Failed to created record, please check server logs."; + // trigger the pre-save + if (triggerPreSaveTriggers) { + try { + record = await this.triggerPreSaveTriggers(null, record, recordType, "onCreate", user); + } catch (err) { + sails.log.error(`${this.logHeader} Failed to run pre-save hooks when updating..`); + sails.log.error(JSON.stringify(err)); + createResponse.message = failedMessage; + return createResponse; + } + } + // save the record ... + createResponse = await this.storageService.create(brand, record, recordType, user); + if (createResponse.isSuccessful()) { + if (triggerPostSaveTriggers) { + // post-save sync + try { + createResponse = await this.triggerPostSaveSyncTriggers(createResponse['oid'], record, recordType, 'onCreate', user, createResponse); + } catch (err) { + sails.log.error(`${this.logHeader} Exception while running post save sync hooks when creating: ${createResponse['oid']}`); + sails.log.error(JSON.stringify(err)); + createResponse.success = false; + createResponse.message = failedMessage; + return createResponse; + } + // Fire Post-save hooks async ... + this.triggerPostSaveTriggers(createResponse['oid'], record, recordType, 'onCreate', user); + // TODO: fire-off audit message + } + } else { + sails.log.error(`${this.logHeader} Failed to create record, storage service response:`); + sails.log.error(JSON.stringify(createResponse)); + createResponse.message = failedMessage; + } + return createResponse; } - updateMeta(brand: any, oid: any, record: any, user ? : any, triggerPreSaveTriggers = true, triggerPostSaveTriggers = true): Promise < any > { - return this.storageService.updateMeta(brand, oid, record, user, triggerPreSaveTriggers, triggerPostSaveTriggers); + + async updateMeta(brand: any, oid: any, record: any, user ? : any, triggerPreSaveTriggers = true, triggerPostSaveTriggers = true) { + let updateResponse = new StorageServiceResponse(); + updateResponse.oid = oid; + let recordType = null; + const failedMessage = "Failed to update record, please check server logs."; + // process pre-save + if (!_.isEmpty(brand) && triggerPreSaveTriggers === true) { + try { + recordType = await RecordTypesService.get(brand, record.metaMetadata.type).toPromise(); + record = await this.triggerPreSaveTriggers(oid, record, recordType, "onUpdate", user); + } catch (err) { + sails.log.error(`${this.logHeader} Failed to run pre-save hooks when updating..`); + sails.log.error(JSON.stringify(err)); + updateResponse.message = failedMessage; + return updateResponse; + } + } + // unsetting the ID just to be safe + _.unset(record, 'id'); + _.unset(record, 'redboxOid'); + // update + updateResponse = await this.storageService.updateMeta(brand, oid, record, user); + if (updateResponse.isSuccessful()) { + // post-save async + if (!_.isEmpty(recordType) && triggerPostSaveTriggers === true) { + // Trigger Post-save sync hooks ... + try { + updateResponse = await this.triggerPostSaveSyncTriggers(updateResponse['oid'], record, recordType, 'onCreate', user, updateResponse); + } catch (err) { + sails.log.error(`${this.logHeader} Exception while running post save sync hooks when updating:`); + sails.log.error(JSON.stringify(err)); + updateResponse.success = false; + updateResponse.message = failedMessage; + return updateResponse; + } + // Fire Post-save hooks async ... + this.triggerPostSaveTriggers(updateResponse['oid'], record, recordType, 'onCreate', user); + } + // TODO: fire-off audit message + } else { + sails.log.error(`${this.logHeader} Failed to update record, storage service response:`); + sails.log.error(JSON.stringify(updateResponse)); + updateResponse.message = failedMessage; + } + return updateResponse; } + getMeta(oid: any): Promise < any > { return this.storageService.getMeta(oid); } From bf96fed6eb7cb0588759af1c43e97e1a0dce5313 Mon Sep 17 00:00:00 2001 From: Moises Sacal Date: Tue, 24 Nov 2020 11:26:22 +1100 Subject: [PATCH 21/48] run as node user, remove vagrant user, fixed sails env vars, changed docker-compose volumes to be relative to installation --- .gitignore | 1 + Dockerfile | 7 +++++-- README.md | 19 +++++++++++++++-- docker-compose.yml | 52 ++++++++++++++++------------------------------ dockerlocal_dev.sh | 6 ++++++ runForDev.sh | 12 +++++------ runForProd.sh | 3 +-- 7 files changed, 54 insertions(+), 46 deletions(-) create mode 100644 dockerlocal_dev.sh diff --git a/.gitignore b/.gitignore index a06e404f09..e4e03b4dc2 100644 --- a/.gitignore +++ b/.gitignore @@ -173,3 +173,4 @@ typescript/api/controllers/**/*.js typescript/api/services/**/*.js assets/angular api/core/*.js +dev \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 445db5334f..1ab2798d20 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,8 @@ FROM node:12.16.0 ENV node_env production +ENV NPM_CONFIG_PREFIX=/home/node/.npm-global +ENV PATH=$PATH:/home/node/.npm-global/bin +VOLUME ["/opt/redbox-portal/","/attachments","/publication","/opt/hooks"] RUN printf "deb http://archive.debian.org/debian/ jessie main\ndeb-src http://archive.debian.org/debian/ jessie main\ndeb http://security.debian.org jessie/updates main\ndeb-src http://security.debian.org jessie/updates main" > /etc/apt/sources.list RUN apt-get update && \ apt-get install --no-install-recommends -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \ @@ -7,7 +10,7 @@ libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 li libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \ libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \ ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget && rm -rf /var/lib/apt/lists/* - -COPY . /opt/redbox-portal/ RUN echo "Australia/Brisbane" > /etc/timezone && dpkg-reconfigure -f noninteractive tzdata +COPY --chown=node:node . /opt/redbox-portal/ +USER node CMD NODE_ENV=$node_env node app.js diff --git a/README.md b/README.md index 3ee8035d3c..2982581179 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,21 @@ The [Queensland Cyber Infrastructure Foundation](http://www.qcif.edu.au) (QCIF) ## Development Requirements: -- Node 8.11.0 +- Docker -Development requires Docker. Run `./runForDev.sh install jit` at least once. +Run `./runForDev.sh install jit` + +It will + - Pull qcifengineering/redbox-portal from docker hub (If a local copy does not exist) + - Compile backend + - Compile frontend + - Then start docker-compose + +Open http://localhost:1500 to start browsing + +### Build local docker image + +Run `./dockerlocal_dev.sh` + +It will + - Build a local docker image of qcifengineering/redbox-portal:latest \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 4fa1e1bb7f..bad81078c4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,24 +6,25 @@ services: build: . image: qcifengineering/redbox-portal:latest ports: - - "1500:1500" + - "1500:1500" + user: node volumes: - - ".:/opt/redbox-portal" - - "/mnt/data/attachments:/attachments" - - "/mnt/data/publication:/publication" - - "/opt/hooks:/opt/hooks" + - ".:/opt/redbox-portal:delegated" + - "./dev/attachments:/attachments:delegated" + - "./dev/publication:/publication:delegated" + - "./dev/hooks:/opt/hooks:delegated" expose: - - "1500" + - "1500" environment: - NODE_ENV=docker - PORT=1500 - sails_redbox__apiKey=c8e844fc-8550-497f-b970-7900ec8741ca - - sails_record__baseUrl_redbox=http://redbox:9000/redbox - - sails_record__baseUrl_mint=https://demo.redboxresearchdata.com.au/mint + - sails_record__baseUrl__redbox=http://redbox:9000/redbox + - sails_record__baseUrl__mint=https://demo.redboxresearchdata.com.au/mint networks: - main: - aliases: - - rbportal + main: + aliases: + - rbportal entrypoint: /bin/bash -c "cd /opt/redbox-portal && node app.js" nginx: @@ -32,37 +33,20 @@ services: - "8080:8080" restart: always volumes: - - "/mnt/data/publication:/usr/share/nginx/html" - - "/mnt/nginx.conf:/etc/nginx/conf.d" + - "./dev/publication:/usr/share/nginx/html:delegated" + - "./dev/nginx.conf:/etc/nginx/conf.d:delegated" expose: - "8080" - - redbox: - image: qcifengineering/redbox:2.x - expose: - - "9000" - environment: - - RB_API_KEY=c8e844fc-8550-497f-b970-7900ec8741ca - volumes: - - "/mnt/data/redbox:/opt/redbox/data" - - "/var/log/redbox:/opt/redbox/home/logs" - networks: - main: - aliases: - - redbox - ports: - - "9000:9000" - mongodb: image: mvertes/alpine-mongo:latest volumes: - - "./devdata:/devdata" - - "/mnt/data/mongo/data/db:/data/db" - - "/var/log/mongo:/var/log/mongo" + - "./devdata:/devdata:delegated" + - "./dev/mongo/data/db:/data/db:delegated" + - "./dev/log/mongo:/var/log/mongo:delegated" networks: main: aliases: - mongodb ports: - - "27017:27017" + - "27017:27017" diff --git a/dockerlocal_dev.sh b/dockerlocal_dev.sh new file mode 100644 index 0000000000..999de20617 --- /dev/null +++ b/dockerlocal_dev.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# Simple docker build for local development +REPO=qcifengineering/redbox-portal +TAG=latest + +docker build -f Dockerfile -t $REPO:$TAG . \ No newline at end of file diff --git a/runForDev.sh b/runForDev.sh index b13cde0ad4..0ccf50f7b5 100755 --- a/runForDev.sh +++ b/runForDev.sh @@ -4,29 +4,29 @@ PORTAL_DIR=/opt/redbox-portal PORTAL_IMAGE=qcifengineering/redbox-portal:latest source dev_build/buildFns.sh -sudo chown -R vagrant:vagrant * + watch="false" # Not really needed but I'm putting this in a for loop in case we want to add more arguments later WATCH_COUNT=0 for var in "$@" do if [ $var = "install" ]; then - docker run -it --rm -v $PWD:$PORTAL_DIR $PORTAL_IMAGE /bin/bash -c "cd $PORTAL_DIR; npm -g i typings && npm install" + docker run -it --rm -u "node" -v $PWD:$PORTAL_DIR $PORTAL_IMAGE /bin/bash -c "cd $PORTAL_DIR; npm -g i typings && npm install" fi if [ $var = "jit" ]; then #linkNodeLib "lodash" "lodash-lib" # Build targets are different for assets/angular, clearing all .js files from .ts files cleanUpAllJs export ENV=development - docker run -it --rm -v $PWD:$PORTAL_DIR $PORTAL_IMAGE /bin/bash -c "cd $PORTAL_DIR; npm install -g @angular/cli@1.7.1; npm i --save-dev; node_modules/.bin/tsc --project tsconfig.json; cd angular; npm i; make build-frontend" + docker run -it --rm -u "node" -v $PWD:$PORTAL_DIR $PORTAL_IMAGE /bin/bash -c "cd $PORTAL_DIR; npm install -g @angular/cli@1.7.1; npm i --save-dev; node_modules/.bin/tsc --project tsconfig.json; cd angular; npm i; make build-frontend" fi if [ $var = "jit-skip-frontend" ]; then #linkNodeLib "lodash" "lodash-lib" export ENV=development - docker run -it --rm -v $PWD:$PORTAL_DIR $PORTAL_IMAGE /bin/bash -c "cd $PORTAL_DIR; npm install -g @angular/cli@1.7.1; npm i --save-dev; node_modules/.bin/tsc --project tsconfig.json;" + docker run -it --rm -u "node" -v $PWD:$PORTAL_DIR $PORTAL_IMAGE /bin/bash -c "cd $PORTAL_DIR; npm install -g @angular/cli@1.7.1; npm i --save-dev; node_modules/.bin/tsc --project tsconfig.json;" fi if [ $var == "aot" ]; then - docker run -it --rm -v $PWD:$PORTAL_DIR $PORTAL_IMAGE /bin/bash -c "cd $PORTAL_DIR; export buildTarget=\"${buildTarget}\"; ./runForDev.sh aotCompile" + docker run -it --rm -u "node" -v $PWD:$PORTAL_DIR $PORTAL_IMAGE /bin/bash -c "cd $PORTAL_DIR; export buildTarget=\"${buildTarget}\"; ./runForDev.sh aotCompile" export ENV=development export FORCE_BUNDLE=1 fi @@ -45,7 +45,7 @@ do RBPORTAL_PS=$(docker ps -f name=redbox-portal_redboxportal_1 -q) echo "redbox container is \"${RBPORTAL_PS}\"" echo "ng2App is \"${ng2App}\"" - docker exec --detach $RBPORTAL_PS /bin/bash -c "cd /opt/redbox-portal/angular; npm install -g @angular/cli@1.7.1; npm i; ng build --app=${ng2App} --watch --verbose > ${ng2App}-build.log" || exit + docker exec -u "node" --detach $RBPORTAL_PS /bin/bash -c "cd /opt/redbox-portal/angular; npm install -g @angular/cli@1.7.1; npm i; ng build --app=${ng2App} --watch --verbose > ${ng2App}-build.log" || exit let WATCH_COUNT++ fi done diff --git a/runForProd.sh b/runForProd.sh index 47e6ca52d7..cd803ea807 100755 --- a/runForProd.sh +++ b/runForProd.sh @@ -1,9 +1,8 @@ #!/bin/bash export buildTarget="PROD" source dev_build/buildFns.sh -sudo chown -R vagrant:vagrant * cleanUpAllJs -docker run -it --rm -v $PWD:/opt/rds-rdmp-portal qcifengineering/dlcf-portal:latest /bin/bash -c "cd /opt/rds-rdmp-portal; npm install;" +docker run -it --rm -u "node" -v $PWD:/opt/rds-rdmp-portal qcifengineering/dlcf-portal:latest /bin/bash -c "cd /opt/rds-rdmp-portal; npm install;" linkNodeLib "lodash-es" "lodash-lib" compileAoT export ENV=production From 693e251a8e2d62a7934cc6553c52301e8582c29b Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Wed, 25 Nov 2020 15:57:20 +1030 Subject: [PATCH 22/48] Initial fix to allow initialisation of controllers when merging from a hook --- typescript/api/services/ConfigService.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/typescript/api/services/ConfigService.ts b/typescript/api/services/ConfigService.ts index eecffad00b..7dd7b3edb2 100644 --- a/typescript/api/services/ConfigService.ts +++ b/typescript/api/services/ConfigService.ts @@ -127,7 +127,8 @@ export module Services { fs.ensureSymlinkSync(`${appPath}/api/core`, `${hook_root_dir}/api/core`); } sails.log.verbose(`${hook_log_header}::Adding custom API elements...`); - let apiDirs = ["services", "controllers"]; + + let apiDirs = ["services"]; _.each(apiDirs, (apiType) => { const files = this.walkDirSync(`${hook_root_dir}/api/${apiType}`, []); sails.log.verbose(`${hook_log_header}::Processing '${apiType}':`); @@ -141,6 +142,23 @@ export module Services { }); } }); + + sails.on('lifted', function() { + let apiDirs = ["controllers"]; + _.each(apiDirs, (apiType) => { + const files = this.walkDirSync(`${hook_root_dir}/api/${apiType}`, []); + sails.log.verbose(`${hook_log_header}::Processing '${apiType}':`); + sails.log.verbose(JSON.stringify(files)); + if (!_.isEmpty(files)) { + _.each(files, (file) => { + const apiDef = require(file); + const apiElemName = _.toLower(basename(file, '.js')) + sails[apiType][apiElemName] = apiDef; + }); + } + }); + }); + // for models, we need to copy them over to `api/models`... const modelFiles = this.walkDirSync(`${hook_root_dir}/api/models`, []); if (!_.isEmpty(modelFiles)) { @@ -154,6 +172,7 @@ export module Services { sails.log.verbose(`${hookName}::Merge complete.`); } + private walkDirSync(dir:string, filelist:any[] = []) { if (!fs.pathExistsSync(dir)) { return filelist; From d3495dc9be09fd7c7fd6b47ee9aefa6bb94f0dff Mon Sep 17 00:00:00 2001 From: Shilo B Date: Thu, 26 Nov 2020 13:36:17 +1000 Subject: [PATCH 23/48] Fixed: returning total number of items for dashboard. --- typescript/api/controllers/DashboardController.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/typescript/api/controllers/DashboardController.ts b/typescript/api/controllers/DashboardController.ts index 6e67eca837..6f9d577991 100644 --- a/typescript/api/controllers/DashboardController.ts +++ b/typescript/api/controllers/DashboardController.ts @@ -22,7 +22,7 @@ declare var module; declare var sails; declare var _; import { Observable } from 'rxjs/Rx'; -declare var BrandingService, RolesService, DashboardService, RecordsService; +declare var BrandingService, RolesService, RecordsService; /** * Package that contains all Controllers. @@ -126,7 +126,7 @@ export module Controllers { return null; } - var totalItems = _.size(results.items); + var totalItems = results.totalItems; var startIndex = start; var noItems = rows; var pageNumber = (startIndex / noItems) + 1; From 98e0b393c0cc2e7678b7379955989ef98e099c1c Mon Sep 17 00:00:00 2001 From: Moises Sacal Date: Mon, 30 Nov 2020 15:46:18 +1100 Subject: [PATCH 24/48] missing jquery declare, fails compile on production only --- angular/shared/form/html-event.component.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/angular/shared/form/html-event.component.ts b/angular/shared/form/html-event.component.ts index f1d103e164..1be7c87f09 100644 --- a/angular/shared/form/html-event.component.ts +++ b/angular/shared/form/html-event.component.ts @@ -3,6 +3,8 @@ import { SimpleComponent } from './field-simple.component'; import { FieldBase } from './field-base'; import { NotInFormField } from './field-simple'; import * as _ from "lodash"; +declare var jQuery: any; + /** * Handles low-level event handling. Warning: for data changes, etc. please use the field-level subscribe-publish model. * From c8e40ca64d050598b2b3ddd86d8d9734e720d233 Mon Sep 17 00:00:00 2001 From: Shilo B Date: Wed, 2 Dec 2020 12:20:38 +1000 Subject: [PATCH 25/48] Shifted indexing and search to external SOLR. --- config/agendaQueue.js | 6 + config/indexer.js | 3 - config/recordtype.js | 4 +- config/search.js | 3 + config/solr.js | 126 +++++++ package-lock.json | 245 ++++++++++++- package.json | 2 + .../integration-testing/docker-compose.yml | 19 + .../api/controllers/RecordController.ts | 18 +- typescript/api/core/CoreService.ts | 6 + typescript/api/core/RecordsService.ts | 2 +- typescript/api/core/SearchService.ts | 3 +- typescript/api/services/RecordsService.ts | 37 +- typescript/api/services/SolrSearchService.ts | 325 ++++++++++++++++++ typescript/api/services/VocabService.ts | 16 - 15 files changed, 750 insertions(+), 65 deletions(-) delete mode 100644 config/indexer.js create mode 100644 config/search.js create mode 100644 config/solr.js create mode 100644 typescript/api/services/SolrSearchService.ts diff --git a/config/agendaQueue.js b/config/agendaQueue.js index 8dc0e51c0e..f4716b5b4e 100644 --- a/config/agendaQueue.js +++ b/config/agendaQueue.js @@ -14,4 +14,10 @@ module.exports.agendaQueue = { // } // } // ] + jobs: [ + { + name: 'SolrSearchService-CreateOrUpdateIndex', + fnName: 'solrsearchservice.addOrUpdate' + } + ] }; diff --git a/config/indexer.js b/config/indexer.js deleted file mode 100644 index ef27150d39..0000000000 --- a/config/indexer.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports.indexer = { - serviceName: 'solrindexerservice' -}; diff --git a/config/recordtype.js b/config/recordtype.js index 0ae8ab15b9..19ff08118c 100644 --- a/config/recordtype.js +++ b/config/recordtype.js @@ -97,13 +97,13 @@ module.exports.recordtype = { } }, searchFilters: [{ - name: "text_title", + name: "title", title: "search-refine-title", type: "exact", typeLabel: "search-refine-contains" }, { - name: "text_description", + name: "description", title: "search-refine-description", type: "exact", typeLabel: "search-refine-contains" diff --git a/config/search.js b/config/search.js new file mode 100644 index 0000000000..036f86b07c --- /dev/null +++ b/config/search.js @@ -0,0 +1,3 @@ +module.exports.search = { + serviceName: 'solrsearchservice' +}; diff --git a/config/solr.js b/config/solr.js new file mode 100644 index 0000000000..1a1a4ce79f --- /dev/null +++ b/config/solr.js @@ -0,0 +1,126 @@ +module.exports.solr = { + createOrUpdateJobName: 'SolrSearchService-CreateOrUpdateIndex', + options: { + host: 'solr', + port: '8983', + core: 'redbox' + }, + maxWaitTries: 12, + waitTime: 5000, + schema: { + 'add-field': [ + { + name: "full_text", + type: "text_general", + indexed: true, + stored: false, + multiValued: true + }, + { + name: "title", + type: "text_general", + indexed: true, + stored: true, + multiValued: false + }, + { + name: "description", + type: "text_general", + indexed: true, + stored: true, + multiValued: false + }, + { + name: "grant_number_name", + type: "text_general", + indexed: true, + stored: true, + multiValued: true + }, + { + name: "finalKeywords", + type: "text_general", + indexed: true, + stored: true, + multiValued: true + } + ], + 'add-dynamic-field': [ + { + name: "date_*", + type: "pdate", + indexed: true, + stored: true + } + ], + 'add-copy-field': [ + { + source: "*", + dest: "full_text" + } + ] + }, + // note that the original object will be cloned + // all 'source' values will refer to the path in the original object + preIndex: { + // remove the 'metadata' nested key + move: [ + { + source: 'metadata', + dest: '' // the root object when empty, otherwise a path value used in _.set() + } + ], + copy: [ + { + source: 'dateCreated', + dest: 'date_object_created' + }, + { + source: 'lastSaveDate', + dest: 'date_object_modified' + } + ], + flatten: { + // uncomment below to pass in specific options when flattening using https://www.npmjs.com/package/flat + // options: { + // + // }, + special: [ + { + source: 'workflow', + options: { + safe: false, + delimiter: '_' + } + }, + { + source: 'authorization', + options: { + safe: true, + delimiter: '_' + } + }, + { + source: 'metaMetadata', + options: { + safe: false, + delimiter: '_' + } + }, + { + source: 'metadata.finalKeywords', + dest: 'finalKeywords', + options: { + safe: true + } + } + ] + } + }, + initSchemaFlag: { + name: 'schema_initialised', + type: 'text_general', + stored: false, + required: false + } +}; diff --git a/package-lock.json b/package-lock.json index 499b7f47fa..a424b09407 100644 --- a/package-lock.json +++ b/package-lock.json @@ -401,11 +401,35 @@ } } }, + "@sindresorhus/is": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.0.tgz", + "integrity": "sha512-FyD2meJpDPjyNQejSjvnhpgI/azsQkA4lGbuu5BQZfjvJ9cbRZXzeWL2HceCekW4lixO9JPesIIQkSoLjeJHNQ==" + }, + "@szmarczak/http-timer": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", + "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", + "requires": { + "defer-to-connect": "^2.0.0" + } + }, "@transloadit/prettier-bytes": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/@transloadit/prettier-bytes/-/prettier-bytes-0.0.7.tgz", "integrity": "sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==" }, + "@types/cacheable-request": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", + "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", @@ -417,6 +441,11 @@ "integrity": "sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ==", "dev": true }, + "@types/http-cache-semantics": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", + "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==" + }, "@types/jquery": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.1.tgz", @@ -426,6 +455,14 @@ "@types/sizzle": "*" } }, + "@types/keyv": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", + "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", + "requires": { + "@types/node": "*" + } + }, "@types/leaflet": { "version": "1.5.17", "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.5.17.tgz", @@ -464,6 +501,14 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.11.tgz", "integrity": "sha512-lCvvI24L21ZVeIiyIUHZ5Oflv1hhHQ5E1S25IRlKIXaRkVgmXpJMI3wUJkmym2bTbCe+WoIibQnMVAU3FguaOg==" }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "requires": { + "@types/node": "*" + } + }, "@types/sizzle": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz", @@ -581,6 +626,15 @@ "lodash.throttle": "^4.1.1" } }, + "JSONStream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.0.7.tgz", + "integrity": "sha1-cAyORxH+8c5CH2UL6tVSNbsh194=", + "requires": { + "jsonparse": "^1.1.0", + "through": ">=2.2.7 <3" + } + }, "abab": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", @@ -1300,6 +1354,11 @@ "callsite": "1.0.0" } }, + "bignumber.js": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-1.1.1.tgz", + "integrity": "sha1-GkFdmsAUwTJWrx/u2dGj5XF6jPc=" + }, "binary-extensions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", @@ -1614,6 +1673,44 @@ "unset-value": "^1.0.0" } }, + "cacheable-lookup": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.3.tgz", + "integrity": "sha512-W+JBqF9SWe18A72XFzN/V/CULFzPm7sBXzzR6ekkE+3tLG72wFZrBiBZhrZuDoYexop4PHJVdFAKb/Nj9+tm9w==" + }, + "cacheable-request": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz", + "integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^2.0.0" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, "calcyte": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/calcyte/-/calcyte-1.0.6.tgz", @@ -2061,6 +2158,14 @@ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "requires": { + "mimic-response": "^1.0.0" + } + }, "code-block-writer": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-7.3.1.tgz", @@ -2697,6 +2802,21 @@ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + } + } + }, "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", @@ -2736,6 +2856,11 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, + "defer-to-connect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.0.tgz", + "integrity": "sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg==" + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -2917,8 +3042,7 @@ "duplexer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", - "dev": true + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" }, "duplexer2": { "version": "0.1.4", @@ -4523,6 +4647,24 @@ } } }, + "got": { + "version": "11.8.0", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.0.tgz", + "integrity": "sha512-k9noyoIIY9EejuhaBNLyZ31D5328LeqnyPNXJQb2XlJZcKakLqN5m6O/ikhq/0lw56kUYS54fVm+D1x57YC9oQ==", + "requires": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.1", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + } + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -5258,6 +5400,11 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "hnp": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/hnp/-/hnp-0.0.1.tgz", + "integrity": "sha1-2RSJpd/N9BznQVhCmKcwZldqkoY=" + }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -5329,6 +5476,11 @@ "uuid": "^3.0.0" } }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, "http-errors": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.5.1.tgz", @@ -5368,6 +5520,23 @@ "sshpk": "^1.7.0" } }, + "http2-wrapper": { + "version": "1.0.0-beta.5.2", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz", + "integrity": "sha512-xYz9goEyBnC8XwXDTuC/MZ6t+MrKVQZOk4s7+PaDkwIsQd8IwqvM+0M6bA/2lvG8GHXcPdf+MejTUeO2LCPCeQ==", + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + } + }, + "httperror": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/httperror/-/httperror-0.2.3.tgz", + "integrity": "sha1-yW4NZsvPbg4Z2A5HJ6laCddf4Lg=", + "requires": { + "hnp": "0.0.1" + } + }, "httpntlm": { "version": "1.7.6", "resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.7.6.tgz", @@ -6274,6 +6443,19 @@ "resolved": "https://registry.npmjs.org/json/-/json-9.0.6.tgz", "integrity": "sha1-eXLCpaSKQmeNsnMMfCxO5uTiRYU=" }, + "json-bigint": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.1.4.tgz", + "integrity": "sha1-tdQLipAJ6S8Vf3wHnbCXABgw4B4=", + "requires": { + "bignumber.js": "~1.1.1" + } + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -6410,6 +6592,14 @@ "safe-buffer": "^5.0.1" } }, + "keyv": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", + "requires": { + "json-buffer": "3.0.1" + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -7135,6 +7325,11 @@ "signal-exit": "^3.0.0" } }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + }, "lru-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz", @@ -7810,6 +8005,11 @@ "mime-db": "1.44.0" } }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -8656,6 +8856,11 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" + }, "npm-bundled": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", @@ -8969,6 +9174,11 @@ "os-tmpdir": "^1.0.0" } }, + "p-cancelable": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz", + "integrity": "sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==" + }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -9788,6 +9998,11 @@ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + }, "quote-stream": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/quote-stream/-/quote-stream-1.0.2.tgz", @@ -10280,6 +10495,11 @@ "path-parse": "^1.0.6" } }, + "resolve-alpn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz", + "integrity": "sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA==" + }, "resolve-dir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", @@ -10300,6 +10520,14 @@ "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" }, + "responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "requires": { + "lowercase-keys": "^2.0.0" + } + }, "restructure": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/restructure/-/restructure-0.5.4.tgz", @@ -15726,6 +15954,19 @@ } } }, + "solr-client": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/solr-client/-/solr-client-0.7.1.tgz", + "integrity": "sha512-8jfyInG1o/fakCLlLCQUnS/BPyY0HJ67zLQPdUUtWYfswwzMkS5O8ej1SKGsmDTE0utwPdWFKiKQQhX0VmoxRQ==", + "requires": { + "JSONStream": "~1.0.6", + "bluebird": "^3.5.0", + "duplexer": "~0.1.1", + "httperror": "~0.2.3", + "json-bigint": "~0.1.4", + "request": "^2.88.0" + } + }, "sort-route-addresses": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/sort-route-addresses/-/sort-route-addresses-0.0.3.tgz", diff --git a/package.json b/package.json index 5fcacd8e7a..a57e2bf7ab 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "flat": "^5.0.2", "font-awesome-sass": "4.7.0", "fs-extra": "^9.0.1", + "got": "^11.8.0", "grunt-ts": "^6.0.0-beta.22", "har-validator": "5.1.5", "i18next": "19.7.0", @@ -58,6 +59,7 @@ "sails-hook-sockets": "^2.0.0", "sails-mongo": "^1.2.0", "skipper-gridfs": "^1.0.2", + "solr-client": "^0.7.1", "systemjs": "6.5.0", "ts-node": "^9.0.0", "ts-smart-logger": "0.1.0", diff --git a/support/integration-testing/docker-compose.yml b/support/integration-testing/docker-compose.yml index fae8dbb781..1bc6267002 100644 --- a/support/integration-testing/docker-compose.yml +++ b/support/integration-testing/docker-compose.yml @@ -33,3 +33,22 @@ services: - mongodb ports: - "27017:27017" + solr: + image: solr:8.6.3 + expose: + - "8983" + ports: + - "8983:8983" + environment: + - SOLR_HOME=/var/solr/data + # volumes: + # Please do before: sudo chown 8983:8983 /mnt/data/solr + # - "/mnt/data/solr:/var/solr/data" + networks: + main: + aliases: + - solr + entrypoint: + - docker-entrypoint.sh + - solr-precreate + - redbox diff --git a/typescript/api/controllers/RecordController.ts b/typescript/api/controllers/RecordController.ts index d3c541fb2e..d0893b3a86 100644 --- a/typescript/api/controllers/RecordController.ts +++ b/typescript/api/controllers/RecordController.ts @@ -46,7 +46,7 @@ export module Controllers { export class Record extends controller.Controllers.Core.Controller { recordsService: RecordsService = RecordsService; - searchService: SearchService = RecordsService; + searchService: SearchService; datastreamService: DatastreamService = RecordsService; constructor() { @@ -58,6 +58,7 @@ export module Controllers { if (datastreamServiceName != undefined) { that.datastreamService = sails.services[datastreamServiceName]; } + that.searchService = sails.services[sails.config.search.serviceName]; }); } @@ -1045,7 +1046,7 @@ export module Controllers { // } // } - public search(req, res) { + public async search(req, res) { const brand = BrandingService.getBrand(req.session.branding); const type = req.param('type'); const workflow = req.query.workflow; @@ -1068,13 +1069,12 @@ export module Controllers { }); }); - - Observable.fromPromise(this.searchService.searchFuzzy(type, workflow, searchString, exactSearches, facetSearches, brand, req.user, req.user.roles, sails.config.record.search.returnFields)) - .subscribe(searchRes => { - this.ajaxOk(req, res, null, searchRes); - }, error => { - this.ajaxFail(req, res, error.message); - }); + try { + const searchRes = await this.searchService.searchFuzzy(type, workflow, searchString, exactSearches, facetSearches, brand, req.user, req.user.roles, sails.config.record.search.returnFields); + this.ajaxOk(req, res, null, searchRes); + } catch (error) { + this.ajaxFail(req, res, error.message); + } } /** Returns the RecordType configuration */ public getType(req, res) { diff --git a/typescript/api/core/CoreService.ts b/typescript/api/core/CoreService.ts index 25a2796eb6..37faef7785 100644 --- a/typescript/api/core/CoreService.ts +++ b/typescript/api/core/CoreService.ts @@ -103,5 +103,11 @@ export module Services.Core { return "false"; } } + + protected sleep(ms) { + return new Promise(resolve => { + setTimeout(resolve, ms) + }); + } } } diff --git a/typescript/api/core/RecordsService.ts b/typescript/api/core/RecordsService.ts index b4e321f61b..c9a94ee8a1 100644 --- a/typescript/api/core/RecordsService.ts +++ b/typescript/api/core/RecordsService.ts @@ -10,7 +10,7 @@ interface RecordsService extends StorageService { appendToRecord(targetRecordOid: string, linkData: any, fieldName: string, fieldType: string, targetRecord: any): Promise updateWorkflowStep(currentRec, nextStep): void; getAttachments(oid: string, labelFilterStr?: string): Promise; - + updateMeta(brand, oid, record, user?, triggerPreSaveTriggers?, triggerPostSaveTriggers?): Promise; // Probably to be retired or reimplemented in a different service checkRedboxRunning(): Promise; diff --git a/typescript/api/core/SearchService.ts b/typescript/api/core/SearchService.ts index 3ce46d34c5..2a574f3346 100644 --- a/typescript/api/core/SearchService.ts +++ b/typescript/api/core/SearchService.ts @@ -1,7 +1,6 @@ interface SearchService{ - - search(type, searchField, searchStr, returnFields): Promise; + index(id:string, data:any):any; searchFuzzy(type, workflowState, searchQuery, exactSearches, facetSearches, brand, user, roles, returnFields): Promise; } diff --git a/typescript/api/services/RecordsService.ts b/typescript/api/services/RecordsService.ts index 8eb28f5b75..2221c263db 100644 --- a/typescript/api/services/RecordsService.ts +++ b/typescript/api/services/RecordsService.ts @@ -22,7 +22,6 @@ import { } from 'rxjs/Rx'; import services = require('../core/CoreService.js'); import DatastreamService from '../core/DatastreamService.js'; -import IndexerService from '../core/Indexer'; import {StorageServiceResponse} from '../core/StorageServiceResponse'; import { Sails, @@ -54,11 +53,11 @@ export module Services { * Author: Shilo Banihit * */ - export class Records extends services.Services.Core.Service implements RecordsService, SearchService { + export class Records extends services.Services.Core.Service implements RecordsService { storageService: StorageService = null; datastreamService: DatastreamService = null; - indexerService:IndexerService = null; + searchService:SearchService = null; constructor() { super(); @@ -67,6 +66,7 @@ export module Services { sails.on('ready', function () { that.getStorageService(); that.getDatastreamService(); + that.searchService = sails.services[sails.config.search.serviceName]; }); } @@ -143,8 +143,9 @@ export module Services { } // Fire Post-save hooks async ... this.triggerPostSaveTriggers(createResponse['oid'], record, recordType, 'onCreate', user); - // TODO: fire-off audit message } + this.searchService.index(createResponse['oid'], record); + // TODO: fire-off audit message } else { sails.log.error(`${this.logHeader} Failed to create record, storage service response:`); sails.log.error(JSON.stringify(createResponse)); @@ -191,6 +192,7 @@ export module Services { // Fire Post-save hooks async ... this.triggerPostSaveTriggers(updateResponse['oid'], record, recordType, 'onCreate', user); } + this.searchService.index(oid, record); // TODO: fire-off audit message } else { sails.log.error(`${this.logHeader} Failed to update record, storage service response:`); @@ -275,12 +277,6 @@ export module Services { return false; } - private sleep(ms) { - return new Promise(resolve => { - setTimeout(resolve, ms) - }); - } - private info(): Promise < any > { const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.info.url); @@ -427,27 +423,8 @@ export module Services { } - - public search(type, searchField, searchStr, returnFields): Promise < any > { - const url = `${this.getSearchTypeUrl(type, searchField, searchStr)}&start=0&rows=${sails.config.record.export.maxRecords}`; - sails.log.verbose(`Searching using: ${url}`); - const options = this.getOptions(url); - return Observable.fromPromise(request[sails.config.record.api.search.method](options)) - .flatMap(resp => { - let response: any = resp; - const customResp = []; - _.forEach(response.response.docs, solrdoc => { - const customDoc = {}; - _.forEach(returnFields, retField => { - customDoc[retField] = solrdoc[retField][0]; - }); - customResp.push(customDoc); - }); - return Observable.of(customResp); - }).toPromise(); - } - public searchFuzzy(type, workflowState, searchQuery, exactSearches, facetSearches, brand, user, roles, returnFields): Promise < any > { + const username = user.username; // const url = `${this.getSearchTypeUrl(type, searchField, searchStr)}&start=0&rows=${sails.config.record.export.maxRecords}`; let searchParam = workflowState ? ` AND workflow_stage:${workflowState} ` : ''; diff --git a/typescript/api/services/SolrSearchService.ts b/typescript/api/services/SolrSearchService.ts new file mode 100644 index 0000000000..f3914c5e91 --- /dev/null +++ b/typescript/api/services/SolrSearchService.ts @@ -0,0 +1,325 @@ +// Copyright (c) 2020 Queensland Cyber Infrastructure Foundation (http://www.qcif.edu.au/) +// +// GNU GENERAL PUBLIC LICENSE +// Version 2, June 1991 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +declare var module; +import services = require('../core/CoreService.js'); +import QueueService from '../core/QueueService.js'; +import SearchService from '../core/SearchService'; +import solr = require('solr-client'); +const got = require('got'); +const util = require('util'); +import { + Sails +} from "sails"; +declare var sails: Sails; +declare var _; +declare var _this; +import * as flat from 'flat'; +import * as luceneEscapeQuery from "lucene-escape-query"; + +declare var RecordsService; + +export module Services { + /** + * Service class for adding documents to Solr. + * + */ + export class SolrSearchService extends services.Services.Core.Service implements SearchService { + protected _exportedMethods: any = [ + 'index', + 'remove', + 'searchFuzzy', + 'addOrUpdate' + ]; + + protected queueService: QueueService; + private client:any; + private baseUrl: string; + + constructor() { + super(); + let that = this; + this.logHeader = "SolrIndexer::"; + sails.on('ready', async function () { + that.queueService = sails.services[sails.config.queue.serviceName]; + that.initClient(); + await that.buildSchema(); + }); + } + + protected initClient() { + this.client = solr.createClient(sails.config.solr.options); + this.client.autoCommit = true; + this.baseUrl = this.getBaseUrl(); + } + + protected async buildSchema() { + const coreName = sails.config.solr.options.core; + // wait for SOLR to start up + await this.waitForSolr(); + // check if the schema is built.... + try { + const flagName = sails.config.solr.initSchemaFlag.name; + const schemaInitFlag = await this.getSchemaEntry(coreName, 'fields', flagName); + if (!_.isEmpty(schemaInitFlag)) { + sails.log.verbose(`${this.logHeader} Schema flag found: ${flagName}. Schema is already initialised, skipping build.`); + return; + } + } catch (err) { + sails.log.verbose(JSON.stringify(err)); + } + sails.log.verbose(`${this.logHeader} Schema not initialised, building schema...`) + const schemaUrl = `${this.baseUrl}${coreName}/schema`; + try { + const schemaDef = sails.config.solr.schema; + if (_.isEmpty(schemaDef)) { + sails.log.verbose(`${this.logHeader} Schema definition empty, skipping build.`); + return; + } + // append the init flag + if (_.isEmpty(schemaDef['add-field'])) { + schemaDef['add-field'] = []; + } + schemaDef['add-field'].push(sails.config.solr.initSchemaFlag); + sails.log.verbose(`${this.logHeader} sending schema definition:`); + sails.log.verbose(JSON.stringify(schemaDef)); + const response = await got.post(schemaUrl, { + json: schemaDef, + responseType: 'json' + }).json(); + sails.log.verbose(`${this.logHeader} Schema build successful, response: `); + sails.log.verbose(JSON.stringify(response)); + } catch (err) { + sails.log.error(`${this.logHeader} Failed to build SOLR schema:`); + sails.log.error(JSON.stringify(err)); + } + } + + private async getSchemaEntry(core: string, fieldName: string, name: string) { + const schemaResp = await this.getSchema(core); + return _.find(_.get(schemaResp.schema,fieldName), (schemaDef) => { return schemaDef.name == name }); + } + + private async getSchema(core:string) { + const schemaUrl = `${this.baseUrl}${core}/schema?wt=json`; + return await got(schemaUrl).json(); + } + + private async waitForSolr() { + let solrUp = false; + let tryCtr = 0; + const coreName = sails.config.solr.options.core; + const urlCheck = `${this.baseUrl}admin/cores?action=STATUS&core=${coreName}`; + while (!solrUp && tryCtr <= sails.config.solr.maxWaitTries) { + try { + tryCtr++; + sails.log.verbose(`${this.logHeader} Checking if SOLR is up, try #${tryCtr}... ${urlCheck}`); + const solrStat = await got.get(urlCheck).json(); + sails.log.verbose(`${this.logHeader} Response is:`); + sails.log.verbose(JSON.stringify(solrStat)); + if (solrStat.status[coreName].instanceDir) { + sails.log.info(`${this.logHeader} SOLR core is available: ${coreName}`); + solrUp = true; + } else { + throw new Error(`SOLR core: ${coreName} is still loading.`); + } + } catch (err) { + sails.log.info(`${this.logHeader} SOLR core: ${coreName} is still down, waiting.`); + sails.log.info(JSON.stringify(err)); + if (tryCtr == sails.config.solr.maxWaitTries) { + sails.log.error(`${this.logHeader} SOLR seemed to have failed startup, giving up on waiting.`); + break; + } + await this.sleep(sails.config.solr.waitTime); + } + } + } + + private getBaseUrl(): string { + return `${sails.config.solr.options.https ? 'https': 'http'}://${sails.config.solr.options.host}:${sails.config.solr.options.port}/solr/`; + } + + public index(id: string, data: any) { + sails.log.verbose(`${this.logHeader} adding indexing job: ${id} with data:`); + // storage_id is used as the main ID in searches + _.set(data, 'storage_id', id); + _.set(data, 'id', id); + sails.log.verbose(JSON.stringify(data)); + this.queueService.now(sails.config.solr.createOrUpdateJobName, data); + } + + public remove(id: string) { + + } + + public async searchFuzzy(type, workflowState, searchQuery, exactSearches, facetSearches, brand, user, roles, returnFields): Promise { + const username = user.username; + const coreName = sails.config.solr.options.core; + // const url = `${this.getSearchTypeUrl(type, searchField, searchStr)}&start=0&rows=${sails.config.record.export.maxRecords}`; + let searchParam = workflowState ? ` AND workflow_stage:${workflowState} ` : ''; + searchParam = `${searchParam} AND full_text:${searchQuery}`; + _.forEach(exactSearches, (exactSearch) => { + searchParam = `${searchParam}&fq=${exactSearch.name}:${this.luceneEscape(exactSearch.value)}` + }); + if (facetSearches.length > 0) { + searchParam = `${searchParam}&facet=true` + _.forEach(facetSearches, (facetSearch) => { + searchParam = `${searchParam}&facet.field=${facetSearch.name}${_.isEmpty(facetSearch.value) ? '' : `&fq=${facetSearch.name}:${this.luceneEscape(facetSearch.value)}`}` + }); + } + + let url = `${this.baseUrl}${coreName}/select?q=metaMetadata_brandId:${brand.id} AND metaMetadata_type:${type}${searchParam}&version=2.2&wt=json&sort=date_object_modified desc`; + url = this.addAuthFilter(url, username, roles, brand, false) + sails.log.verbose(`Searching fuzzy using: ${url}`); + const response = await got(url).json(); + const customResp = { + records: [] + }; + _.forEach(response.response.docs, solrdoc => { + const customDoc = {}; + _.forEach(returnFields, retField => { + if (_.isArray(solrdoc[retField])) { + customDoc[retField] = solrdoc[retField][0]; + } else { + customDoc[retField] = solrdoc[retField]; + } + }); + customDoc["hasEditAccess"] = RecordsService.hasEditAccess(brand, user, roles, solrdoc); + customResp.records.push(customDoc); + }); + // check if have facets turned on... + if (response.facet_counts) { + customResp['facets'] = []; + _.forOwn(response.facet_counts.facet_fields, (facet_field, facet_name) => { + const numFacetsValues = _.size(facet_field) / 2; + const facetValues = []; + for (var i = 0, j = 0; i < numFacetsValues; i++) { + facetValues.push({ + value: facet_field[j++], + count: facet_field[j++] + }); + } + customResp['facets'].push({ + name: facet_name, + values: facetValues + }); + }); + } + return customResp; + } + + public async addOrUpdate(job:any) { + try { + let data = job.attrs.data; + sails.log.verbose(`${this.logHeader} adding document: ${data.id} to index`); + // flatten the JSON + const processedData = this.preIndex(data); + sails.log.verbose(JSON.stringify(processedData)); + this.client.add(processedData, (err, obj) => { + if (err) { + sails.log.error(`${this.logHeader} Failed to add document: `); + sails.log.error(err); + return; + } + this.client.commit((commitErr, commitObj) => { + sails.log.verbose(`${this.logHeader} document added to SOLR: ${data.id}`); + sails.log.verbose(obj); + }); + }); + } catch (err) { + sails.log.error(`${this.logHeader} Failed to addOrUpdate, while pre-processing index: `); + sails.log.error(JSON.stringify(err)); + } + } + + private preIndex(data: any) { + let processedData = _.cloneDeep(data); + // moving + _.each(sails.config.solr.preIndex.move, (moveConfig) => { + const source = moveConfig.source; + const dest = moveConfig.dest; + // the data used will always be the original object + const moveData = _.get(data, source); + if (!_.isEmpty(moveData)) { + _.unset(processedData, source); + if (_.isEmpty(dest)) { + // empty destination means the root object + _.merge(processedData, moveData); + } else { + _.set(processedData, dest); + } + } else { + sails.log.verbose(`${this.logHeader} no data to move from: ${moveConfig.source}, ignoring.`); + } + }); + // copying + _.each(sails.config.solr.preIndex.copy, (copyConfig) => { + _.set(processedData, copyConfig.dest, _.get(data, copyConfig.source)); + }); + // flattening... + // first remove those with special flattening options + _.each(sails.config.solr.preIndex.flatten.special, (specialFlattenConfig) => { + _.unset(processedData, specialFlattenConfig.field); + }); + processedData = flat.flatten(processedData, sails.config.solr.preIndex.flatten.options); + _.each(sails.config.solr.preIndex.flatten.special, (specialFlattenConfig) => { + const dataToFlatten = {}; + if (specialFlattenConfig.dest) { + _.set(dataToFlatten, specialFlattenConfig.dest, _.get(data, specialFlattenConfig.source)); + } else { + _.set(dataToFlatten, specialFlattenConfig.source, _.get(data, specialFlattenConfig.source)); + } + let flattened = flat.flatten(dataToFlatten, specialFlattenConfig.options); + _.merge(processedData, flattened); + }); + // sanitise any empty keys so SOLR doesn't complain + _.forOwn(processedData, (v, k) => { + if (_.isEmpty(k)) { + _.unset(processedData, k); + } + }); + return processedData; + } + + protected luceneEscape(str: string) { + return luceneEscapeQuery.escape(str); + } + + protected addAuthFilter(url, username, roles, brand, editAccessOnly = undefined) { + + var roleString = "" + var matched = false; + for (var i = 0; i < roles.length; i++) { + var role = roles[i] + if (role.branding == brand.id) { + if (matched) { + roleString += " OR "; + matched = false; + } + roleString += roles[i].name; + matched = true; + } + } + url = url + "&fq=authorization_edit:" + username + (editAccessOnly ? "" : (" OR authorization_view:" + username + " OR authorization_viewRoles:(" + roleString + ")")) + " OR authorization_editRoles:(" + roleString + ")"; + return url; + } + } +} + +module.exports = new Services.SolrSearchService().exports(); diff --git a/typescript/api/services/VocabService.ts b/typescript/api/services/VocabService.ts index 6f9c53042a..3d8889cdf4 100644 --- a/typescript/api/services/VocabService.ts +++ b/typescript/api/services/VocabService.ts @@ -220,22 +220,6 @@ export module Services { }); } - saveInst(instItems) { - _.forEach(instItems, item => { - // added for Solr case-insensi search - item.text_name = item.name; - }); - return RecordsService.createBatch(sails.config.vocab.collection['grid'].type, instItems, 'grid_id'); - } - - searchInst(searchString, fields) { - return RecordsService.search(sails.config.vocab.collection['grid'].type, sails.config.vocab.collection['grid'].searchField, searchString, sails.config.vocab.collection['grid'].fields); - } - - getInst(collectionId) { - return RecordsService.getOne(sails.config.vocab.collection[collectionId].type); - } - protected getMintOptions(url) { return {url:url, json:true, headers: {'Authorization': `Bearer ${sails.config.mint.apiKey}`, 'Content-Type': 'application/json; charset=utf-8'}}; } From 1273e984cb1b65861e0627fa824e2d82e32de02d Mon Sep 17 00:00:00 2001 From: Shilo B Date: Thu, 3 Dec 2020 14:16:16 +1000 Subject: [PATCH 26/48] Fixed transfer of responsibility --- config/recordtype.js | 4 +- config/solr.js | 22 +++ .../api/controllers/RecordController.ts | 168 +++++++++--------- 3 files changed, 107 insertions(+), 87 deletions(-) diff --git a/config/recordtype.js b/config/recordtype.js index 19ff08118c..0ae8ab15b9 100644 --- a/config/recordtype.js +++ b/config/recordtype.js @@ -97,13 +97,13 @@ module.exports.recordtype = { } }, searchFilters: [{ - name: "title", + name: "text_title", title: "search-refine-title", type: "exact", typeLabel: "search-refine-contains" }, { - name: "description", + name: "text_description", title: "search-refine-description", type: "exact", typeLabel: "search-refine-contains" diff --git a/config/solr.js b/config/solr.js index 1a1a4ce79f..fe062c9805 100644 --- a/config/solr.js +++ b/config/solr.js @@ -43,6 +43,20 @@ module.exports.solr = { indexed: true, stored: true, multiValued: true + }, + { + name: "text_title", + type: "text_general", + indexed: true, + stored: true, + multiValued: true + }, + { + name: "text_description", + type: "text_general", + indexed: true, + stored: true, + multiValued: true } ], 'add-dynamic-field': [ @@ -57,6 +71,14 @@ module.exports.solr = { { source: "*", dest: "full_text" + }, + { + source: 'title', + dest: 'text_title' + }, + { + source: 'description', + dest: 'text_description' } ] }, diff --git a/typescript/api/controllers/RecordController.ts b/typescript/api/controllers/RecordController.ts index d0893b3a86..322edda0f1 100644 --- a/typescript/api/controllers/RecordController.ts +++ b/typescript/api/controllers/RecordController.ts @@ -239,92 +239,67 @@ export module Controllers { if (records.length > 0) { let completeRecordSet = []; let hasError = false; - _.forEach(records, rec => { + _.forEach(records, (rec) => { // First: check if this user has edit access to this record, we don't want Gremlins sneaking in on us // Not trusting what was posted, retrieving from DB... sails.log.verbose(`updateResponsibilities() -> Processing:${rec.oid}`); - this.getRecord(rec.oid).subscribe(recObj => { + this.getRecord(rec.oid).subscribe(async (recObj) => { if (this.recordsService.hasEditAccess(brand, user, user.roles, recObj)) { let recType = recObj.metaMetadata.type; - let relatedRecords = this.recordsService.getRelatedRecords(rec.oid, brand); - - relatedRecords.then(relatedRecords => { + let relatedRecords = await this.recordsService.getRelatedRecords(rec.oid, brand); + sails.log.verbose(`updateResponsibilities() -> Related records:`); + sails.log.verbose(JSON.stringify(relatedRecords)); + let relationships = relatedRecords['processedRelationships']; + let relatedObjects = relatedRecords['relatedObjects']; + + //If there are no relationships, the record isn't related to any others so manually inject the info needed to have this record processed + if (relationships.indexOf(recType) == -1) { + relationships.push(recType); + relatedObjects[recType] = [{ + redboxOid: rec.oid + }]; + } + let relationshipCount = 0; + _.each(relationships, relationship => { + let relationshipObjects = relatedObjects[relationship]; + relationshipCount++; + let relationshipObjectCount = 0; + _.each(relationshipObjects, relationshipObject => { + + const oid = relationshipObject.redboxOid; + let record = null; + this.getRecord(oid) + .flatMap((rec) => { + record = rec; + return RecordTypesService.get(brand, record.metaMetadata.type); + }) + .subscribe(recordTypeObj => { + + const transferConfig = recordTypeObj['transferResponsibility']; + if (transferConfig) { + record = this.updateResponsibility(transferConfig, role, record, updateData); + + sails.log.verbose(`Updating record ${oid}`); + sails.log.verbose(JSON.stringify(record)); + Observable.fromPromise(this.recordsService.updateMeta(brand, oid, record)).subscribe(response => { + relationshipObjectCount++; + if (response && response.isSuccessful()) { + if (oid == rec.oid) { + recordCtr++; + } + var to = toEmail; + var subject = "Ownership transfered"; + var data = {}; + data['record'] = record; + data['name'] = toName; + data['oid'] = oid; + EmailService.sendTemplate(to, subject, "transferOwnerTo", data); - let relationships = relatedRecords['processedRelationships']; - let relatedObjects = relatedRecords['relatedObjects']; - //If there are no relationships, the record isn't related to any others so manually inject the info needed to have this record processed - if (relationships.indexOf(recType) == -1) { - relationships.push(recType); - relatedObjects[recType] = [{ - redboxOid: rec.oid - }]; - } - let relationshipCount = 0; - _.each(relationships, relationship => { - let relationshipObjects = relatedObjects[relationship]; - relationshipCount++; - let relationshipObjectCount = 0; - _.each(relationshipObjects, relationshipObject => { - - const oid = relationshipObject.redboxOid; - let record = null; - this.getRecord(oid) - .flatMap((rec) => { - record = rec; - return RecordTypesService.get(brand, record.metaMetadata.type); - }) - .subscribe(recordTypeObj => { - - const transferConfig = recordTypeObj['transferResponsibility']; - if (transferConfig) { - record = this.updateResponsibility(transferConfig, role, record, updateData); - - sails.log.verbose(`Updating record ${oid}`); - sails.log.verbose(JSON.stringify(record)); - Observable.fromPromise(this.recordsService.updateMeta(brand, oid, record)).subscribe(response => { - relationshipObjectCount++; - if (response && response.isSuccessful()) { - if (oid == rec.oid) { - recordCtr++; - } - var to = toEmail; - var subject = "Ownership transfered"; - var data = {}; - data['record'] = record; - data['name'] = toName; - data['oid'] = oid; - EmailService.sendTemplate(to, subject, "transferOwnerTo", data); - - - - if (relationshipCount == relationships.length && relationshipObjectCount == relationshipObjects.length) { - completeRecordSet.push({ - success: true, - record: record - }); - if (completeRecordSet.length == records.length) { - if (hasError) { - return this.ajaxFail(req, res, null, completeRecordSet); - } else { - return this.ajaxOk(req, res, null, completeRecordSet); - } - } else { - sails.log.verbose(`Completed record set:`); - sails.log.verbose(`${completeRecordSet.length} == ${records.length}`); - } - } else { - sails.log.verbose(`Record counter:`); - sails.log.verbose(`${recordCtr} == ${records.length} && ${relationshipCount} == ${relationships.length} && ${relationshipObjectCount} == ${relationshipObjects.length}`); - } - } else { - sails.log.error(`Failed to update authorization:`); - sails.log.error(response); - hasError = true; + if (relationshipCount == relationships.length && relationshipObjectCount == relationshipObjects.length) { completeRecordSet.push({ - success: false, - error: response, + success: true, record: record }); if (completeRecordSet.length == records.length) { @@ -333,15 +308,21 @@ export module Controllers { } else { return this.ajaxOk(req, res, null, completeRecordSet); } + } else { + sails.log.verbose(`Completed record set:`); + sails.log.verbose(`${completeRecordSet.length} == ${records.length}`); } + } else { + sails.log.verbose(`Record counter:`); + sails.log.verbose(`${recordCtr} == ${records.length} && ${relationshipCount} == ${relationships.length} && ${relationshipObjectCount} == ${relationshipObjects.length}`); } - }, error => { - sails.log.error("Error updating auth:"); - sails.log.error(error); + } else { + sails.log.error(`Failed to update authorization:`); + sails.log.error(response); hasError = true; completeRecordSet.push({ success: false, - error: error.message, + error: response, record: record }); if (completeRecordSet.length == records.length) { @@ -351,14 +332,31 @@ export module Controllers { return this.ajaxOk(req, res, null, completeRecordSet); } } + } + }, error => { + sails.log.error("Error updating auth:"); + sails.log.error(error); + hasError = true; + completeRecordSet.push({ + success: false, + error: error.message, + record: record }); + if (completeRecordSet.length == records.length) { + if (hasError) { + return this.ajaxFail(req, res, null, completeRecordSet); + } else { + return this.ajaxOk(req, res, null, completeRecordSet); + } + } + }); - } - }); - }); + } + }); + }); - }) }); + } else { const errorMsg = `Attempted to transfer responsibilities, but user: '${user.username}' has no access to record: ${rec.oid}`; sails.log.error(errorMsg); From 5c68b03886aec7647521996480b37b08828750ad Mon Sep 17 00:00:00 2001 From: Shilo B Date: Tue, 8 Dec 2020 12:56:47 +1000 Subject: [PATCH 27/48] Fixed: missing function, failing test in SOLR. --- package-lock.json | 154 +++++++---------- package.json | 4 +- .../docker-compose.mocha.yml | 14 ++ .../docker-compose.newman.yml | 14 ++ test/postman/runNewmanTests.js | 2 +- test/postman/test-collection.json | 155 +++++++++--------- typescript/api/services/ConfigService.ts | 29 ++-- 7 files changed, 185 insertions(+), 187 deletions(-) diff --git a/package-lock.json b/package-lock.json index a424b09407..ab7074cdab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -337,9 +337,9 @@ } }, "@researchdatabox/sails-hook-redbox-storage-mongo": { - "version": "0.0.2-alpha", - "resolved": "https://registry.npmjs.org/@researchdatabox/sails-hook-redbox-storage-mongo/-/sails-hook-redbox-storage-mongo-0.0.2-alpha.tgz", - "integrity": "sha512-EVokF0CXqJL5LyoMW5LrxQKu7rct+v8nc/P20dVWyV8WRlQQpyjcwmfREodjcb77PGTFqbIJiBTzkqVX/QkvJQ==", + "version": "0.0.3-alpha", + "resolved": "https://registry.npmjs.org/@researchdatabox/sails-hook-redbox-storage-mongo/-/sails-hook-redbox-storage-mongo-0.0.3-alpha.tgz", + "integrity": "sha512-Q41UzIEJOJTyDdyifBpK9AO43uE2aQyg1YUQ/19uNSVONfCCDSipL8vAnd/fJBz2iWxASeRLZeawjOiwH8049g==", "requires": { "json2csv": "^5.0.3", "lodash": "^4.17.20", @@ -1917,9 +1917,9 @@ } }, "chardet": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-1.2.1.tgz", - "integrity": "sha512-bnDt+69nS8Hi7Xmt0uDiQ32/hRpMK/0pc4lc32Y87voBgdsz26VZXOyo5VNQWQfe7rUsevZIfWB82C5QSpCXcQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-1.3.0.tgz", + "integrity": "sha512-cyTQGGptIjIT+CMGT5J/0l9c6Fb+565GCFjjeUTKxUO7w3oR+FcNCMEKTn5xtVKaLFmladN7QF68IiQsv5Fbdw==", "dev": true }, "charenc": { @@ -4097,9 +4097,9 @@ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==" }, "flatted": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", - "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.0.tgz", + "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==", "dev": true }, "flaverr": { @@ -6478,9 +6478,9 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json2csv": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/json2csv/-/json2csv-5.0.3.tgz", - "integrity": "sha512-e3gEZU/4fp8CVQMHlwT77RayAR7nylCzCYN7jTIbPTEqk0oTaE8GTcBudLgXrHt4ltOs9SAsbveMJT0YK/QUSg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/json2csv/-/json2csv-5.0.5.tgz", + "integrity": "sha512-/UyvnfuUghRM+C/AiQ02X0LS+/AKfugcwaWo/gAz1pi203v29sUMrMSNEC088i+h0EG39eSsmeL9Z0iK+9MM0A==", "requires": { "commander": "^6.1.0", "jsonparse": "^1.3.1", @@ -6488,9 +6488,9 @@ }, "dependencies": { "commander": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.1.0.tgz", - "integrity": "sha512-wl7PNrYWd2y5mp1OK/LhTlv8Ff4kQJQRXXAvF+uU/TPNiVJUxZLRYGj/B0y/lPGAVcSbJqH2Za/cvHmrPMC8mA==" + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==" } } }, @@ -8533,27 +8533,27 @@ "dev": true }, "newman": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/newman/-/newman-5.2.0.tgz", - "integrity": "sha512-VzvKHhdPM7QvuwQfnJMj50hHLf27AE9hCAbMgbP5aXIQ12eKhrL3if1U0OWcB+BvSLdww9nKvMDwvuF72/ipFw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/newman/-/newman-5.2.1.tgz", + "integrity": "sha512-kEuTMQCiORHZFx92sPVih8RHsJ40JxgxvlsrUe6MFXLQg2/UrO1KaUQCDabTy41tOu0a+dx6Mtg+x+uK1rCPcA==", "dev": true, "requires": { "async": "3.2.0", - "chardet": "1.2.1", + "chardet": "1.3.0", "cli-progress": "3.8.2", "cli-table3": "0.6.0", "colors": "1.4.0", - "commander": "6.1.0", + "commander": "6.2.0", "csv-parse": "4.12.0", "eventemitter3": "4.0.7", "filesize": "6.1.0", "lodash": "4.17.20", "mkdirp": "1.0.4", - "postman-collection": "3.6.6", + "postman-collection": "3.6.8", "postman-collection-transformer": "3.3.3", - "postman-request": "2.88.1-postman.24", - "postman-runtime": "7.26.5", - "pretty-ms": "7.0.0", + "postman-request": "2.88.1-postman.27", + "postman-runtime": "7.26.8", + "pretty-ms": "7.0.1", "semver": "7.3.2", "serialised-error": "1.1.3", "tough-cookie": "3.0.1", @@ -8568,9 +8568,9 @@ "dev": true }, "commander": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.1.0.tgz", - "integrity": "sha512-wl7PNrYWd2y5mp1OK/LhTlv8Ff4kQJQRXXAvF+uU/TPNiVJUxZLRYGj/B0y/lPGAVcSbJqH2Za/cvHmrPMC8mA==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", "dev": true }, "filesize": { @@ -9622,9 +9622,9 @@ } }, "postman-collection": { - "version": "3.6.6", - "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-3.6.6.tgz", - "integrity": "sha512-fm9AGKHbL2coSzD5nw+F07JrX7jzqu2doGIXevPPrwlpTZyTM6yagEdENeO/Na8rSUrI1+tKPj+TgAFiLvtF4w==", + "version": "3.6.8", + "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-3.6.8.tgz", + "integrity": "sha512-TNPaK2dpVRhttUFo/WN0ReopXEtuSQMktwcvwJbQ0z8K+5hftvyx2ia40xgg9qFl/Ra78qoNTUmLL1s3lRqLMg==", "dev": true, "requires": { "escape-html": "1.0.3", @@ -9634,10 +9634,10 @@ "iconv-lite": "0.6.2", "liquid-json": "0.3.1", "lodash": "4.17.20", - "marked": "1.1.1", + "marked": "1.2.0", "mime-format": "2.0.0", "mime-types": "2.1.27", - "postman-url-encoder": "2.1.3", + "postman-url-encoder": "3.0.0", "sanitize-html": "1.20.1", "semver": "7.3.2", "uuid": "3.4.0" @@ -9653,9 +9653,9 @@ } }, "marked": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/marked/-/marked-1.1.1.tgz", - "integrity": "sha512-mJzT8D2yPxoPh7h0UXkB+dBj4FykPJ2OIfxAWeIHrvoHDkFxukV/29QxoFQoPM6RLEwhIFdJpmKBlqVM3s2ZIw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.2.0.tgz", + "integrity": "sha512-tiRxakgbNPBr301ihe/785NntvYyhxlqcL3YaC8CaxJQh7kiaEtrN9B/eK2I2943Yjkh5gw25chYFDQhOMCwMA==", "dev": true }, "semver": { @@ -9707,12 +9707,12 @@ } }, "postman-request": { - "version": "2.88.1-postman.24", - "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.24.tgz", - "integrity": "sha512-afW2QxA9YCSaMUBFGRWvxnyjN4SqgXC5HqKJ0DFNfbx4ZW6AsBCFXeb5NAFgCH3kZ/og0XhUSDV+imjWwahLLg==", + "version": "2.88.1-postman.27", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.27.tgz", + "integrity": "sha512-4Qc7p3/cbp5S4Q6LcOzJ+K5N7loWDKjW0S9hj8M2AMJDUVcFUbdgvQb6ZfTERz2+34xP9ByCy7VhdnNCATe/bA==", "dev": true, "requires": { - "@postman/form-data": "~3.1.0", + "@postman/form-data": "~3.1.1", "@postman/tunnel-agent": "^0.6.3", "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -9750,9 +9750,9 @@ } }, "postman-runtime": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/postman-runtime/-/postman-runtime-7.26.5.tgz", - "integrity": "sha512-5sY4iWjG4GeGQ5o4QAJkAt79jmcZhOrs3XSbtCLrs4NqoZpTBx5WDQr6h1Dy4Y8Q0X/9NMCYqF/c5pnsqUyDCQ==", + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/postman-runtime/-/postman-runtime-7.26.8.tgz", + "integrity": "sha512-ZMUZ7mQ2SMOX/C/ntgx2SAfRt3VV6wOy+aLyWbAqpQPo5Jfodxwv9QhxGj3S2km+IYzO6BT1luzE8X8fr2UafA==", "dev": true, "requires": { "async": "2.6.3", @@ -9766,10 +9766,10 @@ "lodash": "4.17.20", "node-oauth1": "1.3.0", "performance-now": "2.1.0", - "postman-collection": "3.6.6", - "postman-request": "2.88.1-postman.24", - "postman-sandbox": "3.5.9", - "postman-url-encoder": "2.1.3", + "postman-collection": "3.6.8", + "postman-request": "2.88.1-postman.27", + "postman-sandbox": "4.0.0", + "postman-url-encoder": "3.0.0", "resolve-from": "5.0.0", "serialised-error": "1.1.3", "tough-cookie": "3.0.1", @@ -9811,39 +9811,22 @@ } }, "postman-sandbox": { - "version": "3.5.9", - "resolved": "https://registry.npmjs.org/postman-sandbox/-/postman-sandbox-3.5.9.tgz", - "integrity": "sha512-B9mREFulQuYOa9+B7rklb94d9iZ6EYyhsUvdIfxphGUByimb6mOhumWV0sGbrtxVTsCAtTpN/68Shm7NCjrZ0A==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postman-sandbox/-/postman-sandbox-4.0.0.tgz", + "integrity": "sha512-0j1VCDa5MHMTfZqv2XSYUyn+hgT9izoRdGFAvjtHCH+i+2TP1KxqXzjxXzOdx1pt26wpl9APdJ2hKKFpx9UlrQ==", "dev": true, "requires": { - "inherits": "2.0.4", "lodash": "4.17.20", "teleport-javascript": "1.0.0", - "tough-cookie": "3.0.1", - "uuid": "3.4.0", - "uvm": "1.7.9" - }, - "dependencies": { - "tough-cookie": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", - "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", - "dev": true, - "requires": { - "ip-regex": "^2.1.0", - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - } + "uvm": "2.0.1" } }, "postman-url-encoder": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/postman-url-encoder/-/postman-url-encoder-2.1.3.tgz", - "integrity": "sha512-CwQjnoxaugCGeOyzVeZ4k1cNQ6iS8OBCzuWzcf4kLStKeRp0MwmLKYv25frynmDpugUUimq/d+FZCq6GtIX9Ag==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postman-url-encoder/-/postman-url-encoder-3.0.0.tgz", + "integrity": "sha512-bk5wus5/5Ei9pbh+sQXaAxS5n4ZwiNAaIA8VBvRcXP6QyKcue2yF6xk1HqdtaZoH1G8+6509SVeOBojoFQ7nrA==", "dev": true, "requires": { - "postman-collection": "^3.6.4", "punycode": "^2.1.1" } }, @@ -9877,9 +9860,9 @@ } }, "pretty-ms": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.0.tgz", - "integrity": "sha512-J3aPWiC5e9ZeZFuSeBraGxSkGMOvulSWsxDByOcbD1Pr75YL3LSNIKIb52WXbCLE1sS5s4inBBbryjF4Y05Ceg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", "dev": true, "requires": { "parse-ms": "^2.1.0" @@ -17105,29 +17088,12 @@ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, "uvm": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/uvm/-/uvm-1.7.9.tgz", - "integrity": "sha512-Z3Uf7Jm8Cpvxf8FMjE7cFBaXsXzKu2n3kFqV23pQNLg8rrZqi6fmLFnLCn5EhiEC94XdcQCCNjrGeOnFdy1xsA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/uvm/-/uvm-2.0.1.tgz", + "integrity": "sha512-bZAckfNKnr95YkTCVZWyzK+7w1c8sYJuTresCBqhiizByVRtfPqhGJpTwFUSaS2YkaVfsMoN5xZcOCNxTx9uCA==", "dev": true, "requires": { - "flatted": "2.0.1", - "inherits": "2.0.4", - "lodash": "4.17.19", - "uuid": "3.3.2" - }, - "dependencies": { - "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", - "dev": true - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "dev": true - } + "flatted": "3.1.0" } }, "v8flags": { diff --git a/package.json b/package.json index a57e2bf7ab..a86dfc3bac 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "ReDBox 2 Portal", "keywords": [], "dependencies": { - "@researchdatabox/sails-hook-redbox-storage-mongo": "0.0.2-alpha", + "@researchdatabox/sails-hook-redbox-storage-mongo": "0.0.3-alpha", "@sailshq/upgrade": "^1.0.9", "@uppy/core": "^1.13.1", "@uppy/dashboard": "^1.12.5", @@ -100,7 +100,7 @@ "grunt-shell": "^3.0.1", "istanbul": "^0.4.5", "mocha": "^8.1.2", - "newman": "^5.2.0", + "newman": "^5.2.1", "node-sass": "4.14.1", "supertest": "^4.0.2", "uglify-es": "^3.3.10" diff --git a/support/integration-testing/docker-compose.mocha.yml b/support/integration-testing/docker-compose.mocha.yml index 99dab0c945..00145c77a5 100644 --- a/support/integration-testing/docker-compose.mocha.yml +++ b/support/integration-testing/docker-compose.mocha.yml @@ -33,3 +33,17 @@ services: - mongodb ports: - "27017:27017" + solr: + image: solr:8.6.3 + expose: + - "8983" + ports: + - "8983:8983" + networks: + main: + aliases: + - solr + entrypoint: + - docker-entrypoint.sh + - solr-precreate + - redbox diff --git a/support/integration-testing/docker-compose.newman.yml b/support/integration-testing/docker-compose.newman.yml index db06b6fd1a..ba70b46be3 100644 --- a/support/integration-testing/docker-compose.newman.yml +++ b/support/integration-testing/docker-compose.newman.yml @@ -33,3 +33,17 @@ services: - mongodb ports: - "27017:27017" + solr: + image: solr:8.6.3 + expose: + - "8983" + ports: + - "8983:8983" + networks: + main: + aliases: + - solr + entrypoint: + - docker-entrypoint.sh + - solr-precreate + - redbox diff --git a/test/postman/runNewmanTests.js b/test/postman/runNewmanTests.js index 59306c04d9..ce660eaba8 100644 --- a/test/postman/runNewmanTests.js +++ b/test/postman/runNewmanTests.js @@ -31,7 +31,7 @@ sails.lift({ newman.run({ collection: require('./test-collection.json'), reporters: 'cli', - environment: require('./local.environment.json'), + environment: require('./local.environment.json') }, function(err, summary) { const runError = err || summary.run.error || summary.run.failures.length; if (err) { diff --git a/test/postman/test-collection.json b/test/postman/test-collection.json index 28b3276ccc..2f327aab60 100644 --- a/test/postman/test-collection.json +++ b/test/postman/test-collection.json @@ -2768,20 +2768,22 @@ "response": [] }, { - "name": "Search Index", + "name": "Create data publication", "event": [ { "listen": "test", "script": { - "id": "23fe1da6-69cc-4675-b30b-d8e437499f9b", + "id": "c7986692-b565-4c4e-baed-80e951c4a347", "exec": [ + "", "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", "});", "", - "pm.test(\"Has record array\", function () {", + "pm.test(\"Test oid exists\", function () {", " var jsonData = pm.response.json();", - " pm.expect(jsonData).to.have.property(\"records\");", + " pm.expect(jsonData).to.have.property('oid');", + " postman.setEnvironmentVariable(\"dataPublicationOid\", jsonData.oid);", "});" ], "type": "text/javascript" @@ -2789,12 +2791,16 @@ } ], "request": { - "method": "GET", + "method": "POST", "header": [ { "key": "Pragma", "value": "no-cache" }, + { + "key": "Origin", + "value": "{{host}}" + }, { "key": "Accept-Encoding", "value": "gzip, deflate, br" @@ -2811,71 +2817,64 @@ "key": "User-Agent", "value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36" }, + { + "key": "X-Source", + "value": "jsclient" + }, { "key": "Content-Type", - "value": "application/json;charset=utf-8" + "value": "application/json;charset=UTF-8" }, { "key": "Accept", "value": "application/json, text/plain, */*" }, { - "key": "Connection", - "value": "keep-alive" + "key": "Cache-Control", + "value": "no-cache" }, { "key": "Referer", - "value": "{{host}}/default/rdmp/record/search?q=Record" + "value": "{{host}}/default/rdmp/record/dataPublication/edit" }, { "key": "Cookie", "value": "{{cookie}}" }, { - "key": "X-Source", - "value": "jsclient" - }, - { - "key": "Cache-Control", - "value": "no-cache" + "key": "Connection", + "value": "keep-alive" } ], + "body": { + "mode": "raw", + "raw": "{\n \"parameterRetriever\": \"\",\n \"dataRecordGetter\": \"\",\n \"\": {},\n \"dataRecord\": {\n \"oid\": \"{{dataRecordOid}}\",\n \"title\": \"A Data record\"\n },\n \"title\": \"A Data record\",\n \"description\": \"Data record\",\n \"datatype\": \"collection\",\n \"finalKeywords\": [\n \"270199\",\n \"Golgi apparatus\",\n \"endocytosis\",\n \"membrane transport\",\n \"protein trafficking\",\n \"secretion\",\n \"HIV-AIDS\",\n \"bacterial pathogen\",\n \"hormone secretion\",\n \"immune development\",\n \"lysosomal storage disorder\"\n ],\n \"foaf:fundedBy_foaf:Agent\": [\n {}\n ],\n \"foaf:fundedBy_vivo:Grant\": [\n {}\n ],\n \"dc:subject_anzsrc:for\": [\n {\n \"name\": \"01 - MATHEMATICAL SCIENCES\",\n \"label\": \"MATHEMATICAL SCIENCES\",\n \"notation\": \"01\"\n }\n ],\n \"dc:subject_anzsrc:seo\": [],\n \"startDate\": \"\",\n \"endDate\": \"\",\n \"timePeriod\": \"\",\n \"geolocations\": [\n \"\"\n ],\n \"geospatial\": {},\n \"accessRightsToggle\": \"\",\n \"dataLocations\": [\n {\n \"type\": \"attachment\",\n \"location\": \"b869b4fae83a1f01082465d165d868a8/attach/d3de61376e5bc93c814607ee604ebac5\",\n \"mimeType\": \"image/png\",\n \"name\": \"Screen Shot 2018-11-26 at 3.39.48 pm.png\",\n \"fileId\": \"d3de61376e5bc93c814607ee604ebac5\",\n \"uploadUrl\": \"http://localhost:1500/default/rdmp/record/b869b4fae83a1f01082465d165d868a8/attach/d3de61376e5bc93c814607ee604ebac5\",\n \"selected\": true\n }\n ],\n \"dataLicensingAccess_manager\": \"Prof Paul Gleeson\",\n \"dc:accessRights\": \"Open\",\n \"accessRights_url\": \"\",\n \"related_publications\": [\n {\n \"related_title\": \"\",\n \"related_url\": \"\",\n \"related_notes\": \"\"\n }\n ],\n \"related_websites\": [\n {\n \"related_title\": \"\",\n \"related_url\": \"\",\n \"related_notes\": \"\"\n }\n ],\n \"related_metadata\": [\n {\n \"related_title\": \"\",\n \"related_url\": \"\",\n \"related_notes\": \"\"\n }\n ],\n \"related_data\": [\n {\n \"related_title\": \"\",\n \"related_url\": \"\",\n \"related_notes\": \"\"\n }\n ],\n \"related_services\": [\n {\n \"related_title\": \"\",\n \"related_url\": \"\",\n \"related_notes\": \"\"\n }\n ],\n \"license_identifier\": \"\",\n \"license_notes\": \"\",\n \"license_other_url\": \"\",\n \"license_statement\": \"Copyright ReDBox Research Data 2018\",\n \"license_statement_url\": \"\",\n \"citation_doi\": \"\",\n \"requestIdentifier\": [],\n \"citation_title\": \"A Data Record\",\n \"creators\": [\n {\n \"text_full_name\": \"Alberto Zweinstein\",\n \"email\": \"alberto.zweinstein@example.edu.au\",\n \"role\": \"Chief Investigator\",\n \"username\": \"\",\n \"orcid\": \"\",\n \"family_name\": \"Zweinstein\",\n \"given_name\": \"Alberto\"\n },\n {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"role\": \"Data manager\",\n \"username\": \"\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\",\n \"family_name\": \"Paul Gleeson\",\n \"given_name\": \"Prof\"\n },\n {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"role\": \"Contributors\",\n \"username\": \"\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\",\n \"family_name\": \"Paul Gleeson\",\n \"given_name\": \"Prof\"\n },\n {\n \"text_full_name\": null,\n \"email\": null,\n \"role\": \"Supervisor\",\n \"username\": \"\",\n \"orcid\": \"\",\n \"family_name\": \"\",\n \"given_name\": \"\"\n }\n ],\n \"citation_publisher\": \"ReDBox Research Data\",\n \"citation_url\": \"\",\n \"citation_publication_date\": \"\",\n \"citation_generated\": \"Zweinstein, Alberto; Paul Gleeson, Prof; Paul Gleeson, Prof (Invalid date): A Data Record. ReDBox Research Data. {ID_WILL_BE_HERE}\",\n \"dataowner_name\": \"Alberto Zweinstein\",\n \"dataowner_email\": \"alberto.zweinstein@example.edu.au\",\n \"contributor_ci\": {\n \"text_full_name\": \"Alberto Zweinstein\",\n \"full_name_honorific\": \"Dr Alberto Zweinstein\",\n \"email\": \"alberto.zweinstein@example.edu.au\",\n \"given_name\": \"Alberto\",\n \"family_name\": \"Zweinstein\",\n \"honorific\": \"Dr\",\n \"full_name_family_name_first\": \"Zweinstein, Alberto\",\n \"username\": \"\",\n \"role\": \"Chief Investigator\"\n },\n \"contributor_data_manager\": {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"full_name_honorific\": \"\",\n \"email\": \"notAReal@email.edu.au\",\n \"given_name\": \"Prof\",\n \"family_name\": \"Paul Gleeson\",\n \"honorific\": \"\",\n \"full_name_family_name_first\": \"Paul Gleeson, Prof\",\n \"username\": \"\",\n \"role\": \"Data manager\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n },\n \"contributor_supervisor\": {\n \"text_full_name\": \"\",\n \"full_name_honorific\": \"\",\n \"email\": \"\",\n \"given_name\": \"\",\n \"family_name\": \"\",\n \"honorific\": \"\",\n \"full_name_family_name_first\": \"\",\n \"username\": \"\",\n \"role\": \"Supervisor\"\n },\n \"embargoByDate\": \"\",\n \"embargoUntil\": null,\n \"embargoNote\": \"\",\n \"reviewerNote\": \"\",\n \"ckanLocation\": \"\"\n}" + }, "url": { - "raw": "{{host}}/default/rdmp/record/search/rdmp/?searchStr=Andrew&facetNames=grant_number_name,finalKeywords,workflow_stageLabel", + "raw": "{{host}}/default/rdmp/recordmeta/dataPublication", "host": [ "{{host}}" ], "path": [ "default", "rdmp", - "record", - "search", - "rdmp", - "" - ], - "query": [ - { - "key": "searchStr", - "value": "Andrew" - }, - { - "key": "facetNames", - "value": "grant_number_name,finalKeywords,workflow_stageLabel" - } + "recordmeta", + "dataPublication" ] } }, "response": [] }, { - "name": "Create data publication", + "name": "Migrate data publication to reviewing", "event": [ { "listen": "test", "script": { - "id": "c7986692-b565-4c4e-baed-80e951c4a347", + "id": "dd144dff-909f-4430-9bd5-ca5b3045d255", "exec": [ - "", + "console.log(\"Waiting for 10 seconds before testing search so SOLR can complete indexing....\");", + "setTimeout(function() {", "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", "});", @@ -2883,15 +2882,16 @@ "pm.test(\"Test oid exists\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData).to.have.property('oid');", - " postman.setEnvironmentVariable(\"dataPublicationOid\", jsonData.oid);", - "});" + "});", + "}, 10000);", + "" ], "type": "text/javascript" } } ], "request": { - "method": "POST", + "method": "PUT", "header": [ { "key": "Pragma", @@ -2905,10 +2905,6 @@ "key": "Accept-Encoding", "value": "gzip, deflate, br" }, - { - "key": "X-CSRF-Token", - "value": "" - }, { "key": "Accept-Language", "value": "en-US,en;q=0.9,en-AU;q=0.8,it;q=0.7" @@ -2935,7 +2931,7 @@ }, { "key": "Referer", - "value": "{{host}}/default/rdmp/record/dataPublication/edit" + "value": "{{host}}/default/rdmp/record/edit/49a33460c794498a56fa29ed08567cc4" }, { "key": "Cookie", @@ -2951,36 +2947,43 @@ "raw": "{\n \"parameterRetriever\": \"\",\n \"dataRecordGetter\": \"\",\n \"\": {},\n \"dataRecord\": {\n \"oid\": \"{{dataRecordOid}}\",\n \"title\": \"A Data record\"\n },\n \"title\": \"A Data record\",\n \"description\": \"Data record\",\n \"datatype\": \"collection\",\n \"finalKeywords\": [\n \"270199\",\n \"Golgi apparatus\",\n \"endocytosis\",\n \"membrane transport\",\n \"protein trafficking\",\n \"secretion\",\n \"HIV-AIDS\",\n \"bacterial pathogen\",\n \"hormone secretion\",\n \"immune development\",\n \"lysosomal storage disorder\"\n ],\n \"foaf:fundedBy_foaf:Agent\": [\n {}\n ],\n \"foaf:fundedBy_vivo:Grant\": [\n {}\n ],\n \"dc:subject_anzsrc:for\": [\n {\n \"name\": \"01 - MATHEMATICAL SCIENCES\",\n \"label\": \"MATHEMATICAL SCIENCES\",\n \"notation\": \"01\"\n }\n ],\n \"dc:subject_anzsrc:seo\": [],\n \"startDate\": \"\",\n \"endDate\": \"\",\n \"timePeriod\": \"\",\n \"geolocations\": [\n \"\"\n ],\n \"geospatial\": {},\n \"accessRightsToggle\": \"\",\n \"dataLocations\": [\n {\n \"type\": \"attachment\",\n \"location\": \"b869b4fae83a1f01082465d165d868a8/attach/d3de61376e5bc93c814607ee604ebac5\",\n \"mimeType\": \"image/png\",\n \"name\": \"Screen Shot 2018-11-26 at 3.39.48 pm.png\",\n \"fileId\": \"d3de61376e5bc93c814607ee604ebac5\",\n \"uploadUrl\": \"http://localhost:1500/default/rdmp/record/b869b4fae83a1f01082465d165d868a8/attach/d3de61376e5bc93c814607ee604ebac5\",\n \"selected\": true\n }\n ],\n \"dataLicensingAccess_manager\": \"Prof Paul Gleeson\",\n \"dc:accessRights\": \"Open\",\n \"accessRights_url\": \"\",\n \"related_publications\": [\n {\n \"related_title\": \"\",\n \"related_url\": \"\",\n \"related_notes\": \"\"\n }\n ],\n \"related_websites\": [\n {\n \"related_title\": \"\",\n \"related_url\": \"\",\n \"related_notes\": \"\"\n }\n ],\n \"related_metadata\": [\n {\n \"related_title\": \"\",\n \"related_url\": \"\",\n \"related_notes\": \"\"\n }\n ],\n \"related_data\": [\n {\n \"related_title\": \"\",\n \"related_url\": \"\",\n \"related_notes\": \"\"\n }\n ],\n \"related_services\": [\n {\n \"related_title\": \"\",\n \"related_url\": \"\",\n \"related_notes\": \"\"\n }\n ],\n \"license_identifier\": \"\",\n \"license_notes\": \"\",\n \"license_other_url\": \"\",\n \"license_statement\": \"Copyright ReDBox Research Data 2018\",\n \"license_statement_url\": \"\",\n \"citation_doi\": \"\",\n \"requestIdentifier\": [],\n \"citation_title\": \"A Data Record\",\n \"creators\": [\n {\n \"text_full_name\": \"Alberto Zweinstein\",\n \"email\": \"alberto.zweinstein@example.edu.au\",\n \"role\": \"Chief Investigator\",\n \"username\": \"\",\n \"orcid\": \"\",\n \"family_name\": \"Zweinstein\",\n \"given_name\": \"Alberto\"\n },\n {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"role\": \"Data manager\",\n \"username\": \"\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\",\n \"family_name\": \"Paul Gleeson\",\n \"given_name\": \"Prof\"\n },\n {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"role\": \"Contributors\",\n \"username\": \"\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\",\n \"family_name\": \"Paul Gleeson\",\n \"given_name\": \"Prof\"\n },\n {\n \"text_full_name\": null,\n \"email\": null,\n \"role\": \"Supervisor\",\n \"username\": \"\",\n \"orcid\": \"\",\n \"family_name\": \"\",\n \"given_name\": \"\"\n }\n ],\n \"citation_publisher\": \"ReDBox Research Data\",\n \"citation_url\": \"\",\n \"citation_publication_date\": \"\",\n \"citation_generated\": \"Zweinstein, Alberto; Paul Gleeson, Prof; Paul Gleeson, Prof (Invalid date): A Data Record. ReDBox Research Data. {ID_WILL_BE_HERE}\",\n \"dataowner_name\": \"Alberto Zweinstein\",\n \"dataowner_email\": \"alberto.zweinstein@example.edu.au\",\n \"contributor_ci\": {\n \"text_full_name\": \"Alberto Zweinstein\",\n \"full_name_honorific\": \"Dr Alberto Zweinstein\",\n \"email\": \"alberto.zweinstein@example.edu.au\",\n \"given_name\": \"Alberto\",\n \"family_name\": \"Zweinstein\",\n \"honorific\": \"Dr\",\n \"full_name_family_name_first\": \"Zweinstein, Alberto\",\n \"username\": \"\",\n \"role\": \"Chief Investigator\"\n },\n \"contributor_data_manager\": {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"full_name_honorific\": \"\",\n \"email\": \"notAReal@email.edu.au\",\n \"given_name\": \"Prof\",\n \"family_name\": \"Paul Gleeson\",\n \"honorific\": \"\",\n \"full_name_family_name_first\": \"Paul Gleeson, Prof\",\n \"username\": \"\",\n \"role\": \"Data manager\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n },\n \"contributor_supervisor\": {\n \"text_full_name\": \"\",\n \"full_name_honorific\": \"\",\n \"email\": \"\",\n \"given_name\": \"\",\n \"family_name\": \"\",\n \"honorific\": \"\",\n \"full_name_family_name_first\": \"\",\n \"username\": \"\",\n \"role\": \"Supervisor\"\n },\n \"embargoByDate\": \"\",\n \"embargoUntil\": null,\n \"embargoNote\": \"\",\n \"reviewerNote\": \"\",\n \"ckanLocation\": \"\"\n}" }, "url": { - "raw": "{{host}}/default/rdmp/recordmeta/dataPublication", + "raw": "http://localhost:1500/default/rdmp/recordmeta/{{dataPublicationOid}}?targetStep=reviewing", + "protocol": "http", "host": [ - "{{host}}" + "localhost" ], + "port": "1500", "path": [ "default", "rdmp", "recordmeta", - "dataPublication" + "{{dataPublicationOid}}" + ], + "query": [ + { + "key": "targetStep", + "value": "reviewing" + } ] } }, "response": [] }, { - "name": "Migrate data publication to reviewing", + "name": "Search Index", "event": [ { "listen": "test", "script": { - "id": "dd144dff-909f-4430-9bd5-ca5b3045d255", + "id": "23fe1da6-69cc-4675-b30b-d8e437499f9b", "exec": [ - "", "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", "});", "", - "pm.test(\"Test oid exists\", function () {", + "pm.test(\"Has record array\", function () {", " var jsonData = pm.response.json();", - " pm.expect(jsonData).to.have.property('oid');", + " pm.expect(jsonData).to.have.property(\"records\");", "});" ], "type": "text/javascript" @@ -2988,20 +2991,20 @@ } ], "request": { - "method": "PUT", + "method": "GET", "header": [ { "key": "Pragma", "value": "no-cache" }, - { - "key": "Origin", - "value": "{{host}}" - }, { "key": "Accept-Encoding", "value": "gzip, deflate, br" }, + { + "key": "X-CSRF-Token", + "value": "" + }, { "key": "Accept-Language", "value": "en-US,en;q=0.9,en-AU;q=0.8,it;q=0.7" @@ -3010,56 +3013,56 @@ "key": "User-Agent", "value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36" }, - { - "key": "X-Source", - "value": "jsclient" - }, { "key": "Content-Type", - "value": "application/json;charset=UTF-8" + "value": "application/json;charset=utf-8" }, { "key": "Accept", "value": "application/json, text/plain, */*" }, { - "key": "Cache-Control", - "value": "no-cache" + "key": "Connection", + "value": "keep-alive" }, { "key": "Referer", - "value": "{{host}}/default/rdmp/record/edit/49a33460c794498a56fa29ed08567cc4" + "value": "{{host}}/default/rdmp/record/search?q=Record" }, { "key": "Cookie", "value": "{{cookie}}" }, { - "key": "Connection", - "value": "keep-alive" + "key": "X-Source", + "value": "jsclient" + }, + { + "key": "Cache-Control", + "value": "no-cache" } ], - "body": { - "mode": "raw", - "raw": "{\n \"parameterRetriever\": \"\",\n \"dataRecordGetter\": \"\",\n \"\": {},\n \"dataRecord\": {\n \"oid\": \"{{dataRecordOid}}\",\n \"title\": \"A Data record\"\n },\n \"title\": \"A Data record\",\n \"description\": \"Data record\",\n \"datatype\": \"collection\",\n \"finalKeywords\": [\n \"270199\",\n \"Golgi apparatus\",\n \"endocytosis\",\n \"membrane transport\",\n \"protein trafficking\",\n \"secretion\",\n \"HIV-AIDS\",\n \"bacterial pathogen\",\n \"hormone secretion\",\n \"immune development\",\n \"lysosomal storage disorder\"\n ],\n \"foaf:fundedBy_foaf:Agent\": [\n {}\n ],\n \"foaf:fundedBy_vivo:Grant\": [\n {}\n ],\n \"dc:subject_anzsrc:for\": [\n {\n \"name\": \"01 - MATHEMATICAL SCIENCES\",\n \"label\": \"MATHEMATICAL SCIENCES\",\n \"notation\": \"01\"\n }\n ],\n \"dc:subject_anzsrc:seo\": [],\n \"startDate\": \"\",\n \"endDate\": \"\",\n \"timePeriod\": \"\",\n \"geolocations\": [\n \"\"\n ],\n \"geospatial\": {},\n \"accessRightsToggle\": \"\",\n \"dataLocations\": [\n {\n \"type\": \"attachment\",\n \"location\": \"b869b4fae83a1f01082465d165d868a8/attach/d3de61376e5bc93c814607ee604ebac5\",\n \"mimeType\": \"image/png\",\n \"name\": \"Screen Shot 2018-11-26 at 3.39.48 pm.png\",\n \"fileId\": \"d3de61376e5bc93c814607ee604ebac5\",\n \"uploadUrl\": \"http://localhost:1500/default/rdmp/record/b869b4fae83a1f01082465d165d868a8/attach/d3de61376e5bc93c814607ee604ebac5\",\n \"selected\": true\n }\n ],\n \"dataLicensingAccess_manager\": \"Prof Paul Gleeson\",\n \"dc:accessRights\": \"Open\",\n \"accessRights_url\": \"\",\n \"related_publications\": [\n {\n \"related_title\": \"\",\n \"related_url\": \"\",\n \"related_notes\": \"\"\n }\n ],\n \"related_websites\": [\n {\n \"related_title\": \"\",\n \"related_url\": \"\",\n \"related_notes\": \"\"\n }\n ],\n \"related_metadata\": [\n {\n \"related_title\": \"\",\n \"related_url\": \"\",\n \"related_notes\": \"\"\n }\n ],\n \"related_data\": [\n {\n \"related_title\": \"\",\n \"related_url\": \"\",\n \"related_notes\": \"\"\n }\n ],\n \"related_services\": [\n {\n \"related_title\": \"\",\n \"related_url\": \"\",\n \"related_notes\": \"\"\n }\n ],\n \"license_identifier\": \"\",\n \"license_notes\": \"\",\n \"license_other_url\": \"\",\n \"license_statement\": \"Copyright ReDBox Research Data 2018\",\n \"license_statement_url\": \"\",\n \"citation_doi\": \"\",\n \"requestIdentifier\": [],\n \"citation_title\": \"A Data Record\",\n \"creators\": [\n {\n \"text_full_name\": \"Alberto Zweinstein\",\n \"email\": \"alberto.zweinstein@example.edu.au\",\n \"role\": \"Chief Investigator\",\n \"username\": \"\",\n \"orcid\": \"\",\n \"family_name\": \"Zweinstein\",\n \"given_name\": \"Alberto\"\n },\n {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"role\": \"Data manager\",\n \"username\": \"\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\",\n \"family_name\": \"Paul Gleeson\",\n \"given_name\": \"Prof\"\n },\n {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"role\": \"Contributors\",\n \"username\": \"\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\",\n \"family_name\": \"Paul Gleeson\",\n \"given_name\": \"Prof\"\n },\n {\n \"text_full_name\": null,\n \"email\": null,\n \"role\": \"Supervisor\",\n \"username\": \"\",\n \"orcid\": \"\",\n \"family_name\": \"\",\n \"given_name\": \"\"\n }\n ],\n \"citation_publisher\": \"ReDBox Research Data\",\n \"citation_url\": \"\",\n \"citation_publication_date\": \"\",\n \"citation_generated\": \"Zweinstein, Alberto; Paul Gleeson, Prof; Paul Gleeson, Prof (Invalid date): A Data Record. ReDBox Research Data. {ID_WILL_BE_HERE}\",\n \"dataowner_name\": \"Alberto Zweinstein\",\n \"dataowner_email\": \"alberto.zweinstein@example.edu.au\",\n \"contributor_ci\": {\n \"text_full_name\": \"Alberto Zweinstein\",\n \"full_name_honorific\": \"Dr Alberto Zweinstein\",\n \"email\": \"alberto.zweinstein@example.edu.au\",\n \"given_name\": \"Alberto\",\n \"family_name\": \"Zweinstein\",\n \"honorific\": \"Dr\",\n \"full_name_family_name_first\": \"Zweinstein, Alberto\",\n \"username\": \"\",\n \"role\": \"Chief Investigator\"\n },\n \"contributor_data_manager\": {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"full_name_honorific\": \"\",\n \"email\": \"notAReal@email.edu.au\",\n \"given_name\": \"Prof\",\n \"family_name\": \"Paul Gleeson\",\n \"honorific\": \"\",\n \"full_name_family_name_first\": \"Paul Gleeson, Prof\",\n \"username\": \"\",\n \"role\": \"Data manager\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n },\n \"contributor_supervisor\": {\n \"text_full_name\": \"\",\n \"full_name_honorific\": \"\",\n \"email\": \"\",\n \"given_name\": \"\",\n \"family_name\": \"\",\n \"honorific\": \"\",\n \"full_name_family_name_first\": \"\",\n \"username\": \"\",\n \"role\": \"Supervisor\"\n },\n \"embargoByDate\": \"\",\n \"embargoUntil\": null,\n \"embargoNote\": \"\",\n \"reviewerNote\": \"\",\n \"ckanLocation\": \"\"\n}" - }, "url": { - "raw": "http://localhost:1500/default/rdmp/recordmeta/{{dataPublicationOid}}?targetStep=reviewing", - "protocol": "http", + "raw": "{{host}}/default/rdmp/record/search/rdmp/?searchStr=Andrew&facetNames=grant_number_name,finalKeywords,workflow_stageLabel", "host": [ - "localhost" + "{{host}}" ], - "port": "1500", "path": [ "default", "rdmp", - "recordmeta", - "{{dataPublicationOid}}" + "record", + "search", + "rdmp", + "" ], "query": [ { - "key": "targetStep", - "value": "reviewing" + "key": "searchStr", + "value": "Andrew" + }, + { + "key": "facetNames", + "value": "grant_number_name,finalKeywords,workflow_stageLabel" } ] } diff --git a/typescript/api/services/ConfigService.ts b/typescript/api/services/ConfigService.ts index 7dd7b3edb2..16617eaf94 100644 --- a/typescript/api/services/ConfigService.ts +++ b/typescript/api/services/ConfigService.ts @@ -51,6 +51,7 @@ export module Services { } public mergeHookConfig(hookName:string, configMap:any=sails.config, config_dirs: string[] = ["form-config", "config"], dontMergeFields:any[] = ["fields"]) { + const that = this; var hook_root_dir = `${sails.config.appPath}/node_modules/${hookName}`; var appPath = sails.config.appPath; // check if the app path was launched from the hook directory, e.g. when launching tests. @@ -127,7 +128,7 @@ export module Services { fs.ensureSymlinkSync(`${appPath}/api/core`, `${hook_root_dir}/api/core`); } sails.log.verbose(`${hook_log_header}::Adding custom API elements...`); - + let apiDirs = ["services"]; _.each(apiDirs, (apiType) => { const files = this.walkDirSync(`${hook_root_dir}/api/${apiType}`, []); @@ -142,22 +143,22 @@ export module Services { }); } }); - + sails.on('lifted', function() { let apiDirs = ["controllers"]; - _.each(apiDirs, (apiType) => { - const files = this.walkDirSync(`${hook_root_dir}/api/${apiType}`, []); - sails.log.verbose(`${hook_log_header}::Processing '${apiType}':`); - sails.log.verbose(JSON.stringify(files)); - if (!_.isEmpty(files)) { - _.each(files, (file) => { - const apiDef = require(file); - const apiElemName = _.toLower(basename(file, '.js')) - sails[apiType][apiElemName] = apiDef; - }); - } + _.each(apiDirs, (apiType) => { + const files = that.walkDirSync(`${hook_root_dir}/api/${apiType}`, []); + sails.log.verbose(`${hook_log_header}::Processing '${apiType}':`); + sails.log.verbose(JSON.stringify(files)); + if (!_.isEmpty(files)) { + _.each(files, (file) => { + const apiDef = require(file); + const apiElemName = _.toLower(basename(file, '.js')) + sails[apiType][apiElemName] = apiDef; + }); + } + }); }); - }); // for models, we need to copy them over to `api/models`... const modelFiles = this.walkDirSync(`${hook_root_dir}/api/models`, []); From 066ebc32088f255eddf8d96f280919bc746d31b4 Mon Sep 17 00:00:00 2001 From: Shilo B Date: Tue, 8 Dec 2020 13:46:47 +1000 Subject: [PATCH 28/48] Fixed: failing test due to missing SOLR field. --- config/solr.js | 42 +++++++++++++++++++++++++++++++ test/postman/test-collection.json | 4 +-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/config/solr.js b/config/solr.js index fe062c9805..1ae1ee3da9 100644 --- a/config/solr.js +++ b/config/solr.js @@ -57,6 +57,48 @@ module.exports.solr = { indexed: true, stored: true, multiValued: true + }, + { + name: "authorization_view", + type: "text_general", + indexed: true, + stored: true, + multiValued: true + }, + { + name: "authorization_edit", + type: "text_general", + indexed: true, + stored: true, + multiValued: true + }, + { + name: "metaMetadata_brandId", + type: "text_general", + indexed: true, + stored: true, + multiValued: false + }, + { + name: "metaMetadata_type", + type: "text_general", + indexed: true, + stored: true, + multiValued: false + }, + { + name: "workflow_stageLabel", + type: "text_general", + indexed: true, + stored: true, + multiValued: false + }, + { + name: "workflow_step", + type: "text_general", + indexed: true, + stored: true, + multiValued: false } ], 'add-dynamic-field': [ diff --git a/test/postman/test-collection.json b/test/postman/test-collection.json index 2f327aab60..47c38ffdca 100644 --- a/test/postman/test-collection.json +++ b/test/postman/test-collection.json @@ -2873,8 +2873,6 @@ "script": { "id": "dd144dff-909f-4430-9bd5-ca5b3045d255", "exec": [ - "console.log(\"Waiting for 10 seconds before testing search so SOLR can complete indexing....\");", - "setTimeout(function() {", "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", "});", @@ -2883,7 +2881,7 @@ " var jsonData = pm.response.json();", " pm.expect(jsonData).to.have.property('oid');", "});", - "}, 10000);", + "", "" ], "type": "text/javascript" From bd4eaa7b8e075eb9efa6dd564fcba1d686e4d7ee Mon Sep 17 00:00:00 2001 From: Shilo B Date: Tue, 8 Dec 2020 14:24:28 +1000 Subject: [PATCH 29/48] Fixed: added missing SOLR fields. --- config/solr.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/config/solr.js b/config/solr.js index 1ae1ee3da9..d502f7e9df 100644 --- a/config/solr.js +++ b/config/solr.js @@ -72,6 +72,20 @@ module.exports.solr = { stored: true, multiValued: true }, + { + name: "authorization_viewRoles", + type: "text_general", + indexed: true, + stored: true, + multiValued: true + }, + { + name: "authorization_editRoles", + type: "text_general", + indexed: true, + stored: true, + multiValued: true + }, { name: "metaMetadata_brandId", type: "text_general", From 5a0f61abbfa2e8d3fc5ec12363bd7caff06f6fe2 Mon Sep 17 00:00:00 2001 From: andrewbrazzatti Date: Wed, 9 Dec 2020 10:40:56 +1030 Subject: [PATCH 30/48] Updated Mocha test run to force exit after test run is complete --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a86dfc3bac..5ca3a62318 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "scripts": { "debug": "node debug app.js", "start": "node app.js", - "test": "node ./node_modules/mocha/bin/mocha test/bootstrap.test.js test/unit/**/*.test.js", + "test": "node ./node_modules/mocha/bin/mocha --exit test/bootstrap.test.js test/unit/**/*.test.js", "api-test": "node test/postman/runNewmanTests.js", "doc-ng2": "npm -g install @compodoc/compodoc && npm -g install ejs-cli aglio && compodoc -p angular/tsconfig.json -d support/docs/generated/ng2 -n \"ReDBox Portal - NG2 Apps\" --theme postmark --includes support/docs && ejs-cli views/default/default/apidocsapib.ejs -O '{\"portal\":\"rdmp\",\"branding\":\"default\"}' > apidocs.apib && aglio -i apidocs.apib -o rest-api.html && mv rest-api.html support/docs/generated/ng2/additional-documentation/", "compile": "./node_modules/.bin/tsc --project tsconfig.json" From 401817daa60a96b46925b5516840722f04317e79 Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Mon, 14 Dec 2020 12:48:34 +1030 Subject: [PATCH 31/48] Added solr to developer docker-compose.yml as it's now required Added models to help standardise for API responses --- docker-compose.yml | 15 +++ .../webservice/RecordController.ts | 32 +++---- .../webservice/UserManagementController.ts | 92 +++++++++++++------ typescript/api/core/CoreController.ts | 7 +- typescript/api/core/model/APIErrorResponse.ts | 10 ++ typescript/api/core/model/ListAPIResponse.ts | 12 +++ typescript/api/core/model/User.ts | 9 ++ .../core/model/api/CreateUserAPIResponse.ts | 9 ++ 8 files changed, 135 insertions(+), 51 deletions(-) create mode 100644 typescript/api/core/model/APIErrorResponse.ts create mode 100644 typescript/api/core/model/ListAPIResponse.ts create mode 100644 typescript/api/core/model/User.ts create mode 100644 typescript/api/core/model/api/CreateUserAPIResponse.ts diff --git a/docker-compose.yml b/docker-compose.yml index bad81078c4..0233dec4b5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -50,3 +50,18 @@ services: - mongodb ports: - "27017:27017" + + solr: + image: solr:8.6.3 + expose: + - "8983" + ports: + - "8983:8983" + networks: + main: + aliases: + - solr + entrypoint: + - docker-entrypoint.sh + - solr-precreate + - redbox diff --git a/typescript/api/controllers/webservice/RecordController.ts b/typescript/api/controllers/webservice/RecordController.ts index f629bedaec..6b957d5423 100644 --- a/typescript/api/controllers/webservice/RecordController.ts +++ b/typescript/api/controllers/webservice/RecordController.ts @@ -128,14 +128,14 @@ export module Controllers { return res.json(record["authorization"]); }, error=> { sails.log.error(error); - return this.apiFail(req, res, 500, 'Failed adding an editor, check server logs.'); + return this.apiFail(req, res, 500, new APIErrorResponse('Failed adding an editor, check server logs.')); }); } else { return res.json(result); } }, error=> { sails.log.error(error); - return this.apiFail(req, res, 500, 'Failed adding an editor, check server logs.'); + return this.apiFail(req, res, 500, new APIErrorResponse('Failed adding an editor, check server logs.')); }); }); } @@ -244,7 +244,7 @@ export module Controllers { }, error=> { sails.log.error("Get metadata failed, failed to retrieve existing record.", error); - return this.apiFail(req, res, 500, "Get Metadata failed, failed to retrieve existing record. "); + return this.apiFail(req, res, 500, new APIErrorResponse("Get Metadata failed, failed to retrieve existing record. ")); }); } @@ -283,12 +283,12 @@ export module Controllers { return res.json(result); }, error=> { sails.log.error("Update metadata failed", error); - return this.apiFail(req, res, 500, "Update Metadata failed"); + return this.apiFail(req, res, 500, new APIErrorResponse("Update Metadata failed")); }); }, error=> { sails.log.error("Update metadata failed, failed to retrieve existing record.", error); - return this.apiFail(req, res, 500, "Update Metadata failed, failed to retrieve existing record. "); + return this.apiFail(req, res, 500, new APIErrorResponse("Update Metadata failed, failed to retrieve existing record. ")); }); } @@ -384,16 +384,16 @@ export module Controllers { this.apiRespond(req, res, response, 201); } else { sails.log.error("Create Record failed"); - return this.apiFail(req, res, 500, "Create failed"); + return this.apiFail(req, res, 500, new APIErrorResponse("Create failed")); } }, error=> { sails.log.error("Create Record failed", error); - return this.apiFail(req, res, 500, "Create failed"); + return this.apiFail(req, res, 500, new APIErrorResponse("Create failed")); }); }); } else { - return this.apiFail(req, res, 400, "Record Type provided is not valid"); + return this.apiFail(req, res, 400, new APIErrorResponse("Record Type provided is not valid")); } } ); @@ -543,16 +543,16 @@ export module Controllers { return response.map(results => { + let apiReponse: ListAPIResponse = new ListAPIResponse(); var totalItems = results["response"]["numFound"]; var startIndex = results["response"]["start"]; var noItems = rows; var pageNumber = Math.floor((startIndex / noItems) + 1); - var response = {}; - response["totalItems"] = totalItems; - if(startIndex < totalItems) { - response["currentPage"] = pageNumber; - } + apiReponse.summary.numFound = totalItems; + apiReponse.summary.start = startIndex; + apiReponse.summary.page = pageNumber; + var items = []; var docs = results["response"]["docs"]; @@ -568,9 +568,8 @@ export module Controllers { item["hasEditAccess"] = this.RecordsService.hasEditAccess(brand, user, roles, doc); items.push(item); } - response["noItems"] = items.length; - response["items"] = items; - return Observable.of(response); + apiReponse.records = items; + return Observable.of(apiReponse); }); } @@ -613,6 +612,7 @@ export module Controllers { // sails.log.debug(`getRecords: ${recordType} ${workflowState} ${start}`); // sails.log.debug(`${rows} ${packageType} ${sort}`); this.getRecords(workflowState, recordType, start, rows, user, roles, brand, editAccessOnly, packageType, sort).flatMap(results => { + return results; }).subscribe(response => { res.json(response); diff --git a/typescript/api/controllers/webservice/UserManagementController.ts b/typescript/api/controllers/webservice/UserManagementController.ts index 3995d531eb..810c3781c8 100644 --- a/typescript/api/controllers/webservice/UserManagementController.ts +++ b/typescript/api/controllers/webservice/UserManagementController.ts @@ -23,9 +23,9 @@ declare var sails; declare var BrandingService; declare var RolesService; -declare var DashboardService; -declare var UsersService; -declare var User; +declare var DashboardService; +declare var UsersService; +declare var User; declare var _; /** * Package that contains all Controllers. @@ -43,9 +43,10 @@ export module Controllers { * Exported methods, accessible from internet. */ protected _exportedMethods: any = [ - 'render', - 'listUsers', - 'findUser' + 'render', + 'listUsers', + 'getUser', + 'createUser' ]; /** @@ -66,48 +67,79 @@ export module Controllers { public listUsers(req, res) { var page = req.param('page'); var pageSize = req.param('pageSize'); - if(page == null) { + var searchField = req.param('searchBy'); + var query = req.param('query'); + var queryObject = {}; + if (searchField != null && query != null) { + queryObject[searchField] = query; + } + if (page == null) { page = 1; } - if(pageSize == null) { + if (pageSize == null) { pageSize = 10; } - let skip = (page-1)*pageSize; - User.count().exec(function (err,count) { - var response = {}; - response["summary"] = {}; - response["summary"]["numFound"] = count; - response["summary"]["page"] = page; - if(count == 0) { + let skip = (page - 1) * pageSize; + User.count({ + where: queryObject + }).exec(function (err, count) { + let response: ListAPIResponse < any > = new ListAPIResponse < any > (); + response.summary.numFound = count; + response.summary.page = page; + if (count == 0) { response["records"] = []; return res.json(response); - } else { - User.find({ where: {}, limit: pageSize, skip: skip} ).exec(function (err, users) { - _.each(users, user=> { - delete user["token"]; - }); - response["records"] = users; - return res.json(response); + } else { + User.find({ + where: queryObject, + limit: pageSize, + skip: skip + }).exec(function (err, users) { + _.each(users, user => { + delete user["token"]; + }); + response.records = users; + return this.apiRespond(req, res, response); + }); + } }); } - }); - } - public findUser(req, res) { + public getUser(req, res) { var searchField = req.param('searchBy'); var query = req.param('query'); var queryObject = {}; queryObject[searchField] = query; User.findOne(queryObject).exec(function (err, user) { - if(err != null) { + if (err != null) { return res.serverError(err); } - if(user != null) { - return res.json(user); + if (user != null) { + delete user["token"]; + return this.apiRespond(req, res, user); } - return res.json({}) + + return this.apiFail(req,res,404, new APIErrorResponse("No user found with given criteria", `Searchby: ${searchField} and Query: ${query}`)) + }); + } + + public createUser(req, res) { + let userReq:User = req.body; + + UsersService.addLocalUser(userReq.username, userReq.name, userReq.email, userReq.password).subscribe(response => { + let userResponse: CreateUserAPIResponse = new CreateUserAPIResponse(); + userResponse.username = response.username; + userResponse.name = response.name; + userResponse.email = response.email; + userResponse.type = response.type; + userResponse.lastLogin = response.lastLogin; + return this.apiRespond(req,res,userResponse,201) + }).error(error => { + sails.log.error(error); + return this.apiFail(req,res,500, new APIErrorResponse()) }); + } @@ -120,4 +152,4 @@ export module Controllers { } } -module.exports = new Controllers.UserManagement().exports(); +module.exports = new Controllers.UserManagement().exports(); \ No newline at end of file diff --git a/typescript/api/core/CoreController.ts b/typescript/api/core/CoreController.ts index 59938c136c..896ad4d5d5 100644 --- a/typescript/api/core/CoreController.ts +++ b/typescript/api/core/CoreController.ts @@ -262,11 +262,8 @@ export module Controllers.Core { this.ajaxRespond(req, res, data, forceAjax); } - protected apiFail(req, res, statusCode = 500, msg='', errorDetails='') { - - let data = {status:false, message:msg, details: errorDetails}; - - this.apiRespond(req, res, data, statusCode); + protected apiFail(req, res, statusCode = 500, errorResponse:APIErrorResponse) { + this.apiRespond(req, res, errorResponse, statusCode); } protected apiRespond(req, res, jsonObj=null, statusCode=200) { diff --git a/typescript/api/core/model/APIErrorResponse.ts b/typescript/api/core/model/APIErrorResponse.ts new file mode 100644 index 0000000000..f03ff88656 --- /dev/null +++ b/typescript/api/core/model/APIErrorResponse.ts @@ -0,0 +1,10 @@ +class APIErrorResponse{ + + message:string; + details:string; + + constructor(message:string ='An error has occurred',details:string = '') { + this.message = message; + this.details = details; + } +} diff --git a/typescript/api/core/model/ListAPIResponse.ts b/typescript/api/core/model/ListAPIResponse.ts new file mode 100644 index 0000000000..868930cb6e --- /dev/null +++ b/typescript/api/core/model/ListAPIResponse.ts @@ -0,0 +1,12 @@ +class ListAPIResponse { + + summary: ListAPISummary = new ListAPISummary(); + records: T[]; +} + +class ListAPISummary { + + numFound: number = 0; + page: number = 1; + start: number = 0; +} \ No newline at end of file diff --git a/typescript/api/core/model/User.ts b/typescript/api/core/model/User.ts new file mode 100644 index 0000000000..95cdd014e7 --- /dev/null +++ b/typescript/api/core/model/User.ts @@ -0,0 +1,9 @@ +class User { + username:string + password:string + type: string + name: string + email: string + token: string + lastLogin: Date +} \ No newline at end of file diff --git a/typescript/api/core/model/api/CreateUserAPIResponse.ts b/typescript/api/core/model/api/CreateUserAPIResponse.ts new file mode 100644 index 0000000000..445637de42 --- /dev/null +++ b/typescript/api/core/model/api/CreateUserAPIResponse.ts @@ -0,0 +1,9 @@ +class CreateUserAPIResponse { + + username: string + name:string + email: string + type: string + lastLogin: Date + +} \ No newline at end of file From 969819baff8304e33a728b853ed8d20f884dc703 Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Tue, 15 Dec 2020 12:28:55 +1030 Subject: [PATCH 32/48] Fixed typescript issues with respose models Added Create User API endpoint --- .../api/controllers/webservice/RecordController.ts | 3 +++ .../webservice/UserManagementController.ts | 14 ++++++++++++-- typescript/api/core/CoreController.ts | 8 +++++++- typescript/api/core/model/APIErrorResponse.ts | 2 +- typescript/api/core/model/ListAPIResponse.ts | 14 +++++++++++--- 5 files changed, 34 insertions(+), 7 deletions(-) diff --git a/typescript/api/controllers/webservice/RecordController.ts b/typescript/api/controllers/webservice/RecordController.ts index 6b957d5423..c9c097e36c 100644 --- a/typescript/api/controllers/webservice/RecordController.ts +++ b/typescript/api/controllers/webservice/RecordController.ts @@ -39,6 +39,9 @@ import RecordsService from '../../core/RecordsService.js'; import SearchService from '../../core/SearchService.js'; import DatastreamService from '../../core/DatastreamService.js'; import DatastreamServiceResponse from '../../core/DatastreamServiceResponse'; +import {ListAPIResponse } from '../../core/model/ListAPIResponse'; +import {APIErrorResponse } from '../../core/model/APIErrorResponse'; + const UUIDGenerator = require('uuid/v4'); export module Controllers { diff --git a/typescript/api/controllers/webservice/UserManagementController.ts b/typescript/api/controllers/webservice/UserManagementController.ts index 810c3781c8..ff3711eaeb 100644 --- a/typescript/api/controllers/webservice/UserManagementController.ts +++ b/typescript/api/controllers/webservice/UserManagementController.ts @@ -31,6 +31,8 @@ declare var _; * Package that contains all Controllers. */ import controller = require('../../core/CoreController.js'); +import {ListAPIResponse } from '../../core/model/ListAPIResponse'; +import {APIErrorResponse } from '../../core/model/APIErrorResponse'; export module Controllers { /** * Responsible for all things related to the Dashboard @@ -65,6 +67,7 @@ export module Controllers { public listUsers(req, res) { + let that = this; var page = req.param('page'); var pageSize = req.param('pageSize'); var searchField = req.param('searchBy'); @@ -81,12 +84,16 @@ export module Controllers { pageSize = 10; } let skip = (page - 1) * pageSize; + sails.log.error("List users 1") + User.count({ where: queryObject }).exec(function (err, count) { + sails.log.error("List users 2") let response: ListAPIResponse < any > = new ListAPIResponse < any > (); response.summary.numFound = count; response.summary.page = page; + if (count == 0) { response["records"] = []; return res.json(response); @@ -94,13 +101,16 @@ export module Controllers { User.find({ where: queryObject, limit: pageSize, - skip: skip + skip: skip }).exec(function (err, users) { + sails.log.error("List users 3") _.each(users, user => { delete user["token"]; }); response.records = users; - return this.apiRespond(req, res, response); + sails.log.error("List users 4") + + return that.apiRespond(req, res, response); }); } }); diff --git a/typescript/api/core/CoreController.ts b/typescript/api/core/CoreController.ts index 896ad4d5d5..835eddb54a 100644 --- a/typescript/api/core/CoreController.ts +++ b/typescript/api/core/CoreController.ts @@ -2,6 +2,7 @@ declare var _; declare var sails; import pathExists = require('path-exists'); +import {APIErrorResponse } from './model/APIErrorResponse'; export module Controllers.Core { /** @@ -263,7 +264,12 @@ export module Controllers.Core { } protected apiFail(req, res, statusCode = 500, errorResponse:APIErrorResponse) { - this.apiRespond(req, res, errorResponse, statusCode); + // this.apiRespond(req, res, errorResponse, statusCode); + res.set('Cache-control', 'no-cache'); + res.set('Pragma', 'no-cache'); + res.set('Expires', 0); + res.status(statusCode) + return res.json(errorResponse); } protected apiRespond(req, res, jsonObj=null, statusCode=200) { diff --git a/typescript/api/core/model/APIErrorResponse.ts b/typescript/api/core/model/APIErrorResponse.ts index f03ff88656..07308d4e51 100644 --- a/typescript/api/core/model/APIErrorResponse.ts +++ b/typescript/api/core/model/APIErrorResponse.ts @@ -1,4 +1,4 @@ -class APIErrorResponse{ +export class APIErrorResponse{ message:string; details:string; diff --git a/typescript/api/core/model/ListAPIResponse.ts b/typescript/api/core/model/ListAPIResponse.ts index 868930cb6e..e830526e4a 100644 --- a/typescript/api/core/model/ListAPIResponse.ts +++ b/typescript/api/core/model/ListAPIResponse.ts @@ -1,12 +1,20 @@ -class ListAPIResponse { +export class ListAPIResponse { summary: ListAPISummary = new ListAPISummary(); records: T[]; + + constructor() { + + } } -class ListAPISummary { +export class ListAPISummary { numFound: number = 0; page: number = 1; start: number = 0; -} \ No newline at end of file + + constructor() { + + } +} From 75f5df26ff5deb61fecc9157e2f502c75a980d12 Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Tue, 15 Dec 2020 12:40:51 +1030 Subject: [PATCH 33/48] Removed the requirement to pass an errorResponse object to apiFail. Useful for unexpected errors. --- .../webservice/UserManagementController.ts | 11 ++++++----- typescript/api/core/CoreController.ts | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/typescript/api/controllers/webservice/UserManagementController.ts b/typescript/api/controllers/webservice/UserManagementController.ts index ff3711eaeb..2c7bb190bc 100644 --- a/typescript/api/controllers/webservice/UserManagementController.ts +++ b/typescript/api/controllers/webservice/UserManagementController.ts @@ -103,12 +103,11 @@ export module Controllers { limit: pageSize, skip: skip }).exec(function (err, users) { - sails.log.error("List users 3") + _.each(users, user => { delete user["token"]; }); response.records = users; - sails.log.error("List users 4") return that.apiRespond(req, res, response); }); @@ -117,20 +116,22 @@ export module Controllers { } public getUser(req, res) { + let that = this; var searchField = req.param('searchBy'); var query = req.param('query'); var queryObject = {}; queryObject[searchField] = query; User.findOne(queryObject).exec(function (err, user) { if (err != null) { - return res.serverError(err); + sails.log.error(err) + return that.apiFail(req,res,500) } if (user != null) { delete user["token"]; - return this.apiRespond(req, res, user); + return that.apiRespond(req, res, user); } - return this.apiFail(req,res,404, new APIErrorResponse("No user found with given criteria", `Searchby: ${searchField} and Query: ${query}`)) + return that.apiFail(req,res,404, new APIErrorResponse("No user found with given criteria", `Searchby: ${searchField} and Query: ${query}`)) }); } diff --git a/typescript/api/core/CoreController.ts b/typescript/api/core/CoreController.ts index 835eddb54a..7b12d67162 100644 --- a/typescript/api/core/CoreController.ts +++ b/typescript/api/core/CoreController.ts @@ -263,7 +263,7 @@ export module Controllers.Core { this.ajaxRespond(req, res, data, forceAjax); } - protected apiFail(req, res, statusCode = 500, errorResponse:APIErrorResponse) { + protected apiFail(req, res, statusCode = 500, errorResponse:APIErrorResponse = new APIErrorResponse()) { // this.apiRespond(req, res, errorResponse, statusCode); res.set('Cache-control', 'no-cache'); res.set('Pragma', 'no-cache'); From 20d9f1b6f1e16000492813e744eba41084f3b108 Mon Sep 17 00:00:00 2001 From: Shilo Banihit Date: Tue, 15 Dec 2020 12:36:57 +1000 Subject: [PATCH 34/48] Added record delete record API endpoint. Added interactive flag for runForDev. Renamed SOLR jobs. Bumped MongoStorage version. --- config/agendaQueue.js | 6 +- config/routes.js | 7 +- config/solr.js | 1 + docker-compose.yml | 3 + package-lock.json | 12 +- package.json | 2 +- runForDev.sh | 6 +- test/postman/test-collection.json | 204 ++++++++++++++---- .../webservice/RecordController.ts | 22 +- typescript/api/core/SearchService.ts | 2 +- typescript/api/services/RecordsService.ts | 8 +- typescript/api/services/SolrSearchService.ts | 33 ++- 12 files changed, 245 insertions(+), 61 deletions(-) diff --git a/config/agendaQueue.js b/config/agendaQueue.js index f4716b5b4e..dc4b288783 100644 --- a/config/agendaQueue.js +++ b/config/agendaQueue.js @@ -17,7 +17,11 @@ module.exports.agendaQueue = { jobs: [ { name: 'SolrSearchService-CreateOrUpdateIndex', - fnName: 'solrsearchservice.addOrUpdate' + fnName: 'solrsearchservice.solrAddOrUpdate' + }, + { + name: 'SolrSearchService-DeleteFromIndex', + fnName: 'solrsearchservice.solrDelete' } ] }; diff --git a/config/routes.js b/config/routes.js index 4d10e328cd..d484ef4666 100644 --- a/config/routes.js +++ b/config/routes.js @@ -249,8 +249,13 @@ module.exports.routes = { csrf: false }, 'get /:branding/:portal/api/records/metadata/:oid': 'webservice/RecordController.getMeta', - 'get /:branding/:portal/api/records/list': 'webservice/RecordController.listRecords', + 'get /:branding/:portal/api/records/list': 'webservice/RecordController.listRecords', 'get /:branding/:portal/api/records/objectmetadata/:oid': 'webservice/RecordController.getObjectMeta', + 'delete /:branding/:portal/api/records/metadata/:oid': { + controller: 'webservice/RecordController', + action: 'deleteRecord', + csrf: false + }, 'post /:branding/:portal/api/records/permissions/edit/:oid': { controller: 'webservice/RecordController', action: 'addUserEdit', diff --git a/config/solr.js b/config/solr.js index d502f7e9df..e7c87b2f29 100644 --- a/config/solr.js +++ b/config/solr.js @@ -1,5 +1,6 @@ module.exports.solr = { createOrUpdateJobName: 'SolrSearchService-CreateOrUpdateIndex', + deleteJobName: 'SolrSearchService-DeleteFromIndex', options: { host: 'solr', port: '8983', diff --git a/docker-compose.yml b/docker-compose.yml index 0233dec4b5..80ae6fbb23 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,6 +21,9 @@ services: - sails_redbox__apiKey=c8e844fc-8550-497f-b970-7900ec8741ca - sails_record__baseUrl__redbox=http://redbox:9000/redbox - sails_record__baseUrl__mint=https://demo.redboxresearchdata.com.au/mint + # When testing using the API + # - sails_auth__default__local__default__token=d077835a-696b-4728-85cf-3ffd57152b1e + # - sails_security__csrf=false networks: main: aliases: diff --git a/package-lock.json b/package-lock.json index ab7074cdab..efb22c4296 100644 --- a/package-lock.json +++ b/package-lock.json @@ -337,9 +337,9 @@ } }, "@researchdatabox/sails-hook-redbox-storage-mongo": { - "version": "0.0.3-alpha", - "resolved": "https://registry.npmjs.org/@researchdatabox/sails-hook-redbox-storage-mongo/-/sails-hook-redbox-storage-mongo-0.0.3-alpha.tgz", - "integrity": "sha512-Q41UzIEJOJTyDdyifBpK9AO43uE2aQyg1YUQ/19uNSVONfCCDSipL8vAnd/fJBz2iWxASeRLZeawjOiwH8049g==", + "version": "0.0.4-alpha", + "resolved": "https://registry.npmjs.org/@researchdatabox/sails-hook-redbox-storage-mongo/-/sails-hook-redbox-storage-mongo-0.0.4-alpha.tgz", + "integrity": "sha512-CYFmEiobFOL/gHanfn4lVuWGis4XY45RvkJuQ/mEIdgptHPUK05U6abuHvTeptCpdo8G3C2McW+ps3KJLFG97g==", "requires": { "json2csv": "^5.0.3", "lodash": "^4.17.20", @@ -6488,9 +6488,9 @@ }, "dependencies": { "commander": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", - "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==" + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==" } } }, diff --git a/package.json b/package.json index 5ca3a62318..4003e9d875 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "ReDBox 2 Portal", "keywords": [], "dependencies": { - "@researchdatabox/sails-hook-redbox-storage-mongo": "0.0.3-alpha", + "@researchdatabox/sails-hook-redbox-storage-mongo": "0.0.4-alpha", "@sailshq/upgrade": "^1.0.9", "@uppy/core": "^1.13.1", "@uppy/dashboard": "^1.12.5", diff --git a/runForDev.sh b/runForDev.sh index 0ccf50f7b5..55627d45c4 100755 --- a/runForDev.sh +++ b/runForDev.sh @@ -8,6 +8,7 @@ source dev_build/buildFns.sh watch="false" # Not really needed but I'm putting this in a for loop in case we want to add more arguments later WATCH_COUNT=0 +DAEMONIZE_FLAG="-d" for var in "$@" do if [ $var = "install" ]; then @@ -48,11 +49,14 @@ do docker exec -u "node" --detach $RBPORTAL_PS /bin/bash -c "cd /opt/redbox-portal/angular; npm install -g @angular/cli@1.7.1; npm i; ng build --app=${ng2App} --watch --verbose > ${ng2App}-build.log" || exit let WATCH_COUNT++ fi + if [ $var == "interactive" ]; then + DAEMONIZE_FLAG="" + fi done if [ $watch == "true" ]; then echo "${WATCH_COUNT} watches are running." else echo "${WATCH_COUNT} watches. No watches should be running." - docker-compose up -d + docker-compose up $DAEMONIZE_FLAG fi diff --git a/test/postman/test-collection.json b/test/postman/test-collection.json index 47c38ffdca..540da6709e 100644 --- a/test/postman/test-collection.json +++ b/test/postman/test-collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "7b31db0b-a4cb-4a8b-b8a5-bd99cb7ca23d", + "_postman_id": "fcde6231-9f2d-49a1-b37c-22d01e39c5fc", "name": "Redbox Portal API - With tests", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -14,7 +14,7 @@ { "listen": "test", "script": { - "id": "c6679db8-156d-4591-ae8e-089f7c5ea8de", + "id": "ac672330-4be2-47af-b448-32aa1e25d1e6", "exec": [ "", "pm.test(\"Status code is 201\", function () {", @@ -37,7 +37,7 @@ { "listen": "prerequest", "script": { - "id": "6639f6d9-92b8-46be-8c8d-c7bfbaba30cd", + "id": "cbebf361-9a4a-4890-8534-2221a15261ce", "exec": [ "" ], @@ -87,7 +87,7 @@ { "listen": "test", "script": { - "id": "9d3088cb-a505-47e5-b2f9-02b126f83f29", + "id": "1c2bf1b9-f651-4b8d-9bee-22f5534f7d32", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -140,7 +140,7 @@ { "listen": "test", "script": { - "id": "458bb2ca-c75e-4ee1-b48e-c7db4672f58e", + "id": "295f9f17-cb01-4993-9283-1ac9683d292a", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -195,7 +195,7 @@ { "listen": "test", "script": { - "id": "67c12973-2c76-4d4e-9efa-fdea57611bd8", + "id": "65a4b89d-1f76-4abb-afd7-a67e17d56088", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -251,7 +251,7 @@ { "listen": "test", "script": { - "id": "d364ad2b-a452-4dd3-88e3-95496f0d1b2f", + "id": "fa82de23-39a6-41c1-a109-450031b51068", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -304,7 +304,7 @@ { "listen": "test", "script": { - "id": "d5215d9f-56e4-4b7f-8347-1ab9a4b48f88", + "id": "8999d515-15c2-4890-921a-cc8a858d2805", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -361,7 +361,7 @@ { "listen": "test", "script": { - "id": "a8c30867-8985-4411-9743-5c2ac21d29ea", + "id": "8edad722-ba61-4263-a95e-bb0deb5ff72f", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -410,6 +410,128 @@ "description": "http://localhost:1500/default/rdmp/api/records/rdmp/create" }, "response": [] + }, + { + "name": "Create RDMP For Deletion", + "event": [ + { + "listen": "test", + "script": { + "id": "ba9b6209-95a7-4e50-9663-1886463670ad", + "exec": [ + "", + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + "});", + "", + "pm.test(\"Test oid exists\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('oid');", + " postman.setEnvironmentVariable(\"dmpOidToDelete\", jsonData.oid);", + "});" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "b5071961-5b8f-4167-9282-b82e3dd4f434", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"title\": \"Andrew's Postman test\",\n \"dc:identifier\": \"http://purl.org/au-research/grants/nhmrc/566728\",\n \"description\": \"Movement of molecules within cells by a process known as membrane transport is critical for normal cell function and also exploited by bacteria to promote infection. The pathway that connects the import pathway to the export pathway is essential for the function of a large number of proteins, however this connecting pathway is poorly characterised. This study will define the machinery of this trafficking pathway, which will provide the ability to modulate biological processes and cytotoxicity.\",\n \"finalKeywords\": [\n \"270199\",\n \"Golgi apparatus\",\n \"endocytosis\",\n \"membrane transport\",\n \"protein trafficking\",\n \"secretion\",\n \"HIV-AIDS\",\n \"bacterial pathogen\",\n \"hormone secretion\",\n \"immune development\",\n \"lysosomal storage disorder\"\n ],\n \"contributor_ci\": {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n },\n \"contributor_data_manager\": {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n },\n \"contributors\": [\n {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n }\n ],\n \"vivo:Dataset_redbox:DataCollectionMethodology\": \"The data collection methodology\",\n \"vivo:Dataset_dc_format\": \"xls\",\n \"vivo:Dataset_dc:location_rdf:PlainLiteral\": \"eResearch Store network drive\",\n \"vivo:Dataset_dc:source_dc:location_rdf:PlainLiteral\": \"shared university network drive (e.g. G, H, etc)\",\n \"vivo:Dataset_dc:extent\": \"100GB - 2TB\",\n \"redbox:retentionPeriod_dc:date\": \"1year\",\n \"dc:rightsHolder_dc:name\": \"myUni\",\n \"dc:accessRights\": \"permission from the data manager\",\n \"authorization\": []\n}" + }, + "url": { + "raw": "{{host}}/default/rdmp/api/records/metadata/rdmp", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "records", + "metadata", + "rdmp" + ] + } + }, + "response": [] + }, + { + "name": "Delete Record", + "event": [ + { + "listen": "test", + "script": { + "id": "7e537e3c-0931-475f-b4e9-a7c9c4baa605", + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Success is set and is true\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('success');", + " pm.expect(jsonData.success).to.eql(true);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [], + "url": { + "raw": "{{host}}/default/rdmp/api/records/metadata/{{dmpOidToDelete}}", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "records", + "metadata", + "{{dmpOidToDelete}}" + ] + }, + "description": "Delete Record" + }, + "response": [] } ], "protocolProfileBehavior": {} @@ -423,7 +545,7 @@ { "listen": "test", "script": { - "id": "396e75bc-3b89-4567-a707-e7850aafa56c", + "id": "cf13ac5d-55f0-4707-8492-c215905cf1bc", "exec": [ "postman.setGlobalVariable(\"cookie\",postman.getResponseHeader(\"set-cookie\") );", "", @@ -518,7 +640,7 @@ { "listen": "test", "script": { - "id": "290f42ee-a8bc-40d4-bf23-7d1fd2b1c613", + "id": "4cdfb919-745f-47ef-9a63-5399feb6f6f7", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -606,7 +728,7 @@ { "listen": "test", "script": { - "id": "9d0cce74-a03f-4180-8838-f7d1e6988af1", + "id": "32759972-ad15-4610-954e-c3e1ad84929c", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -683,7 +805,7 @@ { "listen": "test", "script": { - "id": "5358479e-8fc9-4547-b72f-4ed6318ec4a6", + "id": "a122338e-1a80-4c4b-b27d-aeb17b3bdd59", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -760,7 +882,7 @@ { "listen": "test", "script": { - "id": "b32dc7cf-fe3b-4e4d-9561-6e8302da8e22", + "id": "64803a6f-6fe1-411c-8d62-950981f6d30f", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -849,7 +971,7 @@ { "listen": "test", "script": { - "id": "2ce63735-1fdf-4448-9b86-dd9220cc0216", + "id": "995625c5-8a15-4047-889a-d454040d6ca3", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -939,7 +1061,7 @@ { "listen": "test", "script": { - "id": "b508583c-a365-4325-8ff0-a13f43977692", + "id": "558847be-48f1-4bf1-a442-2aa7e8cc348e", "exec": [ "// \"contributor_ci\": {", " // \"text_full_name\": \"Alberto Zweinstein\",", @@ -1054,7 +1176,7 @@ { "listen": "test", "script": { - "id": "775bafd8-2df3-45d6-8fe3-ddff1cd51ace", + "id": "7f31fea7-9ccf-425b-a719-705e10faa257", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1152,7 +1274,7 @@ { "listen": "test", "script": { - "id": "2d5eeed7-9802-4b60-a6aa-18f28f4de4ab", + "id": "f5f27232-892c-47a9-9901-43c7a67d561b", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1256,7 +1378,7 @@ { "listen": "test", "script": { - "id": "0fbe7547-b626-4417-aabe-71585bfda7d2", + "id": "12339795-d347-4bc1-855c-89dbdae3397c", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1362,7 +1484,7 @@ { "listen": "test", "script": { - "id": "0af211c4-31ed-4b1b-b3e7-066eab92155b", + "id": "2624171c-4f3a-4603-b796-dd6b2e621e7d", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1465,7 +1587,7 @@ { "listen": "test", "script": { - "id": "3327710b-6c78-4ac8-99a0-873ac4d482cf", + "id": "fb5606f4-c7cb-485c-8ddc-6ee4eb6f9472", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -1570,7 +1692,7 @@ { "listen": "test", "script": { - "id": "0f3e4e50-0ab6-4f13-867c-edd92245db45", + "id": "a68a36d9-e62e-4cca-bb1d-a161764774dd", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1674,7 +1796,7 @@ { "listen": "test", "script": { - "id": "50716166-d5fd-4fb1-b437-ac1680c372bf", + "id": "2f86b313-3aa1-4cfc-a21f-5287d09586ec", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1855,7 +1977,7 @@ { "listen": "test", "script": { - "id": "910e4abb-f7ac-433e-8439-abf2d406996a", + "id": "9c1ff727-f22d-4bac-941e-3e01825299c3", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1958,7 +2080,7 @@ { "listen": "test", "script": { - "id": "60be7dda-ad7c-4088-a0af-c537cfa23fc0", + "id": "624946d5-a920-47ca-80ab-69d6c8b1669f", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2080,7 +2202,7 @@ { "listen": "test", "script": { - "id": "ec11cf13-a76e-431c-8561-cc58736d0d8c", + "id": "9a615f50-f60c-4377-b9d5-d500715af11e", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -2174,7 +2296,7 @@ { "listen": "test", "script": { - "id": "569b8fdd-0dcd-49ff-b992-79d7f5f5970f", + "id": "a5384016-54f0-4056-b865-608fd16d34e3", "exec": [ "", "pm.test(\"Status code is 201\", function () {", @@ -2276,7 +2398,7 @@ { "listen": "test", "script": { - "id": "4db55ba8-1fea-4816-856d-4d9fc15f461a", + "id": "e67d2394-cdc6-4ba3-af37-518d0e9d2698", "exec": [ "pm.test(\"Status code is 204\", function () {", " pm.response.to.have.status(204);", @@ -2370,7 +2492,7 @@ { "listen": "test", "script": { - "id": "a05e400d-7ba7-4189-9283-22df340db50b", + "id": "4c4a7b35-90ca-4794-926d-8f70cb7f0046", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2462,7 +2584,7 @@ { "listen": "test", "script": { - "id": "5f2ebd69-89f7-4a52-b30f-00f06cad2d5d", + "id": "6184ab65-0c46-4a46-a625-d48da4d92dc8", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2498,7 +2620,7 @@ { "listen": "test", "script": { - "id": "f3db7fa8-7fde-42eb-913c-dfe48f4c6539", + "id": "088e858b-7cc6-4955-af5e-812044c9ac09", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2589,7 +2711,7 @@ { "listen": "test", "script": { - "id": "7973e70c-96d2-455b-ba3d-32b97056931a", + "id": "d9e544a3-6e47-4ee9-bc11-2aac61fb52f0", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2681,7 +2803,7 @@ { "listen": "test", "script": { - "id": "2b75fb88-b3ab-4da1-8605-8c28758dffb9", + "id": "c3137c83-7b3e-437a-8dbc-2e592e863cf3", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2773,7 +2895,7 @@ { "listen": "test", "script": { - "id": "c7986692-b565-4c4e-baed-80e951c4a347", + "id": "0b742f23-a6a2-43a9-b583-6816b2b57c5d", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -2871,7 +2993,7 @@ { "listen": "test", "script": { - "id": "dd144dff-909f-4430-9bd5-ca5b3045d255", + "id": "9f4ff55b-bc2d-4744-96aa-240fb2660312", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2973,7 +3095,7 @@ { "listen": "test", "script": { - "id": "23fe1da6-69cc-4675-b30b-d8e437499f9b", + "id": "3c21a109-e59c-455b-afcf-5548829a67e7", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3143,7 +3265,7 @@ { "listen": "test", "script": { - "id": "8d577856-e300-485a-b1a2-56a8e1bcc70e", + "id": "ad20711b-8b79-4f38-89c4-b21ff770ecf4", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3219,7 +3341,7 @@ { "listen": "test", "script": { - "id": "e8422191-0aee-4124-9d54-f1757bfe7250", + "id": "e86db382-a68a-4f72-a631-af8e3033ac07", "exec": [ "pm.test(\"Status code is 403\", function () {", " pm.response.to.have.status(403);", @@ -3288,7 +3410,7 @@ { "listen": "test", "script": { - "id": "11e34f30-4d2d-43d0-a1a5-e96e61083b50", + "id": "5283bb00-f364-4cf6-8bda-9dd171d2b6aa", "exec": [ "postman.setGlobalVariable(\"cookie\",postman.getResponseHeader(\"set-cookie\") );", "", @@ -3387,7 +3509,7 @@ { "listen": "test", "script": { - "id": "903c1dca-6598-4e69-972a-05051e1bd5ac", + "id": "8187b0cd-55f6-4aeb-b1b2-d36d7aebf931", "exec": [ "postman.setGlobalVariable(\"cookie\",postman.getResponseHeader(\"set-cookie\") );", "", @@ -3483,7 +3605,7 @@ { "listen": "test", "script": { - "id": "60b3b76d-66c9-497e-86cd-2ac252e83bab", + "id": "d3f2f8bb-5688-45e4-876d-8585d32195b1", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", diff --git a/typescript/api/controllers/webservice/RecordController.ts b/typescript/api/controllers/webservice/RecordController.ts index c9c097e36c..6701842e87 100644 --- a/typescript/api/controllers/webservice/RecordController.ts +++ b/typescript/api/controllers/webservice/RecordController.ts @@ -71,7 +71,8 @@ export module Controllers { 'getPermissions', 'getDataStream', 'addDataStreams', - 'listRecords' + 'listRecords', + 'deleteRecord' ]; constructor() { @@ -555,7 +556,7 @@ export module Controllers { apiReponse.summary.numFound = totalItems; apiReponse.summary.start = startIndex; apiReponse.summary.page = pageNumber; - + var items = []; var docs = results["response"]["docs"]; @@ -615,7 +616,7 @@ export module Controllers { // sails.log.debug(`getRecords: ${recordType} ${workflowState} ${start}`); // sails.log.debug(`${rows} ${packageType} ${sort}`); this.getRecords(workflowState, recordType, start, rows, user, roles, brand, editAccessOnly, packageType, sort).flatMap(results => { - + return results; }).subscribe(response => { res.json(response); @@ -627,6 +628,21 @@ export module Controllers { }); } } + + public async deleteRecord(req, res) { + const oid = req.param('oid'); + if (_.isEmpty(oid)) { + return this.apiFail(req, res, 400, new APIErrorResponse("Missing ID of record.")); + } + const response = await this.RecordsService.delete(oid); + if (response.isSuccessful()) { + this.apiRespond(req, res, response); + } else { + sails.log.verbose(`Delete attempt failed for OID: ${oid}`); + sails.log.verbose(JSON.stringify(response)); + this.apiFail(req, res, 500, new APIErrorResponse(response.message, response.details)); + } + } } } diff --git a/typescript/api/core/SearchService.ts b/typescript/api/core/SearchService.ts index 2a574f3346..2bd7056715 100644 --- a/typescript/api/core/SearchService.ts +++ b/typescript/api/core/SearchService.ts @@ -2,6 +2,6 @@ interface SearchService{ index(id:string, data:any):any; searchFuzzy(type, workflowState, searchQuery, exactSearches, facetSearches, brand, user, roles, returnFields): Promise; - + remove(id: string): any; } export default SearchService diff --git a/typescript/api/services/RecordsService.ts b/typescript/api/services/RecordsService.ts index 2221c263db..f4d5da8594 100644 --- a/typescript/api/services/RecordsService.ts +++ b/typescript/api/services/RecordsService.ts @@ -214,8 +214,12 @@ export module Services { getRelatedRecords(oid: any, brand: any): Promise < any > { return this.storageService.getRelatedRecords(oid, brand); } - delete(oid: any): Promise < any > { - return this.storageService.delete(oid); + async delete(oid: any) { + const response = await this.storageService.delete(oid); + if (response.isSuccessful()) { + this.searchService.remove(oid); + } + return response; } updateNotificationLog(oid: any, record: any, options: any): Promise < any > { return this.storageService.updateNotificationLog(oid, record, options); diff --git a/typescript/api/services/SolrSearchService.ts b/typescript/api/services/SolrSearchService.ts index f3914c5e91..f07dc15ba4 100644 --- a/typescript/api/services/SolrSearchService.ts +++ b/typescript/api/services/SolrSearchService.ts @@ -45,7 +45,8 @@ export module Services { 'index', 'remove', 'searchFuzzy', - 'addOrUpdate' + 'solrAddOrUpdate', + 'solrDelete' ]; protected queueService: QueueService; @@ -165,7 +166,10 @@ export module Services { } public remove(id: string) { - + sails.log.verbose(`${this.logHeader} adding delete-index job: ${id} with data:`); + const data = {id: id}; + sails.log.verbose(JSON.stringify(data)); + this.queueService.now(sails.config.solr.deleteJobName, data); } public async searchFuzzy(type, workflowState, searchQuery, exactSearches, facetSearches, brand, user, roles, returnFields): Promise { @@ -224,7 +228,7 @@ export module Services { return customResp; } - public async addOrUpdate(job:any) { + public async solrAddOrUpdate(job:any) { try { let data = job.attrs.data; sails.log.verbose(`${this.logHeader} adding document: ${data.id} to index`); @@ -243,7 +247,7 @@ export module Services { }); }); } catch (err) { - sails.log.error(`${this.logHeader} Failed to addOrUpdate, while pre-processing index: `); + sails.log.error(`${this.logHeader} Failed to solrAddOrUpdate, while pre-processing index: `); sails.log.error(JSON.stringify(err)); } } @@ -319,6 +323,27 @@ export module Services { url = url + "&fq=authorization_edit:" + username + (editAccessOnly ? "" : (" OR authorization_view:" + username + " OR authorization_viewRoles:(" + roleString + ")")) + " OR authorization_editRoles:(" + roleString + ")"; return url; } + + public solrDelete(job:any) { + try { + let data = job.attrs.data; + sails.log.verbose(`${this.logHeader} deleting document: ${data.id}`); + this.client.delete('id', data.id, (err, obj) => { + if (err) { + sails.log.error(`${this.logHeader} Failed to delete document: ${data.id}`); + sails.log.error(err); + return; + } + this.client.commit((commitErr, commitObj) => { + sails.log.verbose(`${this.logHeader} document deleted in SOLR: ${data.id}`); + sails.log.verbose(obj); + }); + }); + } catch (err) { + sails.log.error(`${this.logHeader} Failed to solrDelete:`); + sails.log.error(JSON.stringify(err)); + } + } } } From 520f6b574963e5c051d1bccba09cf7621673de57 Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Wed, 16 Dec 2020 12:00:04 +1030 Subject: [PATCH 35/48] Added further User Management REST API Endpoints --- config/routes.js | 10 +- .../webservice/UserManagementController.ts | 196 +++++++++++++++--- typescript/api/core/model/User.ts | 2 + .../core/model/api/CreateUserAPIResponse.ts | 4 +- .../core/model/api/UserAPITokenAPIResponse.ts | 5 + 5 files changed, 185 insertions(+), 32 deletions(-) create mode 100644 typescript/api/core/model/api/UserAPITokenAPIResponse.ts diff --git a/config/routes.js b/config/routes.js index d484ef4666..97736a83d9 100644 --- a/config/routes.js +++ b/config/routes.js @@ -287,8 +287,14 @@ module.exports.routes = { 'get /:branding/:portal/api/users': 'webservice/UserManagementController.listUsers', - 'get /:branding/:portal/api/users/find': 'webservice/UserManagementController.findUser', - + 'get /:branding/:portal/api/users/find': 'webservice/UserManagementController.getUser', + 'get /:branding/:portal/api/users/get': 'webservice/UserManagementController.getUser', + 'post /:branding/:portal/api/users': 'webservice/UserManagementController.createUser', + 'put /:branding/:portal/api/users': 'webservice/UserManagementController.updateUser', + 'get /:branding/:portal/api/users/token/generate': 'webservice/UserManagementController.generateAPIToken', + 'get /:branding/:portal/api/users/token/revoke': 'webservice/UserManagementController.revokeAPIToken', + 'get /:branding/:portal/api/roles': 'webservice/UserManagementController.listSystemRoles', + 'post /:branding/:portal/api/sendNotification': { controller: 'EmailController', action: 'sendNotification', diff --git a/typescript/api/controllers/webservice/UserManagementController.ts b/typescript/api/controllers/webservice/UserManagementController.ts index 2c7bb190bc..06c9908b27 100644 --- a/typescript/api/controllers/webservice/UserManagementController.ts +++ b/typescript/api/controllers/webservice/UserManagementController.ts @@ -31,8 +31,20 @@ declare var _; * Package that contains all Controllers. */ import controller = require('../../core/CoreController.js'); -import {ListAPIResponse } from '../../core/model/ListAPIResponse'; -import {APIErrorResponse } from '../../core/model/APIErrorResponse'; +import { + ListAPIResponse +} from '../../core/model/ListAPIResponse'; +import { + APIErrorResponse +} from '../../core/model/APIErrorResponse'; +import { + UserAPITokenAPIResponse +} from '../../core/model/api/UserAPITokenAPIResponse'; +import { + CreateUserAPIResponse +} from '../../core/model/api/CreateUserAPIResponse'; +import * as uuidv4 from 'uuid/v4'; + export module Controllers { /** * Responsible for all things related to the Dashboard @@ -45,10 +57,14 @@ export module Controllers { * Exported methods, accessible from internet. */ protected _exportedMethods: any = [ - 'render', 'listUsers', 'getUser', - 'createUser' + 'createUser', + 'updateUser', + 'generateAPIToken', + 'revokeAPIToken', + 'listSystemRoles', + 'deleteUser' ]; /** @@ -61,11 +77,6 @@ export module Controllers { } - public render(req, res) { - return this.sendView(req, res, 'dashboard'); - } - - public listUsers(req, res) { let that = this; var page = req.param('page'); @@ -84,16 +95,14 @@ export module Controllers { pageSize = 10; } let skip = (page - 1) * pageSize; - sails.log.error("List users 1") - + User.count({ where: queryObject }).exec(function (err, count) { - sails.log.error("List users 2") let response: ListAPIResponse < any > = new ListAPIResponse < any > (); response.summary.numFound = count; response.summary.page = page; - + if (count == 0) { response["records"] = []; return res.json(response); @@ -101,14 +110,14 @@ export module Controllers { User.find({ where: queryObject, limit: pageSize, - skip: skip + skip: skip }).exec(function (err, users) { _.each(users, user => { delete user["token"]; }); response.records = users; - + return that.apiRespond(req, res, response); }); } @@ -124,35 +133,166 @@ export module Controllers { User.findOne(queryObject).exec(function (err, user) { if (err != null) { sails.log.error(err) - return that.apiFail(req,res,500) + return that.apiFail(req, res, 500) } if (user != null) { delete user["token"]; return that.apiRespond(req, res, user); } - return that.apiFail(req,res,404, new APIErrorResponse("No user found with given criteria", `Searchby: ${searchField} and Query: ${query}`)) + return that.apiFail(req, res, 404, new APIErrorResponse("No user found with given criteria", `Searchby: ${searchField} and Query: ${query}`)) }); } public createUser(req, res) { - let userReq:User = req.body; - + let userReq: User = req.body; + UsersService.addLocalUser(userReq.username, userReq.name, userReq.email, userReq.password).subscribe(response => { - let userResponse: CreateUserAPIResponse = new CreateUserAPIResponse(); - userResponse.username = response.username; - userResponse.name = response.name; - userResponse.email = response.email; - userResponse.type = response.type; - userResponse.lastLogin = response.lastLogin; - return this.apiRespond(req,res,userResponse,201) - }).error(error => { + + if (userReq.roles) { + let roles = userReq.roles; + let brand = BrandingService.getBrand(req.session.branding); + let roleIds = RolesService.getRoleIds(brand.roles, roles); + UsersService.updateUserRoles(response.id, roleIds).subscribe(user => { + sails.log.error(user) + let userResponse = new CreateUserAPIResponse(); + userResponse.id = response.id; + userResponse.username = response.username; + userResponse.name = response.name; + userResponse.email = response.email; + userResponse.type = response.type; + userResponse.lastLogin = response.lastLogin; + return this.apiRespond(req, res, userResponse, 201); + }, error => { + sails.log.error("Failed to update user roles:"); + sails.log.error(error); + //TODO: Find more appropriate status code + this.apiFail(req, res, 500, new APIErrorResponse(error.message)); + }); + } else { + let userResponse = new CreateUserAPIResponse(); + userResponse.id = response.id; + userResponse.username = response.username; + userResponse.name = response.name; + userResponse.email = response.email; + userResponse.type = response.type; + userResponse.lastLogin = response.lastLogin; + return this.apiRespond(req, res, userResponse, 201); + } + }, error => { + sails.log.error(error); + return this.apiFail(req, res, 500) + }); + + } + + + public updateUser(req, res) { + let userReq: User = req.body; + + UsersService.updateUserDetails(userReq.id, userReq.name, userReq.email, userReq.password).subscribe(response => { + + let user = response; + sails.log.error(user) + if (!_.isEmpty(response) && _.isArray(response)) { + for (let userItem of response) { + if (!_.isEmpty(response) && _.isArray(userItem)) { + user = userItem[0]; + break; + } + } + } + + if (userReq.roles) { + let roles = userReq.roles; + let brand = BrandingService.getBrand(req.session.branding); + let roleIds = RolesService.getRoleIds(brand.roles, roles); + UsersService.updateUserRoles(response.id, roleIds).subscribe(user => { + //TODO: Add roles to the response + let userResponse = new CreateUserAPIResponse(); + userResponse.id = response.id; + userResponse.username = response.username; + userResponse.name = response.name; + userResponse.email = response.email; + userResponse.type = response.type; + userResponse.lastLogin = response.lastLogin; + return this.apiRespond(req, res, userResponse, 201); + }, error => { + sails.log.error("Failed to update user roles:"); + sails.log.error(error); + //TODO: Find more appropriate status code + this.apiFail(req, res, 500, new APIErrorResponse(error.message)); + }); + } else { + let userResponse: CreateUserAPIResponse = new CreateUserAPIResponse(); + userResponse.id = user.id; + userResponse.username = user.username; + userResponse.name = user.name; + userResponse.email = user.email; + userResponse.type = user.type; + userResponse.lastLogin = user.lastLogin; + + return this.apiRespond(req, res, userResponse, 201) + } + }, error => { sails.log.error(error); - return this.apiFail(req,res,500, new APIErrorResponse()) + if (error.message.indexOf('No such user with id:') != -1) { + return this.apiFail(req, res, 404, new APIErrorResponse(error.message)) + } else { + return this.apiFail(req, res, 500) + } }); } + public generateAPIToken(req, res) { + let userid = req.param('id'); + + if (userid) { + var uuid = uuidv4(); + UsersService.setUserKey(userid, uuid).subscribe(user => { + let response = new UserAPITokenAPIResponse(); + response.id = userid + response.username = user.username + response.token = uuid + this.apiRespond(req, res, response) + }, error => { + sails.log.error("Failed to set UUID:"); + sails.log.error(error); + this.apiFail(req, res, 500, new APIErrorResponse(error.message)); + }); + } else { + return this.apiFail(req, res, 400, new APIErrorResponse("unable to get user ID.")); + } + } + + + public revokeAPIToken(req, res) { + + let userid = req.param('id'); + + if (userid) { + var uuid = null; + UsersService.setUserKey(userid, uuid).subscribe(user => { + let response = new UserAPITokenAPIResponse(); + response.id = userid + response.username = user.username + response.token = uuid + this.apiRespond(req, res, response) + }, error => { + sails.log.error("Failed to set UUID:"); + sails.log.error(error); + this.apiFail(req, res, 500, new APIErrorResponse(error.message)); + }); + } else { + return this.apiFail(req, res, 400, new APIErrorResponse("unable to get user ID.")); + } + } + + public listSystemRoles(req, res) { + let brand = BrandingService.getBrand(req.session.branding); + return this.apiRespond(req,res,brand.roles); + } /** diff --git a/typescript/api/core/model/User.ts b/typescript/api/core/model/User.ts index 95cdd014e7..bd3cb561a8 100644 --- a/typescript/api/core/model/User.ts +++ b/typescript/api/core/model/User.ts @@ -1,9 +1,11 @@ class User { + id:string username:string password:string type: string name: string email: string token: string + roles: string[] lastLogin: Date } \ No newline at end of file diff --git a/typescript/api/core/model/api/CreateUserAPIResponse.ts b/typescript/api/core/model/api/CreateUserAPIResponse.ts index 445637de42..f6e1ec9412 100644 --- a/typescript/api/core/model/api/CreateUserAPIResponse.ts +++ b/typescript/api/core/model/api/CreateUserAPIResponse.ts @@ -1,5 +1,5 @@ -class CreateUserAPIResponse { - +export class CreateUserAPIResponse { + id: string username: string name:string email: string diff --git a/typescript/api/core/model/api/UserAPITokenAPIResponse.ts b/typescript/api/core/model/api/UserAPITokenAPIResponse.ts new file mode 100644 index 0000000000..9539083242 --- /dev/null +++ b/typescript/api/core/model/api/UserAPITokenAPIResponse.ts @@ -0,0 +1,5 @@ +export class UserAPITokenAPIResponse { + id: string + username: string + token:string +} \ No newline at end of file From 3a354b7d75bdb462b2c876db72704edb215c8cbb Mon Sep 17 00:00:00 2001 From: Shilo Banihit Date: Wed, 16 Dec 2020 12:46:01 +1000 Subject: [PATCH 36/48] Added transition workflow API endpoint. --- config/routes.js | 7 +++- .../webservice/RecordController.ts | 34 ++++++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/config/routes.js b/config/routes.js index 97736a83d9..3cdf8802c5 100644 --- a/config/routes.js +++ b/config/routes.js @@ -256,6 +256,11 @@ module.exports.routes = { action: 'deleteRecord', csrf: false }, + 'post /:branding/:portal/api/records/workflow/step/:targetStep/:oid': { + controller: 'webservice/RecordController', + action: 'transitionWorkflow', + csrf: false + }, 'post /:branding/:portal/api/records/permissions/edit/:oid': { controller: 'webservice/RecordController', action: 'addUserEdit', @@ -294,7 +299,7 @@ module.exports.routes = { 'get /:branding/:portal/api/users/token/generate': 'webservice/UserManagementController.generateAPIToken', 'get /:branding/:portal/api/users/token/revoke': 'webservice/UserManagementController.revokeAPIToken', 'get /:branding/:portal/api/roles': 'webservice/UserManagementController.listSystemRoles', - + 'post /:branding/:portal/api/sendNotification': { controller: 'EmailController', action: 'sendNotification', diff --git a/typescript/api/controllers/webservice/RecordController.ts b/typescript/api/controllers/webservice/RecordController.ts index 6701842e87..ff62a267f1 100644 --- a/typescript/api/controllers/webservice/RecordController.ts +++ b/typescript/api/controllers/webservice/RecordController.ts @@ -72,7 +72,8 @@ export module Controllers { 'getDataStream', 'addDataStreams', 'listRecords', - 'deleteRecord' + 'deleteRecord', + 'transitionWorkflow' ]; constructor() { @@ -643,6 +644,37 @@ export module Controllers { this.apiFail(req, res, 500, new APIErrorResponse(response.message, response.details)); } } + + public async transitionWorkflow(req, res) { + const oid = req.param('oid'); + const targetStepName = req.param('targetStep'); + try { + if (_.isEmpty(oid)) { + return this.apiFail(req, res, 400, new APIErrorResponse("Missing ID of record.")); + } + const brand = BrandingService.getBrand(req.session.branding); + const record = await this.RecordsService.getMeta(oid); + if (_.isEmpty(record)) { + this.apiFail(req, res, 500, new APIErrorResponse(`Missing OID: ${oid}`)); + return; + } + if (!this.RecordsService.hasEditAccess(brand, req.user, req.user.roles, record)) { + this.apiFail(req, res, 500, new APIErrorResponse(`User has no edit permissions for :${oid}`)); + return; + } + const recType = await RecordTypesService.get(brand, record.metaMetadata.type).toPromise(); + const nextStep = await WorkflowStepsService.get(recType, targetStepName).toPromise(); + this.RecordsService.updateWorkflowStep(record, nextStep); + const response = await this.RecordsService.updateMeta(brand, oid, record, req.user); + this.apiRespond(req, res, response); + } catch (err) { + sails.log.error(`Failed to transitionWorkflow: ${oid} to ${targetStepName}`); + sails.log.error(JSON.stringify(err)); + this.apiFail(req, res, 500, new APIErrorResponse(`Failed to transition workflow, please check server logs.`)); + } + } + + } } From 63ac451c210372367c42cbcb6867426a91cc02d7 Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Wed, 16 Dec 2020 14:49:56 +1030 Subject: [PATCH 37/48] Added search related REST API calls --- .../webservice/SearchController.ts | 125 ++++++++++++++++++ .../api/core/model/APIObjectActionResponse.ts | 12 ++ 2 files changed, 137 insertions(+) create mode 100644 typescript/api/controllers/webservice/SearchController.ts create mode 100644 typescript/api/core/model/APIObjectActionResponse.ts diff --git a/typescript/api/controllers/webservice/SearchController.ts b/typescript/api/controllers/webservice/SearchController.ts new file mode 100644 index 0000000000..f6688ccc15 --- /dev/null +++ b/typescript/api/controllers/webservice/SearchController.ts @@ -0,0 +1,125 @@ +// Copyright (c) 2017 Queensland Cyber Infrastructure Foundation (http://www.qcif.edu.au/) +// +// GNU GENERAL PUBLIC LICENSE +// Version 2, June 1991 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +// +declare var module; +declare var sails; + +declare var BrandingService; +declare var RolesService; +declare var DashboardService; +declare var UsersService; +declare var User; +declare var _; +/** + * Package that contains all Controllers. + */ +import controller = require('../../core/CoreController.js'); +import { APIErrorResponse } from '../../core/model/APIErrorResponse.js'; +import { APIObjectActionResponse } from '../../core/model/APIObjectActionResponse.js'; +import RecordsService from '../../core/RecordsService.js'; +import SearchService from '../../core/SearchService.js'; +import StorageService from '../../core/StorageService.js'; + +export module Controllers { + /** + * Responsible for all things related to the Dashboard + * + * @author Andrew Brazzatti + */ + export class Search extends controller.Controllers.Core.Controller { + + searchService: SearchService; + RecordsService: RecordsService = sails.services.recordsservice; + + constructor() { + super(); + let that = this; + sails.on('ready', function () { + that.searchService = sails.services[sails.config.search.serviceName]; + }); + } + + /** + * Exported methods, accessible from internet. + */ + protected _exportedMethods: any = [ + 'search', + 'index' + ]; + + /** + ************************************************************************************************** + **************************************** Add custom methods ************************************** + ************************************************************************************************** + */ + + public bootstrap() { + + } + + public async index(req, res) { + let oid = req.param('oid'); + let record = await this.RecordsService.getMeta(oid); + await this.searchService.index(oid,record); + + return this.apiRespond(req,res,new APIObjectActionResponse(oid, "Index request added to message queue for processing"),200) + } + + public async search(req, res) { + const brand = BrandingService.getBrand(req.session.branding); + const type = req.query.type; + const workflow = req.query.workflow; + const searchString = req.query.searchStr; + const exactSearchNames = _.isEmpty(req.query.exactNames) ? [] : req.query.exactNames.split(','); + const exactSearches = []; + const facetSearchNames = _.isEmpty(req.query.facetNames) ? [] : req.query.facetNames.split(','); + const facetSearches = []; + + _.forEach(exactSearchNames, (exactSearch) => { + exactSearches.push({ + name: exactSearch, + value: req.query[`exact_${exactSearch}`] + }); + }); + _.forEach(facetSearchNames, (facetSearch) => { + facetSearches.push({ + name: facetSearch, + value: req.query[`facet_${facetSearch}`] + }); + }); + + try { + const searchRes = await this.searchService.searchFuzzy(type, workflow, searchString, exactSearches, facetSearches, brand, req.user, req.user.roles, sails.config.record.search.returnFields); + this.apiRespond(req, res,searchRes); + } catch (error) { + this.apiFail(req, res, 500, new APIErrorResponse(error.message)); + } + } + + + /** + ************************************************************************************************** + **************************************** Override magic methods ********************************** + ************************************************************************************************** + */ + } +} + +module.exports = new Controllers.Search().exports(); \ No newline at end of file diff --git a/typescript/api/core/model/APIObjectActionResponse.ts b/typescript/api/core/model/APIObjectActionResponse.ts new file mode 100644 index 0000000000..d797e08556 --- /dev/null +++ b/typescript/api/core/model/APIObjectActionResponse.ts @@ -0,0 +1,12 @@ +export class APIObjectActionResponse{ + + oid:string; + message:string; + details:string; + + constructor(oid:string, message:string ='Request processed successfully',details:string = '') { + this.oid = oid; + this.message = message; + this.details = details; + } +} From 21eb1fc716fcb9588209e77869030d363b86e1a9 Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Wed, 16 Dec 2020 18:06:33 +1030 Subject: [PATCH 38/48] Added Record Types and Forms API endpoints --- config/routes.js | 10 + .../webservice/FormManagementController.ts | 113 ++++++++++ .../webservice/RecordTypeController.ts | 112 ++++++++++ typescript/api/services/FormsService.ts | 209 ++++++++++-------- 4 files changed, 356 insertions(+), 88 deletions(-) create mode 100644 typescript/api/controllers/webservice/FormManagementController.ts create mode 100644 typescript/api/controllers/webservice/RecordTypeController.ts diff --git a/config/routes.js b/config/routes.js index 97736a83d9..5aef9002b7 100644 --- a/config/routes.js +++ b/config/routes.js @@ -295,6 +295,16 @@ module.exports.routes = { 'get /:branding/:portal/api/users/token/revoke': 'webservice/UserManagementController.revokeAPIToken', 'get /:branding/:portal/api/roles': 'webservice/UserManagementController.listSystemRoles', + + 'get /:branding/:portal/api/search': 'webservice/SearchController.search', + 'get /:branding/:portal/api/search/index': 'webservice/SearchController.index', + + 'get /:branding/:portal/api/forms/get': 'webservice/FormManagementController.getForm', + 'get /:branding/:portal/api/forms': 'webservice/FormManagementController.listForms', + + 'get /:branding/:portal/api/recordtypes/get': 'webservice/RecordTypeController.getRecordType', + 'get /:branding/:portal/api/recordtypes': 'webservice/RecordTypeController.listRecordTypes', + 'post /:branding/:portal/api/sendNotification': { controller: 'EmailController', action: 'sendNotification', diff --git a/typescript/api/controllers/webservice/FormManagementController.ts b/typescript/api/controllers/webservice/FormManagementController.ts new file mode 100644 index 0000000000..516397bf06 --- /dev/null +++ b/typescript/api/controllers/webservice/FormManagementController.ts @@ -0,0 +1,113 @@ +// Copyright (c) 2017 Queensland Cyber Infrastructure Foundation (http://www.qcif.edu.au/) +// +// GNU GENERAL PUBLIC LICENSE +// Version 2, June 1991 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +// +declare var module; +declare var sails; + +declare var BrandingService; +declare var RolesService; +declare var DashboardService; +declare var UsersService; +declare var User; +declare var _; +/** + * Package that contains all Controllers. + */ +import controller = require('../../core/CoreController.js'); +import { + APIErrorResponse +} from '../../core/model/APIErrorResponse.js'; +import { + APIObjectActionResponse +} from '../../core/model/APIObjectActionResponse.js'; +import { + ListAPIResponse, + ListAPISummary +} from '../../core/model/ListAPIResponse.js'; + +declare var FormsService; + +export module Controllers { + /** + * Responsible for all things related to the Dashboard + * + * @author Andrew Brazzatti + */ + export class FormManagement extends controller.Controllers.Core.Controller { + + + + /** + * Exported methods, accessible from internet. + */ + protected _exportedMethods: any = [ + 'getForm', + 'listForms' + ]; + + /** + ************************************************************************************************** + **************************************** Add custom methods ************************************** + ************************************************************************************************** + */ + + public bootstrap() { + + } + + public async getForm(req, res) { + try { + let name = req.param('name'); + let editable = req.param('editable'); + if (editable == null) { + editable = true; + } + let form = await FormsService.getFormByName(name, editable).toPromise(); + + return this.apiRespond(req, res, form, 200) + } catch (error) { + this.apiFail(req, res, 500, new APIErrorResponse(error.message)); + } + } + + public async listForms(req, res) { + try { + let forms = await FormsService.listForms().toPromise(); + let response: ListAPIResponse < any > = new ListAPIResponse(); + let summary: ListAPISummary = new ListAPISummary(); + summary.numFound = forms.length; + response.summary = summary; + response.records = forms; + this.apiRespond(req, res, response); + } catch (error) { + this.apiFail(req, res, 500, new APIErrorResponse(error.message)); + } + } + + + /** + ************************************************************************************************** + **************************************** Override magic methods ********************************** + ************************************************************************************************** + */ + } +} + +module.exports = new Controllers.FormManagement().exports(); \ No newline at end of file diff --git a/typescript/api/controllers/webservice/RecordTypeController.ts b/typescript/api/controllers/webservice/RecordTypeController.ts new file mode 100644 index 0000000000..3e7c7a8863 --- /dev/null +++ b/typescript/api/controllers/webservice/RecordTypeController.ts @@ -0,0 +1,112 @@ +// Copyright (c) 2017 Queensland Cyber Infrastructure Foundation (http://www.qcif.edu.au/) +// +// GNU GENERAL PUBLIC LICENSE +// Version 2, June 1991 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +// +declare var module; +declare var sails; + +declare var BrandingService; +declare var RolesService; +declare var DashboardService; +declare var UsersService; +declare var User; +declare var _; +/** + * Package that contains all Controllers. + */ +import controller = require('../../core/CoreController.js'); +import { + APIErrorResponse +} from '../../core/model/APIErrorResponse.js'; +import { + APIObjectActionResponse +} from '../../core/model/APIObjectActionResponse.js'; +import { + ListAPIResponse, + ListAPISummary +} from '../../core/model/ListAPIResponse.js'; + +declare var RecordTypesService; + +export module Controllers { + /** + * Responsible for all things related to the Dashboard + * + * @author Andrew Brazzatti + */ + export class RecordType extends controller.Controllers.Core.Controller { + + + + /** + * Exported methods, accessible from internet. + */ + protected _exportedMethods: any = [ + 'getRecordType', + 'listRecordTypes' + ]; + + /** + ************************************************************************************************** + **************************************** Add custom methods ************************************** + ************************************************************************************************** + */ + + public bootstrap() { + + } + + public async getRecordType(req, res) { + + try { + let name = req.param('name'); + const brand = BrandingService.getBrand(req.session.branding); + let recordType = await RecordTypesService.get(brand, name).toPromise(); + + return this.apiRespond(req, res, recordType, 200) + } catch (error) { + this.apiFail(req, res, 500, new APIErrorResponse(error.message)); + } + } + + public async listRecordTypes(req, res) { + try { + const brand = BrandingService.getBrand(req.session.branding); + let recordTypes = await RecordTypesService.getAll(brand).toPromise(); + let response: ListAPIResponse < any > = new ListAPIResponse(); + let summary: ListAPISummary = new ListAPISummary(); + summary.numFound = recordTypes.length; + response.summary = summary; + response.records = recordTypes; + this.apiRespond(req, res, response); + } catch (error) { + this.apiFail(req, res, 500, new APIErrorResponse(error.message)); + } + } + + + /** + ************************************************************************************************** + **************************************** Override magic methods ********************************** + ************************************************************************************************** + */ + } +} + +module.exports = new Controllers.RecordType().exports(); \ No newline at end of file diff --git a/typescript/api/services/FormsService.ts b/typescript/api/services/FormsService.ts index 91dd65758d..fbc902220f 100644 --- a/typescript/api/services/FormsService.ts +++ b/typescript/api/services/FormsService.ts @@ -17,9 +17,14 @@ // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import { Observable } from 'rxjs/Rx'; +import { + Observable +} from 'rxjs/Rx'; import services = require('../core/CoreService.js'); -import { Sails, Model } from "sails"; +import { + Sails, + Model +} from "sails"; declare var sails: Sails; declare var Form: Model; @@ -42,98 +47,119 @@ export module Services { 'getForm', 'flattenFields', 'getFormByName', - 'filterFieldsHasEditAccess' + 'filterFieldsHasEditAccess', + 'listForms' ]; - public bootstrap = (workflowStep): Observable => { - let startQ = Form.find({ workflowStep: workflowStep.id }) + public bootstrap = (workflowStep): Observable < any > => { + let startQ = Form.find({ + workflowStep: workflowStep.id + }) if (sails.config.appmode.bootstrapAlways) { sails.log.verbose(`Destroying existing form definitions: ${workflowStep.config.form}`); - startQ = Form.destroy({ name: workflowStep.config.form }) + startQ = Form.destroy({ + name: workflowStep.config.form + }) } let formDefs = []; return super.getObservable(startQ) - .flatMap(form => { - sails.log.verbose("Found : "); - sails.log.verbose(form); - if (!form || form.length == 0) { - sails.log.verbose("Bootstrapping form definitions.."); - // only bootstrap the form for this workflow step - _.forOwn(sails.config.form.forms, (formDef, formName) => { - if (formName == workflowStep.config.form){ - formDefs.push(formName); - } + .flatMap(form => { + sails.log.verbose("Found : "); + sails.log.verbose(form); + if (!form || form.length == 0) { + sails.log.verbose("Bootstrapping form definitions.."); + // only bootstrap the form for this workflow step + _.forOwn(sails.config.form.forms, (formDef, formName) => { + if (formName == workflowStep.config.form) { + formDefs.push(formName); + } + }); + formDefs = _.uniq(formDefs) + sails.log.verbose(JSON.stringify(formDefs)); + return Observable.from(formDefs); + } else { + sails.log.verbose("Not Bootstrapping form definitions... "); + return Observable.of(null); + } + }) + .flatMap(formName => { + // check now if the form already exists, if it does, ignore... + return this.getObservable(Form.find({ + name: formName + })).flatMap(existingFormDef => { + return Observable.of({ + formName: formName, + existingFormDef: existingFormDef + }); }); - formDefs = _.uniq(formDefs) - sails.log.verbose(JSON.stringify(formDefs)); - return Observable.from(formDefs); - } else { - sails.log.verbose("Not Bootstrapping form definitions... "); + }) + .flatMap(existCheck => { + sails.log.verbose(`Existing form check: ${existCheck.formName}`); + sails.log.verbose(JSON.stringify(existCheck)); + if (_.isUndefined(existCheck.existingFormDef) || _.isEmpty(existCheck.existingFormDef)) { + return Observable.of(existCheck.formName); + } else { + sails.log.verbose(`Existing form definition for form name: ${existCheck.existingFormDef.name}, ignoring bootstrap.`); + return Observable.of(null); + } + }) + .flatMap(formName => { + sails.log.verbose("FormName is:"); + sails.log.verbose(formName); + let observable = Observable.of(null); + if (!_.isNull(formName)) { + sails.log.verbose(`Preparing to create form...`); + const formObj = { + name: formName, + fields: sails.config.form.forms[formName].fields, + workflowStep: workflowStep.id, + type: sails.config.form.forms[formName].type, + messages: sails.config.form.forms[formName].messages, + viewCssClasses: sails.config.form.forms[formName].viewCssClasses, + editCssClasses: sails.config.form.forms[formName].editCssClasses, + skipValidationOnSave: sails.config.form.forms[formName].skipValidationOnSave, + attachmentFields: sails.config.form.forms[formName].attachmentFields, + customAngularApp: sails.config.form.forms[formName].customAngularApp || null + }; + + var q = Form.create(formObj); + observable = Observable.bindCallback(q["exec"].bind(q))(); + // var obs = Observable.bindCallback(q["exec"].bind(q))(); + } + return observable; + }) + .flatMap(result => { + if (result) { + sails.log.verbose("Created form record: "); + sails.log.verbose(result); + return Observable.from(result); + } + return Observable.of(result); + }).flatMap(result => { + if (result) { + sails.log.verbose(`Updating workflowstep ${result.workflowStep} to: ${result.id}`); + // update the workflow step... + const q = WorkflowStep.update({ + id: result.workflowStep + }).set({ + form: result.id + }); + return Observable.bindCallback(q["exec"].bind(q))(); + } return Observable.of(null); - } - }) - .flatMap(formName => { - // check now if the form already exists, if it does, ignore... - return this.getObservable(Form.find({name: formName})).flatMap(existingFormDef => { - return Observable.of({formName: formName, existingFormDef: existingFormDef}); }); - }) - .flatMap(existCheck => { - sails.log.verbose(`Existing form check: ${existCheck.formName}`); - sails.log.verbose(JSON.stringify(existCheck)); - if (_.isUndefined(existCheck.existingFormDef) || _.isEmpty(existCheck.existingFormDef)) { - return Observable.of(existCheck.formName); - } else { - sails.log.verbose(`Existing form definition for form name: ${existCheck.existingFormDef.name}, ignoring bootstrap.`); - return Observable.of(null); - } - }) - .flatMap(formName => { - sails.log.verbose("FormName is:"); - sails.log.verbose(formName); - let observable = Observable.of(null); - if (!_.isNull(formName)) { - sails.log.verbose(`Preparing to create form...`); - const formObj = { - name: formName, - fields: sails.config.form.forms[formName].fields, - workflowStep: workflowStep.id, - type: sails.config.form.forms[formName].type, - messages: sails.config.form.forms[formName].messages, - viewCssClasses: sails.config.form.forms[formName].viewCssClasses, - editCssClasses: sails.config.form.forms[formName].editCssClasses, - skipValidationOnSave: sails.config.form.forms[formName].skipValidationOnSave, - attachmentFields: sails.config.form.forms[formName].attachmentFields, - customAngularApp: sails.config.form.forms[formName].customAngularApp || null - }; - - var q = Form.create(formObj); - observable = Observable.bindCallback(q["exec"].bind(q))(); - // var obs = Observable.bindCallback(q["exec"].bind(q))(); - } - return observable; - }) - .flatMap(result => { - if (result) { - sails.log.verbose("Created form record: "); - sails.log.verbose(result); - return Observable.from(result); - } - return Observable.of(result); - }).flatMap(result => { - if (result) { - sails.log.verbose(`Updating workflowstep ${result.workflowStep} to: ${result.id}`); - // update the workflow step... - const q = WorkflowStep.update({id: result.workflowStep}).set({form: result.id}); - return Observable.bindCallback(q["exec"].bind(q))(); - } - return Observable.of(null); - }); } - public getFormByName = (formName, editMode): Observable => { - return super.getObservable(Form.findOne({ name: formName })).flatMap(form => { + public listForms = (): Observable < any > => { + return super.getObservable(Form.find({})); + } + + + public getFormByName = (formName, editMode): Observable < any > => { + return super.getObservable(Form.findOne({ + name: formName + })).flatMap(form => { if (form) { this.setFormEditMode(form.fields, editMode); return Observable.of(form); @@ -142,17 +168,24 @@ export module Services { }); } - public getForm = (branding, recordType, editMode, starting: boolean): Observable => { + public getForm = (branding, recordType, editMode, starting: boolean): Observable < any > => { + + return super.getObservable(RecordType.findOne({ + key: branding + "_" + recordType + })) + .flatMap(recordType => { - return super.getObservable(RecordType.findOne({ key: branding + "_" + recordType })) - .flatMap(recordType => { - - return super.getObservable(WorkflowStep.findOne({ recordType: recordType.id, starting: starting })); + return super.getObservable(WorkflowStep.findOne({ + recordType: recordType.id, + starting: starting + })); }).flatMap(workflowStep => { if (workflowStep.starting == true) { - return super.getObservable(Form.findOne({ name: workflowStep.config.form })); + return super.getObservable(Form.findOne({ + name: workflowStep.config.form + })); } return Observable.of(null); @@ -203,4 +236,4 @@ export module Services { } } } -module.exports = new Services.Forms().exports(); +module.exports = new Services.Forms().exports(); \ No newline at end of file From 9b6921e04ed8403d2cfb67e26cf7796022e7a2d5 Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Wed, 16 Dec 2020 23:42:53 +1030 Subject: [PATCH 39/48] Updated postman tests. Added tests for new APIs --- test/postman/test-collection.json | 1653 ++++++++++++++++++++--------- 1 file changed, 1155 insertions(+), 498 deletions(-) diff --git a/test/postman/test-collection.json b/test/postman/test-collection.json index 540da6709e..8a625c803b 100644 --- a/test/postman/test-collection.json +++ b/test/postman/test-collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "fcde6231-9f2d-49a1-b37c-22d01e39c5fc", + "_postman_id": "2d4266d0-b611-4f9f-9640-5eb29a2fa21b", "name": "Redbox Portal API - With tests", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -9,529 +9,1186 @@ "name": "REST API", "item": [ { - "name": "Create RDMP", - "event": [ + "name": "Record Management", + "item": [ { - "listen": "test", - "script": { - "id": "ac672330-4be2-47af-b448-32aa1e25d1e6", - "exec": [ - "", - "pm.test(\"Status code is 201\", function () {", - " pm.response.to.have.status(201);", - "});", - "", - "pm.test(\"Test oid exists\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData).to.have.property('oid');", - " postman.setEnvironmentVariable(\"dmpOid\", jsonData.oid);", - " var randomNumber = _.random(100);", - " postman.setEnvironmentVariable(\"randomNumber\", randomNumber);", - " postman.setEnvironmentVariable(\"researcherUsername\", \"researcheruser\" + randomNumber);", - " postman.setEnvironmentVariable(\"researcherEmail\", \"researcheruser\" + randomNumber + \"@email.edu.au\");", - "});" + "name": "Create RDMP", + "event": [ + { + "listen": "test", + "script": { + "id": "2b341313-fe5c-4004-8204-1062fd2895d3", + "exec": [ + "", + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + "});", + "", + "pm.test(\"Test oid exists\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('oid');", + " postman.setEnvironmentVariable(\"dmpOid\", jsonData.oid);", + " var randomNumber = _.random(100);", + " postman.setEnvironmentVariable(\"randomNumber\", randomNumber);", + " postman.setEnvironmentVariable(\"researcherUsername\", \"researcheruser\" + randomNumber);", + " postman.setEnvironmentVariable(\"researcherEmail\", \"researcheruser\" + randomNumber + \"@email.edu.au\");", + "});" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "a18caf4f-342c-4a0a-80b6-4035f843a540", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } ], - "type": "text/javascript" - } + "body": { + "mode": "raw", + "raw": "{\n \"title\": \"Andrew's Postman test\",\n \"dc:identifier\": \"http://purl.org/au-research/grants/nhmrc/566728\",\n \"description\": \"Movement of molecules within cells by a process known as membrane transport is critical for normal cell function and also exploited by bacteria to promote infection. The pathway that connects the import pathway to the export pathway is essential for the function of a large number of proteins, however this connecting pathway is poorly characterised. This study will define the machinery of this trafficking pathway, which will provide the ability to modulate biological processes and cytotoxicity.\",\n \"finalKeywords\": [\n \"270199\",\n \"Golgi apparatus\",\n \"endocytosis\",\n \"membrane transport\",\n \"protein trafficking\",\n \"secretion\",\n \"HIV-AIDS\",\n \"bacterial pathogen\",\n \"hormone secretion\",\n \"immune development\",\n \"lysosomal storage disorder\"\n ],\n \"contributor_ci\": {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n },\n \"contributor_data_manager\": {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n },\n \"contributors\": [\n {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n }\n ],\n \"vivo:Dataset_redbox:DataCollectionMethodology\": \"The data collection methodology\",\n \"vivo:Dataset_dc_format\": \"xls\",\n \"vivo:Dataset_dc:location_rdf:PlainLiteral\": \"eResearch Store network drive\",\n \"vivo:Dataset_dc:source_dc:location_rdf:PlainLiteral\": \"shared university network drive (e.g. G, H, etc)\",\n \"vivo:Dataset_dc:extent\": \"100GB - 2TB\",\n \"redbox:retentionPeriod_dc:date\": \"1year\",\n \"dc:rightsHolder_dc:name\": \"myUni\",\n \"dc:accessRights\": \"permission from the data manager\",\n \"authorization\": []\n}" + }, + "url": { + "raw": "{{host}}/default/rdmp/api/records/metadata/rdmp", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "records", + "metadata", + "rdmp" + ] + } + }, + "response": [] }, { - "listen": "prerequest", - "script": { - "id": "cbebf361-9a4a-4890-8534-2221a15261ce", - "exec": [ - "" + "name": "Get RDMP's Metadata", + "event": [ + { + "listen": "test", + "script": { + "id": "f2126c2e-d521-4c1c-832c-a3961084d9e1", + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Title is correct\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.title).to.eql(\"Andrew's Postman test\");", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + }, + { + "key": "", + "value": "", + "type": "text", + "disabled": true + } ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" + "url": { + "raw": "{{host}}/default/rdmp/api/records/metadata/{{dmpOid}}", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "records", + "metadata", + "{{dmpOid}}" + ] + } }, - { - "key": "Authorization", - "value": "Bearer {{token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"title\": \"Andrew's Postman test\",\n \"dc:identifier\": \"http://purl.org/au-research/grants/nhmrc/566728\",\n \"description\": \"Movement of molecules within cells by a process known as membrane transport is critical for normal cell function and also exploited by bacteria to promote infection. The pathway that connects the import pathway to the export pathway is essential for the function of a large number of proteins, however this connecting pathway is poorly characterised. This study will define the machinery of this trafficking pathway, which will provide the ability to modulate biological processes and cytotoxicity.\",\n \"finalKeywords\": [\n \"270199\",\n \"Golgi apparatus\",\n \"endocytosis\",\n \"membrane transport\",\n \"protein trafficking\",\n \"secretion\",\n \"HIV-AIDS\",\n \"bacterial pathogen\",\n \"hormone secretion\",\n \"immune development\",\n \"lysosomal storage disorder\"\n ],\n \"contributor_ci\": {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n },\n \"contributor_data_manager\": {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n },\n \"contributors\": [\n {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n }\n ],\n \"vivo:Dataset_redbox:DataCollectionMethodology\": \"The data collection methodology\",\n \"vivo:Dataset_dc_format\": \"xls\",\n \"vivo:Dataset_dc:location_rdf:PlainLiteral\": \"eResearch Store network drive\",\n \"vivo:Dataset_dc:source_dc:location_rdf:PlainLiteral\": \"shared university network drive (e.g. G, H, etc)\",\n \"vivo:Dataset_dc:extent\": \"100GB - 2TB\",\n \"redbox:retentionPeriod_dc:date\": \"1year\",\n \"dc:rightsHolder_dc:name\": \"myUni\",\n \"dc:accessRights\": \"permission from the data manager\",\n \"authorization\": []\n}" + "response": [] }, - "url": { - "raw": "{{host}}/default/rdmp/api/records/metadata/rdmp", - "host": [ - "{{host}}" - ], - "path": [ - "default", - "rdmp", - "api", - "records", - "metadata", - "rdmp" - ] - } - }, - "response": [] - }, - { - "name": "Get RDMP's Metadata", - "event": [ { - "listen": "test", - "script": { - "id": "1c2bf1b9-f651-4b8d-9bee-22f5534f7d32", - "exec": [ - "pm.test(\"Status code is 200\", function () {", - " pm.response.to.have.status(200);", - "});", - "", - "pm.test(\"Title is correct\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.title).to.eql(\"Andrew's Postman test\");", - "});" + "name": "Get RDMP's ObjectMetadata", + "event": [ + { + "listen": "test", + "script": { + "id": "2df78f4e-d3e7-45ee-a0dc-6d74dc75aafd", + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Type and Form is correct\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.type).to.eql(\"rdmp\");", + " pm.expect(jsonData.form).to.eql(\"default-1.0-draft\");", + " postman.setEnvironmentVariable(\"brandId\", jsonData.brandId);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "", + "type": "text", + "value": "", + "disabled": true + } ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{token}}", - "type": "text" + "url": { + "raw": "{{host}}/default/rdmp/api/records/objectmetadata/{{dmpOid}}", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "records", + "objectmetadata", + "{{dmpOid}}" + ] + } }, - { - "key": "", - "value": "", - "type": "text", - "disabled": true - } - ], - "url": { - "raw": "{{host}}/default/rdmp/api/records/metadata/{{dmpOid}}", - "host": [ - "{{host}}" + "response": [] + }, + { + "name": "Update RDMP Metadata", + "event": [ + { + "listen": "test", + "script": { + "id": "4d35d2c5-e68e-4625-a61e-58a6b7ff682e", + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Test oid is equal to the requested oid value\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('oid');", + " pm.expect(jsonData.oid).to.eql(postman.getEnvironmentVariable(\"dmpOid\")); ", + "});" + ], + "type": "text/javascript" + } + } ], - "path": [ - "default", - "rdmp", - "api", - "records", - "metadata", - "{{dmpOid}}" - ] - } - }, - "response": [] - }, - { - "name": "Get RDMP's ObjectMetadata", - "event": [ + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"title\": \"Andrew's Postman test - Updated\",\n \"dc:identifier\": \"http://purl.org/au-research/grants/nhmrc/566728\",\n \"description\": \"Movement of molecules within cells by a process known as membrane transport is critical for normal cell function and also exploited by bacteria to promote infection. The pathway that connects the import pathway to the export pathway is essential for the function of a large number of proteins, however this connecting pathway is poorly characterised. This study will define the machinery of this trafficking pathway, which will provide the ability to modulate biological processes and cytotoxicity.\",\n \"finalKeywords\": [\n \"270199\",\n \"Golgi apparatus\",\n \"endocytosis\",\n \"membrane transport\",\n \"protein trafficking\",\n \"secretion\",\n \"HIV-AIDS\",\n \"bacterial pathogen\",\n \"hormone secretion\",\n \"immune development\",\n \"lysosomal storage disorder\"\n ],\n \"contributor_ci\": {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n },\n \"contributor_data_manager\": {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n },\n \"contributors\": [\n {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n }\n ],\n \"vivo:Dataset_redbox:DataCollectionMethodology\": \"The data collection methodology\",\n \"vivo:Dataset_dc_format\": \"xls\",\n \"vivo:Dataset_dc:location_rdf:PlainLiteral\": \"eResearch Store network drive\",\n \"vivo:Dataset_dc:source_dc:location_rdf:PlainLiteral\": \"shared university network drive (e.g. G, H, etc)\",\n \"vivo:Dataset_dc:extent\": \"100GB - 2TB\",\n \"redbox:retentionPeriod_dc:date\": \"1year\",\n \"dc:rightsHolder_dc:name\": \"myUni\",\n \"dc:accessRights\": \"permission from the data manager\",\n \"authorization\": []\n}" + }, + "url": { + "raw": "{{host}}/default/rdmp/api/records/metadata/{{dmpOid}}", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "records", + "metadata", + "{{dmpOid}}" + ] + }, + "description": "http://localhost:1500/default/rdmp/api/records/rdmp/create" + }, + "response": [] + }, { - "listen": "test", - "script": { - "id": "295f9f17-cb01-4993-9283-1ac9683d292a", - "exec": [ - "pm.test(\"Status code is 200\", function () {", - " pm.response.to.have.status(200);", - "});", - "", - "pm.test(\"Type and Form is correct\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.type).to.eql(\"rdmp\");", - " pm.expect(jsonData.form).to.eql(\"default-1.0-draft\");", - " postman.setEnvironmentVariable(\"brandId\", jsonData.brandId);", - "});" + "name": "Get RDMP's Metadata - Updated Test", + "event": [ + { + "listen": "test", + "script": { + "id": "73561a47-8aa2-48ca-9e43-f159436832d3", + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Title is correct\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.title).to.eql(\"Andrew's Postman test - Updated\");", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "", + "type": "text", + "value": "", + "disabled": true + } ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token}}" + "url": { + "raw": "{{host}}/default/rdmp/api/records/metadata/{{dmpOid}}", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "records", + "metadata", + "{{dmpOid}}" + ] + } }, - { - "key": "", - "type": "text", - "value": "", - "disabled": true - } - ], - "url": { - "raw": "{{host}}/default/rdmp/api/records/objectmetadata/{{dmpOid}}", - "host": [ - "{{host}}" + "response": [] + }, + { + "name": "Give user edit permissions to RDMP", + "event": [ + { + "listen": "test", + "script": { + "id": "bc8f9850-8003-4315-9da6-058c29b3cc4b", + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Edit permissions applied\", function () {", + " var jsonData = pm.response.json();", + " //TODO: This is because of the save trigger", + " pm.expect(jsonData.edit).to.eql([]);", + "});" + ], + "type": "text/javascript" + } + } ], - "path": [ - "default", - "rdmp", - "api", - "records", - "objectmetadata", - "{{dmpOid}}" - ] + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"users\": [\"user\"]\n}" + }, + "url": { + "raw": "{{host}}/default/rdmp/api/records/permissions/edit/{{dmpOid}}", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "records", + "permissions", + "edit", + "{{dmpOid}}" + ] + }, + "description": "http://localhost:1500/default/rdmp/api/records/rdmp/create" + }, + "response": [] } - }, - "response": [] + ], + "protocolProfileBehavior": {}, + "_postman_isSubFolder": true }, { - "name": "Update RDMP Metadata", - "event": [ + "name": "User Management", + "item": [ { - "listen": "test", - "script": { - "id": "65a4b89d-1f76-4abb-afd7-a67e17d56088", - "exec": [ - "pm.test(\"Status code is 200\", function () {", - " pm.response.to.have.status(200);", - "});", - "", - "pm.test(\"Test oid is equal to the requested oid value\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData).to.have.property('oid');", - " pm.expect(jsonData.oid).to.eql(postman.getEnvironmentVariable(\"dmpOid\")); ", - "});" + "name": "List Users", + "event": [ + { + "listen": "test", + "script": { + "id": "f8d39001-8f2e-446f-b0fa-8c7d911e7998", + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Number of users returned in results array equals numFound or 10\", function () {", + " var jsonData = pm.response.json();", + " ", + " var numFound = jsonData.summary.numFound;", + " ", + " if(numFound < 10) {", + " pm.expect(jsonData.records.length).to.eql(numFound);", + " } else {", + " pm.expect(jsonData.records.length).to.eql(10);", + " }", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{token}}" + } ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" + "url": { + "raw": "{{host}}/default/rdmp/api/users", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "users" + ] + }, + "description": "http://localhost:1500/default/rdmp/api/records/rdmp/create" }, - { - "key": "Authorization", - "value": "Bearer {{token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"title\": \"Andrew's Postman test - Updated\",\n \"dc:identifier\": \"http://purl.org/au-research/grants/nhmrc/566728\",\n \"description\": \"Movement of molecules within cells by a process known as membrane transport is critical for normal cell function and also exploited by bacteria to promote infection. The pathway that connects the import pathway to the export pathway is essential for the function of a large number of proteins, however this connecting pathway is poorly characterised. This study will define the machinery of this trafficking pathway, which will provide the ability to modulate biological processes and cytotoxicity.\",\n \"finalKeywords\": [\n \"270199\",\n \"Golgi apparatus\",\n \"endocytosis\",\n \"membrane transport\",\n \"protein trafficking\",\n \"secretion\",\n \"HIV-AIDS\",\n \"bacterial pathogen\",\n \"hormone secretion\",\n \"immune development\",\n \"lysosomal storage disorder\"\n ],\n \"contributor_ci\": {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n },\n \"contributor_data_manager\": {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n },\n \"contributors\": [\n {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n }\n ],\n \"vivo:Dataset_redbox:DataCollectionMethodology\": \"The data collection methodology\",\n \"vivo:Dataset_dc_format\": \"xls\",\n \"vivo:Dataset_dc:location_rdf:PlainLiteral\": \"eResearch Store network drive\",\n \"vivo:Dataset_dc:source_dc:location_rdf:PlainLiteral\": \"shared university network drive (e.g. G, H, etc)\",\n \"vivo:Dataset_dc:extent\": \"100GB - 2TB\",\n \"redbox:retentionPeriod_dc:date\": \"1year\",\n \"dc:rightsHolder_dc:name\": \"myUni\",\n \"dc:accessRights\": \"permission from the data manager\",\n \"authorization\": []\n}" + "response": [] }, - "url": { - "raw": "{{host}}/default/rdmp/api/records/metadata/{{dmpOid}}", - "host": [ - "{{host}}" + { + "name": "Get Admin User Details", + "event": [ + { + "listen": "test", + "script": { + "id": "064e68b0-2944-40ac-8e01-e7846a15cb9f", + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Username is admin\", function () {", + " var jsonData = pm.response.json();", + " ", + " var username = jsonData.username;", + " ", + " pm.expect(username).to.eql('admin');", + " ", + " ", + "});", + "", + "pm.test(\"email address is admin@redboxresearchdata.com.au\", function () {", + " var jsonData = pm.response.json();", + " ", + " var email = jsonData.email;", + " ", + " pm.expect(email).to.eql('admin@redboxresearchdata.com.au');", + " ", + " ", + "});" + ], + "type": "text/javascript" + } + } ], - "path": [ - "default", - "rdmp", - "api", - "records", - "metadata", - "{{dmpOid}}" - ] + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{host}}/default/rdmp/api/users/get?searchBy=email&query=admin@redboxresearchdata.com.au", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "users", + "get" + ], + "query": [ + { + "key": "searchBy", + "value": "email" + }, + { + "key": "query", + "value": "admin@redboxresearchdata.com.au" + } + ] + }, + "description": "http://localhost:1500/default/rdmp/api/records/rdmp/create" + }, + "response": [] }, - "description": "http://localhost:1500/default/rdmp/api/records/rdmp/create" - }, - "response": [] - }, - { - "name": "Get RDMP's Metadata - Updated Test", - "event": [ { - "listen": "test", - "script": { - "id": "fa82de23-39a6-41c1-a109-450031b51068", - "exec": [ - "pm.test(\"Status code is 200\", function () {", - " pm.response.to.have.status(200);", - "});", - "", - "pm.test(\"Title is correct\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.title).to.eql(\"Andrew's Postman test - Updated\");", - "});" + "name": "Attempt to retrieve user that does not exist", + "event": [ + { + "listen": "test", + "script": { + "id": "3df270ed-86c9-4560-86dc-40116f77579d", + "exec": [ + "pm.test(\"Status code is 404\", function () {", + " pm.response.to.have.status(404);", + "});", + "", + "pm.test(\"Error message is correct\", function () {", + " var jsonData = pm.response.json();", + " ", + " var message = jsonData.message;", + " ", + " pm.expect(message).to.eql('No user found with given criteria');", + " ", + " ", + "});", + "", + "pm.test(\"Details message is correct\", function () {", + " var jsonData = pm.response.json();", + " ", + " var details = jsonData.details;", + " ", + " pm.expect(details).to.eql('Searchby: email and Query: fake@redboxresearchdata.com.au');", + " ", + " ", + "});", + "", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{token}}" + } ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token}}" + "url": { + "raw": "{{host}}/default/rdmp/api/users/get?searchBy=email&query=fake@redboxresearchdata.com.au", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "users", + "get" + ], + "query": [ + { + "key": "searchBy", + "value": "email" + }, + { + "key": "query", + "value": "fake@redboxresearchdata.com.au" + } + ] + }, + "description": "http://localhost:1500/default/rdmp/api/records/rdmp/create" }, - { - "key": "", - "type": "text", - "value": "", - "disabled": true - } - ], - "url": { - "raw": "{{host}}/default/rdmp/api/records/metadata/{{dmpOid}}", - "host": [ - "{{host}}" + "response": [] + }, + { + "name": "Create API Researcher User", + "event": [ + { + "listen": "test", + "script": { + "id": "b2fdd6ba-f6ad-4477-b5bb-28f087fbf42e", + "exec": [ + "", + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + "});", + "", + "pm.test(\"Test response\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('id');", + " pm.expect(jsonData).to.have.property('username');", + " pm.expect(jsonData).to.have.property('name');", + " pm.expect(jsonData).to.have.property('email');", + " pm.expect(jsonData).to.have.property('type');", + " pm.expect(jsonData).to.have.property('lastLogin');", + " postman.setEnvironmentVariable(\"apiUserId\", jsonData.id);", + "});" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "fe67cb7b-f708-4006-8430-cf0c47e4f380", + "exec": [ + "" + ], + "type": "text/javascript" + } + } ], - "path": [ - "default", - "rdmp", - "api", - "records", - "metadata", - "{{dmpOid}}" - ] - } - }, - "response": [] - }, - { - "name": "Give user edit permissions to RDMP", - "event": [ + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + }, + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"apiresearcher34\",\n \"name\": \"researcher created via API\",\n \"email\": \"apiresearcher34@redboxresearchdata.com.au\",\n \"password\": \"a12345672A!\",\n \"roles\": [\"Admin\",\"Researcher\",\"Librarian\"]\n}" + }, + "url": { + "raw": "{{host}}/default/rdmp/api/users", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "users" + ] + } + }, + "response": [] + }, { - "listen": "test", - "script": { - "id": "8999d515-15c2-4890-921a-cc8a858d2805", - "exec": [ - "pm.test(\"Status code is 200\", function () {", - " pm.response.to.have.status(200);", - "});", - "", - "pm.test(\"Edit permissions applied\", function () {", - " var jsonData = pm.response.json();", - " //TODO: This is because of the save trigger", - " pm.expect(jsonData.edit).to.eql([]);", - "});" + "name": "Update API Researcher User", + "event": [ + { + "listen": "test", + "script": { + "id": "363f7eaa-de70-427a-b876-1a5b768e1e60", + "exec": [ + "", + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + "});", + "", + "pm.test(\"Test response\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('id');", + " pm.expect(jsonData).to.have.property('username');", + " pm.expect(jsonData).to.have.property('name');", + " pm.expect(jsonData).to.have.property('email');", + " pm.expect(jsonData).to.have.property('type');", + " pm.expect(jsonData).to.have.property('lastLogin');", + "});" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "bce6c018-de0f-448b-861e-1663ba00aa33", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + }, + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" + "body": { + "mode": "raw", + "raw": "{\n \"id\": \"{{apiUserId}}\",\n \"name\": \"researcher created via API - modified\",\n \"email\": \"apiresearcher@redboxresearchdata.com.au\",\n \"password\": \"a12345672A!\"\n}" + }, + "url": { + "raw": "{{host}}/default/rdmp/api/users", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "users" + ] + } }, - { - "key": "Authorization", - "value": "Bearer {{token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"users\": [\"user\"]\n}" + "response": [] }, - "url": { - "raw": "{{host}}/default/rdmp/api/records/permissions/edit/{{dmpOid}}", - "host": [ - "{{host}}" + { + "name": "Generate API Researcher User API Token", + "event": [ + { + "listen": "test", + "script": { + "id": "a9b9716a-e66c-4a4e-ba42-562d948cee13", + "exec": [ + "", + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Test response\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('token');", + "});" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "bbde1fca-0911-4ed6-9e52-403e7f3a4da6", + "exec": [ + "" + ], + "type": "text/javascript" + } + } ], - "path": [ - "default", - "rdmp", - "api", - "records", - "permissions", - "edit", - "{{dmpOid}}" - ] + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + }, + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{host}}/default/rdmp/api/users/token/generate?id={{apiUserId}}", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "users", + "token", + "generate" + ], + "query": [ + { + "key": "id", + "value": "{{apiUserId}}" + } + ] + } + }, + "response": [] }, - "description": "http://localhost:1500/default/rdmp/api/records/rdmp/create" - }, - "response": [] - }, - { - "name": "List Users", - "event": [ { - "listen": "test", - "script": { - "id": "8edad722-ba61-4263-a95e-bb0deb5ff72f", - "exec": [ - "pm.test(\"Status code is 200\", function () {", - " pm.response.to.have.status(200);", - "});", - "", - "pm.test(\"Number of users returned in results array equals numFound or 10\", function () {", - " var jsonData = pm.response.json();", - " ", - " var numFound = jsonData.summary.numFound;", - " ", - " if(numFound < 10) {", - " pm.expect(jsonData.records.length).to.eql(numFound);", - " } else {", - " pm.expect(jsonData.records.length).to.eql(10);", - " }", - "});" + "name": "Revoke API Researcher User API Token", + "event": [ + { + "listen": "test", + "script": { + "id": "0ebff1d3-0b1a-45f6-8828-9436759e5cec", + "exec": [ + "", + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Test response\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('id');", + "});" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "743cd4e6-65c6-4169-ba36-ed4bb7dfea63", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + }, + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } ], - "type": "text/javascript" - } + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{host}}/default/rdmp/api/users/token/revoke?id={{apiUserId}}", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "users", + "token", + "revoke" + ], + "query": [ + { + "key": "id", + "value": "{{apiUserId}}" + } + ] + } + }, + "response": [] } ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{token}}" - } - ], - "url": { - "raw": "{{host}}/default/rdmp/api/users", - "host": [ - "{{host}}" - ], - "path": [ - "default", - "rdmp", - "api", - "users" - ] - }, - "description": "http://localhost:1500/default/rdmp/api/records/rdmp/create" - }, - "response": [] + "protocolProfileBehavior": {}, + "_postman_isSubFolder": true }, { - "name": "Create RDMP For Deletion", - "event": [ + "name": "Search", + "item": [ { - "listen": "test", - "script": { - "id": "ba9b6209-95a7-4e50-9663-1886463670ad", - "exec": [ - "", - "pm.test(\"Status code is 201\", function () {", - " pm.response.to.have.status(201);", - "});", - "", - "pm.test(\"Test oid exists\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData).to.have.property('oid');", - " postman.setEnvironmentVariable(\"dmpOidToDelete\", jsonData.oid);", - "});" + "name": "Search Index", + "event": [ + { + "listen": "test", + "script": { + "id": "e7461598-d637-4a8f-9d50-68170d1d43a8", + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Has record array\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property(\"records\");", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } ], - "type": "text/javascript" - } + "url": { + "raw": "{{host}}/default/rdmp/api/search/?searchStr=Andrew", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "search", + "" + ], + "query": [ + { + "key": "searchStr", + "value": "Andrew" + } + ] + } + }, + "response": [] }, { - "listen": "prerequest", - "script": { - "id": "b5071961-5b8f-4167-9282-b82e3dd4f434", - "exec": [ - "" + "name": "Index Record", + "event": [ + { + "listen": "test", + "script": { + "id": "fe1b7d3b-e881-40d5-a951-686fa6cd9f2c", + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } ], - "type": "text/javascript" - } + "url": { + "raw": "{{host}}/default/rdmp/api/search/index?oid={{dmpOid}}", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "search", + "index" + ], + "query": [ + { + "key": "oid", + "value": "{{dmpOid}}" + } + ] + } + }, + "response": [] } ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" + "protocolProfileBehavior": {}, + "_postman_isSubFolder": true + }, + { + "name": "Form Management", + "item": [ + { + "name": "List Forms", + "event": [ + { + "listen": "test", + "script": { + "id": "4f87404f-2410-4feb-a907-be31d30b49f7", + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Has forms in response\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property(\"records\");", + " pm.expect(jsonData.records.length).to.be.greaterThan(0)", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{host}}/default/rdmp/api/forms", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "forms" + ] + } }, - { - "key": "Authorization", - "value": "Bearer {{token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"title\": \"Andrew's Postman test\",\n \"dc:identifier\": \"http://purl.org/au-research/grants/nhmrc/566728\",\n \"description\": \"Movement of molecules within cells by a process known as membrane transport is critical for normal cell function and also exploited by bacteria to promote infection. The pathway that connects the import pathway to the export pathway is essential for the function of a large number of proteins, however this connecting pathway is poorly characterised. This study will define the machinery of this trafficking pathway, which will provide the ability to modulate biological processes and cytotoxicity.\",\n \"finalKeywords\": [\n \"270199\",\n \"Golgi apparatus\",\n \"endocytosis\",\n \"membrane transport\",\n \"protein trafficking\",\n \"secretion\",\n \"HIV-AIDS\",\n \"bacterial pathogen\",\n \"hormone secretion\",\n \"immune development\",\n \"lysosomal storage disorder\"\n ],\n \"contributor_ci\": {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n },\n \"contributor_data_manager\": {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n },\n \"contributors\": [\n {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n }\n ],\n \"vivo:Dataset_redbox:DataCollectionMethodology\": \"The data collection methodology\",\n \"vivo:Dataset_dc_format\": \"xls\",\n \"vivo:Dataset_dc:location_rdf:PlainLiteral\": \"eResearch Store network drive\",\n \"vivo:Dataset_dc:source_dc:location_rdf:PlainLiteral\": \"shared university network drive (e.g. G, H, etc)\",\n \"vivo:Dataset_dc:extent\": \"100GB - 2TB\",\n \"redbox:retentionPeriod_dc:date\": \"1year\",\n \"dc:rightsHolder_dc:name\": \"myUni\",\n \"dc:accessRights\": \"permission from the data manager\",\n \"authorization\": []\n}" + "response": [] }, - "url": { - "raw": "{{host}}/default/rdmp/api/records/metadata/rdmp", - "host": [ - "{{host}}" + { + "name": "Get Form", + "event": [ + { + "listen": "test", + "script": { + "id": "3bd6043c-be66-4008-b182-0e44f5d1e1de", + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Has name property\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property(\"name\");", + "});" + ], + "type": "text/javascript" + } + } ], - "path": [ - "default", - "rdmp", - "api", - "records", - "metadata", - "rdmp" - ] + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{host}}/default/rdmp/api/forms/get?name=default-1.0-draft", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "forms", + "get" + ], + "query": [ + { + "key": "name", + "value": "default-1.0-draft" + } + ] + } + }, + "response": [] } - }, - "response": [] + ], + "protocolProfileBehavior": {}, + "_postman_isSubFolder": true }, { - "name": "Delete Record", - "event": [ + "name": "Record Type Management", + "item": [ { - "listen": "test", - "script": { - "id": "7e537e3c-0931-475f-b4e9-a7c9c4baa605", - "exec": [ - "pm.test(\"Status code is 200\", function () {", - " pm.response.to.have.status(200);", - "});", - "", - "pm.test(\"Success is set and is true\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData).to.have.property('success');", - " pm.expect(jsonData.success).to.eql(true);", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "bearer", - "bearer": [ + "name": "List Record Types", + "event": [ { - "key": "token", - "value": "{{token}}", - "type": "string" + "listen": "test", + "script": { + "id": "dd49e6fe-5bcd-4146-ae1e-26a809b2b732", + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Has forms in response\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property(\"records\");", + " pm.expect(jsonData.records.length).to.be.greaterThan(0)", + "});" + ], + "type": "text/javascript" + } } - ] - }, - "method": "DELETE", - "header": [], - "url": { - "raw": "{{host}}/default/rdmp/api/records/metadata/{{dmpOidToDelete}}", - "host": [ - "{{host}}" ], - "path": [ - "default", - "rdmp", - "api", - "records", - "metadata", - "{{dmpOidToDelete}}" - ] + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{host}}/default/rdmp/api/forms", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "forms" + ] + } + }, + "response": [] }, - "description": "Delete Record" - }, - "response": [] + { + "name": "Get Record Type", + "event": [ + { + "listen": "test", + "script": { + "id": "3f2abea2-8c33-4288-ab8b-eb4ecd414427", + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Has name property\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property(\"name\");", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{host}}/default/rdmp/api/forms/get?name=default-1.0-draft", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "forms", + "get" + ], + "query": [ + { + "key": "name", + "value": "default-1.0-draft" + } + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {}, + "_postman_isSubFolder": true } ], "protocolProfileBehavior": {} @@ -545,7 +1202,7 @@ { "listen": "test", "script": { - "id": "cf13ac5d-55f0-4707-8492-c215905cf1bc", + "id": "0c3ae152-ebc0-4574-a02f-67d778b6df82", "exec": [ "postman.setGlobalVariable(\"cookie\",postman.getResponseHeader(\"set-cookie\") );", "", @@ -640,7 +1297,7 @@ { "listen": "test", "script": { - "id": "4cdfb919-745f-47ef-9a63-5399feb6f6f7", + "id": "b2483e8b-42bb-4540-b619-95c967dd4136", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -728,7 +1385,7 @@ { "listen": "test", "script": { - "id": "32759972-ad15-4610-954e-c3e1ad84929c", + "id": "ec7df232-1dae-4296-8787-68abe3997213", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -805,7 +1462,7 @@ { "listen": "test", "script": { - "id": "a122338e-1a80-4c4b-b27d-aeb17b3bdd59", + "id": "f415f694-a389-474f-81ad-4da82fa2ba04", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -882,7 +1539,7 @@ { "listen": "test", "script": { - "id": "64803a6f-6fe1-411c-8d62-950981f6d30f", + "id": "7df62628-3d08-4627-99bc-414a947e5c58", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -971,7 +1628,7 @@ { "listen": "test", "script": { - "id": "995625c5-8a15-4047-889a-d454040d6ca3", + "id": "96488c21-12a0-48c6-84fd-6a2c936ec10a", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1061,7 +1718,7 @@ { "listen": "test", "script": { - "id": "558847be-48f1-4bf1-a442-2aa7e8cc348e", + "id": "c764f8c3-12c8-4368-8042-bb9e6b40d3a2", "exec": [ "// \"contributor_ci\": {", " // \"text_full_name\": \"Alberto Zweinstein\",", @@ -1176,7 +1833,7 @@ { "listen": "test", "script": { - "id": "7f31fea7-9ccf-425b-a719-705e10faa257", + "id": "f8dc7448-8370-4de3-9159-85aa4565a865", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1274,7 +1931,7 @@ { "listen": "test", "script": { - "id": "f5f27232-892c-47a9-9901-43c7a67d561b", + "id": "b40631e8-f48a-4a28-b587-4198e1d62629", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1378,7 +2035,7 @@ { "listen": "test", "script": { - "id": "12339795-d347-4bc1-855c-89dbdae3397c", + "id": "c7743152-17fb-4f77-bc43-7a5f0558d3a1", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1484,7 +2141,7 @@ { "listen": "test", "script": { - "id": "2624171c-4f3a-4603-b796-dd6b2e621e7d", + "id": "bf45f6da-0676-456a-a369-a634c3b1709c", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1587,7 +2244,7 @@ { "listen": "test", "script": { - "id": "fb5606f4-c7cb-485c-8ddc-6ee4eb6f9472", + "id": "01e5495e-ddfa-4e01-affc-54d1e8b0668b", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -1692,7 +2349,7 @@ { "listen": "test", "script": { - "id": "a68a36d9-e62e-4cca-bb1d-a161764774dd", + "id": "390ae50d-f902-4316-ba98-485753524f06", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1796,7 +2453,7 @@ { "listen": "test", "script": { - "id": "2f86b313-3aa1-4cfc-a21f-5287d09586ec", + "id": "647b2163-6716-4c11-b53c-399aaa395e00", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1977,7 +2634,7 @@ { "listen": "test", "script": { - "id": "9c1ff727-f22d-4bac-941e-3e01825299c3", + "id": "7de4bbc4-953b-4a68-a49d-025c7aa84f0f", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2080,7 +2737,7 @@ { "listen": "test", "script": { - "id": "624946d5-a920-47ca-80ab-69d6c8b1669f", + "id": "6e379fe7-087e-45af-979f-8fec13912df2", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2202,7 +2859,7 @@ { "listen": "test", "script": { - "id": "9a615f50-f60c-4377-b9d5-d500715af11e", + "id": "cf33c155-14df-41fb-9cca-58b27a8ff04c", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -2296,7 +2953,7 @@ { "listen": "test", "script": { - "id": "a5384016-54f0-4056-b865-608fd16d34e3", + "id": "84100fd8-6c8f-4f30-87b0-cc914f311ce2", "exec": [ "", "pm.test(\"Status code is 201\", function () {", @@ -2398,7 +3055,7 @@ { "listen": "test", "script": { - "id": "e67d2394-cdc6-4ba3-af37-518d0e9d2698", + "id": "761f85ee-c1cf-471f-a214-daab1c0f7820", "exec": [ "pm.test(\"Status code is 204\", function () {", " pm.response.to.have.status(204);", @@ -2492,7 +3149,7 @@ { "listen": "test", "script": { - "id": "4c4a7b35-90ca-4794-926d-8f70cb7f0046", + "id": "c042be6a-b7de-46f4-8894-97994fddd527", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2584,7 +3241,7 @@ { "listen": "test", "script": { - "id": "6184ab65-0c46-4a46-a625-d48da4d92dc8", + "id": "452aa8ad-d91d-4573-89a3-d95858407ffb", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2620,7 +3277,7 @@ { "listen": "test", "script": { - "id": "088e858b-7cc6-4955-af5e-812044c9ac09", + "id": "830b1c97-77bd-44d5-872f-b18598556b18", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2711,7 +3368,7 @@ { "listen": "test", "script": { - "id": "d9e544a3-6e47-4ee9-bc11-2aac61fb52f0", + "id": "6f10ad4e-6875-4b7d-9c2c-d75782515784", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2803,7 +3460,7 @@ { "listen": "test", "script": { - "id": "c3137c83-7b3e-437a-8dbc-2e592e863cf3", + "id": "e70782a4-70e5-4438-ac41-d18b305b65e0", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2895,7 +3552,7 @@ { "listen": "test", "script": { - "id": "0b742f23-a6a2-43a9-b583-6816b2b57c5d", + "id": "b934a0ec-b1af-40ed-841c-fd5464983f32", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -2993,7 +3650,7 @@ { "listen": "test", "script": { - "id": "9f4ff55b-bc2d-4744-96aa-240fb2660312", + "id": "4cd7af67-2980-4cbc-b7f9-e7e3109faf72", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3095,7 +3752,7 @@ { "listen": "test", "script": { - "id": "3c21a109-e59c-455b-afcf-5548829a67e7", + "id": "bf40e856-df8b-4ed0-87b5-26c01d5c9b95", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3265,7 +3922,7 @@ { "listen": "test", "script": { - "id": "ad20711b-8b79-4f38-89c4-b21ff770ecf4", + "id": "26ae7498-1797-4ef4-845a-373680eb44ed", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3341,7 +3998,7 @@ { "listen": "test", "script": { - "id": "e86db382-a68a-4f72-a631-af8e3033ac07", + "id": "fe69d764-1fc9-4e63-9b07-7ddf9cdf66e1", "exec": [ "pm.test(\"Status code is 403\", function () {", " pm.response.to.have.status(403);", @@ -3410,7 +4067,7 @@ { "listen": "test", "script": { - "id": "5283bb00-f364-4cf6-8bda-9dd171d2b6aa", + "id": "57034378-5e28-46a3-991f-5e8932835d0d", "exec": [ "postman.setGlobalVariable(\"cookie\",postman.getResponseHeader(\"set-cookie\") );", "", @@ -3509,7 +4166,7 @@ { "listen": "test", "script": { - "id": "8187b0cd-55f6-4aeb-b1b2-d36d7aebf931", + "id": "280d2c8b-e4bd-4a4d-9fa7-0c020000132b", "exec": [ "postman.setGlobalVariable(\"cookie\",postman.getResponseHeader(\"set-cookie\") );", "", @@ -3605,7 +4262,7 @@ { "listen": "test", "script": { - "id": "d3f2f8bb-5688-45e4-876d-8585d32195b1", + "id": "837749d8-2b26-4652-a481-9699c3577608", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", From 8c8718039a3edc021e7f51c6f405eff371b39a1c Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Thu, 17 Dec 2020 09:14:41 +1030 Subject: [PATCH 40/48] Removed Nginx from dev docker-compose.yml as its not required. Referenced correct redbox-portal tag --- docker-compose.yml | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 80ae6fbb23..0342409775 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ networks: services: redboxportal: build: . - image: qcifengineering/redbox-portal:latest + image: qcifengineering/redbox-portal:backend-refactor ports: - "1500:1500" user: node @@ -29,18 +29,6 @@ services: aliases: - rbportal entrypoint: /bin/bash -c "cd /opt/redbox-portal && node app.js" - - nginx: - image: nginx - ports: - - "8080:8080" - restart: always - volumes: - - "./dev/publication:/usr/share/nginx/html:delegated" - - "./dev/nginx.conf:/etc/nginx/conf.d:delegated" - expose: - - "8080" - mongodb: image: mvertes/alpine-mongo:latest volumes: @@ -53,7 +41,6 @@ services: - mongodb ports: - "27017:27017" - solr: image: solr:8.6.3 expose: From 43816938130f611bbc2df9c21a49dcca1eaef25c Mon Sep 17 00:00:00 2001 From: Shilo Banihit Date: Thu, 17 Dec 2020 09:31:37 +1000 Subject: [PATCH 41/48] Added list datastreams --- .../webservice/RecordController.ts | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/typescript/api/controllers/webservice/RecordController.ts b/typescript/api/controllers/webservice/RecordController.ts index ff62a267f1..59e471921a 100644 --- a/typescript/api/controllers/webservice/RecordController.ts +++ b/typescript/api/controllers/webservice/RecordController.ts @@ -73,7 +73,8 @@ export module Controllers { 'addDataStreams', 'listRecords', 'deleteRecord', - 'transitionWorkflow' + 'transitionWorkflow', + 'listDatastreams' ]; constructor() { @@ -674,7 +675,24 @@ export module Controllers { } } - + public async listDatastreams(req, res) { + const oid = req.param('oid'); + if (_.isEmpty(oid)) { + return this.apiFail(req, res, 400, new APIErrorResponse("Missing ID of record.")); + } + try { + const attachments = await this.RecordsService.getAttachments(oid); + let response: ListAPIResponse < any > = new ListAPIResponse < any > (); + response.summary.numFound = _.size(attachments); + response.summary.page = 1; + response.records = attachments; + this.apiRespond(req, res, response); + } catch (err) { + sails.log.error(`Failed to list attachments: ${oid}`); + sails.log.error(JSON.stringify(err)); + this.apiFail(req, res, 500, new APIErrorResponse(`Failed to list attachments, please check server logs.`)); + } + } } } From 97ad4359d2cda1d4ad801566f7acf26445a3c6e0 Mon Sep 17 00:00:00 2001 From: Shilo Banihit Date: Thu, 17 Dec 2020 15:27:55 +1000 Subject: [PATCH 42/48] Added remaining API endpoints, added more tests. Removed file-based storage service. --- config/routes.js | 28 +- package.json | 2 +- test/postman/test-collection.json | 585 +++++++++++++++-- .../webservice/RecordController.ts | 120 +++- typescript/api/core/Datastream.ts | 35 + typescript/api/core/DatastreamService.ts | 9 +- typescript/api/services/RecordsService.ts | 9 +- .../api/services/RedboxJavaStorageService.ts | 614 ------------------ 8 files changed, 716 insertions(+), 686 deletions(-) create mode 100644 typescript/api/core/Datastream.ts delete mode 100644 typescript/api/services/RedboxJavaStorageService.ts diff --git a/config/routes.js b/config/routes.js index a6455ad0c9..1b6714bc3b 100644 --- a/config/routes.js +++ b/config/routes.js @@ -276,19 +276,43 @@ module.exports.routes = { action: 'addDataStreams', csrf: false }, + 'get /:branding/:portal/api/records/datastreams/:oid': 'webservice/RecordController.getDataStream', + 'put /:branding/:portal/api/records/datastreams/:oid': { + controller: 'webservice/RecordController', + action: 'listDatastreams', + csrf: false + }, 'delete /:branding/:portal/api/records/permissions/view/:oid': { controller: 'webservice/RecordController', action: 'removeUserView', csrf: false }, + 'post /:branding/:portal/api/records/permissions/editRole/:oid': { + controller: 'webservice/RecordController', + action: 'addRoleEdit', + csrf: false + }, + 'delete /:branding/:portal/api/records/permissions/editRole/:oid': { + controller: 'webservice/RecordController', + action: 'removeRoleEdit', + csrf: false + }, + 'post /:branding/:portal/api/records/permissions/viewRole/:oid': { + controller: 'webservice/RecordController', + action: 'addRoleView', + csrf: false + }, + 'delete /:branding/:portal/api/records/permissions/viewRole/:oid': { + controller: 'webservice/RecordController', + action: 'removeRoleView', + csrf: false + }, 'get /:branding/:portal/api/records/permissions/:oid': 'webservice/RecordController.getPermissions', - 'get /:branding/:portal/api/records/datastreams/:oid': 'webservice/RecordController.getDataStream', 'post /:branding/:portal/api/records/workflow/step/:targetStep/:oid': { controller: 'webservice/RecordController', action: 'transitionWorkflow', csrf: false }, - 'get /:branding/:portal/api/users': 'webservice/UserManagementController.listUsers', 'get /:branding/:portal/api/users/find': 'webservice/UserManagementController.getUser', 'get /:branding/:portal/api/users/get': 'webservice/UserManagementController.getUser', diff --git a/package.json b/package.json index 4003e9d875..db02cc9738 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "ReDBox 2 Portal", "keywords": [], "dependencies": { - "@researchdatabox/sails-hook-redbox-storage-mongo": "0.0.4-alpha", + "@researchdatabox/sails-hook-redbox-storage-mongo": "0.0.6-alpha", "@sailshq/upgrade": "^1.0.9", "@uppy/core": "^1.13.1", "@uppy/dashboard": "^1.12.5", diff --git a/test/postman/test-collection.json b/test/postman/test-collection.json index 8a625c803b..b5ef7509ed 100644 --- a/test/postman/test-collection.json +++ b/test/postman/test-collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "2d4266d0-b611-4f9f-9640-5eb29a2fa21b", + "_postman_id": "c3caf4c0-dee2-4fbf-b517-51b44c5a7a61", "name": "Redbox Portal API - With tests", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -17,7 +17,7 @@ { "listen": "test", "script": { - "id": "2b341313-fe5c-4004-8204-1062fd2895d3", + "id": "dbf0286f-202c-4784-a4df-4a143932638e", "exec": [ "", "pm.test(\"Status code is 201\", function () {", @@ -40,7 +40,7 @@ { "listen": "prerequest", "script": { - "id": "a18caf4f-342c-4a0a-80b6-4035f843a540", + "id": "572eb15b-beb6-4c3e-a70f-39edc51b5c64", "exec": [ "" ], @@ -90,7 +90,7 @@ { "listen": "test", "script": { - "id": "f2126c2e-d521-4c1c-832c-a3961084d9e1", + "id": "688f7a96-395a-4fbf-9e66-7f4741b1e17f", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -143,7 +143,7 @@ { "listen": "test", "script": { - "id": "2df78f4e-d3e7-45ee-a0dc-6d74dc75aafd", + "id": "ca6182fe-b839-4ef7-8554-bd9449898031", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -198,7 +198,7 @@ { "listen": "test", "script": { - "id": "4d35d2c5-e68e-4625-a61e-58a6b7ff682e", + "id": "d0ddcf35-406a-42c4-897e-588412fca4e2", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -254,7 +254,7 @@ { "listen": "test", "script": { - "id": "73561a47-8aa2-48ca-9e43-f159436832d3", + "id": "03d3ed88-fdac-41f9-bc50-c184e28db2b8", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -307,7 +307,7 @@ { "listen": "test", "script": { - "id": "bc8f9850-8003-4315-9da6-058c29b3cc4b", + "id": "263d4151-78ea-445c-a3b2-a1ad93cd19b3", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -357,6 +357,475 @@ "description": "http://localhost:1500/default/rdmp/api/records/rdmp/create" }, "response": [] + }, + { + "name": "Create RDMP For Deletion", + "event": [ + { + "listen": "test", + "script": { + "id": "7814b400-6de9-4d5d-862d-159a25a41cb8", + "exec": [ + "", + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + "});", + "", + "pm.test(\"Test oid exists\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('oid');", + " postman.setEnvironmentVariable(\"dmpOidToDelete\", jsonData.oid);", + "});" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "c63e7d7e-7a6a-4874-ba2d-2753fd9f0009", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"title\": \"Andrew's Postman test\",\n \"dc:identifier\": \"http://purl.org/au-research/grants/nhmrc/566728\",\n \"description\": \"Movement of molecules within cells by a process known as membrane transport is critical for normal cell function and also exploited by bacteria to promote infection. The pathway that connects the import pathway to the export pathway is essential for the function of a large number of proteins, however this connecting pathway is poorly characterised. This study will define the machinery of this trafficking pathway, which will provide the ability to modulate biological processes and cytotoxicity.\",\n \"finalKeywords\": [\n \"270199\",\n \"Golgi apparatus\",\n \"endocytosis\",\n \"membrane transport\",\n \"protein trafficking\",\n \"secretion\",\n \"HIV-AIDS\",\n \"bacterial pathogen\",\n \"hormone secretion\",\n \"immune development\",\n \"lysosomal storage disorder\"\n ],\n \"contributor_ci\": {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n },\n \"contributor_data_manager\": {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n },\n \"contributors\": [\n {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n }\n ],\n \"vivo:Dataset_redbox:DataCollectionMethodology\": \"The data collection methodology\",\n \"vivo:Dataset_dc_format\": \"xls\",\n \"vivo:Dataset_dc:location_rdf:PlainLiteral\": \"eResearch Store network drive\",\n \"vivo:Dataset_dc:source_dc:location_rdf:PlainLiteral\": \"shared university network drive (e.g. G, H, etc)\",\n \"vivo:Dataset_dc:extent\": \"100GB - 2TB\",\n \"redbox:retentionPeriod_dc:date\": \"1year\",\n \"dc:rightsHolder_dc:name\": \"myUni\",\n \"dc:accessRights\": \"permission from the data manager\",\n \"authorization\": []\n}" + }, + "url": { + "raw": "{{host}}/default/rdmp/api/records/metadata/rdmp", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "records", + "metadata", + "rdmp" + ] + } + }, + "response": [] + }, + { + "name": "Delete Record", + "event": [ + { + "listen": "test", + "script": { + "id": "3ea6e9d9-0c89-4c8f-90e7-45732214b93c", + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Success is set and is true\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('success');", + " pm.expect(jsonData.success).to.eql(true);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [], + "url": { + "raw": "{{host}}/default/rdmp/api/records/metadata/{{dmpOidToDelete}}", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "records", + "metadata", + "{{dmpOidToDelete}}" + ] + }, + "description": "Delete Record" + }, + "response": [] + }, + { + "name": "Create Datarecord for Attachments", + "event": [ + { + "listen": "test", + "script": { + "id": "7dfed257-8dfe-4a04-a39d-76d250605638", + "exec": [ + "", + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + "});", + "", + "pm.test(\"Test oid exists\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('oid');", + " postman.setEnvironmentVariable(\"tempDataRecordOid\", jsonData.oid);", + "});" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "e1964662-3864-424b-8756-886ec201a6b7", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + }, + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"parameterRetriever\": \"\",\n \"rdmpGetter\": \"\",\n \"\": {},\n \"rdmp\": {\n \"oid\": \"{{dmpOid}}\",\n \"title\": \"Andrew's Postman test - Updated from form AJAX call\"\n },\n \"aim_project_name\": \"Andrew's Postman test - Updated from form AJAX call\",\n \"foaf:fundedBy_foaf:Agent\": [\n \"\"\n ],\n \"dc:coverage_vivo:DateTimeInterval_vivo:end\": \"\",\n \"foaf:fundedBy_vivo:Grant\": [\n \"\"\n ],\n \"dc:subject_anzsrc:for\": [\n {\n \"name\": \"01 - MATHEMATICAL SCIENCES\",\n \"label\": \"MATHEMATICAL SCIENCES\",\n \"notation\": \"01\"\n }\n ],\n \"dc:subject_anzsrc:seo\": [],\n \"title\": \"A Data record\",\n \"description\": \"Data record\",\n \"datatype\": \"collection\",\n \"finalKeywords\": [\n \"270199\",\n \"Golgi apparatus\",\n \"endocytosis\",\n \"membrane transport\",\n \"protein trafficking\",\n \"secretion\",\n \"HIV-AIDS\",\n \"bacterial pathogen\",\n \"hormone secretion\",\n \"immune development\",\n \"lysosomal storage disorder\"\n ],\n \"contributor_ci\": {\n \"text_full_name\": \"Alberto Zweinstein\",\n \"full_name_honorific\": \"Dr Alberto Zweinstein\",\n \"email\": \"alberto.zweinstein@example.edu.au\",\n \"given_name\": \"Alberto\",\n \"family_name\": \"Zweinstein\",\n \"honorific\": \"Dr\",\n \"full_name_family_name_first\": \"Zweinstein, Alberto\",\n \"username\": \"\",\n \"role\": \"Chief Investigator\",\n \"orcid\": null\n },\n \"contributor_data_manager\": {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"username\": \"\",\n \"role\": \"Data manager\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n },\n \"contributors\": [\n {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"username\": \"\",\n \"role\": \"Contributors\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n }\n ],\n \"contributor_supervisor\": {\n \"text_full_name\": null,\n \"full_name_honorific\": \"\",\n \"email\": null,\n \"given_name\": \"\",\n \"family_name\": \"\",\n \"honorific\": \"\",\n \"full_name_family_name_first\": \"\",\n \"username\": \"\",\n \"role\": \"Supervisor\",\n \"orcid\": null\n },\n \"dataowner_name\": \"Alberto Zweinstein\",\n \"dataowner_email\": \"alberto.zweinstein@example.edu.au\",\n \"redbox:retentionPeriod_dc:date\": \"1year\",\n \"redbox:retentionPeriod_dc:date_skos:note\": \"\",\n \"disposalDate\": \"\",\n \"related_publications\": [\n {\n \"related_url\": \"\",\n \"related_title\": \"\",\n \"related_notes\": \"\"\n }\n ],\n \"dataLocations\": [],\n \"software_equipment\": \"\"\n}" + }, + "url": { + "raw": "{{host}}/default/rdmp/api/records/metadata/dataRecord", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "records", + "metadata", + "dataRecord" + ] + } + }, + "response": [] + }, + { + "name": "Add attachment to Datarecord", + "event": [ + { + "listen": "test", + "script": { + "id": "62138e30-18c2-4b63-b30b-68c3f54b3e73", + "exec": [ + "", + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "c82b8075-1230-4938-97bf-f8af0d0dc614", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + }, + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "attachmentFields", + "type": "file", + "src": "test/postman/attachment.png" + }, + { + "key": "dataLocations", + "value": "", + "type": "text", + "disabled": true + } + ] + }, + "url": { + "raw": "{{host}}/default/rdmp/api/records/datastreams/{{attDataRecordOid}}", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "records", + "datastreams", + "{{attDataRecordOid}}" + ] + } + }, + "response": [] + }, + { + "name": "List Attachments for Datarecord", + "event": [ + { + "listen": "test", + "script": { + "id": "f1b9f11a-b3d9-4e89-9707-b4992ffefc2d", + "exec": [ + "", + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Has attachments\", function () {", + " var jsonData = pm.response.json();", + " // pm.expect()", + " pm.expect(_.size(jsonData)).to.gt(0);", + "});" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "31248bd7-e856-438e-ae8f-dc39f4ab10a7", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + }, + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{host}}/default/rdmp/api/records/datastreams/{{attDataRecordOid}}", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "records", + "datastreams", + "{{attDataRecordOid}}" + ] + } + }, + "response": [] + }, + { + "name": "Create Temporary Datapub", + "event": [ + { + "listen": "test", + "script": { + "id": "3336e9ce-f002-4c90-b162-4462be25c34b", + "exec": [ + "", + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + "});", + "", + "pm.test(\"Test oid exists\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('oid');", + " postman.setEnvironmentVariable(\"tempDataPubOid\", jsonData.oid);", + "});" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "51f38064-b191-4d79-9ddf-de79b92da9fa", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + }, + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"parameterRetriever\": \"\",\n \"dataRecordGetter\": \"\",\n \"\": {},\n \"title\": \"A Data record\",\n \"description\": \"Data record\",\n \"datatype\": \"collection\",\n \"finalKeywords\": [\n \"270199\",\n \"Golgi apparatus\",\n \"endocytosis\",\n \"membrane transport\",\n \"protein trafficking\",\n \"secretion\",\n \"HIV-AIDS\",\n \"bacterial pathogen\",\n \"hormone secretion\",\n \"immune development\",\n \"lysosomal storage disorder\"\n ],\n \"foaf:fundedBy_foaf:Agent\": [\n {}\n ],\n \"foaf:fundedBy_vivo:Grant\": [\n {}\n ],\n \"dc:subject_anzsrc:for\": [\n {\n \"name\": \"01 - MATHEMATICAL SCIENCES\",\n \"label\": \"MATHEMATICAL SCIENCES\",\n \"notation\": \"01\"\n }\n ],\n \"dc:subject_anzsrc:seo\": [],\n \"startDate\": \"\",\n \"endDate\": \"\",\n \"timePeriod\": \"\",\n \"geolocations\": [\n \"\"\n ],\n \"geospatial\": {},\n \"accessRightsToggle\": \"\",\n \"dataLocations\": [\n {\n \"type\": \"attachment\",\n \"location\": \"b869b4fae83a1f01082465d165d868a8/attach/d3de61376e5bc93c814607ee604ebac5\",\n \"mimeType\": \"image/png\",\n \"name\": \"Screen Shot 2018-11-26 at 3.39.48 pm.png\",\n \"fileId\": \"d3de61376e5bc93c814607ee604ebac5\",\n \"uploadUrl\": \"http://localhost:1500/default/rdmp/record/b869b4fae83a1f01082465d165d868a8/attach/d3de61376e5bc93c814607ee604ebac5\",\n \"selected\": true\n }\n ],\n \"dataLicensingAccess_manager\": \"Prof Paul Gleeson\",\n \"dc:accessRights\": \"Open\",\n \"accessRights_url\": \"\",\n \"related_publications\": [\n {\n \"related_title\": \"\",\n \"related_url\": \"\",\n \"related_notes\": \"\"\n }\n ],\n \"related_websites\": [\n {\n \"related_title\": \"\",\n \"related_url\": \"\",\n \"related_notes\": \"\"\n }\n ],\n \"related_metadata\": [\n {\n \"related_title\": \"\",\n \"related_url\": \"\",\n \"related_notes\": \"\"\n }\n ],\n \"related_data\": [\n {\n \"related_title\": \"\",\n \"related_url\": \"\",\n \"related_notes\": \"\"\n }\n ],\n \"related_services\": [\n {\n \"related_title\": \"\",\n \"related_url\": \"\",\n \"related_notes\": \"\"\n }\n ],\n \"license_identifier\": \"\",\n \"license_notes\": \"\",\n \"license_other_url\": \"\",\n \"license_statement\": \"Copyright ReDBox Research Data 2018\",\n \"license_statement_url\": \"\",\n \"citation_doi\": \"\",\n \"requestIdentifier\": [],\n \"citation_title\": \"A Data Record\",\n \"creators\": [\n {\n \"text_full_name\": \"Alberto Zweinstein\",\n \"email\": \"alberto.zweinstein@example.edu.au\",\n \"role\": \"Chief Investigator\",\n \"username\": \"\",\n \"orcid\": \"\",\n \"family_name\": \"Zweinstein\",\n \"given_name\": \"Alberto\"\n },\n {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"role\": \"Data manager\",\n \"username\": \"\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\",\n \"family_name\": \"Paul Gleeson\",\n \"given_name\": \"Prof\"\n },\n {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"email\": \"notAReal@email.edu.au\",\n \"role\": \"Contributors\",\n \"username\": \"\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\",\n \"family_name\": \"Paul Gleeson\",\n \"given_name\": \"Prof\"\n },\n {\n \"text_full_name\": null,\n \"email\": null,\n \"role\": \"Supervisor\",\n \"username\": \"\",\n \"orcid\": \"\",\n \"family_name\": \"\",\n \"given_name\": \"\"\n }\n ],\n \"citation_publisher\": \"ReDBox Research Data\",\n \"citation_url\": \"\",\n \"citation_publication_date\": \"\",\n \"citation_generated\": \"Zweinstein, Alberto; Paul Gleeson, Prof; Paul Gleeson, Prof (Invalid date): A Data Record. ReDBox Research Data. {ID_WILL_BE_HERE}\",\n \"dataowner_name\": \"Alberto Zweinstein\",\n \"dataowner_email\": \"alberto.zweinstein@example.edu.au\",\n \"contributor_ci\": {\n \"text_full_name\": \"Alberto Zweinstein\",\n \"full_name_honorific\": \"Dr Alberto Zweinstein\",\n \"email\": \"alberto.zweinstein@example.edu.au\",\n \"given_name\": \"Alberto\",\n \"family_name\": \"Zweinstein\",\n \"honorific\": \"Dr\",\n \"full_name_family_name_first\": \"Zweinstein, Alberto\",\n \"username\": \"\",\n \"role\": \"Chief Investigator\"\n },\n \"contributor_data_manager\": {\n \"text_full_name\": \"Prof Paul Gleeson\",\n \"full_name_honorific\": \"\",\n \"email\": \"notAReal@email.edu.au\",\n \"given_name\": \"Prof\",\n \"family_name\": \"Paul Gleeson\",\n \"honorific\": \"\",\n \"full_name_family_name_first\": \"Paul Gleeson, Prof\",\n \"username\": \"\",\n \"role\": \"Data manager\",\n \"orcid\": \"http://orcid.org/0000-0000-0000-000\"\n },\n \"contributor_supervisor\": {\n \"text_full_name\": \"\",\n \"full_name_honorific\": \"\",\n \"email\": \"\",\n \"given_name\": \"\",\n \"family_name\": \"\",\n \"honorific\": \"\",\n \"full_name_family_name_first\": \"\",\n \"username\": \"\",\n \"role\": \"Supervisor\"\n },\n \"embargoByDate\": \"\",\n \"embargoUntil\": null,\n \"embargoNote\": \"\",\n \"reviewerNote\": \"\",\n \"ckanLocation\": \"\"\n}" + }, + "url": { + "raw": "{{host}}/default/rdmp/api/records/metadata/dataPublication", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "records", + "metadata", + "dataPublication" + ] + } + }, + "response": [] + }, + { + "name": "Transition Workflow of Data publication", + "event": [ + { + "listen": "test", + "script": { + "id": "10306164-5a3f-4a45-8750-29c8c275cedc", + "exec": [ + "", + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Success is set and is equal to true\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('success');", + " pm.expect(jsonData.success).to.eql(true);", + "});" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "a48a7e9f-2c03-4f2f-ba09-e24e8eaf9ff2", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{host}}/default/rdmp/api/records/workflow/step/queued/{{tempDataPubOid}}", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "records", + "workflow", + "step", + "queued", + "{{tempDataPubOid}}" + ] + } + }, + "response": [] } ], "protocolProfileBehavior": {}, @@ -371,7 +840,7 @@ { "listen": "test", "script": { - "id": "f8d39001-8f2e-446f-b0fa-8c7d911e7998", + "id": "8cac12bc-94ae-4531-aab0-efb3165eba0a", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -427,7 +896,7 @@ { "listen": "test", "script": { - "id": "064e68b0-2944-40ac-8e01-e7846a15cb9f", + "id": "4e6aea4e-2f85-4924-8e6d-334107dab3c8", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -502,7 +971,7 @@ { "listen": "test", "script": { - "id": "3df270ed-86c9-4560-86dc-40116f77579d", + "id": "eb7a4a43-534a-4dc2-97e9-f5f59bc279fa", "exec": [ "pm.test(\"Status code is 404\", function () {", " pm.response.to.have.status(404);", @@ -579,7 +1048,7 @@ { "listen": "test", "script": { - "id": "b2fdd6ba-f6ad-4477-b5bb-28f087fbf42e", + "id": "d38f0e9a-08c0-4ecd-b397-e0722660bf65", "exec": [ "", "pm.test(\"Status code is 201\", function () {", @@ -603,7 +1072,7 @@ { "listen": "prerequest", "script": { - "id": "fe67cb7b-f708-4006-8430-cf0c47e4f380", + "id": "c73e192f-f078-49e5-a725-572b9a8098cf", "exec": [ "" ], @@ -651,7 +1120,7 @@ { "listen": "test", "script": { - "id": "363f7eaa-de70-427a-b876-1a5b768e1e60", + "id": "b7ec1157-4296-4584-86d1-6c9fd934921f", "exec": [ "", "pm.test(\"Status code is 201\", function () {", @@ -674,7 +1143,7 @@ { "listen": "prerequest", "script": { - "id": "bce6c018-de0f-448b-861e-1663ba00aa33", + "id": "75ca2288-b435-4dbb-9557-1cecaa07b157", "exec": [ "" ], @@ -722,7 +1191,7 @@ { "listen": "test", "script": { - "id": "a9b9716a-e66c-4a4e-ba42-562d948cee13", + "id": "300c7a88-6750-4f98-8583-358036d92c18", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -740,7 +1209,7 @@ { "listen": "prerequest", "script": { - "id": "bbde1fca-0911-4ed6-9e52-403e7f3a4da6", + "id": "a976d577-c667-47c8-b3e8-4d37ffd0ba9e", "exec": [ "" ], @@ -799,7 +1268,7 @@ { "listen": "test", "script": { - "id": "0ebff1d3-0b1a-45f6-8828-9436759e5cec", + "id": "8f43338c-2cf5-4bb1-9b63-69650222a99c", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -817,7 +1286,7 @@ { "listen": "prerequest", "script": { - "id": "743cd4e6-65c6-4169-ba36-ed4bb7dfea63", + "id": "16f7b42c-a5e2-46eb-bdcd-a7e4a0dff6d9", "exec": [ "" ], @@ -883,7 +1352,7 @@ { "listen": "test", "script": { - "id": "e7461598-d637-4a8f-9d50-68170d1d43a8", + "id": "bde75493-95b0-4412-9776-e7899bbe2fa2", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -935,7 +1404,7 @@ { "listen": "test", "script": { - "id": "fe1b7d3b-e881-40d5-a951-686fa6cd9f2c", + "id": "9a9ad395-6b99-4761-bb57-1eec14dd8a0d", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -989,7 +1458,7 @@ { "listen": "test", "script": { - "id": "4f87404f-2410-4feb-a907-be31d30b49f7", + "id": "6bf505d5-789f-4346-aa25-2008aeb80178", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1035,7 +1504,7 @@ { "listen": "test", "script": { - "id": "3bd6043c-be66-4008-b182-0e44f5d1e1de", + "id": "7b07e2c8-151b-4e1a-9fbc-7402d5c77fc7", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1094,7 +1563,7 @@ { "listen": "test", "script": { - "id": "dd49e6fe-5bcd-4146-ae1e-26a809b2b732", + "id": "78a1fe27-7456-413a-8210-5c4141cfca73", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1140,7 +1609,7 @@ { "listen": "test", "script": { - "id": "3f2abea2-8c33-4288-ab8b-eb4ecd414427", + "id": "8753e11f-0c11-4c2f-965b-c9458986438f", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1202,7 +1671,7 @@ { "listen": "test", "script": { - "id": "0c3ae152-ebc0-4574-a02f-67d778b6df82", + "id": "234e56b7-ce5c-431e-ae34-683c271163bf", "exec": [ "postman.setGlobalVariable(\"cookie\",postman.getResponseHeader(\"set-cookie\") );", "", @@ -1297,7 +1766,7 @@ { "listen": "test", "script": { - "id": "b2483e8b-42bb-4540-b619-95c967dd4136", + "id": "8e2f422f-352a-4800-8d7d-c37cbfbf22ba", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1385,7 +1854,7 @@ { "listen": "test", "script": { - "id": "ec7df232-1dae-4296-8787-68abe3997213", + "id": "811d97f0-c62c-439e-82ad-8d8bb1e03871", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1462,7 +1931,7 @@ { "listen": "test", "script": { - "id": "f415f694-a389-474f-81ad-4da82fa2ba04", + "id": "0943741c-6eae-4385-ba83-afb6eabd1c3c", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1539,7 +2008,7 @@ { "listen": "test", "script": { - "id": "7df62628-3d08-4627-99bc-414a947e5c58", + "id": "d1b8eb26-1252-48b3-b420-e71bbd2a85d1", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1628,7 +2097,7 @@ { "listen": "test", "script": { - "id": "96488c21-12a0-48c6-84fd-6a2c936ec10a", + "id": "b2991ac7-3f0d-49a0-bebe-252719b2d495", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1718,7 +2187,7 @@ { "listen": "test", "script": { - "id": "c764f8c3-12c8-4368-8042-bb9e6b40d3a2", + "id": "43b24637-e1eb-4abd-bcc7-01a184fb730f", "exec": [ "// \"contributor_ci\": {", " // \"text_full_name\": \"Alberto Zweinstein\",", @@ -1833,7 +2302,7 @@ { "listen": "test", "script": { - "id": "f8dc7448-8370-4de3-9159-85aa4565a865", + "id": "76a41503-d4a0-4060-9ed5-cad1d50a0463", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1931,7 +2400,7 @@ { "listen": "test", "script": { - "id": "b40631e8-f48a-4a28-b587-4198e1d62629", + "id": "85f85f40-7fad-41d6-81dc-072ccd9b105a", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2035,7 +2504,7 @@ { "listen": "test", "script": { - "id": "c7743152-17fb-4f77-bc43-7a5f0558d3a1", + "id": "088655de-6887-422d-b7b3-0f8543410c32", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2141,7 +2610,7 @@ { "listen": "test", "script": { - "id": "bf45f6da-0676-456a-a369-a634c3b1709c", + "id": "134563f5-e205-4c42-be49-51ddae2289e4", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2244,7 +2713,7 @@ { "listen": "test", "script": { - "id": "01e5495e-ddfa-4e01-affc-54d1e8b0668b", + "id": "d8c24406-095c-4937-b3d5-dad3e5edb340", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -2349,7 +2818,7 @@ { "listen": "test", "script": { - "id": "390ae50d-f902-4316-ba98-485753524f06", + "id": "20deb96f-9528-4f9d-a661-fc920b3bb904", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2453,7 +2922,7 @@ { "listen": "test", "script": { - "id": "647b2163-6716-4c11-b53c-399aaa395e00", + "id": "f04065c5-1cec-4b52-9029-e2e7359b87a4", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2634,7 +3103,7 @@ { "listen": "test", "script": { - "id": "7de4bbc4-953b-4a68-a49d-025c7aa84f0f", + "id": "d77526f7-74f9-4e01-899e-e17fedb90673", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2737,7 +3206,7 @@ { "listen": "test", "script": { - "id": "6e379fe7-087e-45af-979f-8fec13912df2", + "id": "eb0c65f7-e71b-4a03-bb74-1c29d043656b", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2859,7 +3328,7 @@ { "listen": "test", "script": { - "id": "cf33c155-14df-41fb-9cca-58b27a8ff04c", + "id": "c05c9c34-da68-4652-9fd5-2183c7a0a9d6", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -2953,7 +3422,7 @@ { "listen": "test", "script": { - "id": "84100fd8-6c8f-4f30-87b0-cc914f311ce2", + "id": "8c4666ba-818b-454e-8d35-4b5a4e460c1e", "exec": [ "", "pm.test(\"Status code is 201\", function () {", @@ -3055,7 +3524,7 @@ { "listen": "test", "script": { - "id": "761f85ee-c1cf-471f-a214-daab1c0f7820", + "id": "1fdbdc1a-8ae5-491d-a5ae-9c80790eabaf", "exec": [ "pm.test(\"Status code is 204\", function () {", " pm.response.to.have.status(204);", @@ -3149,7 +3618,7 @@ { "listen": "test", "script": { - "id": "c042be6a-b7de-46f4-8894-97994fddd527", + "id": "ae7cc735-e702-45f9-8ee3-9fc5b5683f6a", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3241,7 +3710,7 @@ { "listen": "test", "script": { - "id": "452aa8ad-d91d-4573-89a3-d95858407ffb", + "id": "71023be4-a4f2-42b6-99c7-221bc70bfad5", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3277,7 +3746,7 @@ { "listen": "test", "script": { - "id": "830b1c97-77bd-44d5-872f-b18598556b18", + "id": "f8af23dd-ca8c-4129-b6a7-ab19cc4dea46", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3368,7 +3837,7 @@ { "listen": "test", "script": { - "id": "6f10ad4e-6875-4b7d-9c2c-d75782515784", + "id": "d2e07c64-6e98-4810-baad-78829bc8fa18", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3460,7 +3929,7 @@ { "listen": "test", "script": { - "id": "e70782a4-70e5-4438-ac41-d18b305b65e0", + "id": "0d886b12-34b9-4e28-9ba4-47fc91a8931c", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3552,7 +4021,7 @@ { "listen": "test", "script": { - "id": "b934a0ec-b1af-40ed-841c-fd5464983f32", + "id": "503ddcea-6217-4b5b-9775-ad0f4f33169c", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -3650,7 +4119,7 @@ { "listen": "test", "script": { - "id": "4cd7af67-2980-4cbc-b7f9-e7e3109faf72", + "id": "8e0f5d17-0f71-4103-93b4-0107eda2bc41", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3752,7 +4221,7 @@ { "listen": "test", "script": { - "id": "bf40e856-df8b-4ed0-87b5-26c01d5c9b95", + "id": "08513965-3dca-4dfc-80c8-7ac7c8a22f86", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3922,7 +4391,7 @@ { "listen": "test", "script": { - "id": "26ae7498-1797-4ef4-845a-373680eb44ed", + "id": "aaf19837-12b1-4d02-b70d-370c9ed6fcfa", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3998,7 +4467,7 @@ { "listen": "test", "script": { - "id": "fe69d764-1fc9-4e63-9b07-7ddf9cdf66e1", + "id": "96e38f23-01f8-456d-b896-c1f79cf3957d", "exec": [ "pm.test(\"Status code is 403\", function () {", " pm.response.to.have.status(403);", @@ -4067,7 +4536,7 @@ { "listen": "test", "script": { - "id": "57034378-5e28-46a3-991f-5e8932835d0d", + "id": "d15a828b-8845-47a8-a4dd-adacac0e199b", "exec": [ "postman.setGlobalVariable(\"cookie\",postman.getResponseHeader(\"set-cookie\") );", "", @@ -4166,7 +4635,7 @@ { "listen": "test", "script": { - "id": "280d2c8b-e4bd-4a4d-9fa7-0c020000132b", + "id": "190f03b1-7f39-469d-b03e-267b41ed23c5", "exec": [ "postman.setGlobalVariable(\"cookie\",postman.getResponseHeader(\"set-cookie\") );", "", @@ -4262,7 +4731,7 @@ { "listen": "test", "script": { - "id": "837749d8-2b26-4652-a481-9699c3577608", + "id": "2655e549-8e30-4b24-8697-9ce36941bcd4", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -4337,4 +4806,4 @@ } ], "protocolProfileBehavior": {} -} \ No newline at end of file +} diff --git a/typescript/api/controllers/webservice/RecordController.ts b/typescript/api/controllers/webservice/RecordController.ts index 59e471921a..3d614ecaf1 100644 --- a/typescript/api/controllers/webservice/RecordController.ts +++ b/typescript/api/controllers/webservice/RecordController.ts @@ -39,6 +39,7 @@ import RecordsService from '../../core/RecordsService.js'; import SearchService from '../../core/SearchService.js'; import DatastreamService from '../../core/DatastreamService.js'; import DatastreamServiceResponse from '../../core/DatastreamServiceResponse'; +import Datastream from '../../core/Datastream'; import {ListAPIResponse } from '../../core/model/ListAPIResponse'; import {APIErrorResponse } from '../../core/model/APIErrorResponse'; @@ -74,7 +75,11 @@ export module Controllers { 'listRecords', 'deleteRecord', 'transitionWorkflow', - 'listDatastreams' + 'listDatastreams', + 'addRoleEdit', + 'removeRoleEdit', + 'addRoleView', + 'removeRoleView' ]; constructor() { @@ -460,13 +465,13 @@ export module Controllers { sails.log.verbose(UploadedFileMetadata); sails.log.verbose('Succesfully uploaded all file metadata. Sending locations downstream....'); const fileIds = _.map(UploadedFileMetadata, function (nextDescriptor) { - return path.relative(sails.config.record.attachments.stageDir, nextDescriptor.fd); + return new Datastream({fileId: path.relative(sails.config.record.attachments.stageDir, nextDescriptor.fd), name: nextDescriptor.filename, mimeType: nextDescriptor.type, size: nextDescriptor.size }); }); sails.log.verbose('files to send upstream are:'); sails.log.verbose(_.toString(fileIds)); const defaultErrorMessage = 'Error sending datastreams upstream.'; try { - const reqs = this.DatastreamService.addDatastreams(oid, fileIds); + const reqs = self.DatastreamService.addDatastreams(oid, fileIds); return Observable.fromPromise(reqs) .subscribe((result:DatastreamServiceResponse) => { sails.log.verbose(`Done with updating streams and returning response...`); @@ -682,6 +687,7 @@ export module Controllers { } try { const attachments = await this.RecordsService.getAttachments(oid); + sails.log.verbose(JSON.stringify(attachments)); let response: ListAPIResponse < any > = new ListAPIResponse < any > (); response.summary.numFound = _.size(attachments); response.summary.page = 1; @@ -693,6 +699,114 @@ export module Controllers { this.apiFail(req, res, 500, new APIErrorResponse(`Failed to list attachments, please check server logs.`)); } } + + public addRoleEdit(req, res) { + const brand = BrandingService.getBrand(req.session.branding); + + + var oid = req.param('oid'); + var body = req.body; + var roles = body["roles"]; + Observable.fromPromise(this.RecordsService.getMeta(oid)).subscribe(record => { + + if (roles != null && roles.length > 0) { + record["authorization"]["editRoles"] = _.union(record["authorization"]["editRoles"], roles); + } + + var obs = Observable.fromPromise(this.RecordsService.updateMeta(brand, oid, record, req.user)); + obs.subscribe(result => { + if (result.isSuccessful()) { + Observable.fromPromise(this.RecordsService.getMeta(result.oid)).subscribe(record => { + return res.json(record["authorization"]); + }, error=> { + sails.log.error(error); + return this.apiFail(req, res, 500, new APIErrorResponse('Failed adding an editor role, check server logs.')); + }); + } else { + return res.json(result); + } + }, error=> { + sails.log.error(error); + return this.apiFail(req, res, 500, new APIErrorResponse('Failed adding an editor role, check server logs.')); + }); + }); + } + + public addRoleView(req, res) { + const brand = BrandingService.getBrand(req.session.branding); + + + var oid = req.param('oid'); + var body = req.body; + var roles = body["roles"]; + Observable.fromPromise(this.RecordsService.getMeta(oid)).subscribe(record => { + + if (roles != null && roles.length > 0) { + record["authorization"]["viewRoles"] = _.union(record["authorization"]["viewRoles"], roles); + } + + var obs = Observable.fromPromise(this.RecordsService.updateMeta(brand, oid, record, req.user)); + obs.subscribe(result => { + if (result.isSuccessful()) { + Observable.fromPromise(this.RecordsService.getMeta(result["oid"])).subscribe(record => { + return res.json(record["authorization"]); + }); + } else { + return res.json(result); + } + }); + }); + } + + public removeRoleEdit(req, res) { + const brand = BrandingService.getBrand(req.session.branding); + var oid = req.param('oid'); + + var body = req.body; + var roles = body["roles"]; + Observable.fromPromise(this.RecordsService.getMeta(oid)).subscribe(record => { + + if (roles != null && roles.length > 0) { + record["authorization"]["editRoles"] = _.difference(record["authorization"]["editRoles"], roles); + } + + var obs = Observable.fromPromise(this.RecordsService.updateMeta(brand, oid, record,req.user)); + obs.subscribe(result => { + if (result.isSuccessful()) { + Observable.fromPromise(this.RecordsService.getMeta(result["oid"])).subscribe(record => { + return res.json(record["authorization"]); + }); + } else { + return res.json(result); + } + }); + }); + } + + public removeRoleView(req, res) { + const brand = BrandingService.getBrand(req.session.branding); + var oid = req.param('oid'); + + var body = req.body; + var users = body["roles"]; + Observable.fromPromise(this.RecordsService.getMeta(oid)).subscribe(record => { + + if (users != null && users.length > 0) { + record["authorization"]["viewRoles"] = _.difference(record["authorization"]["viewRoles"], users); + } + + var obs = Observable.fromPromise(this.RecordsService.updateMeta(brand, oid, record, req.user)); + obs.subscribe(result => { + if (result.isSuccessful()) { + Observable.fromPromise(this.RecordsService.getMeta(result["oid"])).subscribe(record => { + return res.json(record["authorization"]); + }); + } else { + return res.json(result); + } + }); + }); + } } } diff --git a/typescript/api/core/Datastream.ts b/typescript/api/core/Datastream.ts new file mode 100644 index 0000000000..8067168b31 --- /dev/null +++ b/typescript/api/core/Datastream.ts @@ -0,0 +1,35 @@ +// Copyright (c) 2020 Queensland Cyber Infrastructure Foundation (http://www.qcif.edu.au/) +// +// GNU GENERAL PUBLIC LICENSE +// Version 2, June 1991 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +/** + * Base class for a datastream + * + */ +export class Datastream { + fileId: string; + metadata: any; + + constructor(data:any = undefined) { + if (data) { + this.fileId = data['fileId']; + this.metadata = data; + } + } +} +export default Datastream diff --git a/typescript/api/core/DatastreamService.ts b/typescript/api/core/DatastreamService.ts index b284cc6d86..db2cb8fbcf 100644 --- a/typescript/api/core/DatastreamService.ts +++ b/typescript/api/core/DatastreamService.ts @@ -1,12 +1,13 @@ import DatastreamServiceResponse from './DatastreamServiceResponse'; +import Datastream from './Datastream'; interface DatastreamService{ - addDatastreams(oid: string, fileIds: any[]): DatastreamServiceResponse; + addDatastreams(oid: string, datastreams: Datastream[]): DatastreamServiceResponse; updateDatastream(oid: string, record, newMetadata, fileRoot, fileIdsAdded): any; - removeDatastream(oid, fileId): any; - addDatastream(oid, fileId): any; - addAndRemoveDatastreams(oid, addIds: any[], removeIds: any[]): any; + removeDatastream(oid, datastream: Datastream): any; + addDatastream(oid, datastream: Datastream): any; + addAndRemoveDatastreams(oid, addDatastreams: Datastream[], removeDatastreams: Datastream[]): any; getDatastream(oid, fileId): any; listDatastreams(oid, fileId): any; } diff --git a/typescript/api/services/RecordsService.ts b/typescript/api/services/RecordsService.ts index f4d5da8594..92dd142f74 100644 --- a/typescript/api/services/RecordsService.ts +++ b/typescript/api/services/RecordsService.ts @@ -241,11 +241,12 @@ export module Services { public async getAttachments(oid: string, labelFilterStr: string = undefined): Promise < any > { let datastreams = await this.datastreamService.listDatastreams(oid, null); let attachments = []; - _.each(datastreams['datastreams'], datastream => { + _.each(datastreams, (datastream) => { let attachment = {}; - attachment['dateUpdated'] = moment(datastream['lastModified']['$date']).format(); - attachment['label'] = datastream['label']; - attachment['contentType'] = datastream['contentType']; + attachment['dateUpdated'] = moment(datastream['uploadDate']).format(); + attachment['label'] = _.get(datastream.metadata, 'name'); + attachment['contentType'] = _.get(datastream.metadata, 'mimeType'); + attachment = _.merge(attachment, datastream.metadata); if (_.isUndefined(labelFilterStr) && _.isEmpty(labelFilterStr)) { attachments.push(attachment); } else { diff --git a/typescript/api/services/RedboxJavaStorageService.ts b/typescript/api/services/RedboxJavaStorageService.ts deleted file mode 100644 index ba74cbdbf5..0000000000 --- a/typescript/api/services/RedboxJavaStorageService.ts +++ /dev/null @@ -1,614 +0,0 @@ -// Copyright (c) 2020 Queensland Cyber Infrastructure Foundation (http://www.qcif.edu.au/) -// -// GNU GENERAL PUBLIC LICENSE -// Version 2, June 1991 -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License along -// with this program; if not, write to the Free Software Foundation, Inc., -// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import { - Sails, - Model -} from "sails"; -import services = require('../core/CoreService.js'); -import StorageService from '../core/StorageService.js'; -import RecordsService from '../core/RecordsService.js'; -import DatastreamService from '../core/DatastreamService.js'; -const util = require('util'); -import * as request from "request-promise"; -import { Observable } from "rxjs"; -import moment = require('moment'); -import { SequenceEqualOperator } from "rxjs/internal/operators/sequenceEqual"; -import * as fs from 'fs'; - - -declare var RecordsService, RecordTypesService, FormsService; -declare var sails: Sails; -declare var _; - -export module Services { - /** - * WorkflowSteps related functions... - * - * Author: Shilo Banihit - * - */ - export class RedboxJavaStorage extends services.Services.Core.Service implements StorageService, DatastreamService { - recordsService: RecordsService = null; - - constructor() { - super(); - let that = this; - sails.on('ready', function() { - that.recordsService = RecordsService; - }); - } - - protected _exportedMethods: any = [ - 'create', - 'updateMeta', - 'getMeta', - 'createBatch', - 'provideUserAccessAndRemovePendingAccess', - 'getRelatedRecords', - 'delete', - 'updateNotificationLog', - 'getRecords', - 'exportAllPlans', - 'addDatastreams', - 'updateDatastream', - 'removeDatastream', - 'addDatastream', - 'addAndRemoveDatastreams', - 'getDatastream', - 'listDatastreams' - ]; - - public async create(brand, record, recordType = null, user = null, triggerPreSaveTriggers = true, triggerPostSaveTriggers = true) { - let packageType = recordType.packageType; - let response = await this.createInternal(brand, record, packageType, recordType, user, triggerPreSaveTriggers); - if (triggerPostSaveTriggers) { - if (response && `${response.code}` == "200") { - response = await this.recordsService.triggerPostSaveSyncTriggers(response['oid'], record, recordType, 'onCreate', user, response); - } - if (response && `${response.code}` == "200") { - response.success = true; - this.recordsService.triggerPostSaveTriggers(response['oid'], record, recordType, 'onCreate', user); - } - } - return response; - } - - private async createInternal(brand, record, packageType, recordType = null, user = null, triggerPreSaveTriggers = true) { - // TODO: validate metadata with the form... - const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.create.url, null, packageType); - let response = null; - if (triggerPreSaveTriggers) { - record = await this.recordsService.triggerPreSaveTriggers(null, record, recordType, "onCreate", user); - } - options.body = record; - sails.log.verbose(util.inspect(options, { - showHidden: false, - depth: null - })); - response = await request[sails.config.record.api.create.method](options); - sails.log.verbose(`Create internal response: `); - sails.log.verbose(JSON.stringify(response)); - return response; - } - - public updateMeta(brand, oid, record, user = null, triggerPreSaveTriggers = true, triggerPostSaveTriggers = true): Promise < any > { - if (brand == null) { - return this.updateMetaInternal(brand, oid, record, null, user, false); - - } else { - return RecordTypesService.get(brand, record.metaMetadata.type).flatMap(async (recordType) => { - let response = await this.updateMetaInternal(brand, oid, record, recordType, user, triggerPreSaveTriggers) - if (triggerPostSaveTriggers) { - if (response && `${response.code}` == "200") { - response = this.recordsService.triggerPostSaveSyncTriggers(oid, record, recordType, 'onUpdate', user, response); - } - if (response && `${response.code}` == "200") { - response.success = true; - this.recordsService.triggerPostSaveTriggers(oid, record, recordType, 'onUpdate', user); - } - } else { - response.success = true; - } - return response; - }); - } - } - - - private async updateMetaInternal(brand, oid, record, recordType, user = null, triggerPreSaveTriggers = true) { - // TODO: validate metadata with the form... - const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.updateMeta.url, oid); - - if (triggerPreSaveTriggers) { - record = await this.recordsService.triggerPreSaveTriggers(oid, record, recordType, "onUpdate", user); - options.body = record; - return await request[sails.config.record.api.updateMeta.method](options); - } else { - options.body = record; - sails.log.verbose(util.inspect(options, { - showHidden: false, - depth: null - })); - return await request[sails.config.record.api.updateMeta.method](options); - } - } - - public getMeta(oid): Promise < any > { - const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.getMeta.url, oid); - return request[sails.config.record.api.getMeta.method](options); - } - - public createBatch(type, data, harvestIdFldName): Promise < any > { - const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.harvest.url, null, type); - data = _.map(data, dataItem => { - return { - harvest_id: _.get(dataItem, harvestIdFldName, ''), - metadata: { - metadata: dataItem, - metaMetadata: { - type: type - } - } - }; - }); - options.body = { - records: data - }; - sails.log.verbose(`Sending data:`); - sails.log.verbose(options.body); - return request[sails.config.record.api.harvest.method](options); - } - - public provideUserAccessAndRemovePendingAccess(oid, userid, pendingValue) { - var metadataResponse = this.getMeta(oid); - - Observable.fromPromise(metadataResponse).subscribe(metadata => { - // remove pending edit access and add real edit access with userid - var pendingEditArray = metadata['authorization']['editPending']; - var editArray = metadata['authorization']['edit']; - for (var i = 0; i < pendingEditArray.length; i++) { - if (pendingEditArray[i] == pendingValue) { - pendingEditArray = pendingEditArray.filter(e => e !== pendingValue); - editArray = editArray.filter(e => e !== userid); - editArray.push(userid); - } - } - metadata['authorization']['editPending'] = pendingEditArray; - metadata['authorization']['edit'] = editArray; - - var pendingViewArray = metadata['authorization']['viewPending']; - var viewArray = metadata['authorization']['view']; - for (var i = 0; i < pendingViewArray.length; i++) { - if (pendingViewArray[i] == pendingValue) { - pendingViewArray = pendingViewArray.filter(e => e !== pendingValue); - viewArray = viewArray.filter(e => e !== userid); - viewArray.push(userid); - } - } - metadata['authorization']['viewPending'] = pendingViewArray; - metadata['authorization']['view'] = viewArray; - - this.updateMeta(null, oid, metadata); - }, (error: any) => { - // swallow !!!! - sails.log.warn(`Failed to provide access to OID: ${oid}`); - sails.log.warn(error); - }); - - } - - public async getRelatedRecords(oid, brand) { - let record = await this.getMeta(oid); - - let recordTypeName = record['metaMetadata']['type']; - let recordType = await RecordTypesService.get(brand, recordTypeName).toPromise(); - - let mappingContext = { - 'processedRelationships': [], - 'relatedObjects': {} - }; - let relationships = []; - let processedRelationships = []; - processedRelationships.push(recordType.name); - let relatedTo = recordType['relatedTo']; - if (_.isArray(relatedTo)) { - _.each(relatedTo, relatedObject => { - relationships.push({ - collection: relatedObject['recordType'], - foreignField: relatedObject['foreignField'], - localField: relatedObject['localField'] - }); - }); - - const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.getRecordRelationships.url, oid); - options.body = { - oid: oid, - relationships: relationships - }; - let relatedRecords = await request[sails.config.record.api.updateMeta.method](options); - - for (let i = 0; i < relationships.length; i++) { - let relationship = relationships[i]; - let collectionName = relationship['collection']; - let recordRelationships = relatedRecords[collectionName]; - - let newRelatedObjects = {}; - mappingContext['processedRelationships'].push(collectionName); - newRelatedObjects[collectionName] = recordRelationships; - _.merge(mappingContext, { - relatedObjects: newRelatedObjects - }); - for (let j = 0; j < recordRelationships.length; j++) { - let recordRelationship = recordRelationships[j]; - mappingContext = await this.getRelatedRecordsInternal(recordRelationship.redboxOid, collectionName, brand, mappingContext); - } - } - - return mappingContext; - } else { - return {}; - } - } - - private async getRelatedRecordsInternal(oid, recordTypeName, brand, mappingContext) { - sails.log.debug("Getting related Records for oid: " + oid); - let record = await this.getMeta(oid); - - let recordType = await RecordTypesService.get(brand, recordTypeName).toPromise(); - - let relationships = []; - let processedRelationships = []; - processedRelationships.push(recordType.name); - let relatedTo = recordType['relatedTo']; - if (_.isArray(relatedTo)) { - _.each(relatedTo, relatedObject => { - relationships.push({ - collection: relatedObject['recordType'], - foreignField: relatedObject['foreignField'], - localField: relatedObject['localField'] - }); - }); - - const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.getRecordRelationships.url, oid); - options.body = { - oid: oid, - relationships: relationships - }; - let relatedRecords = await request[sails.config.record.api.updateMeta.method](options); - - for (let i = 0; i < relationships.length; i++) { - let relationship = relationships[i]; - let collectionName = relationship['collection']; - let recordRelationships = relatedRecords[collectionName]; - - let newRelatedObjects = {}; - newRelatedObjects[collectionName] = recordRelationships; - _.merge(mappingContext, { - relatedObjects: newRelatedObjects - }); - if (_.indexOf(mappingContext['processedRelationships'], collectionName) < 0) { - mappingContext['processedRelationships'].push(collectionName); - for (let j = 0; j < recordRelationships.length; j++) { - let recordRelationship = recordRelationships[j]; - mappingContext = await this.getRelatedRecordsInternal(recordRelationship.redboxOid, collectionName, brand, mappingContext); - } - } - } - - } - return mappingContext; - } - - public delete(oid): Promise < any > { - const options = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.delete.url, oid); - return request[sails.config.record.api.delete.method](options); - } - - updateNotificationLog(oid, record, options): Promise { - if (this.metTriggerCondition(oid, record, options) == "true") { - sails.log.verbose(`Updating notification log for oid: ${oid}`); - const logName = _.get(options, 'logName', null); - if (logName) { - let log = _.get(record, logName, null); - const entry = { date: moment().format('YYYY-MM-DDTHH:mm:ss') }; - if (log) { - log.push(entry); - } else { - log = [entry]; - } - _.set(record, logName, log); - } - const updateFlagName = _.get(options, 'flagName', null); - if (updateFlagName) { - _.set(record, updateFlagName, _.get(options, 'flagVal', null)); - } - sails.log.verbose(`======== Notification log updates =========`); - sails.log.verbose(JSON.stringify(record)); - sails.log.verbose(`======== End update =========`); - // ready to update - if (_.get(options, "saveRecord", false)) { - const updateOptions = this.getOptions(sails.config.record.baseUrl.redbox + sails.config.record.api.updateMeta.url, oid); - updateOptions.body = record; - return Observable.fromPromise(request[sails.config.record.api.updateMeta.method](updateOptions)) - .flatMap(resp => { - let response: any = resp; - if (response && response.code != "200") { - sails.log.error(`Error updating notification log: ${oid}`); - sails.log.error(JSON.stringify(response)); - return Observable.throw(new Error('Failed to update notification log')); - } - return Observable.of(record); - }).toPromise(); - } - } else { - sails.log.verbose(`Notification log name: '${options.name}', for oid: ${oid}, not running, condition not met: ${options.triggerCondition}`); - sails.log.verbose(JSON.stringify(record)); - } - // no updates or condition not met ... just return the record - return Observable.of(record).toPromise(); - } - - protected getOptions(url, oid = null, packageType = null, isJson: boolean = true) { - if (!_.isEmpty(oid)) { - url = url.replace('$oid', oid); - } - if (!_.isEmpty(packageType)) { - url = url.replace('$packageType', packageType); - } - const opts: any = { - url: url, - headers: { - 'Authorization': `Bearer ${sails.config.redbox.apiKey}` - } - }; - if (isJson == true) { - opts.json = true; - opts.headers['Content-Type'] = 'application/json; charset=utf-8'; - } else { - opts.encoding = null; - } - return opts; - } - - public getRecords(workflowState, recordType = undefined, start, rows = 10, username, roles, brand, editAccessOnly = undefined, packageType = undefined, sort=undefined) { - - var url = sails.config.record.baseUrl.redbox + sails.config.record.api.query.url + "?collection=metadataDocuments"; - url = this.addPaginationParams(url, start, rows); - if(sort) { - url = url+`&sort=${sort}` - } - - let roleNames = this.getRoleNames(roles, brand); - let andArray = []; - let permissions = { - "$or": [{ "authorization.view": username }, - { "authorization.edit": username }, - { "authorization.editRoles": { "$in": roleNames } }, - { "authorization.viewRoles": { "$in": roleNames } }] - }; - andArray.push(permissions); - if (!_.isUndefined(recordType) && !_.isEmpty(recordType)) { - let typeArray = []; - _.each(recordType, rType => { - typeArray.push({ "metaMetadata.type": rType }); - }); - let types = { "$or": typeArray }; - andArray.push(types); - } - if (!_.isUndefined(packageType) && !_.isEmpty(packageType)) { - let typeArray = []; - _.each(packageType, rType => { - typeArray.push({ "packageType": rType }); - }); - let types = { "$or": typeArray }; - andArray.push(types); - } - - let query = { - "metaMetadata.brandId": brand.id, - "$and":andArray, - }; - - if (workflowState != undefined) { - query["workflow.stage"] = workflowState; - } - - sails.log.verbose(JSON.stringify(query)); - var options = this.getOptions(url); - options['body'] = query; - - return request[sails.config.record.api.query.method](options); - } - - protected addQueryParams(url, workflowState) { - url = url + "?q=metaMetadata_type:rdmp AND workflow_stage:" + workflowState + "&sort=date_object_modified desc&version=2.2" - return url; - } - - protected addPaginationParams(url, start, rows) { - url = url + "&start=" + start + "&rows=" + rows + "&wt=json"; - return url; - } - - protected getRoleNames(roles, brand) { - var roleNames = []; - - for (var i = 0; i < roles.length; i++) { - var role = roles[i] - if (role.branding == brand.id) { - roleNames.push(roles[i].name); - } - } - - return roleNames; - } - - protected addAuthFilter(url, username, roles, brand, editAccessOnly = undefined) { - - var roleString = "" - var matched = false; - for (var i = 0; i < roles.length; i++) { - var role = roles[i] - if (role.branding == brand.id) { - if (matched) { - roleString += " OR "; - matched = false; - } - roleString += roles[i].name; - matched = true; - } - } - url = url + "&fq=authorization_edit:" + username + (editAccessOnly ? "" : (" OR authorization_view:" + username + " OR authorization_viewRoles:(" + roleString + ")")) + " OR authorization_editRoles:(" + roleString + ")"; - return url; - } - - exportAllPlans(username, roles, brand, format, modBefore, modAfter, recType) { - const dateQ = modBefore || modAfter ? ` AND date_object_modified:[${modAfter ? `${modAfter}T00:00:00Z` : '*'} TO ${modBefore ? `${modBefore}T23:59:59Z` : '*'}]` : ''; - var url = sails.config.record.baseUrl.redbox; - url = `${url}${sails.config.record.api.search.url}?q=metaMetadata_type:${recType}${dateQ}&sort=date_object_modified desc&version=2.2&wt=${format}`; - url = `${url}&start=0&rows=${sails.config.record.export.maxRecords}`; - url = this.addAuthFilter(url, username, roles, brand) - url = url + "&fq=metaMetadata_brandId:" + brand.id - var options = this.getOptions(url); - sails.log.verbose("Query URL is: " + url); - return request[sails.config.record.api.search.method](options); - } - - public addDatastream(oid, fileId) { - const apiConfig = sails.config.record.api.addDatastream; - const opts = this.getOptions(`${sails.config.record.baseUrl.redbox}${apiConfig.url}`, oid); - opts.url = `${opts.url}?skipReindex=true&datastreamId=${fileId}`; - const fpath = `${sails.config.record.attachments.stageDir}/${fileId}`; - opts['formData'] = { - content: fs.createReadStream(fpath) - }; - return request[apiConfig.method](opts); - } - - public getDatastream(oid, fileId) { - const apiConfig = sails.config.record.api.getDatastream; - const opts: any = this.getOptions(`${sails.config.record.baseUrl.redbox}${apiConfig.url}`, oid, null, false); - opts.url = `${opts.url}?datastreamId=${fileId}`; - opts.headers['Content-Type'] = 'application/octet-stream'; - opts.headers['accept'] = 'application/octet-stream'; - opts.resolveWithFullResponse = true; - opts.timeout = apiConfig.readTimeout; - sails.log.verbose(`Getting datastream using: `); - sails.log.verbose(JSON.stringify(opts)); - return Observable.fromPromise(request[apiConfig.method](opts)); - } - - public addAndRemoveDatastreams(oid, addIds: any[], removeIds: any[]) { - const apiConfig = sails.config.record.api.addAndRemoveDatastreams; - const opts = this.getOptions(`${sails.config.record.baseUrl.redbox}${apiConfig.url}`, oid); - opts.url = `${opts.url}?skipReindex=false`; - if (!_.isEmpty(removeIds)) { - const removeDataStreamIds = removeIds.join(','); - opts.url = `${opts.url}&removePayloadIds=${removeDataStreamIds}`; - } - if (!_.isEmpty(addIds)) { - const formData = {}; - _.each(addIds, fileId => { - const fpath = `${sails.config.record.attachments.stageDir}/${fileId}`; - formData[fileId] = fs.createReadStream(fpath); - }); - opts['formData'] = formData; - opts.json = false; - opts.headers['Content-Type'] = 'application/octet-stream'; - } - if (_.size(addIds) > 0 || _.size(removeIds) > 0) { - return request[apiConfig.method](opts); - } - } - - public addDatastreams(oid, fileIds: any[]) { - const apiConfig = sails.config.record.api.addDatastreams; - const opts = this.getOptions(`${sails.config.record.baseUrl.redbox}${apiConfig.url}`, oid); - opts.url = `${opts.url}?skipReindex=false&datastreamIds=${fileIds.join(',')}`; - const formData = {}; - _.each(fileIds, fileId => { - const fpath = `${sails.config.record.attachments.stageDir}/${fileId}`; - formData[fileId] = fs.createReadStream(fpath); - }); - opts['formData'] = formData; - - return request[apiConfig.method](opts); - } - - /** - * Compares existing record metadata with new metadata and either removes or deletes the datastream from the record - */ - public updateDatastream(oid, record, newMetadata, fileRoot, fileIdsAdded) { - // loop thru the attachment fields and determine if we need to add or remove - return FormsService.getFormByName(record.metaMetadata.form, true).flatMap(form => { - const reqs = []; - record.metaMetadata.attachmentFields = form.attachmentFields; - _.each(form.attachmentFields, (attField) => { - const oldAttachments = record.metadata[attField]; - const newAttachments = newMetadata[attField]; - const removeIds = []; - // process removals - if (!_.isUndefined(oldAttachments) && !_.isNull(oldAttachments) && !_.isNull(newAttachments)) { - const toRemove = _.differenceBy(oldAttachments, newAttachments, 'fileId'); - _.each(toRemove, (removeAtt) => { - if (removeAtt.type == 'attachment') { - removeIds.push(removeAtt.fileId); - } - }); - } - // process additions - if (!_.isUndefined(newAttachments) && !_.isNull(newAttachments)) { - const toAdd = _.differenceBy(newAttachments, oldAttachments, 'fileId'); - _.each(toAdd, (addAtt) => { - if (addAtt.type == 'attachment') { - fileIdsAdded.push(addAtt.fileId); - } - }); - } - const req = this.addAndRemoveDatastreams(oid, fileIdsAdded, removeIds); - if (req) { - reqs.push(req); - } - }); - if (!_.isEmpty(reqs)) { - return Observable.of(reqs); - } else { - return Observable.of(null); - } - }); - } - - public removeDatastream(oid, fileId) { - const apiConfig = sails.config.record.api.removeDatastream; - const opts = this.getOptions(`${sails.config.record.baseUrl.redbox}${apiConfig.url}`, oid); - opts.url = `${opts.url}?skipReindex=true&datastreamId=${fileId}`; - return request[apiConfig.method](opts); - } - - public listDatastreams(oid, fileId) { - const apiConfig = sails.config.record.api.listDatastreams; - const opts: any = this.getOptions(`${sails.config.record.baseUrl.redbox}${apiConfig.url}`, oid); - - return Observable.fromPromise(request[apiConfig.method](opts)); - } - - } -} - -module.exports = new Services.RedboxJavaStorage().exports(); From fc8e2c4e5355b69caaccea93f0d3ee9acef12edb Mon Sep 17 00:00:00 2001 From: Shilo Banihit Date: Thu, 17 Dec 2020 15:39:41 +1000 Subject: [PATCH 43/48] Fixed TS compile issue. --- test/postman/test-collection.json | 2 +- .../webservice/RecordController.ts | 29 +++++++++---------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/test/postman/test-collection.json b/test/postman/test-collection.json index b5ef7509ed..618a90952a 100644 --- a/test/postman/test-collection.json +++ b/test/postman/test-collection.json @@ -496,7 +496,7 @@ "pm.test(\"Test oid exists\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData).to.have.property('oid');", - " postman.setEnvironmentVariable(\"tempDataRecordOid\", jsonData.oid);", + " postman.setEnvironmentVariable(\"attDataRecordOid\", jsonData.oid);", "});" ], "type": "text/javascript" diff --git a/typescript/api/controllers/webservice/RecordController.ts b/typescript/api/controllers/webservice/RecordController.ts index 3d614ecaf1..14159c4d63 100644 --- a/typescript/api/controllers/webservice/RecordController.ts +++ b/typescript/api/controllers/webservice/RecordController.ts @@ -438,7 +438,7 @@ export module Controllers { ); } - public addDataStreams(req, res) { + public async addDataStreams(req, res) { const brand = BrandingService.getBrand(req.session.branding); var oid = req.param('oid'); const self = this; @@ -456,7 +456,7 @@ export module Controllers { return next(new Error('Could not determine an appropriate filename for uploaded filestream(s).')); } } - }, function (error, UploadedFileMetadata) { + }, async function (error, UploadedFileMetadata) { if (error) { const errorMessage = `There was a problem adding datastream(s) to: ${sails.config.record.attachments.stageDir}.`; sails.log.error(errorMessage, error); @@ -471,20 +471,17 @@ export module Controllers { sails.log.verbose(_.toString(fileIds)); const defaultErrorMessage = 'Error sending datastreams upstream.'; try { - const reqs = self.DatastreamService.addDatastreams(oid, fileIds); - return Observable.fromPromise(reqs) - .subscribe((result:DatastreamServiceResponse) => { - sails.log.verbose(`Done with updating streams and returning response...`); - if (result.isSuccessful()) { - sails.log.verbose("Presuming success..."); - _.merge(result, {fileIds: fileIds}); - return res.json({message: result}); - } else { - return res.status(500).json({message: result.message}); - } - }, error => { - return self.customErrorMessageHandlingOnUpstreamResult(error, res); - }); + const result:DatastreamServiceResponse = await self.DatastreamService.addDatastreams(oid, fileIds); + + sails.log.verbose(`Done with updating streams and returning response...`); + if (result.isSuccessful()) { + sails.log.verbose("Presuming success..."); + _.merge(result, {fileIds: fileIds}); + return res.json({message: result}); + } else { + return res.status(500).json({message: result.message}); + } + } catch (error) { sails.log.error(defaultErrorMessage, error); return res.status(500).json({message: defaultErrorMessage}); From 6b99a436a1903d6779270cac21dfd4025753b593 Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Mon, 21 Dec 2020 12:42:20 +1030 Subject: [PATCH 44/48] Updated API docs to reflect newly added APIs --- config/routes.js | 4 +- test/postman/test-collection.json | 146 +-- .../webservice/RecordController.ts | 3 +- .../webservice/UserManagementController.ts | 9 +- views/default/default/apidocsapib.ejs | 921 +++++++++++------- 5 files changed, 670 insertions(+), 413 deletions(-) diff --git a/config/routes.js b/config/routes.js index 1b6714bc3b..39c604a74e 100644 --- a/config/routes.js +++ b/config/routes.js @@ -316,8 +316,8 @@ module.exports.routes = { 'get /:branding/:portal/api/users': 'webservice/UserManagementController.listUsers', 'get /:branding/:portal/api/users/find': 'webservice/UserManagementController.getUser', 'get /:branding/:portal/api/users/get': 'webservice/UserManagementController.getUser', - 'post /:branding/:portal/api/users': 'webservice/UserManagementController.createUser', - 'put /:branding/:portal/api/users': 'webservice/UserManagementController.updateUser', + 'put /:branding/:portal/api/users': 'webservice/UserManagementController.createUser', + 'post /:branding/:portal/api/users': 'webservice/UserManagementController.updateUser', 'get /:branding/:portal/api/users/token/generate': 'webservice/UserManagementController.generateAPIToken', 'get /:branding/:portal/api/users/token/revoke': 'webservice/UserManagementController.revokeAPIToken', 'get /:branding/:portal/api/roles': 'webservice/UserManagementController.listSystemRoles', diff --git a/test/postman/test-collection.json b/test/postman/test-collection.json index 618a90952a..88d92c2d2c 100644 --- a/test/postman/test-collection.json +++ b/test/postman/test-collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "c3caf4c0-dee2-4fbf-b517-51b44c5a7a61", + "_postman_id": "97d73a1a-f310-409f-ba07-7e8b6c9d6c41", "name": "Redbox Portal API - With tests", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -17,7 +17,7 @@ { "listen": "test", "script": { - "id": "dbf0286f-202c-4784-a4df-4a143932638e", + "id": "f9ec7c38-e9c8-4ba1-8cd5-af537df3259a", "exec": [ "", "pm.test(\"Status code is 201\", function () {", @@ -40,7 +40,7 @@ { "listen": "prerequest", "script": { - "id": "572eb15b-beb6-4c3e-a70f-39edc51b5c64", + "id": "dcb862db-71f8-46ab-a802-68f9b575c965", "exec": [ "" ], @@ -90,7 +90,7 @@ { "listen": "test", "script": { - "id": "688f7a96-395a-4fbf-9e66-7f4741b1e17f", + "id": "e8b733e6-d3c2-4cd1-84b5-35a7e1287bb3", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -143,7 +143,7 @@ { "listen": "test", "script": { - "id": "ca6182fe-b839-4ef7-8554-bd9449898031", + "id": "198ab2d3-e77d-45fc-8605-54a918834cc6", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -198,7 +198,7 @@ { "listen": "test", "script": { - "id": "d0ddcf35-406a-42c4-897e-588412fca4e2", + "id": "80582a54-66b5-44ec-bd2e-be2c177d85d6", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -254,7 +254,7 @@ { "listen": "test", "script": { - "id": "03d3ed88-fdac-41f9-bc50-c184e28db2b8", + "id": "a2624cea-64ab-4156-85ab-9045ce74f9fc", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -307,7 +307,7 @@ { "listen": "test", "script": { - "id": "263d4151-78ea-445c-a3b2-a1ad93cd19b3", + "id": "fe0028e6-881b-4e7a-a7af-3a7ba06a7f4c", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -364,7 +364,7 @@ { "listen": "test", "script": { - "id": "7814b400-6de9-4d5d-862d-159a25a41cb8", + "id": "76ffb7f5-555e-415d-a2cb-76a0531296cb", "exec": [ "", "pm.test(\"Status code is 201\", function () {", @@ -383,7 +383,7 @@ { "listen": "prerequest", "script": { - "id": "c63e7d7e-7a6a-4874-ba2d-2753fd9f0009", + "id": "13782465-d7a9-425a-a9e7-6b4473c9b48d", "exec": [ "" ], @@ -433,7 +433,7 @@ { "listen": "test", "script": { - "id": "3ea6e9d9-0c89-4c8f-90e7-45732214b93c", + "id": "075d0e37-e4c8-4f75-8f3d-5e2da81d4c4d", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -486,7 +486,7 @@ { "listen": "test", "script": { - "id": "7dfed257-8dfe-4a04-a39d-76d250605638", + "id": "dab56e5c-b9bb-4583-8807-d692a9ec3a3d", "exec": [ "", "pm.test(\"Status code is 201\", function () {", @@ -505,7 +505,7 @@ { "listen": "prerequest", "script": { - "id": "e1964662-3864-424b-8756-886ec201a6b7", + "id": "fa94d28f-d780-4868-a1e7-ca701170dedb", "exec": [ "" ], @@ -555,7 +555,7 @@ { "listen": "test", "script": { - "id": "62138e30-18c2-4b63-b30b-68c3f54b3e73", + "id": "c0a6ae8e-b952-4e45-8056-bd5213c44d12", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -569,7 +569,7 @@ { "listen": "prerequest", "script": { - "id": "c82b8075-1230-4938-97bf-f8af0d0dc614", + "id": "22b37cca-4b4f-4925-b28d-dc1cadad4e06", "exec": [ "" ], @@ -632,7 +632,7 @@ { "listen": "test", "script": { - "id": "f1b9f11a-b3d9-4e89-9707-b4992ffefc2d", + "id": "5940b6a4-cd63-4905-a27b-8b39e89a72e4", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -651,7 +651,7 @@ { "listen": "prerequest", "script": { - "id": "31248bd7-e856-438e-ae8f-dc39f4ab10a7", + "id": "f0fb2e7d-7e91-4570-88dc-e6de7b69d70d", "exec": [ "" ], @@ -697,7 +697,7 @@ { "listen": "test", "script": { - "id": "3336e9ce-f002-4c90-b162-4462be25c34b", + "id": "934d0b1b-284d-4e63-a3d9-a7e5bc584564", "exec": [ "", "pm.test(\"Status code is 201\", function () {", @@ -716,7 +716,7 @@ { "listen": "prerequest", "script": { - "id": "51f38064-b191-4d79-9ddf-de79b92da9fa", + "id": "81977619-ea89-44ac-aa87-3df51d1d2cae", "exec": [ "" ], @@ -766,7 +766,7 @@ { "listen": "test", "script": { - "id": "10306164-5a3f-4a45-8750-29c8c275cedc", + "id": "37659128-1673-4449-99df-b0df0e136546", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -785,7 +785,7 @@ { "listen": "prerequest", "script": { - "id": "a48a7e9f-2c03-4f2f-ba09-e24e8eaf9ff2", + "id": "8fd592fa-9985-44a0-873f-2180819934b4", "exec": [ "" ], @@ -840,7 +840,7 @@ { "listen": "test", "script": { - "id": "8cac12bc-94ae-4531-aab0-efb3165eba0a", + "id": "c3b793d0-72fa-438c-bcb0-26496f7a39c8", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -896,7 +896,7 @@ { "listen": "test", "script": { - "id": "4e6aea4e-2f85-4924-8e6d-334107dab3c8", + "id": "85758358-7a47-48e8-8f7c-2fbc2427b043", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -971,7 +971,7 @@ { "listen": "test", "script": { - "id": "eb7a4a43-534a-4dc2-97e9-f5f59bc279fa", + "id": "7e4bf386-1692-45f6-aad6-2ea3a167287a", "exec": [ "pm.test(\"Status code is 404\", function () {", " pm.response.to.have.status(404);", @@ -1048,7 +1048,7 @@ { "listen": "test", "script": { - "id": "d38f0e9a-08c0-4ecd-b397-e0722660bf65", + "id": "b88088b8-4c3a-4163-a83a-af89b40369cb", "exec": [ "", "pm.test(\"Status code is 201\", function () {", @@ -1072,7 +1072,7 @@ { "listen": "prerequest", "script": { - "id": "c73e192f-f078-49e5-a725-572b9a8098cf", + "id": "c188e622-055b-42eb-b644-f588d02af75b", "exec": [ "" ], @@ -1081,7 +1081,7 @@ } ], "request": { - "method": "POST", + "method": "PUT", "header": [ { "key": "Content-Type", @@ -1120,7 +1120,7 @@ { "listen": "test", "script": { - "id": "b7ec1157-4296-4584-86d1-6c9fd934921f", + "id": "635f6916-fa50-475a-b003-6bb272207a9a", "exec": [ "", "pm.test(\"Status code is 201\", function () {", @@ -1143,7 +1143,7 @@ { "listen": "prerequest", "script": { - "id": "75ca2288-b435-4dbb-9557-1cecaa07b157", + "id": "345a88fc-ae05-40a2-be8b-72edcf03625a", "exec": [ "" ], @@ -1152,7 +1152,7 @@ } ], "request": { - "method": "PUT", + "method": "POST", "header": [ { "key": "Content-Type", @@ -1191,7 +1191,7 @@ { "listen": "test", "script": { - "id": "300c7a88-6750-4f98-8583-358036d92c18", + "id": "7ba75c5e-b4b9-4854-bd6d-947f0bf103f5", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -1209,7 +1209,7 @@ { "listen": "prerequest", "script": { - "id": "a976d577-c667-47c8-b3e8-4d37ffd0ba9e", + "id": "2fb11f9c-f1d8-45a9-b4b6-d57981f63e7f", "exec": [ "" ], @@ -1268,7 +1268,7 @@ { "listen": "test", "script": { - "id": "8f43338c-2cf5-4bb1-9b63-69650222a99c", + "id": "d343af0d-10d9-4072-8ffb-509c06bfb381", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -1286,7 +1286,7 @@ { "listen": "prerequest", "script": { - "id": "16f7b42c-a5e2-46eb-bdcd-a7e4a0dff6d9", + "id": "139e05c7-f758-4fe9-9852-5e42427912ee", "exec": [ "" ], @@ -1352,7 +1352,7 @@ { "listen": "test", "script": { - "id": "bde75493-95b0-4412-9776-e7899bbe2fa2", + "id": "6e0752f2-df8b-4187-9599-4ef314ac37a6", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1404,7 +1404,7 @@ { "listen": "test", "script": { - "id": "9a9ad395-6b99-4761-bb57-1eec14dd8a0d", + "id": "d227d549-7911-4fa0-b471-96d9070bed1e", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1458,7 +1458,7 @@ { "listen": "test", "script": { - "id": "6bf505d5-789f-4346-aa25-2008aeb80178", + "id": "15cb0e1f-05eb-4068-97c9-e66dd1d4f6e6", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1504,7 +1504,7 @@ { "listen": "test", "script": { - "id": "7b07e2c8-151b-4e1a-9fbc-7402d5c77fc7", + "id": "2f804880-aaf2-46a3-a9f9-d75eb2462a00", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1563,7 +1563,7 @@ { "listen": "test", "script": { - "id": "78a1fe27-7456-413a-8210-5c4141cfca73", + "id": "889de602-53bd-45f1-8ed5-031c99c62570", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1609,7 +1609,7 @@ { "listen": "test", "script": { - "id": "8753e11f-0c11-4c2f-965b-c9458986438f", + "id": "6b58f3b0-c3a7-45a2-acb7-a0dffda9bfc2", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1671,7 +1671,7 @@ { "listen": "test", "script": { - "id": "234e56b7-ce5c-431e-ae34-683c271163bf", + "id": "dd221b1e-c29f-4573-ab4a-ebdc28204e33", "exec": [ "postman.setGlobalVariable(\"cookie\",postman.getResponseHeader(\"set-cookie\") );", "", @@ -1766,7 +1766,7 @@ { "listen": "test", "script": { - "id": "8e2f422f-352a-4800-8d7d-c37cbfbf22ba", + "id": "cd141c7c-a30b-4650-a2e1-eb8bbcbba857", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1854,7 +1854,7 @@ { "listen": "test", "script": { - "id": "811d97f0-c62c-439e-82ad-8d8bb1e03871", + "id": "827b4a16-38ea-4864-8e6b-4d3de669ed2a", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1931,7 +1931,7 @@ { "listen": "test", "script": { - "id": "0943741c-6eae-4385-ba83-afb6eabd1c3c", + "id": "9d3e6245-d371-40d9-bf73-d4e0a948241f", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2008,7 +2008,7 @@ { "listen": "test", "script": { - "id": "d1b8eb26-1252-48b3-b420-e71bbd2a85d1", + "id": "772b034f-ba5b-47a6-aebc-c7dac05c1979", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2097,7 +2097,7 @@ { "listen": "test", "script": { - "id": "b2991ac7-3f0d-49a0-bebe-252719b2d495", + "id": "8d81de5f-1cba-4958-bbb3-952f372953dc", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2187,7 +2187,7 @@ { "listen": "test", "script": { - "id": "43b24637-e1eb-4abd-bcc7-01a184fb730f", + "id": "26c2ee27-2c85-415b-927c-feb96661e310", "exec": [ "// \"contributor_ci\": {", " // \"text_full_name\": \"Alberto Zweinstein\",", @@ -2302,7 +2302,7 @@ { "listen": "test", "script": { - "id": "76a41503-d4a0-4060-9ed5-cad1d50a0463", + "id": "4e1ae5fd-dc11-4b2a-ad49-73e1dcd3ac09", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2400,7 +2400,7 @@ { "listen": "test", "script": { - "id": "85f85f40-7fad-41d6-81dc-072ccd9b105a", + "id": "b40c8315-6ae1-4565-aca7-f7a567928df8", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2504,7 +2504,7 @@ { "listen": "test", "script": { - "id": "088655de-6887-422d-b7b3-0f8543410c32", + "id": "3da4fbe9-90c0-4721-8c76-7c46d86e5e1d", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2610,7 +2610,7 @@ { "listen": "test", "script": { - "id": "134563f5-e205-4c42-be49-51ddae2289e4", + "id": "39b25935-37cb-4012-9653-9c0f7b51b437", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2713,7 +2713,7 @@ { "listen": "test", "script": { - "id": "d8c24406-095c-4937-b3d5-dad3e5edb340", + "id": "fb08924f-59fb-4be4-95ea-0f7827f6c6c9", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -2818,7 +2818,7 @@ { "listen": "test", "script": { - "id": "20deb96f-9528-4f9d-a661-fc920b3bb904", + "id": "7803bb2a-e13c-4aee-907c-b46972654d53", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2922,7 +2922,7 @@ { "listen": "test", "script": { - "id": "f04065c5-1cec-4b52-9029-e2e7359b87a4", + "id": "91d97af6-0682-4310-887b-53f537f97e6b", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3103,7 +3103,7 @@ { "listen": "test", "script": { - "id": "d77526f7-74f9-4e01-899e-e17fedb90673", + "id": "163a2e38-a27d-4f88-aeb3-0cb333694c4f", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3206,7 +3206,7 @@ { "listen": "test", "script": { - "id": "eb0c65f7-e71b-4a03-bb74-1c29d043656b", + "id": "6306a7a6-3c3b-47da-ab7a-a0e888546680", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3328,7 +3328,7 @@ { "listen": "test", "script": { - "id": "c05c9c34-da68-4652-9fd5-2183c7a0a9d6", + "id": "0a0ad59c-d8fd-44e9-a2d1-b94144080bed", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -3422,7 +3422,7 @@ { "listen": "test", "script": { - "id": "8c4666ba-818b-454e-8d35-4b5a4e460c1e", + "id": "3bd417fd-ba5f-4aa3-b7f7-fe00a57353b4", "exec": [ "", "pm.test(\"Status code is 201\", function () {", @@ -3524,7 +3524,7 @@ { "listen": "test", "script": { - "id": "1fdbdc1a-8ae5-491d-a5ae-9c80790eabaf", + "id": "8cfb5446-28e1-4f72-9dd2-e4cdc521a33f", "exec": [ "pm.test(\"Status code is 204\", function () {", " pm.response.to.have.status(204);", @@ -3618,7 +3618,7 @@ { "listen": "test", "script": { - "id": "ae7cc735-e702-45f9-8ee3-9fc5b5683f6a", + "id": "b4b5c541-0159-45ba-9ce9-96ddbd7b6978", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3710,7 +3710,7 @@ { "listen": "test", "script": { - "id": "71023be4-a4f2-42b6-99c7-221bc70bfad5", + "id": "26419b15-b0ab-4d6b-a089-795499e398db", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3746,7 +3746,7 @@ { "listen": "test", "script": { - "id": "f8af23dd-ca8c-4129-b6a7-ab19cc4dea46", + "id": "5475b6ee-dc3c-4ff4-a43f-4afe80174591", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3837,7 +3837,7 @@ { "listen": "test", "script": { - "id": "d2e07c64-6e98-4810-baad-78829bc8fa18", + "id": "2eb5e2e4-4527-4972-b8a8-fe80c1f93d02", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3929,7 +3929,7 @@ { "listen": "test", "script": { - "id": "0d886b12-34b9-4e28-9ba4-47fc91a8931c", + "id": "6a2854cc-0726-4809-861a-99768337905b", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -4021,7 +4021,7 @@ { "listen": "test", "script": { - "id": "503ddcea-6217-4b5b-9775-ad0f4f33169c", + "id": "bdfc326b-4c43-4c87-8071-8d1330124fba", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -4119,7 +4119,7 @@ { "listen": "test", "script": { - "id": "8e0f5d17-0f71-4103-93b4-0107eda2bc41", + "id": "9c4524e7-3a78-4f34-aaa4-4508dd61db72", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -4221,7 +4221,7 @@ { "listen": "test", "script": { - "id": "08513965-3dca-4dfc-80c8-7ac7c8a22f86", + "id": "009345d0-1808-4004-888b-e9686cff73dd", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -4391,7 +4391,7 @@ { "listen": "test", "script": { - "id": "aaf19837-12b1-4d02-b70d-370c9ed6fcfa", + "id": "b4bd3a5b-db84-4e10-a76d-0ce72fd2cc08", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -4467,7 +4467,7 @@ { "listen": "test", "script": { - "id": "96e38f23-01f8-456d-b896-c1f79cf3957d", + "id": "47459f8e-16a7-4f98-ba80-402c5c205023", "exec": [ "pm.test(\"Status code is 403\", function () {", " pm.response.to.have.status(403);", @@ -4536,7 +4536,7 @@ { "listen": "test", "script": { - "id": "d15a828b-8845-47a8-a4dd-adacac0e199b", + "id": "539d2e29-32bb-49b2-9478-094edd532150", "exec": [ "postman.setGlobalVariable(\"cookie\",postman.getResponseHeader(\"set-cookie\") );", "", @@ -4635,7 +4635,7 @@ { "listen": "test", "script": { - "id": "190f03b1-7f39-469d-b03e-267b41ed23c5", + "id": "8801cb9b-3fab-49b3-b3d1-faf94c8a5d71", "exec": [ "postman.setGlobalVariable(\"cookie\",postman.getResponseHeader(\"set-cookie\") );", "", @@ -4731,7 +4731,7 @@ { "listen": "test", "script": { - "id": "2655e549-8e30-4b24-8697-9ce36941bcd4", + "id": "2a2e39fa-c472-4f7b-84aa-91698625f9e3", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -4806,4 +4806,4 @@ } ], "protocolProfileBehavior": {} -} +} \ No newline at end of file diff --git a/typescript/api/controllers/webservice/RecordController.ts b/typescript/api/controllers/webservice/RecordController.ts index 14159c4d63..e80573cd92 100644 --- a/typescript/api/controllers/webservice/RecordController.ts +++ b/typescript/api/controllers/webservice/RecordController.ts @@ -299,7 +299,7 @@ export module Controllers { }, error=> { sails.log.error("Update metadata failed, failed to retrieve existing record.", error); - return this.apiFail(req, res, 500, new APIErrorResponse("Update Metadata failed, failed to retrieve existing record. ")); + return this.apiFail(req, res, 400, new APIErrorResponse("Update Metadata failed, failed to retrieve existing record. ")); }); } @@ -620,7 +620,6 @@ export module Controllers { // sails.log.debug(`getRecords: ${recordType} ${workflowState} ${start}`); // sails.log.debug(`${rows} ${packageType} ${sort}`); this.getRecords(workflowState, recordType, start, rows, user, roles, brand, editAccessOnly, packageType, sort).flatMap(results => { - return results; }).subscribe(response => { res.json(response); diff --git a/typescript/api/controllers/webservice/UserManagementController.ts b/typescript/api/controllers/webservice/UserManagementController.ts index 06c9908b27..c1071cfd44 100644 --- a/typescript/api/controllers/webservice/UserManagementController.ts +++ b/typescript/api/controllers/webservice/UserManagementController.ts @@ -63,8 +63,7 @@ export module Controllers { 'updateUser', 'generateAPIToken', 'revokeAPIToken', - 'listSystemRoles', - 'deleteUser' + 'listSystemRoles' ]; /** @@ -291,7 +290,11 @@ export module Controllers { public listSystemRoles(req, res) { let brand = BrandingService.getBrand(req.session.branding); - return this.apiRespond(req,res,brand.roles); + let response: ListAPIResponse < any > = new ListAPIResponse < any > (); + response.summary.numFound = brand.roles.length; + response.records = brand.roles; + + return this.apiRespond(req,res,response); } diff --git a/views/default/default/apidocsapib.ejs b/views/default/default/apidocsapib.ejs index 69e95ebb57..5ab3e9a2d0 100644 --- a/views/default/default/apidocsapib.ejs +++ b/views/default/default/apidocsapib.ejs @@ -1,449 +1,704 @@ FORMAT: 1A -HOST: http://localhost:1337 +HOST: https://demo.redboxresearchdata.com.au -# ReDBox Portal API +# ReDBox Portal API The ReDBox Portal API provides authorized access to manage functions. -## Record Collection +# Group Record -### List records in the system [GET /<%= branding %>/<%= portal %>/api/records/list] +## Record Actions [/<%= branding %>/<%= portal %>/api/records/] + +### List records in the system [GET /<%= branding %>/<%= portal %>/api/records/list{?packageType,recordType,sort,start,rows}] + Parameters - + packageType (string) ... The type of package - + recordType (string) ... The type of record - + sort (object) ... Sort by object Example: date_object_modified:-1 - + start (number) ... The number to start the records - + rows (number) ... The number of records per request + + recordType: `rdmp` (string, required) - The record type name + + packageType: `rdmp` (string, optional) - The type of ReDBox package to return + + sort: `date_object_modified:-1`(object, optional) - Sort results by this parameter. + Parameter should be of the pattern `:` where sort direction is 1 for ascending order and -1 for descending order + + Default: date_object_modified:-1 + + start: `0` (number, optional) - The index number for the first value to return from the result set. + + Default: 0 + + rows: `10` (number,optional) - The number of records to return in the request + + Default: 10 + Request Get Records (application/json) + + Headers - Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc - + Body - { - "totalItems": 3, - "currentPage": 1, - "noItems": 3, - "items": [ - { - "oid": "946a62900e045873b0318f44e775d7da", - "title": "test3", - "metadata": { - ... - }, - "dateCreated": "2020-05-20T06:22:56.113Z", - "dateModified": "2020-05-20T06:22:56.121Z", - "hasEditAccess": true - }, - { - ... - }, - { - ... - } - ] - } + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + ++ Response 200 (application/json) + + Attributes (ListResponse, fixed-type) + - records (array[ListRecord]) + + ++ Response 400 (application/json) + + Attributes (Error, fixed-type) + - message: You have reached the maximum of request available; Max rows per request 10 + ++ Response 500 (application/json) + + Attributes (Error, fixed-type) + - message: An error has occurred ### Create Record [POST /<%= branding %>/<%= portal %>/api/records/metadata/{recordType}] + Parameters - + recordType (string) ... The type of record to create + + recordType: `rdmp` (string, required) - The record type name + + Request Create Record setting metadata, authorization and workflowStage (application/json) + Headers - Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc - + Body - { - "authorization": { - "edit": ["username"], - "view": ["username"], - "editPending":["anEmail@redboxresearchdata.com.au"], - "viewPending":["anEmail@redboxresearchdata.com.au"] - }, - "metadata":{ - "title": "A sample title", - "description": "A description" - }, - "workflowStage": "draft" - } + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + + Body + + Attributes (CreateRecordRequestFull, fixed-type) + - metadata + - title: A sample title + - description: A description + + Request Create Record only setting metadata (application/json) + Headers - Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Body - { - "title": "A sample title", - "description": "A description" - } + + Attributes (CreateRecordRequest, fixed-type) + - metadata + - title: A sample title + - description: A description + Request Create Record setting metadata and workflowStage (application/json) + Headers - Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc - + Body - { - "metadata":{ - "title": "A sample title", - "description": "A description" - }, - "workflowStage": "draft" - } - -+ Request Create Record setting metadata and authorization (application/json) - + Headers - Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Body - { - "authorization": { - "edit": ["username"], - "view": ["username"], - "editPending":["anEmail@redboxresearchdata.com.au"], - "viewPending":["anEmail@redboxresearchdata.com.au"] - }, - "metadata":{ - "title": "A sample title", - "description": "A description" - } - } + + Attributes (CreateRecordRequestWithWorkflowStage, fixed-type) + + Response 201 (application/json) + + Headers + + Location: /default/rdmp/api/records/metadata/7e72b5952e8e323c77f72ae268a27c46 + + Body + + Attributes (ObjectActionResponse, fixed-type) + - message: Record created successfully + + ++ Response 400 (application/json) + + Attributes (Error, fixed-type) + - message: Record Type provided is not valid + ++ Response 500 (application/json) + + Attributes (Error, fixed-type) + - message: Create failed + +### Transition record through workflow [POST /<%= branding %>/<%= portal %>/api/records/workflow/step/{workflowStage}/{oid}] ++ Parameters + + oid: `7e72b5952e8e323c77f72ae268a27c46` (string, required) - The identifier for the record + + workflowStage: `queued` (string, required) - The workflow stage to transition the record to. + + ++ Request Transition record to queued workflow stage (application/json) + Headers - Location: /<%= branding %>/<%= portal %>/api/records/metadata/a2985faeabb6d92b56af665ca466a965 - + Body - { - "code": "201", - "oid": "a2985faeabb6d92b56af665ca466a965" - } + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + ++ Response 200 (application/json) + + Body + + Attributes (DatastreamResponse, fixed-type) + ++ Response 500 (application/json) + + Attributes (Error, fixed-type) + - message: Failed to transition workflow, please check server logs. + +## Record Metadata Actions [/<%= branding %>/<%= portal %>/api/records/metadata/{oid}] -### Update Record Metadata [PUT /<%= branding %>/<%= portal %>/api/records/metadata/{id}] +### Update Record Metadata [PUT] + Parameters - + id (string) ... The id of the record to update + + oid: `7e72b5952e8e323c77f72ae268a27c46` (string, required) - The identifier for the record + Request Update record metadata (application/json) + Headers - Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + Body - { - "title": "A sample title", - "description": "A description" - } + + Attributes (object, fixed-type) + - title: A sample title + - description: A sample description + Response 200 (application/json) - { - "code": "200", - "oid": "a2985faeabb6d92b56af665ca466a965" - } + + Attributes (ObjectActionResponse, fixed-type) + - message: Record updated successfully + ++ Response 400 (application/json) + + Attributes (Error, fixed-type) + - message: Update metadata failed, failed to retrieve existing record. + ++ Response 500 (application/json) + + Attributes (Error, fixed-type) + - message: Update Metadata failed + +### Update Record Metadata [DELETE] -### Get Record Metadata [GET /<%= branding %>/<%= portal %>/api/records/metadata/{id}] + Parameters - + id (string) ... The id of the record to update + + oid: `7e72b5952e8e323c77f72ae268a27c46` (string, required) - The identifier for the record + ++ Request Update record metadata (application/json) + + Headers + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + ++ Response 200 (application/json) + + Attributes (ObjectActionResponse, fixed-type) + - message: Record deleted successfully + ++ Response 400 (application/json) + + Attributes (Error, fixed-type) + - message: Delete metadata failed, failed to retrieve existing record. + ++ Response 500 (application/json) + + Attributes (Error, fixed-type) + - message: Delete Metadata failed + +### Get Record Metadata [GET] + ++ Parameters + + oid: `7e72b5952e8e323c77f72ae268a27c46` (string, required) - The identifier for the record + + Request + Headers - Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + Response 200 (application/json) + Body - { - "title": "A sample title", - "description": "A description" - } + + { + "title": "A sample title", + "description": "A description" + } ++ Response 500 (application/json) + + Attributes (Error, fixed-type) + - message: Get Metadata failed, failed to retrieve existing record. -### Give users edit access to record [POST /<%= branding %>/<%= portal %>/api/records/permissions/edit/{id}] +## Datastream Actions [/<%= branding %>/<%= portal %>/api/records/datastreams] + +### Add attachment to record [POST /<%= branding %>/<%= portal %>/api/records/datastreams/{oid}] + Parameters - + id (string) ... The id of the record to update + + oid: `7e72b5952e8e323c77f72ae268a27c46` (string, required) - The identifier for the record + ++ Request (multipart/form-data; boundary={boundary value}) + + Headers + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + + Body + + --{boundary value} + Content-Disposition: form-data; name="attachmentFields"; filename="researchdata.zip" + Content-Type: application/zip + Content-Transfer-Encoding: base64 + + {file content} + --{boundary value} + ++ Response 200 (application/json) + + Body + + Attributes (DatastreamResponse, fixed-type) + - message: Attachment added successfully + ++ Response 500 (application/json) + + Attributes (Error, fixed-type) + - message: Get Metadata failed, failed to retrieve existing record. + +### List attachments in record [PUT /<%= branding %>/<%= portal %>/api/datastreams/{oid}] ++ Parameters + + oid: `7e72b5952e8e323c77f72ae268a27c46` (string, required) - The identifier for the record + ++ Request Get Records (application/json) + + + Headers + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + ++ Response 200 (application/json) + + Attributes (ListResponse, fixed-type) + - records (array[Attachment]) + + ++ Response 400 (application/json) + + Attributes (Error, fixed-type) + - message: Missing ID of record. + ++ Response 500 (application/json) + + Attributes (Error, fixed-type) + - message: Failed to list attachments, please check server logs. + + + +## Record Permission Actions [/<%= branding %>/<%= portal %>/api/records/permissions] + +### Give users edit access to record [POST /<%= branding %>/<%= portal %>/api/records/permissions/edit/{oid}] ++ Parameters + + oid: `7e72b5952e8e323c77f72ae268a27c46` (string, required) - The identifier for the record + Request Give users edit access to record (application/json) + Headers - Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Body - { - "users": ["username1","username2"] - } + + { + "users": ["username1","username2"] + } -+ Request Give users (pending login) edit access to record (application/json) ++ Request Give users pending edit access to record. (application/json) + Headers - Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + Body - { - "usersPending": ["pendingusername1@email.com","pendingusername2@email.com"] - } + + { + "usersPending": ["pendingusername1@email.com","pendingusername2@email.com"] + } + + -+ Request Give users (known usernames and pending login) edit access to record (application/json) ++ Request Give users pending and immediate edit access to record (application/json) + Headers - Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc - { - "users": ["username1","username2"], - "usersPending": ["pendingusername1@email.com","pendingusername2@email.com"] - } + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + + Body + + { + "users": ["username1","username2"], + "usersPending": ["pendingusername1@email.com","pendingusername2@email.com"] + } + Response 200 (application/json) -{ - "viewRoles": [ - "Admin", - "Librarians" - ], - "editRoles": [ - "Admin", - "Librarians" - ], - "view": [ - "username1" - ], - "edit": [ - "username1", - "username2" - ], - "viewPending": [ - ], - "editPending": ["pendingusername1@email.com","pendingusername2@email.com"] -} - -### Remove users edit access to record [DELETE /<%= branding %>/<%= portal %>/api/records/permissions/edit/{id}] + + Attributes (CreateRecordAuthorization, fixed-type) + - edit (array) + - username1 + - username2 + - view (array) + - username1 + - username2 + - editRoles (array) + - Admin + - viewRoles (array) + - Admin + +### Remove users edit access to record [DELETE /<%= branding %>/<%= portal %>/api/records/permissions/edit/{oid}] + Parameters - + id (string) ... The id of the record to update - -+ Headers + + oid: `7e72b5952e8e323c77f72ae268a27c46` (string, required) - The identifier for the record + Request Remove users edit access to record (application/json) + Headers - Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + Body - { - "users": ["username2"] - } -+ Request Remove users (pending login) edit access to record (application/json) + { + "users": ["username2"] + } + ++ Request Remove pending users edit access to record (application/json) + Headers - Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc - { - "usersPending": ["pendingusername2@email.com"] - } -+ Request Remove users (known usernames and pending login) edit access to record (application/json) + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + + Body + + { + "usersPending": ["pendingusername2@email.com"] + } + ++ Request Remove known users and pending users edit access to record (application/json) + Headers - Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + Body - { - "users": ["username2"], - "usersPending": ["pendingusername2@email.com"] - } + + { + "users": ["username2"], + "usersPending": ["pendingusername2@email.com"] + } + Response 200 (application/json) -{ - "viewRoles": [ - "Admin", - "Librarians" - ], - "editRoles": [ - "Admin", - "Librarians" - ], - "view": [ - "username1" - ], - "edit": [ - "username2" - ], - "viewPending": [ - ], - "editPending": ["pendingusername2@email.com"] -} - -### Give users view access to record [POST /<%= branding %>/<%= portal %>/api/records/permissions/view/{id}] + + Attributes (CreateRecordAuthorization, fixed-type) + - edit (array) + - username1 + - view (array) + - username1 + - username2 + - editRoles (array) + - Admin + - viewRoles (array) + - Admin + +### Give users view access to record [POST /<%= branding %>/<%= portal %>/api/records/permissions/view/{oid}] + Parameters - + id (string) ... The id of the record to update - -+ Headers + + oid: `7e72b5952e8e323c77f72ae268a27c46` (string, required) - The identifier for the record + Request Give users view access to record (application/json) + Headers - Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Body - { - "users": ["username1","username2"] - } + + { + "users": ["username1","username2"] + } -+ Request Give users (pending login) view access to record (application/json) ++ Request Give users pending view access to record. (application/json) + Headers - Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + Body - { - "usersPending": ["pendingusername1@email.com","pendingusername2@email.com"] - } + + { + "usersPending": ["pendingusername1@email.com","pendingusername2@email.com"] + } + -+ Request Give users (known usernames and pending login) view access to record (application/json) + ++ Request Give users pending and immediate view access to record (application/json) + Headers - Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc - + Body - { - "users": ["username1","username2"], - "usersPending": ["pendingusername1@email.com","pendingusername2@email.com"] - } + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + + Body + + { + "users": ["username1","username2"], + "usersPending": ["pendingusername1@email.com","pendingusername2@email.com"] + } + Response 200 (application/json) -{ - "viewRoles": [ - "Admin", - "Librarians" - ], - "editRoles": [ - "Admin", - "Librarians" - ], - "view": [ - "username1", - "username2" - ], - "edit": [ - - ], - "viewPending": ["pendingusername1@email.com","pendingusername2@email.com" - ], - "editPending": [] -} - -### Remove users view access to record [DELETE /<%= branding %>/<%= portal %>/api/records/permissions/view/{id}] + + Attributes (CreateRecordAuthorization, fixed-type) + - edit (array) + - username1 + - username2 + - view (array) + - username1 + - username2 + - editRoles (array) + - Admin + - viewRoles (array) + - Admin + +### Remove users view access to record [DELETE /<%= branding %>/<%= portal %>/api/records/permissions/view/{oid}] + Parameters - + id (string) ... The id of the record to update - -+ Headers + + oid: `7e72b5952e8e323c77f72ae268a27c46` (string, required) - The identifier for the record + Request Remove users view access to record (application/json) + Headers - Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + Body - { - "users": ["username2"] - } -+ Request Remove users (pending login) view access to record (application/json) + { + "users": ["username2"] + } + ++ Request Remove pending users view access to record (application/json) + Headers - Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Body - { - "usersPending": ["pendingusername2@email.com"] - } + + { + "usersPending": ["pendingusername2@email.com"] + } -+ Request Remove users (known usernames and pending login) view access to record (application/json) ++ Request Remove known users and pending users view access to record (application/json) + Headers - Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + Body - { - "users": ["username2"], - "usersPending": ["pendingusername2@email.com"] - } + + { + "users": ["username2"], + "usersPending": ["pendingusername2@email.com"] + } + Response 200 (application/json) -{ - "viewRoles": [ - "Admin", - "Librarians" - ], - "editRoles": [ - "Admin", - "Librarians" - ], - "view": [ - "username2" - ], - "edit": [ - "username1" - ], - "viewPending": ["pendingusername2@email.com" - ], - "editPending": [] -} - -### Get access permissions for record [GET /<%= branding %>/<%= portal %>/api/records/permissions/{id}] + + Attributes (CreateRecordAuthorization, fixed-type) + - edit (array) + - username1 + - view (array) + - username1 + - username2 + - editRoles (array) + - Admin + - viewRoles (array) + - Admin + +### Get access permissions for record [GET /<%= branding %>/<%= portal %>/api/records/permissions/{oid}] + Parameters - + id (string) ... The id of the record to update + + oid: `7e72b5952e8e323c77f72ae268a27c46` (string, required) - The identifier for the record -+Request ++ Request + Headers - Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + Response 200 (application/json) -{ - "viewRoles": [ - "Admin", - "Librarians" - ], - "editRoles": [ - "Admin", - "Librarians" - ], - "view": [ - "username1" - ], - "edit": [ - "username1", - "username2" - ], - "viewPending": [ - ], - "editPending": ["pendingusername1@email.com","pendingusername2@email.com"] -} - -## User Collection - -### List users in the system [GET /<%= branding %>/<%= portal %>/api/users] + + Attributes (CreateRecordAuthorization, fixed-type) + +# Group User Management + +## User Management Actions [/<%= branding %>/<%= portal %>/api/users] + +### List users in the system [GET /<%= branding %>/<%= portal %>/api/users{?searchBy,query,start,rows}] + Parameters - + page (number) ... The page number. Defaults to 1. - + pageSize (number) ... The number of results to return per page. Defaults to 10 + + start: `0` (number, optional) - The index number for the first value to return from the result set. + + Default: 0 + + rows: `10` (number,optional) - The number of records to return in the request + + Default: 10 + + searchBy: `type` (string, optional) - The attribute to search by. e.g. email + + query: `local` (string, optional) - The value to query. Only exact matches. -+ Headers ++ Request + + Headers + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + Response 200 (application/json) -{ - "summary": { - "numFound": 1 - }, - "records": [ - { - "type": "local", - "name": "Local user", - "username": "user1", - "email": "localuser@redboxresearchdata.com.au", - "token": "16f613d9-29da-4326-a120-1ad70078f3c5", - "createdAt": "2017-10-09T03:34:47.660Z", - "updatedAt": "2017-11-20T04:08:33.061Z", - "id": "59daee5720b453050057c2f5" - } - ] -} - -### Find user in the system [GET /<%= branding %>/<%= portal %>/api/users/find] + + Attributes (ListResponse, fixed-type) + - records (array[User]) + +### Find user in the system [GET /<%= branding %>/<%= portal %>/api/users/get{?searchBy,query}] + Parameters - + searchBy (string) ... The attribute to search by. e.g. email - + query (string) ... The value to query. Only exact matches. + + searchBy: `email` (string) - The attribute to search by. e.g. email + + query: `localuser@redboxresearchdata.com.au` (string) - The value to query. Only exact matches. -+ Headers ++ Request + + Headers + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + Response 200 (application/json) -{ - "type": "local", - "name": "Local user", - "username": "user1", - "email": "localuser@redboxresearchdata.com.au", - "token": "16f613d9-29da-4326-a120-1ad70078f3c5", - "createdAt": "2017-10-09T03:34:47.660Z", - "updatedAt": "2017-11-20T04:08:33.061Z", - "id": "59daee5720b453050057c2f5" -} + + Attributes (User, fixed-type) + + -## Other Functions Collection +### Create Local User [PUT /<%= branding %>/<%= portal %>/api/users] -### Send an email [POST /<%= branding %>/<%= portal %>/api/email] ++ Request Create Local User (application/json) + + Headers + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Body + + Attributes (CreateUser, fixed-type) + ++ Response 200 (application/json) + + Attributes (User, fixed-type) + ++ Response 500 (application/json) + + Attributes (Error, fixed-type) + - message: User creation failed + + +### Update Local User [POST /<%= branding %>/<%= portal %>/api/users] + ++ Request Create Local User (application/json) + + Headers + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Body + + Attributes (UpdateUser, fixed-type) + ++ Response 200 (application/json) + + Attributes (User, fixed-type) + + ++ Response 500 (application/json) + + Attributes (Error, fixed-type) + - message: User update failed + +### Generate API Token for user [GET /<%= branding %>/<%= portal %>/api/users/token/generate(?id}] + ++ Parameters + + id: `59daee5720b453050057c2f5` (string, required) - The user's identifier + ++ Request Generate API Token for user (application/json) + + Headers + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + ++ Response 200 (application/json) + + Attributes (UserAPITokenResponse, fixed-type) + ++ Response 400 (application/json) + + Attributes (Error, fixed-type) + - message: Unable to get user ID + ++ Response 500 (application/json) + + Attributes (Error, fixed-type) + - message: Token generation failed + +### Revoke API Token for user [GET /<%= branding %>/<%= portal %>/api/users/token/revoke(?id}] + ++ Parameters + + id: `59daee5720b453050057c2f5` (string, required) - The user's identifier + ++ Request Revoke API Token for user (application/json) + + Headers + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + ++ Response 200 (application/json) + + Attributes (UserAPITokenResponse, fixed-type) + - token (string, nullable) + ++ Response 400 (application/json) + + Attributes (Error, fixed-type) + - message: Unable to get user ID + ++ Response 500 (application/json) + + Attributes (Error, fixed-type) + - message: Token revocation failed + + +# Group Roles + +## Role Actions [/<%= branding %>/<%= portal %>/api/roles] + +### List System Roles [GET] + ++ Request List configured System Roles (application/json) + + Headers + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + ++ Response 200 (application/json) + + Attributes (ListResponse, fixed-type) + - records (array[Role]) + ++ Response 500 (application/json) + + Attributes (Error, fixed-type) + +# Group Other + +## Email Actions [/<%= branding %>/<%= portal %>/api/sendNotification] + +### Send an email [POST] + Request (application/json) + Headers - Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + + Authorization: Bearer abcabcab-abca-abca-abca-abcabcabcabc + Body - { - "to": ["user@redboxresearchdata.com.au"], - "subject":"A sample subject", - "template": "emailTemplateName", - "data": {"property": "values to populate in template"} - } + + { + "to": ["user@redboxresearchdata.com.au"], + "subject":"A sample subject", + "template": "emailTemplateName", + "data": {"property": "values to populate in template"} + } + ++ Response 200 (application/json) + + Attributes (ObjectActionResponse, fixed-type) + +## Data Structures + +### Error (object) +- message: An error has occurred - A string stating the error +- description - A description of the error + +### ListResponse (object) +- summary + - numFound: 1 (required) + - page: 1 (required) + - start: 0 (required) +- records (array) + +### ListRecord (object) +- oid: 7e72b5952e8e323c77f72ae268a27c46 (required) +- title: A sample record title (required) +- dateCreated: 2020-12-02T00:00:33.01Z (required) +- dateModified: 2020-12-02T00:00:33.01Z (required) +- metadata (object) - Record Metadata, this varies depending on the record type + +### CreateRecordRequest (object) +- metadata (object) (required) + +### CreateRecordRequestWithWorkflowStage (CreateRecordRequest) +- workflowStage: draft (optional) + +### CreateRecordRequestFull (CreateRecordRequestWithWorkflowStage) +- authorization (CreateRecordAuthorization) (nullable) + + +### CreateRecordAuthorization (object) +- edit (array) (nullable) + - username +- view (array) (nullable) + - username +- editRoles (array) (nullable) + - Admin +- viewRoles (array) (nullable) + - Admin + + +### ObjectActionResponse (object) +- message - Message describing action +- description - A description of the error +- oid: 7e72b5952e8e323c77f72ae268a27c46 (required) + +### User (object) +- type: local +- name: Local user +- username: user1 +- email: localuser@redboxresearchdata.com.au +- createdAt: `2017-10-09T03:34:47.660Z` +- updatedAt: `2017-11-20T04:08:33.061Z` +- id: 59daee5720b453050057c2f5 + +### CreateUser (object) +- name: Local user +- username: user1 +- email: localuser@redboxresearchdata.com.au +- password: Password123! +- roles (array) + - Guest + - Researcher + - Librarian + - Admin + +### UpdateUser (object) +- id: 59daee5720b453050057c2f5 +- name: New Local user name (optional) +- password: Password123! (optional) +- roles (array) (optional) + - Guest + - Researcher + - Librarian + - Admin + +### UserAPITokenResponse (object) +- id: 59daee5720b453050057c2f5 +- username: user1 +- token: aaf688be-0ade-4dbf-845c-d34d9f4cb4ac + +### Role (object) +- name: Admin + +### DatastreamResponse (object) +- message +- success: true (boolean) + +### Attachment (object) +- filename: researchdata.zip + From 607574cba6e38a97eb724731381eaa98075f5ed8 Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Tue, 22 Dec 2020 13:35:29 +1030 Subject: [PATCH 45/48] Tweaks to runForDev to run compliation and installation tasks as node user --- runForDev.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/runForDev.sh b/runForDev.sh index 55627d45c4..ebb4b87428 100755 --- a/runForDev.sh +++ b/runForDev.sh @@ -12,22 +12,22 @@ DAEMONIZE_FLAG="-d" for var in "$@" do if [ $var = "install" ]; then - docker run -it --rm -u "node" -v $PWD:$PORTAL_DIR $PORTAL_IMAGE /bin/bash -c "cd $PORTAL_DIR; npm -g i typings && npm install" + docker run -it --rm -u "node" -e NPM_CONFIG_PREFIX=/home/node/.npm-global -v $PWD:$PORTAL_DIR $PORTAL_IMAGE /bin/bash -c "cd $PORTAL_DIR && npm -g i typings && npm install" fi if [ $var = "jit" ]; then #linkNodeLib "lodash" "lodash-lib" # Build targets are different for assets/angular, clearing all .js files from .ts files cleanUpAllJs export ENV=development - docker run -it --rm -u "node" -v $PWD:$PORTAL_DIR $PORTAL_IMAGE /bin/bash -c "cd $PORTAL_DIR; npm install -g @angular/cli@1.7.1; npm i --save-dev; node_modules/.bin/tsc --project tsconfig.json; cd angular; npm i; make build-frontend" + docker run -it --rm -u "node" -e NPM_CONFIG_PREFIX=/home/node/.npm-global -v $PWD:$PORTAL_DIR $PORTAL_IMAGE /bin/bash -c "cd $PORTAL_DIR; npm i --save-dev; node_modules/.bin/tsc --project tsconfig.json; cd angular; npm i; make build-frontend" fi if [ $var = "jit-skip-frontend" ]; then #linkNodeLib "lodash" "lodash-lib" export ENV=development - docker run -it --rm -u "node" -v $PWD:$PORTAL_DIR $PORTAL_IMAGE /bin/bash -c "cd $PORTAL_DIR; npm install -g @angular/cli@1.7.1; npm i --save-dev; node_modules/.bin/tsc --project tsconfig.json;" + docker run -it --rm -u "node" -e NPM_CONFIG_PREFIX=/home/node/.npm-global -v $PWD:$PORTAL_DIR $PORTAL_IMAGE /bin/bash -c "cd $PORTAL_DIR; npm i --save-dev; node_modules/.bin/tsc --project tsconfig.json;" fi if [ $var == "aot" ]; then - docker run -it --rm -u "node" -v $PWD:$PORTAL_DIR $PORTAL_IMAGE /bin/bash -c "cd $PORTAL_DIR; export buildTarget=\"${buildTarget}\"; ./runForDev.sh aotCompile" + docker run -it --rm -u "node" -e NPM_CONFIG_PREFIX=/home/node/.npm-global -v $PWD:$PORTAL_DIR $PORTAL_IMAGE /bin/bash -c "cd $PORTAL_DIR; export buildTarget=\"${buildTarget}\"; ./runForDev.sh aotCompile" export ENV=development export FORCE_BUNDLE=1 fi @@ -46,7 +46,7 @@ do RBPORTAL_PS=$(docker ps -f name=redbox-portal_redboxportal_1 -q) echo "redbox container is \"${RBPORTAL_PS}\"" echo "ng2App is \"${ng2App}\"" - docker exec -u "node" --detach $RBPORTAL_PS /bin/bash -c "cd /opt/redbox-portal/angular; npm install -g @angular/cli@1.7.1; npm i; ng build --app=${ng2App} --watch --verbose > ${ng2App}-build.log" || exit + docker exec -u "node" --detach $RBPORTAL_PS /bin/bash -c "cd /opt/redbox-portal/angular; npm i; node_modules/.bin/ng build --app=${ng2App} --watch --verbose > ${ng2App}-build.log" || exit let WATCH_COUNT++ fi if [ $var == "interactive" ]; then From d294872bdb5e6ceb6dcda723fb0e21df597bbe8c Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Wed, 23 Dec 2020 16:01:59 +1030 Subject: [PATCH 46/48] Changed favicon to use ReDBox branding --- assets/android-chrome-192x192.png | Bin 0 -> 22273 bytes assets/android-chrome-512x512.png | Bin 0 -> 100909 bytes assets/apple-touch-icon.png | Bin 0 -> 19968 bytes assets/favicon-16x16.png | Bin 0 -> 714 bytes assets/favicon-32x32.png | Bin 0 -> 1734 bytes assets/favicon.ico | Bin 864 -> 15406 bytes views/default/default/layout.ejs | 4 ++++ 7 files changed, 4 insertions(+) create mode 100644 assets/android-chrome-192x192.png create mode 100644 assets/android-chrome-512x512.png create mode 100644 assets/apple-touch-icon.png create mode 100644 assets/favicon-16x16.png create mode 100644 assets/favicon-32x32.png diff --git a/assets/android-chrome-192x192.png b/assets/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..600fd6ef15c27a5d64010faf94f24680acbe78a1 GIT binary patch literal 22273 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 literal 0 HcmV?d00001 diff --git a/assets/android-chrome-512x512.png b/assets/android-chrome-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..e4acf5187047ad50ca7cf9d5e705318e8f52994c GIT binary patch literal 100909 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 z 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%0Ag} zudM|~%qj|cMeKA0;r|FSe$n_7DSicr===ockeGM9mR}m=sXjsv$>!sD9+Me@N{Yor z-JPSFpyrKfU&>zl?3y5|Q-+Pql8A>vaFy-k=+LAMeusPyYGH3r0^C1O#e1+ikv3uU zBm2B>srlqFd$iQ#@N-q_ovN{wDo{y=u74ek*&Bc#P!qvEQZX=W){>{8KN~$V2>| zu14Q7%_;@d|5R=*`;5$dAlcS+CHwIX#O}fxfkD;V;hr-i(og9HenE!v2b=&SjuGm< z`O4%6hC6)C6}7`XNduUu*Af&W&zg>4Jhgj+*KfR$TI z`IZi?rV%15UggzC>+%9Tu+q)j7psGmKc%9(NN69W>EZGn+1{^JY?otzrw#u_w|mE} zGX&)!e^?6gLoB*#4lPa69vg$@#T}m*fXJs#O7KmQn;y>R*fK*dhY9~g@7J`N3emw? zo?)iDI85fQ-!c1k$-8sbx!voCKtFo=E*$n$i_MHiz!lPKZV+tw%;`Qh@zSp^QYS~O4FWZ# zPp9+EjaN{rfwJ4`P$7;Wz*PML=V2MZF7njFOndbV?GR~ z^>e6BE9S)6V7~!XsKVl&omX4WpM7JYqmkc{2MowAi~wEa(Ogy(#zh(f`t%;KPExNQ z5~m5O2wmh2i-@}p^>Yoq>)TgRj^-^zNx4U91do!zx3;-*RbS zFv#NuwDnjbF!2bt6c=2Y7%Hbos>TXf2%CF@zjj1c;DT;0rg7mz5rP^x2lxHvcC_5C zjDWLWQ=0y={%4o}kfCp#fnm`y8c?Z|YH_IH2swIwU!`+Z_ajCL5Dt^P&`PR*WZFY2 zXgh!ixz>*X4tAg(00!fm{$OiE#<9)lH`IWqUI8g!l@Visscp9Bx|ii(NfXO3_;Bfb z_%(Zx^eJjy-|wN6AEz@SgxVAL&41vqYZkft(dEvabz*Arvw%fRoeSufl<_- zVsfnC@Rx0!kk+)4mvMzrBejBPYUSc3TD;;hSIJ+#4| z+v{_|68bEkQ*oYdH<(ouT{+V#&j%AR=Q-<}y8Px2PcdgVsw%M?QnLRH*EMvU=hS5$ z6nP@6=S2U|Wci_6asVL;7e03g7Cv5kkg^P`+Fv(4t2)yho&#R7R9fWT8#m{g+pO~+ zWLq2V72Dwgo7_7K^1;?o*qFMbd^a8+_1Pe0sVAe(fJvf11H6uJ0)jE-Z;xk$*QTh7r>#erB;Qgx z%8I|G-M;(WRc%tuiO%4j!*J|F?!Bz>Rpu{EHLpRXcAM{k{=LrGnb8{bBY*LGq0>>P zo=l3Bxr4j+3#UgmWEtc3_Pe9bOTR|6$`yEq(gDwBT0L?6!WMpEnch@$%K~ z0~=o6|1RDNvc_lN4Vfx8q-3314-4BsR8|TYiP1DqJU-#&+{e4I`V}7Vy?$(UCD}y@ z^lR8ztF4jD2R8MKw`b(P&Ms@t%03wZGyt_>PbnGTtAbu^n$1u3Sl+`~cX!8nmVlOn zl-Lw1i-R~7Fek&0<#l;{tL@hhY^(m%WB!lu>QhOg{4^G{{E1JJs*=cLez^=|!so z+7SfMII`hBIXEVGuF26N&YXlvQ0TE1sYtjm&CKRCB z;7Dl3rQ+`L%NKsZZwuv9*-KiQ%R+KpeVi(MbVR{1&pan=SWvgta@xo~{=E;RKuY;piRK^<6i8Gm^-AnH6Ux9jDzZlCK0i__5H;teMcwvHPX)s|0*v#4;dwNnf-s&`u+ zvk+h_eC*1A#1HMWo8v%giY>l#qDm8YRvPXQfeN_`o##C3uI4ny*H&v4BC2al3fZkc zoLjv+uDD^In~i(+vENLr?f9%Zzn=Fbb?-@M&-8slR4%)%vpmP1U=NHi2I93B9zAxC zb5oj(wrClF@vOznCKp=7R#f;PE!h{OMz9~)G+I=qwhDq~Q50jiROc<$P3t+hL8{QY z&Y3YU27&7fsX<19LTN$cpkp!Gd(;dh=lNTwKvcgl|H_;4X0qGVV2#cvnxiNABnVr7 zYcw)IM0Z~HecD;kDA(g)&BETM3tS`Zw-%B|^6Ba;Dqkx>9+dWf)}V>A=Aw^d=sE+z zIB$O@Cy4`A!d9^8U-rUPb*AQyY7yf4--C>|$nn(CKaqCPD=3?VA<@a6GI>M7Xb$hR?l~G7{lSo8@XGJhXE}9Ieh@r6z@1C4kys< zpkr~%co`8UD-U;4;H1@y+q?zeK*MItOKKEVlrd<)rk#HbSezr2v{3RzVCA7@(qxgW zC}rg2o4yy~3TvrMEgQssb-g<*nf_s;^oHC6)&#u*ww;_GN7bB`4p=TR8pxWQea$v> zLawXbV!-NFQS0v6r(0`pmZqd6nq79fkH6?C0`Ytd4jw8zXKn(bv& z&(T)@Z^9gc@tH~i?ju9N6xRtVb*Jz_p2k^3@U~$;n<+{#_sG29=t-vG(4Qn3W8b;k zcPL}u{7CfiZ?L$Td;??nFVF*E117A#p~s9(=21vJ#>k$puhZsLEB|3)=^Dc=xi4rmdtBU$^bpOXxKmpE%n9X%3FEdu|Q-Q_pz zW6L{QtCfVF{4k;17j=u^=v-2>CI*(E@P9WtB1am7SE!0RZu$$w?#Bk?x>U1Bg92e2 zdT8~^3S}P{eb$|8;4x3-t?1!5!ktV6K!Z@b=2IlNP5k;VI_p!Ltqq;9RsHol!8?M( zj3LuD5Pd@xiZeGtvHaa`MBHNZ;+WU+lQeJ`n<_2N{&s2OV~;F%wV*wqZ}G0GF5@CHJz zrvLkYxl%r6q^DmC#f)@l6P{43GUQSB-ZAIkeOB1z0P;v;heJxhc5%_2hC%x#!Rq*` zY4P2%2|As`ZD1oG3KYtUm2CAg8`iZHVh5t|w5AiX_iML|HCZqDj)GHnh?2YwY-w5I zURLrOrp1R@Svt)KvnYNa9qe5-Z;sRJ~*`zG(3+w zKj38N=RlkZ$qHsRt)1Qs)55VZbS5szw@~x?ZX(%1MPwkbYtY#{^lp&WW%CK!bf#0I z;KHtC#0Lmw3hkU?(csa?&%q_d(L{9UPO^G|sC|#YgAQZ4=-5HDOqHY+IJCthwZ4Dp z<%IA&pYK|pcg+w$?{CAjy0!5Ufl^NBmK^8rrBCS{t50$YqtYob?#sw8?F&#*SGx}u z=Bhg$vpYrqGwh>aSeyNSzo>&c13Zw>ZeAW9BGBNm`p~sh_DqYc@0>w=^(dDb6ex3t zvIfPug#vL`P_SVf2w<-=14_;hU6dh0zj3TwZd%&u8QNYk5VzZ@Ucw_YVoM$P+j5?T zmQYt|JqU+K14oR$baaNBo8So1d=)T}utD|b2rPfj^#mnwgW|+jW($I336J)^` z3}nHP%=@GH9$RBC%XJ%7g^3m5=r7+FIQeZG5JzrY2{GS7Z~k628Hh?E;LrCKN7Sn{ z;4Vo2b2aFlf!>AC8aw4?wA6|S#Dy7l1sKZOg?JO8K-lw&G%ECA0g=w)NiN-fsKZtQ zspzf_8XmJ$6806U8Kgkp7j%5+QIx@MXb`>xbdR0f_Bp+4`q^=(RX%B&gkz&bu4#_b zg;3yZD4BPr<*rw{BJ{UrO~V1B{bMb^+!S2(HF0as;xg?D#9Qs3Jdh1*o}@%MiiAnL z$s#T(?&gpB#iRqGC28Fz1!+bvQww)I}Crf`c9#S9eGtk zHo0gB6zs3qT_l~zc4CR=AGuxP@K!94%}qPudvtB80j2W&!?y{Zr-{@b`<~Xkw)wZ_ zIHZMwA7m`sNx;W|a`j2(?5Lz2+YQQPqfLu@H|mn%k8N-~3$pu8TqLw>mUp_4 zp1yC8P}j5uaRI-;Kv}vvk$2j3`uEfp1FP7kUToc3($^=yth>JfNs0e`3SCFMa21Jr zr(Be@4en;plt3iHrPyOe{&)cKt~t*kX99ezPET`7cdA2MZ%Y5AnoDR_PX28lBQ^o? zO-JNgi*df>s~N&6}I>tu#a_;XXYe;>_{`E;Fb<(0a})zivkz%vR-40;S3OXIp8 z#m2lILuI5szK?T0%JmDTT$EKf(6VEM3EeT|zE3<}H$+{LHt<35MljSM4VMa)>12_!OWa-~=ykvC?1*Hb}9T>Bo*~L3^)N(cUEer17!AfrKVm z=^k$zKE4~9#dL{KV+5}FNH&FilbERl-Kt9 znI7>n3$ZJzhxB8C;DXUU#2fV~kRPMFEUMuK708N%zpD4F7!u&~m!>qh1&Vg!vYom3 zO}17<$e)_nr*>z@qBCaD zaTH-$h#CjpJ!732tUnNHhT{SwI$deT0(9cmmk+c5JsCh?-L&&?!U`h|6XGr8#ZWRyDZLw*wA&L zKY0NH)orzpqW0S4xr-k93T_I3BqVd3f8$=5aHi{Rgv!v-urlg3WTGLhNYnMXROL+; znc#W)hLi0-?}X>G_Wm7+mnf=e|K|(jVgS8POh!iS_M#c32RfLp%4}W5BX<_fM<*B3uZt`>EqwQr6Ql@vhKH(P-@viT6?GAMBwHebQt8X|@ScD>e`q z2j)dz6R{&A+x3TbsRfxcF5eM>m%<_! z9-e6`vTd2QCYAZ?+9LX@mZR3y{IJLG8DilY_6}+{e>vBp&5GNS->p|_n~VK+Gz3G7 zsN|oU{TCJf@6tt!U!l~B7Fe90_5k8mhpo*N_=lWAO^ejpe*Rigvz!BkD9dsopI}83 z8}zvp4ZX<>f(uxHUGd;Phe~N4to(LXQKQz5jeD@jvMJrluuB z5F|klF{bjRoRr6y2v zp!GvVb{OKmThT!xPl+7SC7cGGC40lJ&9`6D225iva4pbXVw|Oy5SMo9ulQK5-5;Wr z5r13L*}i2x*?RlmUt=u3l4XK4F0CjJdEyAUQ_j`*tHS(-7k*gp4!Nq za6!Z0uUe~b!~XgJ%xPj^_m6{q%MWyz-gCW})qQ#@yoM-Iy$hlvKSQuuX9m=oZuJ7h z)&e<|89b|R6CRG<(F)Uf9^J!8hGmAW>iGr89=0Oz{-*qgnM5LCNq}?Hk|t&W&~j+! zm<>HfVqtc8hup7RpU~sih1nK@%YrP&Dqtj<{1sa7NfTBXil#80W2$<0{@&5VDWAw% z9|FRx>ncWz6}4JT6pGk)t@d<^cur@~8E-1e(?V*oY(?NbJQx+#bb6e}ed>p=aJ57` zCI$8%uo#n?_R@M{z#<@;w&emLuV9Oz(a$)&TgsWSmOr_pT#gi}tV^H1N^%8BGJg zU#@5rTA6niZWG$_OTjXh-ThX$B=?AqTl$qhvnbLwrRr|E6PZ14|1i`3C^2vR-xmi; zbhcMNOuVnk#tq`50R&xACA?qR8+1ojGzJV$E^9?zvsxfc+~#x~l1R~)VclvjhTUM2 zWBPNN+ZKD!t8iH4bA~a~=sFYW>s|_)(l~7v4*WA0v@_cj5v62f4!s7^cs0AO?#f%| zsgQlq8P^Vn|LiLu_@8|#$TizN-AoDX3cI|X@GHzMl)yZ zYOH^oWW0kE4fAf4z$EQiX@D#N-Tt*ED&eC@1(G*`;CbGTyW!cFJ!@{q&3cjwQnloE ze|&*VAMr^uI6ZtI4d<0I0bERX^`W!RD~NjyVfBF8wifYnOE?C}oxKPn~Wx zqp`*i(S&}bx(yCtoo@D>hUFqK0w$Hq{xADu;*nJT8Uq=`s)@IS~WCB?w&q;ER&IGU@*Ep63 zr>;MGS0&$Pq`%DH`ziY*(>$$`^G`Y!yUH$am0GC>FY?(P8qc>GJIdVn@>Zeq`iBY9 z4B3H>zez4O=B?z^{(_^u&JhRI#C!)m1y>m&ZZ87w!n+DcSbY~kKlNxXUAGqqejtr1oz97NoI&#DP5QjP&Q zjRVHD>C7SEBmifw%Uy%d3Ib67E}>5UqI5-HoZ9C50D^#A-A|KR?1`%&#(~82$4#&m zXx)gF*YLHjgLO%Rgb8`~TSK|M7?{`)pbnSEqoN5IAKiU1P)W}@N-2eap M07*qoM6N<$g6m-jg#Z8m literal 0 HcmV?d00001 diff --git a/assets/apple-touch-icon.png b/assets/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f801b3e1d156294c1e26f35d42a4086a4beaf269 GIT binary patch literal 19968 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 P00000NkvXXu0mjfB7(N{ literal 0 HcmV?d00001 diff --git a/assets/favicon-16x16.png b/assets/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..eb1e4cd9b2b0a726684ba84cd056cb0f2fcbd132 GIT binary patch literal 714 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&Rm5z+~d-;uvBf zm>cMu9qcG#`}y8x&sobfIdyL>Q}If=r15bzo48Y8c2I%hl&A}>(&4Y}wQAQl?hp_Z zbUoOWqS)mf-lWjf>6N=UcFGbDFGt^uq|YDI)_uM-GETkyc*d=bZQqloiN#v$p8e-+*l1$R^Xcv$!#(OVYv%I1 z$=7{o{T=!!EKfMXSEWtG;GLK6qHj+mW77RL#`*3xy0L!$O{EpfeHUC!&}8a~{U|Z# zrRR*JNxSQOJMMmGZtU>dRy3Qz!ryw*;}o$lh2$&q(r1Kjak4qH@cBDl_VxFJEX)`u ze@-b@JTEnW6Hi-WVWQB*e4(DkoeMYaK6v2X^4tZVZ?1Wz;+te!v)AjK{~8$&38>lC5&4;QJ}t`DZgv%H5H2H8tS3Myx<$uf?36iz`m|I>Z#+w%@9oD7;}E zyI%aFd!3W7Oq%M;RJcrWkNq(x~59XQkA*Y*ThCHWWR3N#ZdN^`BQiD z@a&hbm$R9s{ASDKiQhjchScjga%{Y}Hla8mH}ss@Gx2wSYQM)>@b`Fbn=|iD+D@K* z8ymgM%%3m#^)rt#b=m9-K^kU?9@Q$(cdX}dTvp>NTcW%5kL_jOZI5JGbrj@#tG?_i o+kN230?%7lR~||_xhwp-ebmdKI;Vst0CNf~00000 literal 0 HcmV?d00001 diff --git a/assets/favicon-32x32.png b/assets/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..e82c0d36eef8617e71307c1171b03d6afc7e4fa6 GIT binary patch literal 1734 zcmV;%208hOP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91AfN*P1ONa40RR91AOHXW0IY^$^8f$_FiAu~R9Fe6R%=XDM-)CYcke!V zxU7Hz0)n6@DoCM8V-+7ot+p{Srm?Li+Eim-*4Gb{CQ{qfnwXfHwrW#?CjH?Pt+h67 zYPDJ$+W?|ykf;HRP(U8S;x1h7z0)%n_UHI}%FCbxo<6QxrmPGayvVna+sa0n(q>NUzH7HOc+!3mflW94SU9y%0*t zbz7XVt5hH_bD4EKhWMjLfW(dk_10}ylq|o#I?CxU1(oK|Xj9RBmj7-b+`&d+sN^9<&zFa5|XEWB{{D$##{R zuN_F5Sn$6HsGW}sF}z^Lv+vxq3#6(WpfoiTrS%b*k`kYa-NF=o4gIF$q5b_pLh{c2 zlO|bo#V~?l2AI@8kK0RC)x>?U1eh!XiEx10-T^{<@ROio`a4urF9-KS5>`fOv8h+S zd~jUyRK7OcFeKn*5GymSw?L|_A|2ay5GE_2JnjPOiV2G4MgksSK?HmTW+3_FES7D) z`Otqu0xF@T9|-9~5_lN_>vjY5^+Dg2t6)k?1u*&hw>2RBfdJ7|UR zl%hM&1}Kfmh$%#5iaI;UWn zoC)5)%As|~$0TCmJh0}@&@+Jo5eFMBu4n;ET(KPfDLD)roDZq39h8863Bc7wLjnv2 z8cXqbAZEilh+MXqbsZ}M*VYeoXD;|!?ccLM68Xs!?VZpJki0p@2|YD;1}>8zFaN}_ z1l)uW0)4k@KzY>0#MESB&6x?dSvmX}W@@)P;E17!fc%m2y${4Jf`|mVs&8Sf{D4Cx z!wmQgd(Qt1w&!M%&TmSgZTDW{Ew2P>w`uCZ*Jz+5oPdf*NScqB=L^`Q3zr7VVa#Ae z0^aiL#CxL}oa@&z>F#~fxMn?(n_GRE*TD224vxoHjErQ-Z@$JJd|Rrkj08gh1k}Um zH_;Z$Ddd5=`6k3A?xNOCt9?;Y4M;(<*{&115$9 zGV5zgMWWeHJ5>1G@|@{B95Nwdlub#Nsf{$V#(PtaY@iQDfHtDklfJ{us#wGQ4 c>YpXjf5Mtj9xqhS^#A|>07*qoM6N<$f}<%AZ~y=R literal 0 HcmV?d00001 diff --git a/assets/favicon.ico b/assets/favicon.ico index f59e35835c1950b8a5127e6ce6029e4d04c767f5..383d206bb4c41e72ac7dd4f8e37de57641b1b04c 100644 GIT binary patch literal 15406 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 literal 864 zcmV-m1E2hfP)BdQjq$;JF5Y%WB38D}rr3}-#^LOuY zfn%}0+jE}0C+~UTI%x_6FxP$O;kjeG7w3*OFV1z}eR%zBz2tgWdI)AyyYDfCCPXX* z0UDHW#O&Sr$`2EjoeE9qad1r?n?-rROe-hN$jT@Zto3T*KHJb2>uKR)RZ3D)mgfaIChM8t`<<&E~2 zq8kn#sSP)L_r}-*!FEpOYij*vTlIXds{Wam4oPffOuujYt@lvDsTxlt5(9E{^!`^P zCej5yzA8n(@LGPO^G@HsbMybn2O>jf29w2-IblmVa6%Z7;|9u|^|3}8YHrQBP8db^ z^zA!0|Ha!);R-xGyx$eb=bYMyx71WqSHIPZ$KyEGcAB7E_O2FJ@B;f~(lTlxPUNR>4BEXL$hzKfz!UzD4 zhEpq_I!zhpgB97Amcd zk$Bk;9-1wMPF0-!>dmLrh9C3^Z}H399x3M=`zlSgBL~C>m73Tut#y$Q8$(VhoA=z& z*`2%e8(zkXwflj$U1EE{#*3PtbEPodqA83Bz8M}?2Sm$7Cg-mdxU`tFORE9ZmY-;# zKc_c8D?xrrGZSd$qQ(0aDdr}nhd>p_9+*Y}W-sSDV__Q;rKKF`gu~1c*~S7){jOt; qPX^_yo3t>ySHzq*ezX%Z0QxsWUT8=+WjOKx0000 + + + + <%=typeof title == 'undefined' ? TranslationService.t('default-title') : title%> From 86e3fba1d5cfc2c9a2596da19cb18bdf42474ebd Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Wed, 13 Jan 2021 15:24:46 +1030 Subject: [PATCH 47/48] Fixed builds to use node_modules versions of dependencies such as angular-cli --- angular/Makefile | 66 +++++++++++++++++++++---------------------- dev_build/buildFns.sh | 4 +-- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/angular/Makefile b/angular/Makefile index b200dba1c5..79cc288204 100644 --- a/angular/Makefile +++ b/angular/Makefile @@ -4,72 +4,72 @@ help: build-frontend: @echo "Building local_auth" - ng build -a=0 --extract-css true + node_modules/.bin/ng build -a=0 --extract-css true @echo "Building dashboard" - ng build -a=1 --extract-css true + node_modules/.bin/ng build -a=1 --extract-css true @echo "Building record_search" - ng build -a=2 --extract-css true + node_modules/.bin/ng build -a=2 --extract-css true @echo "Building transfer_owner" - ng build -a=3 --extract-css true + node_modules/.bin/ng build -a=3 --extract-css true @echo "Building report" - ng build -a=4 --extract-css true + node_modules/.bin/ng build -a=4 --extract-css true @echo "Building manageRoles" - ng build -a=5 --extract-css true + node_modules/.bin/ng build -a=5 --extract-css true @echo "Building manageUsers" - ng build -a=6 --extract-css true + node_modules/.bin/ng build -a=6 --extract-css true @echo "Building export" - ng build -a=7 --extract-css true + node_modules/.bin/ng build -a=7 --extract-css true @echo "Building userProfile" - ng build -a=8 --extract-css true + node_modules/.bin/ng build -a=8 --extract-css true @echo "Building dmp" - ng build -a=9 --extract-css true + node_modules/.bin/ng build -a=9 --extract-css true @echo "Building workspace_list" - ng build -a=10 --extract-css true + node_modules/.bin/ng build -a=10 --extract-css true build-frontend-prod: @echo "Building local_auth" - ng build -a=0 --prod --build-optimizer --output-hashing=none --extract-css true + node_modules/.bin/ng build -a=0 --prod --build-optimizer --output-hashing=none --extract-css true @echo "Building dashboard" - ng build -a=1 --prod --build-optimizer --output-hashing=none --extract-css true + node_modules/.bin/ng build -a=1 --prod --build-optimizer --output-hashing=none --extract-css true @echo "Building record_search" - ng build -a=2 --prod --build-optimizer --output-hashing=none --extract-css true + node_modules/.bin/ng build -a=2 --prod --build-optimizer --output-hashing=none --extract-css true @echo "Building transfer_owner" - ng build -a=3 --prod --build-optimizer --output-hashing=none --extract-css true + node_modules/.bin/ng build -a=3 --prod --build-optimizer --output-hashing=none --extract-css true @echo "Building report" - ng build -a=4 --prod --build-optimizer --output-hashing=none --extract-css true + node_modules/.bin/ng build -a=4 --prod --build-optimizer --output-hashing=none --extract-css true @echo "Building manageRoles" - ng build -a=5 --prod --build-optimizer --output-hashing=none --extract-css true + node_modules/.bin/ng build -a=5 --prod --build-optimizer --output-hashing=none --extract-css true @echo "Building manageUsers" - ng build -a=6 --prod --build-optimizer --output-hashing=none --extract-css true + node_modules/.bin/ng build -a=6 --prod --build-optimizer --output-hashing=none --extract-css true @echo "Building export" - ng build -a=7 --prod --build-optimizer --output-hashing=none --extract-css true + node_modules/.bin/ng build -a=7 --prod --build-optimizer --output-hashing=none --extract-css true @echo "Building userProfile" - ng build -a=8 --prod --build-optimizer --output-hashing=none --extract-css true + node_modules/.bin/ng build -a=8 --prod --build-optimizer --output-hashing=none --extract-css true @echo "Building dmp" - ng build -a=9 --prod --build-optimizer --output-hashing=none --extract-css true + node_modules/.bin/ng build -a=9 --prod --build-optimizer --output-hashing=none --extract-css true @echo "Building workspace_list" - ng build -a=10 --prod --build-optimizer --output-hashing=none --extract-css true + node_modules/.bin/ng build -a=10 --prod --build-optimizer --output-hashing=none --extract-css true build-frontend-prod-sourcemaps: @echo "Building local_auth" - ng build -a=0 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true + node_modules/.bin/ng build -a=0 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true @echo "Building dashboard" - ng build -a=1 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true + node_modules/.bin/ng build -a=1 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true @echo "Building record_search" - ng build -a=2 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true + node_modules/.bin/ng build -a=2 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true @echo "Building transfer_owner" - ng build -a=3 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true + node_modules/.bin/ng build -a=3 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true @echo "Building report" - ng build -a=4 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true + node_modules/.bin/ng build -a=4 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true @echo "Building manageRoles" - ng build -a=5 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true + node_modules/.bin/ng build -a=5 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true @echo "Building manageUsers" - ng build -a=6 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true + node_modules/.bin/ng build -a=6 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true @echo "Building export" - ng build -a=7 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true + node_modules/.bin/ng build -a=7 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true @echo "Building userProfile" - ng build -a=8 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true + node_modules/.bin/ng build -a=8 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true @echo "Building dmp" - ng build -a=9 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true + node_modules/.bin/ng build -a=9 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true @echo "Building workspace_list" - ng build -a=10 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true + node_modules/.bin/ng build -a=10 --prod --build-optimizer --output-hashing=none --sourcemaps=true --extract-css true diff --git a/dev_build/buildFns.sh b/dev_build/buildFns.sh index 3a072ce0d2..8bc11a2623 100644 --- a/dev_build/buildFns.sh +++ b/dev_build/buildFns.sh @@ -67,6 +67,6 @@ function compileAoT() { function convertApiSpec() { echo "Running AoT compile..." npm install -g api-spec-converter - api-spec-converter views/default/default/apidocsapib.ejs --from=api_blueprint --to=swagger_2 --syntax=yaml > views/default/default/apidocsswaggeryaml.ejs - api-spec-converter views/default/default/apidocsapib.ejs --from=api_blueprint --to=swagger_2 --syntax=json > views/default/default/apidocsswaggerjson.ejs + api-spec-converter views/default/default/apidocsapib.ejs --from=api_blueprint --to=openapi_3 --syntax=yaml > views/default/default/apidocsswaggeryaml.ejs + api-spec-converter views/default/default/apidocsapib.ejs --from=api_blueprint --to=openapi_3 --syntax=json > views/default/default/apidocsswaggerjson.ejs } From fabf5591156015c242bec14d9d25ef0b255303a0 Mon Sep 17 00:00:00 2001 From: Moises Sacal Date: Thu, 14 Jan 2021 10:10:10 +1100 Subject: [PATCH 48/48] add completer email as a description --- angular/transfer_owner/app/transfer_owner.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/angular/transfer_owner/app/transfer_owner.component.ts b/angular/transfer_owner/app/transfer_owner.component.ts index 808b926e28..785ed59b72 100644 --- a/angular/transfer_owner/app/transfer_owner.component.ts +++ b/angular/transfer_owner/app/transfer_owner.component.ts @@ -140,6 +140,7 @@ export class TransferOwnerComponent extends LoadableComponent { fieldNames: ['text_full_name', 'storage_id', {'email': 'Email[0]'}, {'full_name_honorific': 'text_full_name_honorific'}, {'given_name': 'text_given_name'}, {'family_name': 'text_family_name'}, {'honorific': 'Honorific[0]'}, {'full_name_family_name_first': 'dc_title'}, {'username': 'username'} ], searchFields: 'text_full_name', titleFieldArr: ['text_full_name'], + titleCompleterDescription: 'Email', vocabId: 'Parties AND repository_name:People', editMode: true, placeHolder: this.translationService.t('transfer-ownership-researcher-name'),