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 61f3775cb5..4e0a7434de 100644 --- a/README.md +++ b/README.md @@ -16,3 +16,22 @@ Requirements: - Node 12.16.0 Development requires Docker. Run `./runForDev.sh install jit` at least once. + +- Docker + +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 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/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. * 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'), diff --git a/assets/android-chrome-192x192.png b/assets/android-chrome-192x192.png new file mode 100644 index 0000000000..600fd6ef15 Binary files /dev/null and b/assets/android-chrome-192x192.png differ diff --git a/assets/android-chrome-512x512.png b/assets/android-chrome-512x512.png new file mode 100644 index 0000000000..e4acf51870 Binary files /dev/null and b/assets/android-chrome-512x512.png differ diff --git a/assets/apple-touch-icon.png b/assets/apple-touch-icon.png new file mode 100644 index 0000000000..f801b3e1d1 Binary files /dev/null and b/assets/apple-touch-icon.png differ diff --git a/assets/favicon-16x16.png b/assets/favicon-16x16.png new file mode 100644 index 0000000000..eb1e4cd9b2 Binary files /dev/null and b/assets/favicon-16x16.png differ diff --git a/assets/favicon-32x32.png b/assets/favicon-32x32.png new file mode 100644 index 0000000000..e82c0d36ee Binary files /dev/null and b/assets/favicon-32x32.png differ diff --git a/assets/favicon.ico b/assets/favicon.ico index f59e35835c..383d206bb4 100644 Binary files a/assets/favicon.ico and b/assets/favicon.ico differ diff --git a/config/agendaQueue.js b/config/agendaQueue.js new file mode 100644 index 0000000000..dc4b288783 --- /dev/null +++ b/config/agendaQueue.js @@ -0,0 +1,27 @@ +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' + // } + // } + // ] + jobs: [ + { + name: 'SolrSearchService-CreateOrUpdateIndex', + fnName: 'solrsearchservice.solrAddOrUpdate' + }, + { + name: 'SolrSearchService-DeleteFromIndex', + fnName: 'solrsearchservice.solrDelete' + } + ] +}; 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/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/routes.js b/config/routes.js index 38a524ece4..ae38f1f510 100644 --- a/config/routes.js +++ b/config/routes.js @@ -246,8 +246,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', @@ -268,18 +273,61 @@ 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', + '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', + 'get /:branding/:portal/api/search': 'webservice/SearchController.search', + 'get /:branding/:portal/api/search/index': 'webservice/SearchController.index', - 'get /:branding/:portal/api/users': 'webservice/UserManagementController.listUsers', - 'get /:branding/:portal/api/users/find': 'webservice/UserManagementController.findUser', + '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', 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..e7c87b2f29 --- /dev/null +++ b/config/solr.js @@ -0,0 +1,205 @@ +module.exports.solr = { + createOrUpdateJobName: 'SolrSearchService-CreateOrUpdateIndex', + deleteJobName: 'SolrSearchService-DeleteFromIndex', + 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 + }, + { + name: "text_title", + type: "text_general", + indexed: true, + stored: true, + multiValued: true + }, + { + name: "text_description", + type: "text_general", + 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: "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", + 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': [ + { + name: "date_*", + type: "pdate", + indexed: true, + stored: true + } + ], + 'add-copy-field': [ + { + source: "*", + dest: "full_text" + }, + { + source: 'title', + dest: 'text_title' + }, + { + source: 'description', + dest: 'text_description' + } + ] + }, + // 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/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/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 } diff --git a/docker-compose.yml b/docker-compose.yml index 4fa1e1bb7f..0342409775 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,65 +4,54 @@ networks: services: redboxportal: build: . - image: qcifengineering/redbox-portal:latest + image: qcifengineering/redbox-portal:backend-refactor 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 - 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" + - 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: - - redbox - ports: - - "9000:9000" - + - rbportal + entrypoint: /bin/bash -c "cd /opt/redbox-portal && node app.js" 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" + 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/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/package-lock.json b/package-lock.json index 56201b1bac..80654e80dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "redbox-portal", - "version": "1.1.1", + "version": "2.0.0-alpha", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -336,6 +336,23 @@ "safe-buffer": "^5.0.1" } }, + "@researchdatabox/sails-hook-redbox-storage-mongo": { + "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", + "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", @@ -384,6 +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", @@ -395,15 +441,28 @@ "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.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": "*" } }, + "@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", @@ -442,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", @@ -449,109 +516,125 @@ "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" } }, + "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", @@ -570,6 +653,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", @@ -638,6 +726,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", @@ -981,6 +1124,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", @@ -1042,11 +1190,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": { @@ -1194,6 +1349,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", @@ -1238,11 +1398,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", @@ -1512,6 +1667,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", @@ -1539,6 +1732,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", @@ -1951,6 +2149,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", @@ -2277,6 +2483,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", @@ -2406,9 +2620,15 @@ } }, "csv-parse": { +<<<<<<< HEAD "version": "4.14.2", "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.14.2.tgz", "integrity": "sha512-YE2xlTKtM035/94llhgsp9qFQxGi47EkQJ1pZ+mLT/98GpIsbjkMGAb7Rmu9hNxVfYFOLf10hP+rPVqnoccLgw==", +======= + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.12.0.tgz", + "integrity": "sha512-wPQl3H79vWLPI8cgKFcQXl0NBgYYEqVnT1i6/So7OjMpsI540oD7p93r3w6fDSyPvwkTepG05F69/7AViX2lXg==", +>>>>>>> backend-refactor "dev": true }, "cuid": { @@ -2494,6 +2714,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", @@ -2513,6 +2751,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", @@ -2551,6 +2797,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", @@ -2590,6 +2851,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", @@ -2767,16 +3033,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", @@ -2844,14 +3100,17 @@ "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.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", @@ -2862,12 +3121,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", @@ -2909,35 +3162,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", @@ -2947,7 +3197,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" }, @@ -2958,13 +3208,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" } } } @@ -3261,6 +3509,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", @@ -3720,6 +3973,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", @@ -3827,11 +4088,6 @@ "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==", "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", @@ -3978,22 +4234,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==" } } }, @@ -4363,6 +4626,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", @@ -5063,6 +5344,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", @@ -5131,6 +5417,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", @@ -5170,6 +5461,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", @@ -5203,17 +5511,30 @@ "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", "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": { @@ -5685,6 +6006,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", @@ -5905,6 +6231,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", @@ -6011,6 +6378,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", @@ -6032,6 +6412,23 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, + "json2csv": { + "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", + "lodash.get": "^4.4.2" + }, + "dependencies": { + "commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==" + } + } + }, "json5": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.0.0.tgz", @@ -6066,6 +6463,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", @@ -6125,6 +6527,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", @@ -6642,12 +7052,22 @@ "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", "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", @@ -6703,11 +7123,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", @@ -6744,38 +7159,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" } } } @@ -6814,6 +7253,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", @@ -6850,6 +7294,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=" + } + } } } }, @@ -7007,15 +7466,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": { @@ -7026,55 +7485,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" } } } @@ -7098,53 +7526,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" } } } @@ -7255,6 +7662,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", @@ -7300,6 +7712,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", @@ -7324,18 +7741,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": { @@ -7516,6 +7926,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", @@ -7581,23 +7996,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", @@ -7625,22 +8040,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", @@ -7653,9 +8052,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" @@ -7667,6 +8066,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", @@ -7679,14 +8094,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": { @@ -7695,28 +8119,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": { @@ -7800,6 +8218,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 } } }, @@ -8040,9 +8492,15 @@ "dev": true }, "newman": { +<<<<<<< HEAD "version": "5.2.2", "resolved": "https://registry.npmjs.org/newman/-/newman-5.2.2.tgz", "integrity": "sha512-aRkh8eeRkKBlwxwBSdYQFZVLvSNMfRDmwPUfddlYR6PwZVKwukmoJmo9QTkRmo6vehgMmt2RTVkfwIIp73X1gg==", +======= + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/newman/-/newman-5.2.1.tgz", + "integrity": "sha512-kEuTMQCiORHZFx92sPVih8RHsJ40JxgxvlsrUe6MFXLQg2/UrO1KaUQCDabTy41tOu0a+dx6Mtg+x+uK1rCPcA==", +>>>>>>> backend-refactor "dev": true, "requires": { "async": "3.2.0", @@ -8050,18 +8508,32 @@ "cli-progress": "3.8.2", "cli-table3": "0.6.0", "colors": "1.4.0", +<<<<<<< HEAD "commander": "6.2.1", "csv-parse": "4.14.2", +======= + "commander": "6.2.0", + "csv-parse": "4.12.0", +>>>>>>> backend-refactor "eventemitter3": "4.0.7", "filesize": "6.1.0", "lodash": "4.17.20", "mkdirp": "1.0.4", +<<<<<<< HEAD "postman-collection": "3.6.9", "postman-collection-transformer": "4.0.0", "postman-request": "2.88.1-postman.28", "postman-runtime": "7.26.10", "pretty-ms": "7.0.1", "semver": "7.3.4", +======= + "postman-collection": "3.6.8", + "postman-collection-transformer": "3.3.3", + "postman-request": "2.88.1-postman.27", + "postman-runtime": "7.26.8", + "pretty-ms": "7.0.1", + "semver": "7.3.2", +>>>>>>> backend-refactor "serialised-error": "1.1.3", "tough-cookie": "3.0.1", "word-wrap": "1.2.3", @@ -8075,9 +8547,15 @@ "dev": true }, "commander": { +<<<<<<< HEAD "version": "6.2.1", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", +======= + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", +>>>>>>> backend-refactor "dev": true }, "filesize": { @@ -8086,6 +8564,7 @@ "integrity": "sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg==", "dev": true }, +<<<<<<< HEAD "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -8095,6 +8574,8 @@ "yallist": "^4.0.0" } }, +======= +>>>>>>> backend-refactor "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -8121,6 +8602,17 @@ "punycode": "^2.1.1" } }, + "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", @@ -8142,9 +8634,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", @@ -8174,12 +8666,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": { @@ -8372,6 +8863,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", @@ -8606,11 +9102,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", @@ -8685,6 +9176,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", @@ -9121,9 +9617,15 @@ } }, "postman-collection": { +<<<<<<< HEAD "version": "3.6.9", "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-3.6.9.tgz", "integrity": "sha512-R3A4tM/Ll6ekkfsXqqefR2r/jSyubXc9Pa/DQSbocEUKzdT3QMMb0QR5SHNjDuR1qE+bywC5dyD2FO2+DFXJ6w==", +======= + "version": "3.6.8", + "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-3.6.8.tgz", + "integrity": "sha512-TNPaK2dpVRhttUFo/WN0ReopXEtuSQMktwcvwJbQ0z8K+5hftvyx2ia40xgg9qFl/Ra78qoNTUmLL1s3lRqLMg==", +>>>>>>> backend-refactor "dev": true, "requires": { "escape-html": "1.0.3", @@ -9133,10 +9635,17 @@ "iconv-lite": "0.6.2", "liquid-json": "0.3.1", "lodash": "4.17.20", +<<<<<<< HEAD "marked": "1.2.7", "mime-format": "2.0.0", "mime-types": "2.1.28", "postman-url-encoder": "3.0.1", +======= + "marked": "1.2.0", + "mime-format": "2.0.0", + "mime-types": "2.1.27", + "postman-url-encoder": "3.0.0", +>>>>>>> backend-refactor "sanitize-html": "1.20.1", "semver": "7.3.4", "uuid": "3.4.0" @@ -9151,6 +9660,7 @@ "safer-buffer": ">= 2.1.2 < 3.0.0" } }, +<<<<<<< HEAD "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -9170,6 +9680,12 @@ "version": "1.45.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==", +======= + "marked": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.2.0.tgz", + "integrity": "sha512-tiRxakgbNPBr301ihe/785NntvYyhxlqcL3YaC8CaxJQh7kiaEtrN9B/eK2I2943Yjkh5gw25chYFDQhOMCwMA==", +>>>>>>> backend-refactor "dev": true }, "mime-types": { @@ -9251,9 +9767,15 @@ } }, "postman-request": { +<<<<<<< HEAD "version": "2.88.1-postman.28", "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.28.tgz", "integrity": "sha512-QjhC9tIuHZTlYJafzCz7u+Nq57NgtOhJmt94RjcNyzpIts1QbihmAgYm0OhNoqcOIU91sNi2aYw2PCyAJR3kcQ==", +======= + "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==", +>>>>>>> backend-refactor "dev": true, "requires": { "@postman/form-data": "~3.1.1", @@ -9294,6 +9816,7 @@ } }, "postman-runtime": { +<<<<<<< HEAD "version": "7.26.10", "resolved": "https://registry.npmjs.org/postman-runtime/-/postman-runtime-7.26.10.tgz", "integrity": "sha512-rYrSF/G0FoWHvOfbEuKVAgKYU8Nl6ow4+iYfdpRfJuTNcrHmfsdumzzdRJ8n0ZY0awAyrrtcAUr/NvSfdof3qA==", @@ -9301,6 +9824,15 @@ "requires": { "async": "2.6.3", "aws4": "1.11.0", +======= + "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", + "aws4": "1.10.1", +>>>>>>> backend-refactor "eventemitter3": "4.0.7", "handlebars": "4.7.6", "http-reasons": "0.1.0", @@ -9310,10 +9842,17 @@ "lodash": "4.17.20", "node-oauth1": "1.3.0", "performance-now": "2.1.0", +<<<<<<< HEAD "postman-collection": "3.6.9", "postman-request": "2.88.1-postman.28", "postman-sandbox": "4.0.1", "postman-url-encoder": "3.0.1", +======= + "postman-collection": "3.6.8", + "postman-request": "2.88.1-postman.27", + "postman-sandbox": "4.0.0", + "postman-url-encoder": "3.0.0", +>>>>>>> backend-refactor "resolve-from": "5.0.0", "serialised-error": "1.1.3", "tough-cookie": "3.0.1", @@ -9330,9 +9869,15 @@ } }, "aws4": { +<<<<<<< HEAD "version": "1.11.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", +======= + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", + "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==", +>>>>>>> backend-refactor "dev": true }, "resolve-from": { @@ -9355,9 +9900,15 @@ } }, "postman-sandbox": { +<<<<<<< HEAD "version": "4.0.1", "resolved": "https://registry.npmjs.org/postman-sandbox/-/postman-sandbox-4.0.1.tgz", "integrity": "sha512-m0Jw95y5kzSdCY3yWf/hZlkqYxRyBG5sxqiI2D/48nKiHnYLkyMSHErRDJbMj7K4tjXBXn+BKWpWnRsf+iBdlw==", +======= + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postman-sandbox/-/postman-sandbox-4.0.0.tgz", + "integrity": "sha512-0j1VCDa5MHMTfZqv2XSYUyn+hgT9izoRdGFAvjtHCH+i+2TP1KxqXzjxXzOdx1pt26wpl9APdJ2hKKFpx9UlrQ==", +>>>>>>> backend-refactor "dev": true, "requires": { "lodash": "4.17.20", @@ -9366,18 +9917,24 @@ } }, "postman-url-encoder": { +<<<<<<< HEAD "version": "3.0.1", "resolved": "https://registry.npmjs.org/postman-url-encoder/-/postman-url-encoder-3.0.1.tgz", "integrity": "sha512-dMPqXnkDlstM2Eya+Gw4MIGWEan8TzldDcUKZIhZUsJ/G5JjubfQPhFhVWKzuATDMvwvrWbSjF+8VmAvbu6giw==", +======= + "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==", +>>>>>>> backend-refactor "dev": true, "requires": { "punycode": "^2.1.1" } }, "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", @@ -9394,11 +9951,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", @@ -9525,9 +10077,14 @@ "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==" + }, + "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", @@ -9803,13 +10360,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": { @@ -9823,9 +10380,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" @@ -9932,14 +10489,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": { @@ -10004,6 +10571,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", @@ -10023,6 +10595,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", @@ -10128,20 +10708,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" } @@ -10170,9 +10745,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", @@ -10189,37 +10764,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", @@ -10233,16 +10813,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", @@ -10268,51 +10855,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" } } } @@ -10325,27 +10885,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": { @@ -10355,6 +10907,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=" } } }, @@ -14450,18 +15047,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": { @@ -14609,19 +15206,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": { @@ -15341,25 +15931,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==" } } }, @@ -15369,22 +15964,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": { @@ -15392,25 +15988,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": { @@ -15426,19 +16013,25 @@ } } }, + "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.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": { @@ -15563,11 +16156,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", @@ -15940,9 +16528,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", @@ -16195,13 +16783,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", @@ -16264,17 +16851,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==" } } }, @@ -16314,11 +16907,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", @@ -16572,12 +17160,6 @@ "flatted": "3.1.0" } }, - "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", @@ -17396,9 +17978,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 4c07e879e5..8a8de27cb3 100644 --- a/package.json +++ b/package.json @@ -1,75 +1,79 @@ { "name": "redbox-portal", - "version": "1.1.2", + "version": "2.0.0-alpha", "description": "ReDBox 2 Portal", "keywords": [], "dependencies": { + "@researchdatabox/sails-hook-redbox-storage-mongo": "0.0.6-alpha", "@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", + "agenda": "^3.1.0", "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": "2.0.3", - "core-js": "^3.0.1", - "datacrate": "^1.0.4", + "connect-mongo": "3.2.0", + "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", + "got": "^11.8.0", + "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", - "request-promise": "^4.2.0", - "rxjs": "6.5.3", + "redux": "4.0.5", + "request-promise": "^4.2.6", + "rxjs": "6.6.2", "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-grunt": "3.1.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", + "solr-client": "^0.7.1", + "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", "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", - "node-sass": "4.14.1" + "node-sass": "4.14.1", + "zone.js": "^0.11.1" }, "scripts": { "debug": "node debug app.js", @@ -87,17 +91,23 @@ "author": "QCIF Engineering", "license": "GPL2", "devDependencies": { - "@compodoc/compodoc": "^1.1.6", - "@types/jquery": "^3.3.2", + "@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", - "ejs-cli": "^2.0.1", + "@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", + "grunt-contrib-uglify": "^5.0.0", + "grunt-sass": "3.1.0", + "grunt-shell": "^3.0.1", "istanbul": "^0.4.5", - "mocha": "^8.1.1", - "newman": "^5.2.0", + "mocha": "^8.1.2", + "newman": "^5.2.1", + "node-sass": "4.14.1", "supertest": "^4.0.2", "uglify-es": "^3.3.10" }, diff --git a/runForDev.sh b/runForDev.sh index b13cde0ad4..ebb4b87428 100755 --- a/runForDev.sh +++ b/runForDev.sh @@ -4,29 +4,30 @@ 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 +DAEMONIZE_FLAG="-d" 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" -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 -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 -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 -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 @@ -45,14 +46,17 @@ 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 i; node_modules/.bin/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/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 diff --git a/support/integration-testing/docker-compose.mocha.yml b/support/integration-testing/docker-compose.mocha.yml index 8b16d8c9fc..00145c77a5 100644 --- a/support/integration-testing/docker-compose.mocha.yml +++ b/support/integration-testing/docker-compose.mocha.yml @@ -33,15 +33,17 @@ services: - mongodb ports: - "27017:27017" - redbox: - image: qcifengineering/redbox:2.x + solr: + image: solr:8.6.3 expose: - - "9000" - environment: - - RB_API_KEY=c8e844fc-8550-497f-b970-7900ec8741ca + - "8983" + ports: + - "8983:8983" networks: main: - aliases: - - redbox - ports: - - "9000:9000" + 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 3277bdce16..ba70b46be3 100644 --- a/support/integration-testing/docker-compose.newman.yml +++ b/support/integration-testing/docker-compose.newman.yml @@ -33,15 +33,17 @@ services: - mongodb ports: - "27017:27017" - redbox: - image: qcifengineering/redbox:2.x + solr: + image: solr:8.6.3 expose: - - "9000" - environment: - - RB_API_KEY=c8e844fc-8550-497f-b970-7900ec8741ca + - "8983" + ports: + - "8983:8983" networks: main: - aliases: - - redbox - ports: - - "9000:9000" + aliases: + - solr + entrypoint: + - docker-entrypoint.sh + - solr-precreate + - redbox diff --git a/support/integration-testing/docker-compose.travis.yml b/support/integration-testing/docker-compose.travis.yml deleted file mode 100644 index 8ceee445e3..0000000000 --- a/support/integration-testing/docker-compose.travis.yml +++ /dev/null @@ -1,48 +0,0 @@ -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 "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;" - 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/docker-compose.yml b/support/integration-testing/docker-compose.yml index 37a78c36e3..1bc6267002 100644 --- a/support/integration-testing/docker-compose.yml +++ b/support/integration-testing/docker-compose.yml @@ -33,15 +33,22 @@ services: - mongodb ports: - "27017:27017" - redbox: - image: qcifengineering/redbox:2.x + solr: + image: solr:8.6.3 expose: - - "9000" + - "8983" + ports: + - "8983:8983" environment: - - RB_API_KEY=c8e844fc-8550-497f-b970-7900ec8741ca + - 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: - - redbox - ports: - - "9000:9000" + aliases: + - solr + entrypoint: + - docker-entrypoint.sh + - solr-precreate + - redbox 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/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 feb5d5990f..8af4076cbd 100644 --- a/test/postman/test-collection.json +++ b/test/postman/test-collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "33205a61-6802-437d-9b8b-07f91eb4f7ae", + "_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" }, @@ -9,400 +9,1655 @@ "name": "REST API", "item": [ { - "name": "Create RDMP", - "event": [ + "name": "Record Management", + "item": [ { - "listen": "test", - "script": { - "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": "f9ec7c38-e9c8-4ba1-8cd5-af537df3259a", + "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": "dcb862db-71f8-46ab-a802-68f9b575c965", + "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": { - "exec": [ - "" + "name": "Get RDMP's Metadata", + "event": [ + { + "listen": "test", + "script": { + "id": "e8b733e6-d3c2-4cd1-84b5-35a7e1287bb3", + "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}}" + { + "name": "Get RDMP's ObjectMetadata", + "event": [ + { + "listen": "test", + "script": { + "id": "198ab2d3-e77d-45fc-8605-54a918834cc6", + "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" + } + } ], - "path": [ - "default", - "rdmp", - "api", - "records", - "metadata", - "rdmp" - ] - } - }, - "response": [] - }, - { - "name": "Get RDMP's Metadata", - "event": [ + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "", + "type": "text", + "value": "", + "disabled": true + } + ], + "url": { + "raw": "{{host}}/default/rdmp/api/records/objectmetadata/{{dmpOid}}", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "records", + "objectmetadata", + "{{dmpOid}}" + ] + } + }, + "response": [] + }, { - "listen": "test", - "script": { - "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": "Update RDMP Metadata", + "event": [ + { + "listen": "test", + "script": { + "id": "80582a54-66b5-44ec-bd2e-be2c177d85d6", + "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" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{token}}" + } ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{token}}", - "type": "text" + "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" }, - { - "key": "", - "value": "", - "type": "text", - "disabled": true - } - ], - "url": { - "raw": "{{host}}/default/rdmp/api/records/metadata/{{dmpOid}}", - "host": [ - "{{host}}" + "response": [] + }, + { + "name": "Get RDMP's Metadata - Updated Test", + "event": [ + { + "listen": "test", + "script": { + "id": "a2624cea-64ab-4156-85ab-9045ce74f9fc", + "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" + } + } ], - "path": [ - "default", - "rdmp", - "api", - "records", - "metadata", - "{{dmpOid}}" - ] - } - }, - "response": [] - }, - { - "name": "Get RDMP's ObjectMetadata", - "event": [ + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, + { + "key": "", + "type": "text", + "value": "", + "disabled": true + } + ], + "url": { + "raw": "{{host}}/default/rdmp/api/records/metadata/{{dmpOid}}", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "records", + "metadata", + "{{dmpOid}}" + ] + } + }, + "response": [] + }, { - "listen": "test", - "script": { - "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": "Give user edit permissions to RDMP", + "event": [ + { + "listen": "test", + "script": { + "id": "fe0028e6-881b-4e7a-a7af-3a7ba06a7f4c", + "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" + } + } + ], + "request": { + "method": "POST", + "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}}" + "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" }, - { - "key": "", - "type": "text", - "value": "", - "disabled": true - } - ], - "url": { - "raw": "{{host}}/default/rdmp/api/records/objectmetadata/{{dmpOid}}", - "host": [ - "{{host}}" + "response": [] + }, + { + "name": "Create RDMP For Deletion", + "event": [ + { + "listen": "test", + "script": { + "id": "76ffb7f5-555e-415d-a2cb-76a0531296cb", + "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": "13782465-d7a9-425a-a9e7-6b4473c9b48d", + "exec": [ + "" + ], + "type": "text/javascript" + } + } ], - "path": [ - "default", - "rdmp", - "api", - "records", - "objectmetadata", - "{{dmpOid}}" - ] - } - }, - "response": [] - }, - { - "name": "Update RDMP Metadata", - "event": [ + "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": [] + }, { - "listen": "test", - "script": { - "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": "Delete Record", + "event": [ + { + "listen": "test", + "script": { + "id": "075d0e37-e4c8-4f75-8f3d-5e2da81d4c4d", + "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": "dab56e5c-b9bb-4583-8807-d692a9ec3a3d", + "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(\"attDataRecordOid\", jsonData.oid);", + "});" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "fa94d28f-d780-4868-a1e7-ca701170dedb", + "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}}" + } ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" + "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" + ] + } }, - { - "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": "Add attachment to Datarecord", + "event": [ + { + "listen": "test", + "script": { + "id": "c0a6ae8e-b952-4e45-8056-bd5213c44d12", + "exec": [ + "", + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "22b37cca-4b4f-4925-b28d-dc1cadad4e06", + "exec": [ + "" + ], + "type": "text/javascript" + } + } ], - "path": [ - "default", - "rdmp", - "api", - "records", - "metadata", - "{{dmpOid}}" - ] + "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": [] }, - "description": "http://localhost:1500/default/rdmp/api/records/rdmp/create" - }, - "response": [] - }, - { - "name": "Get RDMP's Metadata - Updated Test", - "event": [ { - "listen": "test", - "script": { - "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": "List Attachments for Datarecord", + "event": [ + { + "listen": "test", + "script": { + "id": "5940b6a4-cd63-4905-a27b-8b39e89a72e4", + "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": "f0fb2e7d-7e91-4570-88dc-e6de7b69d70d", + "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": "GET", - "header": [ - { - "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}}" + ] + } }, - { - "key": "", - "type": "text", - "value": "", - "disabled": true - } - ], - "url": { - "raw": "{{host}}/default/rdmp/api/records/metadata/{{dmpOid}}", - "host": [ - "{{host}}" + "response": [] + }, + { + "name": "Create Temporary Datapub", + "event": [ + { + "listen": "test", + "script": { + "id": "934d0b1b-284d-4e63-a3d9-a7e5bc584564", + "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": "81977619-ea89-44ac-aa87-3df51d1d2cae", + "exec": [ + "" + ], + "type": "text/javascript" + } + } ], - "path": [ - "default", - "rdmp", - "api", - "records", - "metadata", - "{{dmpOid}}" - ] + "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": "37659128-1673-4449-99df-b0df0e136546", + "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": "8fd592fa-9985-44a0-873f-2180819934b4", + "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": [] } - }, - "response": [] + ], + "protocolProfileBehavior": {}, + "_postman_isSubFolder": true }, { - "name": "Give user edit permissions to RDMP", - "event": [ + "name": "User Management", + "item": [ { - "listen": "test", - "script": { - "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(['admin']);", - " pm.expect(true).to.eql(true);", - "});" + "name": "List Users", + "event": [ + { + "listen": "test", + "script": { + "id": "c3b793d0-72fa-438c-bcb0-26496f7a39c8", + "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}}" + } + ], + "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": [] + }, + { + "name": "Get Admin User Details", + "event": [ + { + "listen": "test", + "script": { + "id": "85758358-7a47-48e8-8f7c-2fbc2427b043", + "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" + } + } + ], + "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": [] + }, + { + "name": "Attempt to retrieve user that does not exist", + "event": [ + { + "listen": "test", + "script": { + "id": "7e4bf386-1692-45f6-aad6-2ea3a167287a", + "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}}" + } + ], + "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" + }, + "response": [] + }, + { + "name": "Create API Researcher User", + "event": [ + { + "listen": "test", + "script": { + "id": "b88088b8-4c3a-4163-a83a-af89b40369cb", + "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": "c188e622-055b-42eb-b644-f588d02af75b", + "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}}" + } + ], + "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": [] + }, + { + "name": "Update API Researcher User", + "event": [ + { + "listen": "test", + "script": { + "id": "635f6916-fa50-475a-b003-6bb272207a9a", + "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": "345a88fc-ae05-40a2-be8b-72edcf03625a", + "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 \"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" + ] + } + }, + "response": [] + }, + { + "name": "Generate API Researcher User API Token", + "event": [ + { + "listen": "test", + "script": { + "id": "7ba75c5e-b4b9-4854-bd6d-947f0bf103f5", + "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": "2fb11f9c-f1d8-45a9-b4b6-d57981f63e7f", + "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}}" + } + ], + "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": [] + }, + { + "name": "Revoke API Researcher User API Token", + "event": [ + { + "listen": "test", + "script": { + "id": "d343af0d-10d9-4072-8ffb-509c06bfb381", + "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": "139e05c7-f758-4fe9-9852-5e42427912ee", + "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": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" + "protocolProfileBehavior": {}, + "_postman_isSubFolder": true + }, + { + "name": "Search", + "item": [ + { + "name": "Search Index", + "event": [ + { + "listen": "test", + "script": { + "id": "6e0752f2-df8b-4187-9599-4ef314ac37a6", + "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" + } + ], + "url": { + "raw": "{{host}}/default/rdmp/api/search/?searchStr=Andrew", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "search", + "" + ], + "query": [ + { + "key": "searchStr", + "value": "Andrew" + } + ] + } }, - { - "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": "Index Record", + "event": [ + { + "listen": "test", + "script": { + "id": "d227d549-7911-4fa0-b471-96d9070bed1e", + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});" + ], + "type": "text/javascript" + } + } ], - "path": [ - "default", - "rdmp", - "api", - "records", - "permissions", - "edit", - "{{dmpOid}}" - ] - }, - "description": "http://localhost:1500/default/rdmp/api/records/rdmp/create" - }, - "response": [] + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + } + ], + "url": { + "raw": "{{host}}/default/rdmp/api/search/index?oid={{dmpOid}}", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "search", + "index" + ], + "query": [ + { + "key": "oid", + "value": "{{dmpOid}}" + } + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {}, + "_postman_isSubFolder": true }, { - "name": "List Users", - "event": [ + "name": "Form Management", + "item": [ { - "listen": "test", - "script": { - "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": "List Forms", + "event": [ + { + "listen": "test", + "script": { + "id": "15cb0e1f-05eb-4068-97c9-e66dd1d4f6e6", + "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}}" + } ], - "type": "text/javascript" - } + "url": { + "raw": "{{host}}/default/rdmp/api/forms", + "host": [ + "{{host}}" + ], + "path": [ + "default", + "rdmp", + "api", + "forms" + ] + } + }, + "response": [] + }, + { + "name": "Get Form", + "event": [ + { + "listen": "test", + "script": { + "id": "2f804880-aaf2-46a3-a9f9-d75eb2462a00", + "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": [] } ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{token}}" - } - ], - "url": { - "raw": "{{host}}/default/rdmp/api/users", - "host": [ - "{{host}}" + "protocolProfileBehavior": {}, + "_postman_isSubFolder": true + }, + { + "name": "Record Type Management", + "item": [ + { + "name": "List Record Types", + "event": [ + { + "listen": "test", + "script": { + "id": "889de602-53bd-45f1-8ed5-031c99c62570", + "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" + } + } ], - "path": [ - "default", - "rdmp", - "api", - "users" - ] + "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": "http://localhost:1500/default/rdmp/api/records/rdmp/create" - }, - "response": [] + { + "name": "Get Record Type", + "event": [ + { + "listen": "test", + "script": { + "id": "6b58f3b0-c3a7-45a2-acb7-a0dffda9bfc2", + "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 } ] }, @@ -415,6 +1670,7 @@ { "listen": "test", "script": { + "id": "dd221b1e-c29f-4573-ab4a-ebdc28204e33", "exec": [ "postman.setGlobalVariable(\"cookie\",postman.getResponseHeader(\"set-cookie\") );", "", @@ -509,6 +1765,7 @@ { "listen": "test", "script": { + "id": "cd141c7c-a30b-4650-a2e1-eb8bbcbba857", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -555,7 +1812,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}}" ], @@ -575,7 +1832,7 @@ }, { "key": "sort", - "value": "date_object_modified:-1" + "value": "\"lastSaveDate\":-1" }, { "key": "start", @@ -596,6 +1853,7 @@ { "listen": "test", "script": { + "id": "827b4a16-38ea-4864-8e6b-4d3de669ed2a", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -672,6 +1930,7 @@ { "listen": "test", "script": { + "id": "9d3e6245-d371-40d9-bf73-d4e0a948241f", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -748,6 +2007,7 @@ { "listen": "test", "script": { + "id": "772b034f-ba5b-47a6-aebc-c7dac05c1979", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -836,6 +2096,7 @@ { "listen": "test", "script": { + "id": "8d81de5f-1cba-4958-bbb3-952f372953dc", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -925,6 +2186,7 @@ { "listen": "test", "script": { + "id": "26c2ee27-2c85-415b-927c-feb96661e310", "exec": [ "// \"contributor_ci\": {", " // \"text_full_name\": \"Alberto Zweinstein\",", @@ -1013,7 +2275,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", @@ -1039,6 +2301,7 @@ { "listen": "test", "script": { + "id": "4e1ae5fd-dc11-4b2a-ad49-73e1dcd3ac09", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1112,7 +2375,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", @@ -1136,6 +2399,7 @@ { "listen": "test", "script": { + "id": "b40c8315-6ae1-4565-aca7-f7a567928df8", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1215,7 +2479,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", @@ -1239,6 +2503,7 @@ { "listen": "test", "script": { + "id": "3da4fbe9-90c0-4721-8c76-7c46d86e5e1d", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1344,6 +2609,7 @@ { "listen": "test", "script": { + "id": "39b25935-37cb-4012-9653-9c0f7b51b437", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1446,6 +2712,7 @@ { "listen": "test", "script": { + "id": "fb08924f-59fb-4be4-95ea-0f7827f6c6c9", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -1550,6 +2817,7 @@ { "listen": "test", "script": { + "id": "7803bb2a-e13c-4aee-907c-b46972654d53", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1629,7 +2897,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", @@ -1653,6 +2921,7 @@ { "listen": "test", "script": { + "id": "91d97af6-0682-4310-887b-53f537f97e6b", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1833,6 +3102,7 @@ { "listen": "test", "script": { + "id": "163a2e38-a27d-4f88-aeb3-0cb333694c4f", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1935,6 +3205,7 @@ { "listen": "test", "script": { + "id": "6306a7a6-3c3b-47da-ab7a-a0e888546680", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -1952,7 +3223,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;", @@ -2056,6 +3327,7 @@ { "listen": "test", "script": { + "id": "0a0ad59c-d8fd-44e9-a2d1-b94144080bed", "exec": [ "", "pm.test(\"Status code is 200\", function () {", @@ -2149,6 +3421,7 @@ { "listen": "test", "script": { + "id": "3bd417fd-ba5f-4aa3-b7f7-fe00a57353b4", "exec": [ "", "pm.test(\"Status code is 201\", function () {", @@ -2250,6 +3523,7 @@ { "listen": "test", "script": { + "id": "8cfb5446-28e1-4f72-9dd2-e4cdc521a33f", "exec": [ "pm.test(\"Status code is 204\", function () {", " pm.response.to.have.status(204);", @@ -2343,6 +3617,7 @@ { "listen": "test", "script": { + "id": "b4b5c541-0159-45ba-9ce9-96ddbd7b6978", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2434,6 +3709,7 @@ { "listen": "test", "script": { + "id": "26419b15-b0ab-4d6b-a089-795499e398db", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2469,6 +3745,7 @@ { "listen": "test", "script": { + "id": "5475b6ee-dc3c-4ff4-a43f-4afe80174591", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2559,6 +3836,7 @@ { "listen": "test", "script": { + "id": "2eb5e2e4-4527-4972-b8a8-fe80c1f93d02", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2650,6 +3928,7 @@ { "listen": "test", "script": { + "id": "6a2854cc-0726-4809-861a-99768337905b", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -2736,19 +4015,22 @@ "response": [] }, { - "name": "Search Index", + "name": "Create data publication", "event": [ { "listen": "test", "script": { + "id": "bdfc326b-4c43-4c87-8071-8d1330124fba", "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" @@ -2756,12 +4038,16 @@ } ], "request": { - "method": "GET", + "method": "POST", "header": [ { "key": "Pragma", "value": "no-cache" }, + { + "key": "Origin", + "value": "{{host}}" + }, { "key": "Accept-Encoding", "value": "gzip, deflate, br" @@ -2778,70 +4064,62 @@ "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": "9c4524e7-3a78-4f34-aaa4-4508dd61db72", "exec": [ - "", "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", "});", @@ -2849,15 +4127,16 @@ "pm.test(\"Test oid exists\", function () {", " var jsonData = pm.response.json();", " pm.expect(jsonData).to.have.property('oid');", - " postman.setEnvironmentVariable(\"dataPublicationOid\", jsonData.oid);", - "});" + "});", + "", + "" ], "type": "text/javascript" } } ], "request": { - "method": "POST", + "method": "PUT", "header": [ { "key": "Pragma", @@ -2871,10 +4150,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" @@ -2901,7 +4176,7 @@ }, { "key": "Referer", - "value": "{{host}}/default/rdmp/record/dataPublication/edit" + "value": "{{host}}/default/rdmp/record/edit/49a33460c794498a56fa29ed08567cc4" }, { "key": "Cookie", @@ -2917,35 +4192,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": "009345d0-1808-4004-888b-e9686cff73dd", "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" @@ -2953,20 +4236,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" @@ -2975,56 +4258,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" } ] } @@ -3107,6 +4390,7 @@ { "listen": "test", "script": { + "id": "b4bd3a5b-db84-4e10-a76d-0ce72fd2cc08", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", @@ -3182,6 +4466,7 @@ { "listen": "test", "script": { + "id": "47459f8e-16a7-4f98-ba80-402c5c205023", "exec": [ "pm.test(\"Status code is 403\", function () {", " pm.response.to.have.status(403);", @@ -3250,6 +4535,7 @@ { "listen": "test", "script": { + "id": "539d2e29-32bb-49b2-9478-094edd532150", "exec": [ "postman.setGlobalVariable(\"cookie\",postman.getResponseHeader(\"set-cookie\") );", "", @@ -3325,7 +4611,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", @@ -3348,6 +4634,7 @@ { "listen": "test", "script": { + "id": "8801cb9b-3fab-49b3-b3d1-faf94c8a5d71", "exec": [ "postman.setGlobalVariable(\"cookie\",postman.getResponseHeader(\"set-cookie\") );", "", @@ -3355,7 +4642,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);", @@ -3443,6 +4730,7 @@ { "listen": "test", "script": { + "id": "2a2e39fa-c472-4f7b-84aa-91698625f9e3", "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", diff --git a/typescript/api/controllers/DashboardController.ts b/typescript/api/controllers/DashboardController.ts index 1d3d1cb9d5..88a3ba27e9 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, TranslationService; +declare var BrandingService, RolesService, RecordsService; /** * Package that contains all Controllers. @@ -68,7 +68,7 @@ export module Controllers { } - public getRecordList(req, res) { + public async getRecordList(req, res) { const brand = BrandingService.getBrand(req.session.branding); @@ -93,20 +93,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) { @@ -122,7 +120,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(','); @@ -130,38 +128,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 = results.totalItems; + 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/EmailController.ts b/typescript/api/controllers/EmailController.ts index 5efbef3610..2358c86265 100644 --- a/typescript/api/controllers/EmailController.ts +++ b/typescript/api/controllers/EmailController.ts @@ -57,14 +57,14 @@ 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) */ @@ -72,15 +72,15 @@ export module Controllers { public 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; } @@ -119,4 +119,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 0a65b60802..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. */ @@ -52,19 +55,20 @@ 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'); 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}"`); - DashboardService.exportAllPlans(req.user.username, req.user.roles, brand, format, before, after, recType).subscribe(response => { - 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/controllers/RecordController.ts b/typescript/api/controllers/RecordController.ts index e6ab541cb9..5d57b77590 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, RecordsService, 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,25 @@ export module Controllers { */ export class Record extends controller.Controllers.Core.Controller { + recordsService: RecordsService = RecordsService; + searchService: SearchService; + datastreamService: DatastreamService = RecordsService; + + constructor() { + super(); + 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]; + } + that.searchService = sails.services[sails.config.search.serviceName]; + }); + } + + + /** * Exported methods, accessible from internet. */ @@ -71,19 +95,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 +129,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 +147,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) { @@ -184,6 +227,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; @@ -195,97 +239,92 @@ 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... - this.getRecord(rec.oid).subscribe(recObj => { - if (RecordsService.hasEditAccess(brand, user, user.roles, recObj)) { - let recType = rec.metadata.metaMetadata.type; - let relatedRecords = RecordsService.getRelatedRecords(rec.oid, brand); - - relatedRecords.then(relatedRecords => { + sails.log.verbose(`updateResponsibilities() -> Processing:${rec.oid}`); + this.getRecord(rec.oid).subscribe(async (recObj) => { + if (this.recordsService.hasEditAccess(brand, user, user.roles, recObj)) { + let recType = recObj.metaMetadata.type; + 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)); - RecordsService.updateMeta(brand, oid, record).subscribe(response => { - relationshipObjectCount++; - if (response && response.code == "200") { - 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; - completeRecordSet.push({ success: false, error: response, record: record }); + 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}`); } - }, 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, record: record }); + completeRecordSet.push({ + success: false, + error: response, + record: record + }); if (completeRecordSet.length == records.length) { if (hasError) { return this.ajaxFail(req, res, null, completeRecordSet); @@ -293,18 +332,39 @@ 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); - 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 +403,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 +455,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 +472,21 @@ 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 +495,83 @@ 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.isSuccessful()) { + 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 + } } 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 } - } 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.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); + _.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); + } 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 }; + if (response && response.isSuccessful()) { + 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,8 +682,8 @@ export module Controllers { }).subscribe(record => { if (metadata.delete) { - RecordsService.delete(oid).subscribe(response => { - if (response && response.code == "200") { + Observable.fromPromise(this.recordsService.delete(oid)).subscribe(response => { + if (response && response.isSuccessful()) { response.success = true; sails.log.verbose(`Successfully deleted: ${oid}`); this.ajaxOk(req, res, null, response); @@ -631,7 +710,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); @@ -654,13 +733,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...`); @@ -702,19 +776,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 +800,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}`)); } @@ -738,14 +816,14 @@ export module Controllers { currentRec.metaMetadata.lastSaveDate = moment().format(); sails.log.verbose(`Calling record service...`); sails.log.verbose(currentRec); - return 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 RecordsService.updateMeta(brand, oid, currentRec, user); + return Observable.fromPromise(this.recordsService.updateMeta(brand, oid, currentRec, user)); } public stepTo(req, res) { @@ -755,31 +833,32 @@ 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") { + if (response && response.isSuccessful()) { response.success = true; this.ajaxOk(req, res, null, response); } else { @@ -805,7 +884,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 +933,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) { @@ -909,10 +990,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; @@ -972,7 +1053,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 rows = req.param('rows'); @@ -986,19 +1067,24 @@ 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, rows, page) - .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) { @@ -1071,50 +1157,49 @@ 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) { - 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}`); + 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 { + // 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 => { @@ -1150,18 +1235,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']; @@ -1170,11 +1263,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); }); } @@ -1183,7 +1283,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); }); } @@ -1202,13 +1302,13 @@ 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) => { - 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); }); } 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/RecordController.ts b/typescript/api/controllers/webservice/RecordController.ts index a0971ac4f6..e80573cd92 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,16 +35,27 @@ 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'; +import DatastreamServiceResponse from '../../core/DatastreamServiceResponse'; +import Datastream from '../../core/Datastream'; +import {ListAPIResponse } from '../../core/model/ListAPIResponse'; +import {APIErrorResponse } from '../../core/model/APIErrorResponse'; + 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; + DatastreamService: DatastreamService; /** * Exported methods, accessible from internet. */ @@ -62,9 +72,28 @@ export module Controllers { 'getPermissions', 'getDataStream', 'addDataStreams', - 'listRecords' + 'listRecords', + 'deleteRecord', + 'transitionWorkflow', + 'listDatastreams', + 'addRoleEdit', + 'removeRoleEdit', + 'addRoleView', + 'removeRoleView' ]; + 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 ************************************** @@ -79,7 +108,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 +122,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,18 +132,21 @@ 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 => { + 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, 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.')); }); }); } @@ -127,7 +159,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 +169,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 => { + if (result.isSuccessful()) { + Observable.fromPromise(this.RecordsService.getMeta(result["oid"])).subscribe(record => { return res.json(record["authorization"]); }); } else { @@ -157,7 +189,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 +199,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 => { + if (result.isSuccessful()) { + Observable.fromPromise(this.RecordsService.getMeta(result["oid"])).subscribe(record => { return res.json(record["authorization"]); }); } else { @@ -187,7 +219,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 +229,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 => { + if (result.isSuccessful()) { + Observable.fromPromise(this.RecordsService.getMeta(result["oid"])).subscribe(record => { return res.json(record["authorization"]); }); } else { @@ -215,7 +247,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}`)); } @@ -223,7 +255,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. ")); }); } @@ -233,7 +265,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 +275,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,17 +289,17 @@ 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=> { 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, 400, new APIErrorResponse("Update Metadata failed, failed to retrieve existing record. ")); }); } @@ -275,9 +307,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 +340,6 @@ export module Controllers { var recordTypeObservable = RecordTypesService.get(brand, recordType); recordTypeObservable.subscribe(recordTypeModel => { - if (recordTypeModel) { var metadata = body["metadata"]; var workflowStage = body["workflowStage"]; @@ -356,23 +387,24 @@ export module Controllers { } }); - - var obs = Observable.fromPromise(RecordsService.create(brand, request, recordTypeModel)); - obs.subscribe(result => { - if (result["code"] == "200") { - result["code"] = 201; - res.set('Location', sails.config.appUrl + BrandingService.getBrandAndPortalPath(req) + "/api/records/metadata/" + result["oid"]); + let createPromise = this.RecordsService.create(brand, request, recordTypeModel) + var obs = Observable.fromPromise(createPromise); + 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, new APIErrorResponse("Create failed")); } - - return res.status(201).json(result); }, 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")); } } ); @@ -384,18 +416,17 @@ 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}"`); 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 => { @@ -407,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; @@ -425,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); @@ -434,33 +465,23 @@ 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 { - let datastreamServiceName = sails.config.record.datastreamService; - if(datastreamServiceName == undefined) { - datastreamServiceName = "recordsservice"; + 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}); } - let datastreamService = sails.services[datastreamServiceName]; - - const reqs = 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 { - sails.log.verbose("Presuming success..."); - _.merge(result, {fileIds: fileIds}); - return res.json({message: result}); - } - }, error => { - return self.customErrorMessageHandlingOnUpstreamResult(error, res); - }); + } catch (error) { sails.log.error(defaultErrorMessage, error); return res.status(500).json({message: defaultErrorMessage}); @@ -530,16 +551,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"]; @@ -552,12 +573,11 @@ 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; - response["items"] = items; - return Observable.of(response); + apiReponse.records = items; + return Observable.of(apiReponse); }); } @@ -611,6 +631,178 @@ 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)); + } + } + + 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.`)); + } + } + + 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); + sails.log.verbose(JSON.stringify(attachments)); + 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.`)); + } + } + + 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/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/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/controllers/webservice/UserManagementController.ts b/typescript/api/controllers/webservice/UserManagementController.ts index 3995d531eb..c1071cfd44 100644 --- a/typescript/api/controllers/webservice/UserManagementController.ts +++ b/typescript/api/controllers/webservice/UserManagementController.ts @@ -23,14 +23,28 @@ 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. */ import controller = require('../../core/CoreController.js'); +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 @@ -43,9 +57,13 @@ export module Controllers { * Exported methods, accessible from internet. */ protected _exportedMethods: any = [ - 'render', - 'listUsers', - 'findUser' + 'listUsers', + 'getUser', + 'createUser', + 'updateUser', + 'generateAPIToken', + 'revokeAPIToken', + 'listSystemRoles' ]; /** @@ -58,59 +76,227 @@ 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'); 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 that.apiRespond(req, res, response); + }); + } }); } - }); - } - public findUser(req, res) { + 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); + if (err != null) { + sails.log.error(err) + return that.apiFail(req, res, 500) } - if(user != null) { - return res.json(user); + 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}`)) + }); + } + + public createUser(req, res) { + let userReq: User = req.body; + + UsersService.addLocalUser(userReq.username, userReq.name, userReq.email, userReq.password).subscribe(response => { + + 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); } - return res.json({}) + }, 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); + 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); + let response: ListAPIResponse < any > = new ListAPIResponse < any > (); + response.summary.numFound = brand.roles.length; + response.records = brand.roles; + + return this.apiRespond(req,res,response); + } + /** ************************************************************************************************** @@ -120,4 +306,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/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 diff --git a/typescript/api/core/CoreController.ts b/typescript/api/core/CoreController.ts index 59938c136c..7b12d67162 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 { /** @@ -262,11 +263,13 @@ 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 = new APIErrorResponse()) { + // 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/CoreService.ts b/typescript/api/core/CoreService.ts index 45296a36fb..37faef7785 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 */ @@ -101,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/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 fd67c7f95b..db2cb8fbcf 100644 --- a/typescript/api/core/DatastreamService.ts +++ b/typescript/api/core/DatastreamService.ts @@ -1,10 +1,13 @@ +import DatastreamServiceResponse from './DatastreamServiceResponse'; +import Datastream from './Datastream'; + interface DatastreamService{ - addDatastreams(oid: string, fileIds: any[]): any; + 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/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/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/RecordsService.ts b/typescript/api/core/RecordsService.ts new file mode 100644 index 0000000000..c9a94ee8a1 --- /dev/null +++ b/typescript/api/core/RecordsService.ts @@ -0,0 +1,18 @@ +import StorageService from "./StorageService"; + +interface RecordsService extends StorageService { + + 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; + updateMeta(brand, oid, record, user?, triggerPreSaveTriggers?, triggerPostSaveTriggers?): 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..2bd7056715 --- /dev/null +++ b/typescript/api/core/SearchService.ts @@ -0,0 +1,7 @@ +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/core/StorageService.ts b/typescript/api/core/StorageService.ts new file mode 100644 index 0000000000..578a81be0a --- /dev/null +++ b/typescript/api/core/StorageService.ts @@ -0,0 +1,18 @@ +import {Readable} from 'stream'; + +interface StorageService{ + + 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; + getRelatedRecords(oid, brand): Promise; + 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): Readable; + +} +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/core/model/APIErrorResponse.ts b/typescript/api/core/model/APIErrorResponse.ts new file mode 100644 index 0000000000..07308d4e51 --- /dev/null +++ b/typescript/api/core/model/APIErrorResponse.ts @@ -0,0 +1,10 @@ +export 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/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; + } +} diff --git a/typescript/api/core/model/ListAPIResponse.ts b/typescript/api/core/model/ListAPIResponse.ts new file mode 100644 index 0000000000..e830526e4a --- /dev/null +++ b/typescript/api/core/model/ListAPIResponse.ts @@ -0,0 +1,20 @@ +export class ListAPIResponse { + + summary: ListAPISummary = new ListAPISummary(); + records: T[]; + + constructor() { + + } +} + +export class ListAPISummary { + + numFound: number = 0; + page: number = 1; + start: number = 0; + + constructor() { + + } +} diff --git a/typescript/api/core/model/User.ts b/typescript/api/core/model/User.ts new file mode 100644 index 0000000000..bd3cb561a8 --- /dev/null +++ b/typescript/api/core/model/User.ts @@ -0,0 +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 new file mode 100644 index 0000000000..f6e1ec9412 --- /dev/null +++ b/typescript/api/core/model/api/CreateUserAPIResponse.ts @@ -0,0 +1,9 @@ +export class CreateUserAPIResponse { + id: string + username: string + name:string + email: string + type: string + lastLogin: Date + +} \ No newline at end of file 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 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/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) { diff --git a/typescript/api/services/ConfigService.ts b/typescript/api/services/ConfigService.ts index cde9960616..a14d473033 100644 --- a/typescript/api/services/ConfigService.ts +++ b/typescript/api/services/ConfigService.ts @@ -51,7 +51,14 @@ 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}`; + 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. + 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, object, source, stack) { @@ -124,15 +131,16 @@ 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"]; + + let apiDirs = ["services"]; _.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) => { @@ -143,10 +151,37 @@ export module Services { }); } }); + + sails.on('lifted', function() { + let apiDirs = ["controllers"]; + _.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`, []); + if (!_.isEmpty(modelFiles)) { + _.each(modelFiles, (modelFile) => { + const dest = `${appPath}/api/models/${basename(modelFile)}`; + sails.log.verbose(`Copying ${modelFile} to ${dest}`) + fs.copySync(modelFile, dest); + }); + } sails.log.verbose(`${hook_log_header}::Adding custom API elements...completed.`); sails.log.verbose(`${hookName}::Merge complete.`); } + private walkDirSync(dir:string, filelist:any[] = []) { if (!fs.pathExistsSync(dir)) { return filelist; 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/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 diff --git a/typescript/api/services/RecordsService.ts b/typescript/api/services/RecordsService.ts index 0bee32e523..9f90ddf951 100644 --- a/typescript/api/services/RecordsService.ts +++ b/typescript/api/services/RecordsService.ts @@ -17,17 +17,31 @@ // 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 {StorageServiceResponse} from '../core/StorageServiceResponse'; +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 StorageService from '../core/StorageService.js'; +import {Readable} from 'stream'; + 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; @@ -39,7 +53,38 @@ 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 RecordsService { + + storageService: StorageService = null; + datastreamService: DatastreamService = null; + searchService:SearchService = null; + + constructor() { + super(); + this.logHeader = "RecordsService::"; + let that = this; + sails.on('ready', function () { + that.getStorageService(); + that.getDatastreamService(); + that.searchService = sails.services[sails.config.search.serviceName]; + }); + } + + 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', @@ -47,17 +92,10 @@ export module Services { 'getMeta', 'hasEditAccess', 'hasViewAccess', - 'getOne', 'search', 'createBatch', 'provideUserAccessAndRemovePendingAccess', 'searchFuzzy', - 'addDatastream', - 'addDatastreams', - 'removeDatastream', - 'updateDatastream', - 'getDatastream', - 'listDatastreams', 'deleteFilesFromStageDir', 'getRelatedRecords', 'delete', @@ -68,98 +106,216 @@ export module Services { 'triggerPostSaveSyncTriggers', 'checkRedboxRunning', 'getAttachments', - 'appendToRecord' + 'appendToRecord', + 'getRecords', + 'exportAllPlans' ]; + + + 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); + } + 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)); + createResponse.message = failedMessage; + } + return createResponse; + } + + 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); + } + this.searchService.index(oid, record); + // 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); + } + 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); + } + 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); + } + + 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) : Readable { + 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) { - 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)) { + public async getAttachments(oid: string, labelFilterStr: string = undefined): Promise < any > { + let datastreams = await this.datastreamService.listDatastreams(oid, null); + let attachments = []; + _.each(datastreams, (datastream) => { + let attachment = {}; + 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 { + if (datastream['label'] && datastream['label'].indexOf(labelFilterStr) != -1) { attachments.push(attachment); - } else { - if (datastream['label'] && datastream['label'].indexOf(labelFilterStr) != -1) { - attachments.push(attachment); - } } - }); - return Observable.of(attachments); + } }); + return attachments; } - public async checkRedboxRunning(): Promise { - let retries = 1000; - for(let i =0; i< retries; i++) { + /* + * + */ + 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++) { 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) - }); - } - - 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 @@ -175,7 +331,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)) { @@ -188,197 +344,7 @@ export module Services { } _.set(targetRecord, fieldName, linkData); sails.log.verbose(`RecordsService::Updating record:${targetRecordOid}`); - return await this.updateMeta(null, targetRecordOid, targetRecord).toPromise(); - } - - public delete(oid): Observable { - 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)); - } - - public updateMeta(brand, oid, record, user = null, triggerPreSaveTriggers = true, triggerPostSaveTriggers = true): Observable { - if(brand == null ) { - return Observable.from(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 = await 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) { - 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)); - } - /** - * 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); - }); - } - - 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; + return await this.updateMeta(null, targetRecordOid, targetRecord); } /** @@ -461,37 +427,9 @@ export module Services { // }); } - public createBatch(type, data, harvestIdFldName) { - 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 Observable.fromPromise(request[sails.config.record.api.harvest.method](options)); - } - public search(type, searchField, searchStr, returnFields) { - 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); - }); - } + public searchFuzzy(type, workflowState, searchQuery, exactSearches, facetSearches, brand, user, roles, returnFields): Promise < any > { - public searchFuzzy(type, workflowState, searchQuery, exactSearches, facetSearches, brand, user, roles, returnFields, rows = null, page = 1) { 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} ` : ''; @@ -523,9 +461,9 @@ export module Services { return Observable.fromPromise(request[sails.config.record.api.search.method](options)) .flatMap(resp => { let response: any = resp; - let totalItems = response["response"]["numFound"]; - var currentPage = _.toInteger(page); - const customResp = { records: [], totalItems: totalItems, currentPage: currentPage }; + const customResp = { + records: [] + }; _.forEach(response.response.docs, solrdoc => { const customDoc = {}; _.forEach(returnFields, retField => { @@ -550,11 +488,14 @@ 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); - }); + }).toPromise(); } protected addAuthFilter(url, username, roles, brand, editAccessOnly = undefined) { @@ -576,207 +517,26 @@ 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) { - var metadataResponse = this.getMeta(oid); - - 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).toPromise(); - - 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).toPromise(); - - 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) { - 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); - }); - } - } 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); - } /** - * 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:`); @@ -827,7 +587,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; @@ -866,8 +626,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); } } @@ -875,7 +635,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(`triggerPostSaveSyncTriggers::Got initial response:`); @@ -895,7 +655,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}`) @@ -911,7 +672,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); @@ -940,19 +703,18 @@ export module Services { } } - protected shouldRunTrigger(triggerConfig:any, oid: string, record: any, recordType: any, mode:string, user: object = undefined, response:any = {}) { - if (_.isEmpty(triggerConfig.conditionTemplate)) { - sails.log.verbose(`Condition template config empty, always running: ${_.get(triggerConfig, 'function', null)}`); - return true; + private resolveHookResponse(hookResponse) { + let response = hookResponse; + if (isObservable(hookResponse)) { + response = hookResponse.toPromise(); + } else { + response = Promise.resolve(hookResponse); } - const imports = _.extend({oid: oid, record: record, recordType: recordType, mode: mode, user:user, response:response, moment: moment, util:util }, {}); - const templateData = {imports: imports}; - const template = _.template(triggerConfig.conditionTemplate, templateData); - const templateRes = template(); - sails.log.verbose(`Condition template: ${triggerConfig.conditionTemplate} == ${templateRes}`); - return templateRes == "true"; + return response; } + + } } module.exports = new Services.Records().exports(); diff --git a/typescript/api/services/SolrSearchService.ts b/typescript/api/services/SolrSearchService.ts new file mode 100644 index 0000000000..f07dc15ba4 --- /dev/null +++ b/typescript/api/services/SolrSearchService.ts @@ -0,0 +1,350 @@ +// 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', + 'solrAddOrUpdate', + 'solrDelete' + ]; + + 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) { + 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 { + 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 solrAddOrUpdate(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 solrAddOrUpdate, 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; + } + + 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)); + } + } + } +} + +module.exports = new Services.SolrSearchService().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)) { 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'}}; } 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 + diff --git a/views/default/default/layout.ejs b/views/default/default/layout.ejs index 674419ebc1..0f8fc364f5 100644 --- a/views/default/default/layout.ejs +++ b/views/default/default/layout.ejs @@ -7,6 +7,10 @@ + + + + <%=typeof title == 'undefined' ? TranslationService.t('default-title') : title%>