diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..6ec890f49 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: 'npm' + directory: '/' + schedule: + interval: 'monthly' \ No newline at end of file diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..33eddae9e --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,74 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "dev", "UAT", "UATBeta", "master", "release" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "dev", "UAT", "UATBeta", "master", "release" ] + schedule: + - cron: '26 5 * * 1' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/dev_deployment.yaml b/.github/workflows/dev_deployment.yaml new file mode 100644 index 000000000..60603063b --- /dev/null +++ b/.github/workflows/dev_deployment.yaml @@ -0,0 +1,158 @@ +name: Build and Deploy to Cloud Run + +on: + push: + branches: + - 'dev' +# paths: +# - 'Chart.yaml' + +env: + PROJECT_ID: '${{ secrets.PROJECT_ID }}' # TODO: update Google Cloud project id. + GAR_LOCATION: '${{ secrets.GAR_LOCATION }}' # TODO: update Artifact Registry location + SLACK_WEBHOOK_URL: '${{ secrets.SLACK_WEBHOOK_URL}}' + SLACK_CHANNEL: '${{ secrets.GITHUBACTIONS_SLACK_CHANNEL }}' + +jobs: + build: + # needs: analyze + # Add 'id-token' with the intended permissions for workload identity federation + permissions: + contents: write + id-token: write + + runs-on: ubuntu-latest + environment: legacy-dev + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: dev + + - name: Read VERSION file + id: getversion + run: echo "version=$(cat Chart.yaml)" >> $GITHUB_OUTPUT + + - name: Google Auth + id: auth + uses: 'google-github-actions/auth@v0' + with: + token_format: 'access_token' + workload_identity_provider: '${{ secrets.WIF_PROVIDER }}' + service_account: '${{ secrets.WIF_SERVICE_ACCOUNT }}' + + - name: Login to GAR + id: garlogin + uses: docker/login-action@v2 + with: + registry: ${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.PROJECT_ID }} + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + + - name: Build and Push Container + id: build + shell: bash + env: + GAR_LOCATION: ${{ secrets.GAR_LOCATION }} + PROJECT_ID: ${{ secrets.PROJECT_ID }} + GAR_NAME: ${{ secrets.GAR_NAME_API }} + + run: |- + docker build -t '${{ env.GAR_LOCATION }}'-docker.pkg.dev/'${{ env.PROJECT_ID }}'/'${{ env.GAR_NAME }}'/${{ steps.getversion.outputs.version }}:${{ github.sha }} -t '${{ env.GAR_LOCATION }}'-docker.pkg.dev/'${{ env.PROJECT_ID }}'/'${{ env.GAR_NAME }}'/${{ steps.getversion.outputs.version }}:latest ./ + docker push --all-tags '${{ env.GAR_LOCATION }}'-docker.pkg.dev/'${{ env.PROJECT_ID }}'/'${{ env.GAR_NAME }}'/${{ steps.getversion.outputs.version }} + + - name: Build Notification + id: buildnotificationsent + uses: act10ns/slack@v1 + with: + status: ${{ job.status }} + steps: ${{ toJson(steps) }} + channel: ${{ env.SLACK_CHANNEL }} + message: Building {{ env.GITHUB_REF_NAME }} branch + if: always() + # END - Docker auth and build + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + # Deployment please don't modify anything here as the infrastructure is controlled by terraform any changes here please agree with chris and reuben + deploy: + needs: build + permissions: + contents: write + id-token: write + + runs-on: ubuntu-latest + environment: legacy-dev + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: dev + + - name: Google Auth + id: auth + uses: 'google-github-actions/auth@v0' + with: + token_format: 'access_token' + workload_identity_provider: '${{ secrets.WIF_PROVIDER }}' + service_account: '${{ secrets.WIF_SERVICE_ACCOUNT }}' + + - name: Read VERSION file + id: getversion + run: echo "version=$(cat Chart.yaml)" >> $GITHUB_OUTPUT + + - name: Deploy to Cloud Run + uses: actions-hub/gcloud@master + id: deploy + env: + PROJECT_ID: ${{ secrets.PROJECT_ID }} + GAR_LOCATION: ${{ secrets.GAR_LOCATION }} + GAR_NAME: ${{ secrets.GAR_NAME_API }} + SERVICE_NAME: '${{ secrets.SERVICE_NAME_API }}' + SERVICE_REGION: '${{ secrets.SERVICE_REGION_API }}' + + with: + args: run services update '${{ env.SERVICE_NAME }}' --image='${{ env.GAR_LOCATION }}'-docker.pkg.dev/'${{ env.PROJECT_ID }}'/'${{ env.GAR_NAME }}'/${{ steps.getversion.outputs.version }}:${{ github.sha }} --region='${{ env.SERVICE_REGION }}' --project='${{ env.PROJECT_ID }}' + + - name: Deploy Notification + id: deploynotificationsent + uses: act10ns/slack@v1 + with: + status: ${{ job.status }} + steps: ${{ toJson(steps) }} + channel: ${{ env.SLACK_CHANNEL }} + message: Deploying {{ env.GITHUB_REF_NAME }} branch + if: always() + # If required, use the Cloud Run url output in later steps diff --git a/.github/workflows/preprod_deployment.yaml b/.github/workflows/preprod_deployment.yaml new file mode 100755 index 000000000..8aded9b95 --- /dev/null +++ b/.github/workflows/preprod_deployment.yaml @@ -0,0 +1,67 @@ +name: Deploy to Cloud Run + +on: + pull_request: + types: + - closed + branches: + - 'preprod' + +env: + PROJECT_ID: '${{ secrets.PROJECT_ID }}' # TODO: update Google Cloud project id. + GAR_LOCATION: '${{ secrets.GAR_LOCATION }}' # TODO: update Artifact Registry location + SLACK_WEBHOOK_URL: '${{ secrets.SLACK_WEBHOOK_URL}}' + SLACK_CHANNEL: '${{ secrets.GITHUBACTIONS_SLACK_CHANNEL }}' + +jobs: # Deployment please don't modify anything here as the infrastructure is controlled by terraform any changes here please agree with chris and reuben + deploy: + if: github.event.pull_request.merged == true + permissions: + contents: write + id-token: write + + runs-on: ubuntu-latest + environment: legacy-preprod + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: preprod + + - name: Google Auth + id: auth + uses: 'google-github-actions/auth@v0' + with: + token_format: 'access_token' + workload_identity_provider: '${{ secrets.WIF_PROVIDER }}' + service_account: '${{ secrets.WIF_SERVICE_ACCOUNT }}' + + - name: Read VERSION file + id: getversion + run: echo "version=$(cat Chart.yaml)" >> $GITHUB_OUTPUT + + - name: Deploy to Cloud Run + uses: actions-hub/gcloud@master + id: deploy + env: + PROJECT_ID: ${{ secrets.PROJECT_ID }} + DEV_PROJECT_ID: ${{ secrets.DEV_PROJECT_ID }} + GAR_LOCATION: ${{ secrets.GAR_LOCATION }} + GAR_NAME: ${{ secrets.GAR_NAME_API }} + SERVICE_NAME: '${{ secrets.SERVICE_NAME_API }}' + SERVICE_REGION: '${{ secrets.SERVICE_REGION_API }}' + + with: + # args: run services update '${{ env.SERVICE_NAME }}' --image='${{ env.GAR_LOCATION }}'-docker.pkg.dev/'${{ env.DEV_PROJECT_ID }}'/'${{ env.GAR_NAME }}'/${{ steps.getversion.outputs.version }}:${{ github.sha }} --region='${{ env.SERVICE_REGION }}' --project='${{ env.PROJECT_ID }}' + args: run services update '${{ env.SERVICE_NAME }}' --image='${{ env.GAR_LOCATION }}'-docker.pkg.dev/'${{ env.DEV_PROJECT_ID }}'/'${{ env.GAR_NAME }}'/${{ steps.getversion.outputs.version }}:latest --region='${{ env.SERVICE_REGION }}' --project='${{ env.PROJECT_ID }}' + # If required, use the Cloud Run url output in later steps + + - name: Deploy Notification + id: deploynotificationsent + uses: act10ns/slack@v1 + with: + status: ${{ job.status }} + steps: ${{ toJson(steps) }} + channel: ${{ env.SLACK_CHANNEL }} + message: Deploying {{ env.GITHUB_REF_NAME }} branch + if: always() diff --git a/.github/workflows/prod_deployment.yaml b/.github/workflows/prod_deployment.yaml new file mode 100755 index 000000000..3c62899fe --- /dev/null +++ b/.github/workflows/prod_deployment.yaml @@ -0,0 +1,74 @@ +name: Deploy to Cloud Run + +on: + pull_request: + types: + - closed + branches: + - 'master' + +env: + PROJECT_ID: '${{ secrets.PROJECT_ID }}' # TODO: update Google Cloud project id. + GAR_LOCATION: '${{ secrets.GAR_LOCATION }}' # TODO: update Artifact Registry location + SLACK_WEBHOOK_URL: '${{ secrets.SLACK_WEBHOOK_URL}}' + SLACK_CHANNEL: '${{ secrets.GITHUBACTIONS_SLACK_CHANNEL }}' + +jobs: # Deployment please don't modify anything here as the infrastructure is controlled by terraform any changes here please agree with chris and reuben + deploy: + if: github.event.pull_request.merged == true + permissions: + contents: write + id-token: write + + runs-on: ubuntu-latest + environment: legacy-prod + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: master + + - name: Google Auth + id: auth + uses: 'google-github-actions/auth@v0' + with: + token_format: 'access_token' + workload_identity_provider: '${{ secrets.WIF_PROVIDER }}' + service_account: '${{ secrets.WIF_SERVICE_ACCOUNT }}' + + - name: Read VERSION file + id: getversion + run: echo "version=$(cat Chart.yaml)" >> $GITHUB_OUTPUT + + - name: Create the release + uses: "marvinpinto/action-automatic-releases@latest" + with: + repo_token: "${{ secrets.GITHUB_TOKEN }}" + automatic_release_tag: '${{ steps.getversion.outputs.version }}' + prerelease: false + + - name: Deploy to Cloud Run + uses: actions-hub/gcloud@master + id: deploy + env: + PROJECT_ID: ${{ secrets.PROJECT_ID }} + DEV_PROJECT_ID: ${{ secrets.DEV_PROJECT_ID }} + GAR_LOCATION: ${{ secrets.GAR_LOCATION }} + GAR_NAME: ${{ secrets.GAR_NAME_API }} + SERVICE_NAME: '${{ secrets.SERVICE_NAME_API }}' + SERVICE_REGION: '${{ secrets.SERVICE_REGION_API }}' + + with: + # args: run services update '${{ env.SERVICE_NAME }}' --image='${{ env.GAR_LOCATION }}'-docker.pkg.dev/'${{ env.DEV_PROJECT_ID }}'/'${{ env.GAR_NAME }}'/${{ steps.getversion.outputs.version }}:${{ github.sha }} --region='${{ env.SERVICE_REGION }}' --project='${{ env.PROJECT_ID }}' + args: run services update '${{ env.SERVICE_NAME }}' --image='${{ env.GAR_LOCATION }}'-docker.pkg.dev/'${{ env.DEV_PROJECT_ID }}'/'${{ env.GAR_NAME }}'/${{ steps.getversion.outputs.version }}:latest --region='${{ env.SERVICE_REGION }}' --project='${{ env.PROJECT_ID }}' + # If required, use the Cloud Run url output in later steps + + - name: Deploy Notification + id: deploynotificationsent + uses: act10ns/slack@v1 + with: + status: ${{ job.status }} + steps: ${{ toJson(steps) }} + channel: ${{ env.SLACK_CHANNEL }} + message: Deploying {{ env.GITHUB_REF_NAME }} branch + if: always() diff --git a/.github/workflows/uat_deployment.yaml b/.github/workflows/uat_deployment.yaml new file mode 100755 index 000000000..88592c623 --- /dev/null +++ b/.github/workflows/uat_deployment.yaml @@ -0,0 +1,75 @@ +name: Deploy to Cloud Run + +on: + pull_request: + types: + - closed + branches: + - 'release' + + +env: + PROJECT_ID: '${{ secrets.PROJECT_ID }}' # TODO: update Google Cloud project id. + GAR_LOCATION: '${{ secrets.GAR_LOCATION }}' # TODO: update Artifact Registry location + SLACK_WEBHOOK_URL: '${{ secrets.SLACK_WEBHOOK_URL}}' + SLACK_CHANNEL: '${{ secrets.GITHUBACTIONS_SLACK_CHANNEL }}' + +jobs: # Deployment please don't modify anything here as the infrastructure is controlled by terraform any changes here please agree with chris and reuben. + # catchsha: + # uses: HDRUK/gateway-api/.github/workflows/dev_deployment.yaml@dev + deploy: + # if: github.event.pull_request.merged == true + permissions: + contents: write + id-token: write + + runs-on: ubuntu-latest + environment: legacy-uat + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: release + + - name: Google Auth + id: auth + uses: 'google-github-actions/auth@v0' + with: + token_format: 'access_token' + workload_identity_provider: '${{ secrets.WIF_PROVIDER }}' + service_account: '${{ secrets.WIF_SERVICE_ACCOUNT }}' + + - name: Read VERSION file + id: getversion + # run: echo "::set-output name=version::$(cat Chart.yaml)" + run: echo "version=$(cat Chart.yaml)" >> $GITHUB_OUTPUT + + # - name: Get SHA + # id: getsha + # run: echo ${{ needs.catchsha.outputs.GITHUB_SHA }} + + - name: Deploy to Cloud Run + uses: actions-hub/gcloud@master + id: deploy + env: + PROJECT_ID: ${{ secrets.PROJECT_ID }} + DEV_PROJECT_ID: ${{ secrets.DEV_PROJECT_ID }} + GAR_LOCATION: ${{ secrets.GAR_LOCATION }} + GAR_NAME: ${{ secrets.GAR_NAME_API }} + SERVICE_NAME: '${{ secrets.SERVICE_NAME_API }}' + SERVICE_REGION: '${{ secrets.SERVICE_REGION_API }}' + + with: + # args: run services update '${{ env.SERVICE_NAME }}' --image='${{ env.GAR_LOCATION }}'-docker.pkg.dev/'${{ env.DEV_PROJECT_ID }}'/'${{ env.GAR_NAME }}'/${{ steps.getversion.outputs.version }}:{{ steps.catchsha.outputs.GITHUB_SHA}} --region='${{ env.SERVICE_REGION }}' --project='${{ env.PROJECT_ID }}' + # Functionality not supported by Github Actions one to ccheck back agin in the future + args: run services update '${{ env.SERVICE_NAME }}' --image='${{ env.GAR_LOCATION }}'-docker.pkg.dev/'${{ env.DEV_PROJECT_ID }}'/'${{ env.GAR_NAME }}'/${{ steps.getversion.outputs.version }}:latest --region='${{ env.SERVICE_REGION }}' --project='${{ env.PROJECT_ID }}' + + - name: Deploy Notification + id: deploynotificationsent + uses: act10ns/slack@v1 + with: + status: ${{ job.status }} + steps: ${{ toJson(steps) }} + channel: ${{ env.SLACK_CHANNEL }} + message: Deploying {{ env.GITHUB_REF_NAME }} branch + if: always() diff --git a/.gitignore b/.gitignore index f95121ebc..9554a6c63 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ npm-debug.log* package-lock.json .env globalConfig.json +google_analytics.json diff --git a/migrations/1620418612003-test_migration.js b/.old.migrations/1620418612003-test_migration.js similarity index 88% rename from migrations/1620418612003-test_migration.js rename to .old.migrations/1620418612003-test_migration.js index 459f89ae7..0f0ed9545 100644 --- a/migrations/1620418612003-test_migration.js +++ b/.old.migrations/1620418612003-test_migration.js @@ -1,4 +1,5 @@ //import { UserModel } from '../src/resources/user/user.model'; +// Something /** * Make any changes you need to make to the database here @@ -6,7 +7,7 @@ async function up() { // Write migration here //await UserModel.findOneAndUpdate({ email: 'robin.kavanagh@paconsulting.com' }, { firstname: 'robin2' }); - console.log('Sample migration ran successfully'); + process.stdout.write(`Sample migration ran successfully\n`); } /** diff --git a/migrations/1620558117918-applications_versioning.js b/.old.migrations/1620558117918-applications_versioning.js similarity index 100% rename from migrations/1620558117918-applications_versioning.js rename to .old.migrations/1620558117918-applications_versioning.js diff --git a/migrations/1622731580031-first_message_is5safes.js b/.old.migrations/1622731580031-first_message_is5safes.js similarity index 100% rename from migrations/1622731580031-first_message_is5safes.js rename to .old.migrations/1622731580031-first_message_is5safes.js diff --git a/migrations/1623235509532-authors_uploaders.js b/.old.migrations/1623235509532-authors_uploaders.js similarity index 100% rename from migrations/1623235509532-authors_uploaders.js rename to .old.migrations/1623235509532-authors_uploaders.js diff --git a/migrations/1623322905323-5safes_nhsd_removal.js b/.old.migrations/1623322905323-5safes_nhsd_removal.js similarity index 100% rename from migrations/1623322905323-5safes_nhsd_removal.js rename to .old.migrations/1623322905323-5safes_nhsd_removal.js diff --git a/migrations/1627566998386-add_globals.js b/.old.migrations/1627566998386-add_globals.js similarity index 100% rename from migrations/1627566998386-add_globals.js rename to .old.migrations/1627566998386-add_globals.js diff --git a/migrations/1631268706696-shared_applications_versioning.js b/.old.migrations/1631268706696-shared_applications_versioning.js similarity index 89% rename from migrations/1631268706696-shared_applications_versioning.js rename to .old.migrations/1631268706696-shared_applications_versioning.js index e01801703..e3eca7a23 100644 --- a/migrations/1631268706696-shared_applications_versioning.js +++ b/.old.migrations/1631268706696-shared_applications_versioning.js @@ -1,4 +1,5 @@ import { DataRequestModel } from '../src/resources/datarequest/datarequest.model'; +// Something else async function up() { // 1. Add default application type to all applications @@ -26,7 +27,7 @@ async function up() { }, }); } catch (err) { - console.error(err); + process.stdout.write(`Migration error - shared applications versioning: ${err.message}\n`); } }); diff --git a/migrations/1631621029553-update_publishers_uses5safes.js b/.old.migrations/1631621029553-update_publishers_uses5safes.js similarity index 100% rename from migrations/1631621029553-update_publishers_uses5safes.js rename to .old.migrations/1631621029553-update_publishers_uses5safes.js diff --git a/migrations/1631633422670-message_user_types.js b/.old.migrations/1631633422670-message_user_types.js similarity index 100% rename from migrations/1631633422670-message_user_types.js rename to .old.migrations/1631633422670-message_user_types.js diff --git a/migrations/1633525344331-Ig_2354_replace_hubs_with_hub.js b/.old.migrations/1633525344331-Ig_2354_replace_hubs_with_hub.js similarity index 100% rename from migrations/1633525344331-Ig_2354_replace_hubs_with_hub.js rename to .old.migrations/1633525344331-Ig_2354_replace_hubs_with_hub.js diff --git a/migrations/1638716002879-remove_projects_from_related_resources.js b/.old.migrations/1638716002879-remove_projects_from_related_resources.js similarity index 100% rename from migrations/1638716002879-remove_projects_from_related_resources.js rename to .old.migrations/1638716002879-remove_projects_from_related_resources.js diff --git a/.old.migrations/README.md b/.old.migrations/README.md new file mode 100644 index 000000000..ce9cf4bb5 --- /dev/null +++ b/.old.migrations/README.md @@ -0,0 +1,40 @@ +# HDR UK GATEWAY - Data Migrations + +The primary data source used by the Gateway Project is the noSQL solution provided by MongoDb. Data migration strategy is a fundamental part of software development and release cycles for a data intensive web application. The project team have chosen the NPM package Migrate-Mongoose - https://www.npmjs.com/package/migrate-mongoose to assist in the management of data migration scripts. This package allows developers to write versioned, reversible data migration scripts using the Mongoose library. + +For more information on what migration scripts are and their purpose, please see sample background reading here - https://www.red-gate.com/simple-talk/sql/database-administration/using-migration-scripts-in-database-deployments/ + +### Using migration scrips + +To create a data migration script, follow these steps: + +#### Step 1 + +Ensure your terminal's working directory is the Gateway API and that node packages have been installed using 'npm i'. + +#### Step 2 + +Run the command below, replacing 'my_new_migration_script' with the name of the script you want to create. The name does not need to be unique, as it will be prefixed automatically with a timestamp, but it should be easily recognisable and relate strongly to the database change that will take place if the script is executed. + +./node_modules/.bin/migrate create my_new_migration_script + +#### Step 3 + +Your new migration scripts should now be available in './migrations/', which you can now modify. You can import the required Mongoose models as normal to interact with the MongoDb database. The migration scripts that run locally will use the connection string taken from your .env file against the variable 'MIGRATE_dbConnectionUri'. + +Complete the scripts required for the UP process, and if possible, the DOWN process. For awareness, the UP scripts run automatically as part of our CI/CD pipeline, and the DOWN scripts exist to reverse database changes if necessary, this is a manual process. + +#### Step 4 + +With the scripts written, the functions can be tested by running the following command, replacing 'my_new_migration_script' with the name of the script you want to execute without the time stamp so for example +node -r esm migrations/migrate.js up add_globals + +node -r esm migrations/migrate.js up my_new_migration_script + +When this process is completed, the connected database will have a new document representing your migration scripts inside the 'migrations' collection, which tracks the state of the migration. If you need to run your scripts multiple times for test purposes, you can change the state of the migration to 'Down'. + +During this process, please ensure you are using a personal database. + +#### Step 5 + +Commit the code to the relevant git branch and raise a pull request. The migration script will run automatically as the code moves through each environment. diff --git a/migrations/migrate.js b/.old.migrations/migrate.js similarity index 65% rename from migrations/migrate.js rename to .old.migrations/migrate.js index 7226f512d..43130a3b1 100644 --- a/migrations/migrate.js +++ b/.old.migrations/migrate.js @@ -1,7 +1,7 @@ import cli from 'migrate-mongoose/src/cli'; //lgtm [js/unused-local-variable] import mongoose from 'mongoose'; -mongoose.connect(process.env.MIGRATE_dbConnectionUri, { +mongoose.connect(`${process.env.MIGRATE_dbConnectionUri}/${process.env.database}/?retryWrites=true&w=majority`, { useNewUrlParser: true, useFindAndModify: false, useUnifiedTopology: true, diff --git a/Chart.yaml b/Chart.yaml new file mode 100644 index 000000000..5025efcc6 --- /dev/null +++ b/Chart.yaml @@ -0,0 +1 @@ +v5.0.8 diff --git a/Dockerfile b/Dockerfile index f265113a3..546f167e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:12 +FROM node:16 # Create app directory WORKDIR /usr/src/app diff --git a/Dockerfile.dev b/Dockerfile.dev index a9f1591d0..f1a98dadd 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,4 +1,4 @@ -FROM node:14 +FROM node:18.14.2 ENV GOOGLE_APPLICATION_CREDENTIALS="/usr/local/etc/gcloud/application_default_credentials.json" @@ -12,4 +12,4 @@ COPY . . EXPOSE 3001 -CMD ["npm", "run", "server"] \ No newline at end of file +CMD ["npm", "run", "server"] \ No newline at end of file diff --git a/README.md b/README.md index 8ef7f6e10..7bfcf4ed6 100644 --- a/README.md +++ b/README.md @@ -151,4 +151,8 @@ terraform plan -var-file=vars.tfvars -out=tf_apply terraform apply tf_apply && rm tf_apply ``` + [Link to terraform file](deployment/GCP/api.tf) + + +... \ No newline at end of file diff --git a/cloudbuild_dynamic.yaml b/cloudbuild_dynamic.yaml deleted file mode 100644 index ebb677b26..000000000 --- a/cloudbuild_dynamic.yaml +++ /dev/null @@ -1,46 +0,0 @@ -steps: - - name: 'gcr.io/cloud-builders/docker' - entrypoint: 'bash' - args: ['-c', 'docker pull gcr.io/$PROJECT_ID/${_APP_NAME}:${_ENVIRONMENT} || exit 0'] - - name: 'gcr.io/cloud-builders/docker' - args: - [ - 'build', - '-t', - 'gcr.io/$PROJECT_ID/${_APP_NAME}:${_ENVIRONMENT}', - '--cache-from', - 'gcr.io/$PROJECT_ID/${_APP_NAME}:${_ENVIRONMENT}', - '.', - ] - - name: 'gcr.io/cloud-builders/docker' - args: ['push', 'gcr.io/$PROJECT_ID/${_APP_NAME}:${_ENVIRONMENT}'] - - name: 'gcr.io/cloud-builders/gcloud' - args: - [ - 'run', - 'deploy', - '${_ENVIRONMENT}-api', - '--image', - 'gcr.io/$PROJECT_ID/${_APP_NAME}:${_ENVIRONMENT}', - '--platform', - 'managed', - '--region', - '${_REGION}', - '--allow-unauthenticated', - ] - - name: 'node' - args: ['npm', 'install'] - - name: 'node' - args: ['-r', 'esm', 'migrations/migrate.js', 'up', '--autosync', 'true'] - env: - - 'MIGRATE_dbConnectionUri=${_MIGRATE_DBCONNECTIONURI}' - - name: 'node' - args: ['npm', 'test'] - env: - - 'URL=https://${_TEST_URL}' - -images: - - gcr.io/$PROJECT_ID/${_APP_NAME}:${_ENVIRONMENT} -timeout: 1200s -options: - machineType: 'E2_HIGHCPU_8' diff --git a/google_analytics.json b/google_analytics.json new file mode 100644 index 000000000..544b7b4dd --- /dev/null +++ b/google_analytics.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/migrate-mongo-config.js b/migrate-mongo-config.js new file mode 100644 index 000000000..419d12bfc --- /dev/null +++ b/migrate-mongo-config.js @@ -0,0 +1,46 @@ +// In this file you can configure migrate-mongo + +// Have to call this as this is pre-app start, thus env hasn't +// been populated yet +require('dotenv').config(); + +const config = { + mongodb: { + // TODO Change (or review) the url to your MongoDB: + url: 'mongodb+srv://' + + process.env.user + + ':' + + process.env.password + + '@' + + process.env.cluster + + '?ssl=true&retryWrites=true&w=majority', + + // TODO Change this to your database name: + databaseName: process.env.database, + + options: { + useNewUrlParser: true, // removes a deprecation warning when connecting + useUnifiedTopology: true, // removes a deprecating warning when connecting + // connectTimeoutMS: 3600000, // increase connection timeout to 1 hour + // socketTimeoutMS: 3600000, // increase socket timeout to 1 hour + } + }, + + // The migrations dir, can be an relative or absolute path. Only edit this when really necessary. + migrationsDir: "migrations", + + // The mongodb collection where the applied changes are stored. Only edit this when really necessary. + changelogCollectionName: "changelog", + + // The file extension to create migrations and search for in migration dir + migrationFileExtension: ".js", + + // Enable the algorithm to create a checksum of the file contents and use that in the comparison to determine + // if the file should be run. Requires that scripts are coded to be run multiple times. + useFileHash: false, + + // Don't change this, unless you know what you're doing + moduleSystem: 'commonjs', +}; + +module.exports = config; diff --git a/migrations/20221122095337-add_published_flag_to_data_requests.js b/migrations/20221122095337-add_published_flag_to_data_requests.js new file mode 100644 index 000000000..aabdc3f69 --- /dev/null +++ b/migrations/20221122095337-add_published_flag_to_data_requests.js @@ -0,0 +1,30 @@ +module.exports = { + async up(db, client) { + // TODO write your migration here. + // See https://github.com/seppevs/migrate-mongo/#creating-a-new-migration-script + + /** + * Update DAR to include an overriding published field to determine the published + * state of a DAR edit form publication by a custodian + */ + // await db.collection('data_requests').updateMany({ + // $set: { "published_form": false }, + // }); + + await db.collection('data_requests').updateMany({}, + { + $set: { "publishedForm": false } + } + ); + }, + + async down(db, client) { + // TODO write the statements to rollback your migration (if possible) + + await db.collection('data_requests').updateMany({}, + { + $unset: { "publishedForm": false } + } + ); + } +}; diff --git a/migrations/README.md b/migrations/README.md index ce9cf4bb5..619c20296 100644 --- a/migrations/README.md +++ b/migrations/README.md @@ -1,8 +1,13 @@ # HDR UK GATEWAY - Data Migrations -The primary data source used by the Gateway Project is the noSQL solution provided by MongoDb. Data migration strategy is a fundamental part of software development and release cycles for a data intensive web application. The project team have chosen the NPM package Migrate-Mongoose - https://www.npmjs.com/package/migrate-mongoose to assist in the management of data migration scripts. This package allows developers to write versioned, reversible data migration scripts using the Mongoose library. +The primary data source used by the Gateway Project is the noSQL solution provided by MongoDb. +Data migration strategy is a fundemental part of software development and release cycles for a +data intensive web application. The project team have chosen the NPM package Migrate-Mongo - https://www.npmjs.com/package/migrate-mongo +to assist in the management of data migration scripts. This package allows developers to write versioned, +reversible data migration scripts using the Mongoose library. -For more information on what migration scripts are and their purpose, please see sample background reading here - https://www.red-gate.com/simple-talk/sql/database-administration/using-migration-scripts-in-database-deployments/ +For more information on what migration scripts are and their purpose, please see sample +background reading here - https://www.red-gate.com/simple-talk/sql/database-administration/using-migration-scripts-in-database-deployments/ ### Using migration scrips @@ -10,31 +15,54 @@ To create a data migration script, follow these steps: #### Step 1 -Ensure your terminal's working directory is the Gateway API and that node packages have been installed using 'npm i'. +Ensure your terminal's working directory is the Gateway API and that node packages have +been installed using 'npm i'. #### Step 2 -Run the command below, replacing 'my_new_migration_script' with the name of the script you want to create. The name does not need to be unique, as it will be prefixed automatically with a timestamp, but it should be easily recognisable and relate strongly to the database change that will take place if the script is executed. +Run the command below, replacing 'my_new_migration_script' with the name of the script +you want to create. The name does not need to be unique, as it will be prefixed automatically +with a timestamp, but it should be easily recognisable and relate strongly to the database +change that will take place if the script is executed. -./node_modules/.bin/migrate create my_new_migration_script +./node_modules/.bin/migrate-mongo create my_new_migration_script #### Step 3 -Your new migration scripts should now be available in './migrations/', which you can now modify. You can import the required Mongoose models as normal to interact with the MongoDb database. The migration scripts that run locally will use the connection string taken from your .env file against the variable 'MIGRATE_dbConnectionUri'. +Your new migration scripts should now be available in './migrations/', which you can now modify. +You can interact directly with the database. The migration scripts that run locally will use the +connection string config taken from your .env file against the variables: database, user, password and cluster. -Complete the scripts required for the UP process, and if possible, the DOWN process. For awareness, the UP scripts run automatically as part of our CI/CD pipeline, and the DOWN scripts exist to reverse database changes if necessary, this is a manual process. +Complete the scripts required for the UP process, and if possible, the DOWN process. For awareness, the UP +scripts run automatically as part of our CI/CD pipeline, and the DOWN scripts exist to reverse +database changes if necessary, this is a manual process. #### Step 4 -With the scripts written, the functions can be tested by running the following command, replacing 'my_new_migration_script' with the name of the script you want to execute without the time stamp so for example -node -r esm migrations/migrate.js up add_globals +With the scripts written, the functions can be tested by running the following command, +replacing 'my_new_migration_script' with the name of the script you want to execute without +the time stamp so for example -node -r esm migrations/migrate.js up my_new_migration_script +./node_modules/.bin/migrate-mongo up (to run all migration updates) +./node_modules/.bin/migrate-mongo down (to rollback migration updates) +./node_modules/.bin/migrate-mongo up my_new_migration_script (to run a single migration update) +./node_modules/.bin/migrate-mongo down my_new_migration_script (to rollback a single migration update) +./node_modules/.bin/migrate-mongo status (to list any pending migrations yet to be run) -When this process is completed, the connected database will have a new document representing your migration scripts inside the 'migrations' collection, which tracks the state of the migration. If you need to run your scripts multiple times for test purposes, you can change the state of the migration to 'Down'. +When this process is completed, the connected database will have a new document representing your +migration scripts inside the 'migrations' collection, which tracks the state of the migration. +If you need to run your scripts multiple times for test purposes, you can change the state of +the migration to 'Down'. During this process, please ensure you are using a personal database. #### Step 5 -Commit the code to the relevant git branch and raise a pull request. The migration script will run automatically as the code moves through each environment. +Commit the code to the relevant git branch and raise a pull request. The migration script +will run automatically as the code moves through each environment. + +#### Note + +You can avoid running migrations manually, you can use `npm run start-with-migrate` to launch the api +locally, with any pending migrations to be run - Ensure the targetted database is correct to avoid any +unwanted migrations elsewhere. diff --git a/package.json b/package.json index 45bdd4046..121321685 100644 --- a/package.json +++ b/package.json @@ -9,13 +9,11 @@ "private": true, "dependencies": { "@google-cloud/bigquery": "^5.9.3", - "@google-cloud/monitoring": "^2.1.0", + "@google-cloud/monitoring": "^3.0.3", "@google-cloud/pubsub": "^2.19.4", "@google-cloud/storage": "^5.3.0", "@hubspot/api-client": "^4.1.0", "@sendgrid/mail": "^7.1.0", - "@sentry/node": "^6.4.1", - "@sentry/tracing": "^6.4.1", "ajv": "^8.1.0", "ajv-formats": "^2.0.2", "async": "^3.2.0", @@ -49,7 +47,7 @@ "keygrip": "^1.1.0", "lodash": "^4.17.19", "mailchimp-api-v3": "^1.15.0", - "migrate-mongoose": "^4.0.0", + "migrate-mongo": "^9.0.0", "moment": "^2.29.3", "mongoose": "^5.12.7", "morgan": "^1.10.0", @@ -69,7 +67,7 @@ "randomstring": "^1.1.5", "redis": "4.0.0", "simple-gcp-logging": "git+https://github.com/HDRUK/simple-gcp-logging.git#main", - "sinon": "^9.2.4", + "sinon": "^15.0.0", "snyk": "^1.334.0", "swagger-ui-express": "^4.1.4", "test": "^0.6.0", @@ -86,9 +84,10 @@ "mongodb-memory-server": "6.9.2", "nodemon": "^2.0.3", "plop": "^2.7.4", - "supertest": "^4.0.2" + "supertest": "^6.3.3" }, "scripts": { + "start-with-migrate": "./node_modules/.bin/migrate up && node index.js", "start": "node index.js", "server": "nodemon --ignore 'src/**/*.json' index.js", "debug": "nodemon --inspect=0.0.0.0:3001 index.js", diff --git a/plop-templates/repositoryPattern/controller.hbs b/plop-templates/repositoryPattern/controller.hbs index f5d405132..49380d35a 100644 --- a/plop-templates/repositoryPattern/controller.hbs +++ b/plop-templates/repositoryPattern/controller.hbs @@ -37,7 +37,7 @@ export default class {{capitalise entityName}}Controller extends Controller { }); } catch (err) { // Return error response if something goes wrong - console.error(err.message); + process.stdout.write(`repositoryPatter: ${err.message}\n`); return res.status(500).json({ success: false, message: 'A server error occurred, please try again', diff --git a/src/config/db.js b/src/config/db.js index f0d67cf1f..f889fea1a 100644 --- a/src/config/db.js +++ b/src/config/db.js @@ -23,9 +23,9 @@ const connectToDatabase = async () => { bufferMaxEntries: 0, }); - console.log('MongoDB connected...'); + process.stdout.write(`MongoDB connected...\n`); } catch (err) { - console.error(err.message); + process.stdout.write(`connectToDatabase : ${err.message}\n`); process.exit(1); } diff --git a/src/config/server.js b/src/config/server.js index cbdb1bbd8..1a2158978 100644 --- a/src/config/server.js +++ b/src/config/server.js @@ -4,44 +4,19 @@ import express from 'express'; import Provider from 'oidc-provider'; import swaggerUi from 'swagger-ui-express'; import cors from 'cors'; -import logger from 'morgan'; +import morgan from 'morgan'; import passport from 'passport'; import cookieParser from 'cookie-parser'; import bodyParser from 'body-parser'; import { connectToDatabase } from './db'; import { initialiseAuthentication } from '../resources/auth'; -import * as Sentry from '@sentry/node'; -import * as Tracing from '@sentry/tracing'; -import helper from '../resources/utilities/helper.util'; + +import { errorHandler } from '../middlewares'; require('dotenv').config(); var app = express(); -const readEnv = process.env.ENV || 'prod'; -if (readEnv === 'test' || readEnv === 'prod') { - Sentry.init({ - dsn: 'https://b6ea46f0fbe048c9974718d2c72e261b@o444579.ingest.sentry.io/5653683', - environment: helper.getEnvironment(), - integrations: [ - // enable HTTP calls tracing - new Sentry.Integrations.Http({ tracing: true }), - // enable Express.js middleware tracing - new Tracing.Integrations.Express({ - // trace all requests to the default router - app, - }), - ], - tracesSampleRate: 1.0, - }); - // RequestHandler creates a separate execution context using domains, so that every - // transaction/span/breadcrumb is attached to its own Hub instance - app.use(Sentry.Handlers.requestHandler()); - // TracingHandler creates a trace for every incoming request - app.use(Sentry.Handlers.tracingHandler()); - app.use(Sentry.Handlers.errorHandler()); -} - const Account = require('./account'); const configuration = require('./configuration'); @@ -84,7 +59,7 @@ connectToDatabase(); app.use(bodyParser.json({ limit: '10mb', extended: true })); app.use(bodyParser.urlencoded({ limit: '10mb', extended: false })); -app.use(logger('dev')); +app.use(morgan('tiny')); app.use(cookieParser()); app.use(passport.initialize()); app.use(passport.session()); @@ -191,10 +166,18 @@ app.use('/api/v1/auth/register', require('../resources/user/user.register.route' app.use('/api/v1/users', require('../resources/user/user.route')); app.use('/api/v1/topics', require('../resources/topic/topic.route')); app.use('/api/v1/publishers', require('../resources/publisher/publisher.route')); + app.use('/api/v1/teams', require('../resources/team/team.route')); +app.use('/api/v3/teams', require('../resources/team/v3/team.route')); + app.use('/api/v1/workflows', require('../resources/workflow/workflow.route')); + app.use('/api/v1/messages', require('../resources/message/message.route')); -app.use('/api/v1/reviews', require('../resources/tool/review.route')); +app.use('/api/v3/messages', require('../resources/message/v3/message.route')); + +app.use('/api/v1/reviews', require('../resources/review/v1/review.route')); +app.use('/api/v3/reviews', require('../resources/review/v3/review.route')); + app.use('/api/v1/relatedobject/', require('../resources/relatedobjects/relatedobjects.route')); app.use('/api/v1/accounts', require('../resources/account/account.route')); @@ -258,7 +241,11 @@ app.use('/api/v2/questionbank', require('../resources/questionbank/questionbank. app.use('/api/v2/data-use-registers', require('../resources/dataUseRegister/dataUseRegister.route')); app.use('/api/v1/locations', require('../resources/spatialfilter/SpatialRouter')); +app.use(errorHandler); + +app.use('/api/v1/metadata', require('../resources/metadata/metadata.route')); + initialiseAuthentication(app); // launch our backend into a port -app.listen(API_PORT, () => console.log(`LISTENING ON PORT ${API_PORT}`)); +app.listen(API_PORT, () => process.stdout.write(`LISTENING ON PORT ${API_PORT}\n`)); diff --git a/src/controllers/datasetonboarding.controller.js b/src/controllers/datasetonboarding.controller.js index 260ddabf8..984849281 100644 --- a/src/controllers/datasetonboarding.controller.js +++ b/src/controllers/datasetonboarding.controller.js @@ -3,7 +3,6 @@ import _ from 'lodash'; import axios from 'axios'; import FormData from 'form-data'; import { v4 as uuidv4 } from 'uuid'; -import * as Sentry from '@sentry/node'; import { isEmpty, escapeRegExp } from 'lodash'; import { Data } from '../resources/tool/data.model'; @@ -14,8 +13,6 @@ import { PublisherModel } from '../resources/publisher/publisher.model'; import { activityLogService } from '../resources/activitylog/dependency'; const HttpClient = require('../services/httpClient/httpClient'); -const readEnv = process.env.ENV || 'prod'; - export default class DatasetOnboardingController { constructor(datasetonboardingService) { this.datasetonboardingService = datasetonboardingService; @@ -614,10 +611,7 @@ export default class DatasetOnboardingController { return res.status(400).json({ success: false, message: 'No metadata found' }); } } catch (err) { - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.captureException(err); - } - process.stdout.write(`${err.message}\n`); + process.stdout.write(`DATASETONBOARDING - Bulk upload of metadata failed : ${err.message}\n`); return res.status(500).json({ success: false, message: 'Bulk upload of metadata failed', error: err.message }); } }; diff --git a/src/exceptions/HttpExceptions.js b/src/exceptions/HttpExceptions.js index be980e7ae..77b251b96 100644 --- a/src/exceptions/HttpExceptions.js +++ b/src/exceptions/HttpExceptions.js @@ -1,6 +1,6 @@ export default class HttpExceptions extends Error { - constructor(message) { + constructor(message, statusCode = 500) { super(message); - this.message = message; + this.status = statusCode; } } \ No newline at end of file diff --git a/src/middlewares/__tests__/activitylog.middleware.test.js b/src/middlewares/__tests__/activitylog.middleware.test.js index 71d9cd3df..7f47cf2ed 100644 --- a/src/middlewares/__tests__/activitylog.middleware.test.js +++ b/src/middlewares/__tests__/activitylog.middleware.test.js @@ -150,7 +150,6 @@ describe('Testing the ActivityLog middleware', () => { await authoriseView(req, res, nextFunction); expect(versionsStub.calledOnce).toBe(true); - expect(nextFunction.mock.calls.length).toBe(1); }); it('Should respond 401 if an error is thrown', async () => { diff --git a/src/middlewares/checkAccessTeamMiddleware.js b/src/middlewares/checkAccessTeamMiddleware.js new file mode 100644 index 000000000..de51e826c --- /dev/null +++ b/src/middlewares/checkAccessTeamMiddleware.js @@ -0,0 +1,17 @@ +import HttpExceptions from '../exceptions/HttpExceptions'; +import teamV3Util from '../resources/utilities/team.v3.util'; + +const checkAccessToTeamMiddleware = (arrayAllowedPermissions) => (req, res, next) => { + const teamId = req.params.teamid || ''; + const currentUserId = req.user._id || ''; + + if (!teamId || !currentUserId) { + throw new HttpExceptions('One or more required parameters missing', 400); + } + + req.allowPerms = arrayAllowedPermissions; + + next(); +} + +export { checkAccessToTeamMiddleware }; \ No newline at end of file diff --git a/src/middlewares/errorHandler.middleware.js b/src/middlewares/errorHandler.middleware.js new file mode 100644 index 000000000..112bbe063 --- /dev/null +++ b/src/middlewares/errorHandler.middleware.js @@ -0,0 +1,26 @@ + +import { LoggingService } from "../services"; + +const errorHandler = (error, req, res, next) => { + const errorStatusCode = error.status || 500; + const loggingService = new LoggingService(); + const loggingEnabled = parseInt(process.env.LOGGING_LOG_ENABLED) || 0; + + const errorMessage = { + type: 'error', + message: error.message, + stack: error.stack.split("\n"), + }; + + process.stdout.write(JSON.stringify(errorMessage)); + + if (loggingEnabled) { + loggingService.sendDataInLogging(errorMessage, 'ERROR'); + } + + res.status(errorStatusCode).json(errorMessage); + + return; +} + +export { errorHandler } \ No newline at end of file diff --git a/src/middlewares/index.js b/src/middlewares/index.js index 74cdab05c..6addd4715 100644 --- a/src/middlewares/index.js +++ b/src/middlewares/index.js @@ -12,6 +12,7 @@ import checkInputMiddleware from './checkInputMiddleware'; import checkMinLengthMiddleware from './checkMinLengthMiddleware'; import checkStringMiddleware from './checkStringMiddleware'; import { validateUpdateRequest, validateUploadRequest, authorizeUpdate, authorizeUpload } from './dataUseRegister.middleware'; +import { errorHandler } from './errorHandler.middleware'; export { checkIDMiddleware, @@ -30,4 +31,5 @@ export { validateUploadRequest, authorizeUpdate, authorizeUpload, + errorHandler, }; diff --git a/src/middlewares/spatialFilterMiddleware.js b/src/middlewares/spatialFilterMiddleware.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/resources/account/account.route.js b/src/resources/account/account.route.js index f2aba717f..b3d36bcf6 100644 --- a/src/resources/account/account.route.js +++ b/src/resources/account/account.route.js @@ -169,7 +169,7 @@ router.put('/status', passport.authenticate('jwt'), utils.checkIsInRole(ROLES.Ad return res.json({ success: true }); } catch (err) { - console.error(err.message); + process.stdout.write(`ACCOUNT - status : ${err.message}\n`); return res.status(500).json({ success: false, error: err }); } }); diff --git a/src/resources/auth/sso/sso.discourse.router.js b/src/resources/auth/sso/sso.discourse.router.js index e77669837..7b0f667d2 100644 --- a/src/resources/auth/sso/sso.discourse.router.js +++ b/src/resources/auth/sso/sso.discourse.router.js @@ -19,7 +19,7 @@ router.get('/', function (req, res, next) { try { redirectUrl = discourseLogin(req.query.sso, req.query.sig, req.user); } catch (err) { - console.error(err.message); + process.stdout.write(`Single Sign On for Discourse forum : ${err.message}\n`); return res.status(500).send('Error authenticating the user.'); } } diff --git a/src/resources/auth/utils.js b/src/resources/auth/utils.js index b3e1ff550..9f8218bf9 100644 --- a/src/resources/auth/utils.js +++ b/src/resources/auth/utils.js @@ -129,7 +129,7 @@ const getTeams = async () => { const catchLoginErrorAndRedirect = (req, res, next) => { if (req.auth.err || !req.auth.user) { - if (req.auth.err === 'loginError') { + if (req.auth.err === 'loginError' || req.auth.user === undefined) { return res.status(200).redirect(process.env.homeURL + '/loginerror'); } @@ -178,7 +178,7 @@ const loginAndSignToken = (req, res, next) => { try { redirectUrl = discourseLogin(queryStringParsed.sso, queryStringParsed.sig, req.user); } catch (err) { - console.error(err.message); + process.stdout.write(`UTILS - loginAndSignToken : ${err.message}\n`); return res.status(500).send('Error authenticating the user.'); } } @@ -205,7 +205,11 @@ const loginAndSignToken = (req, res, next) => { const userIsTeamManager = () => async (req, res, next) => { const { user, params } = req; const members = await TeamModel.findOne({ _id: params.id }, { _id: 0, members: { $elemMatch: { memberid: user._id } } }).lean(); - if ((!isEmpty(members) && members.members[0].roles.includes(constants.roleTypes.MANAGER)) || user.role === 'Admin') return next(); + + const isDarManager = members.members[0].roles.includes(constants.roleMemberTeam.CUST_DAR_MANAGER); + const isTeamAdmin = members.members[0].roles.includes(constants.roleMemberTeam.CUST_TEAM_ADMIN); + + if ((!isEmpty(members) && (isDarManager || isTeamAdmin)) || user.role === 'Admin') return next(); return res.status(401).json({ status: 'error', diff --git a/src/resources/bpmnworkflow/bpmnworkflow.controller.js b/src/resources/bpmnworkflow/bpmnworkflow.controller.js index d02fda88d..f1272746b 100644 --- a/src/resources/bpmnworkflow/bpmnworkflow.controller.js +++ b/src/resources/bpmnworkflow/bpmnworkflow.controller.js @@ -48,7 +48,7 @@ module.exports = { businessKey: businessKey.toString(), }; await axios.post(`${bpmnBaseUrl}/engine-rest/process-definition/key/GatewayWorkflowSimple/start`, data, config).catch(err => { - console.error(err.message); + process.stdout.write(`BPMN - postCreateProcess : ${err.message}\n`); }); }, @@ -80,7 +80,7 @@ module.exports = { }, }; await axios.post(`${bpmnBaseUrl}/engine-rest/task/${taskId}/complete`, data, config).catch(err => { - console.error(err.message); + process.stdout.write(`BPMN - postUpdateProcess : ${err.message}\n`); }); }, @@ -106,7 +106,7 @@ module.exports = { businessKey: businessKey.toString(), }; await axios.post(`${bpmnBaseUrl}/engine-rest/process-definition/key/GatewayReviewWorkflowComplex/start`, data, config).catch(err => { - console.error(err.message); + process.stdout.write(`BPMN - postStartPreReview : ${err.message}\n`); }); }, @@ -134,7 +134,7 @@ module.exports = { }, }; await axios.post(`${bpmnBaseUrl}/engine-rest/task/${taskId}/complete`, data, config).catch(err => { - console.error(err.message); + process.stdout.write(`BPMN - postStartManagerReview : ${err.message}\n`); }); }, @@ -142,7 +142,7 @@ module.exports = { // Manager has approved sectoin let { businessKey } = bpmContext; await axios.post(`${bpmnBaseUrl}/api/gateway/workflow/v1/manager/completed/${businessKey}`, bpmContext.config).catch(err => { - console.error(err.message); + process.stdout.write(`BPMN - postManagerApproval : ${err.message}\n`); }); }, @@ -150,7 +150,7 @@ module.exports = { //Start Step-Review process let { businessKey } = bpmContext; await axios.post(`${bpmnBaseUrl}/api/gateway/workflow/v1/complete/review/${businessKey}`, bpmContext, config).catch(err => { - console.error(err.message); + process.stdout.write(`BPMN - postStartStepReview : ${err.message}\n`); }); }, @@ -158,7 +158,7 @@ module.exports = { //Start Next-Step process let { businessKey } = bpmContext; await axios.post(`${bpmnBaseUrl}/api/gateway/workflow/v1/reviewer/complete/${businessKey}`, bpmContext, config).catch(err => { - console.error(err.message); + process.stdout.write(`BPMN - postCompleteReview : ${err.message}\n`); }); }, }; diff --git a/src/resources/cohort/cohort.controller.js b/src/resources/cohort/cohort.controller.js index d0c56ccc7..a802f5d1f 100644 --- a/src/resources/cohort/cohort.controller.js +++ b/src/resources/cohort/cohort.controller.js @@ -37,7 +37,7 @@ export default class CohortController extends Controller { }); } catch (err) { // Return error response if something goes wrong - console.error(err.message); + process.stdout.write(`COHORT - getCohort : ${err.message}\n`); return res.status(500).json({ success: false, message: 'A server error occurred, please try again', diff --git a/src/resources/cohortprofiling/cohortprofiling.controller.js b/src/resources/cohortprofiling/cohortprofiling.controller.js index 819c74452..06855ccd2 100644 --- a/src/resources/cohortprofiling/cohortprofiling.controller.js +++ b/src/resources/cohortprofiling/cohortprofiling.controller.js @@ -35,7 +35,7 @@ export default class CohortProfilingController extends Controller { }); } catch (err) { // Return error response if something goes wrong - console.error(err.message); + process.stdout.write(`COHORT - getCohortProfilingByVariable : ${err.message}\n`); return res.status(500).json({ success: false, message: 'A server error occurred, please try again', @@ -52,7 +52,7 @@ export default class CohortProfilingController extends Controller { // 2. Return Cohort Profiling data return res.status(200).json({ success: true, cohortProfiling }); } catch (err) { - console.error(err.message); + process.stdout.write(`COHORT - getCohortProfiling : ${err.message}\n`); return res.status(500).json({ success: false, message: err.message }); } } @@ -86,7 +86,7 @@ export default class CohortProfilingController extends Controller { // Return Cohort Profiling data return res.status(200).json({ success: true, cohortProfiling }); } catch (err) { - console.error(err.message); + process.stdout.write(`COHORT - saveCohortProfiling : ${err.message}\n`); return res.status(500).json({ success: false, message: err.message }); } } diff --git a/src/resources/collections/collectioncounter.route.js b/src/resources/collections/collectioncounter.route.js index 52e8628c1..d359fc9e7 100644 --- a/src/resources/collections/collectioncounter.route.js +++ b/src/resources/collections/collectioncounter.route.js @@ -12,7 +12,7 @@ const datasetLimiter = rateLimit({ router.post('/update', datasetLimiter, async (req, res) => { const { id, counter } = req.body; - Collections.findOneAndUpdate({ id: { $eq: id } }, { counter }, err => { + Collections.findOneAndUpdate({ id: { $eq: id } }, { $set: { counter: counter } }, { timestamps: false }, err => { if (err) return res.json({ success: false, error: err }); return res.json({ success: true }); }); diff --git a/src/resources/collections/collections.controller.js b/src/resources/collections/collections.controller.js index 632cc4daa..146b7622c 100644 --- a/src/resources/collections/collections.controller.js +++ b/src/resources/collections/collections.controller.js @@ -76,7 +76,6 @@ export default class CollectionsController extends Controller { async getCollectionRelatedResources(req, res) { let collectionID = parseInt(req.params.collectionID); - try { const data = await this.collectionsService.getCollectionObjects(collectionID); return res.json({ success: true, data: data }); diff --git a/src/resources/collections/collections.service.js b/src/resources/collections/collections.service.js index d495a5962..a6ed00928 100644 --- a/src/resources/collections/collections.service.js +++ b/src/resources/collections/collections.service.js @@ -33,6 +33,7 @@ export default class CollectionsService { } else { for (let object of res[0].relatedObjects) { let relatedObject = await this.getCollectionObject(object.objectId, object.objectType, object.pid, object.updated); + if (!_.isUndefined(relatedObject)) { relatedObjects.push(relatedObject); } else { @@ -112,7 +113,18 @@ export default class CollectionsService { } ) .populate([ - { path: 'gatewayDatasetsInfo', select: { name: 1 } }, + { + path: 'gatewayDatasetsInfo', + match: { + activeflag: { + $eq: 'active' + } + }, + select: { + name: 1, + activeflag: 1 + } + }, { path: 'publisherInfo', select: { name: 1, _id: 0 }, @@ -241,7 +253,7 @@ export default class CollectionsService { } } - let relatedObject = { ...data[0], updated: Date.parse(updated) }; + let relatedObject = { ...data[0], updated: new Date(Date.parse(updated)).toISOString() }; resolve(relatedObject); }); } diff --git a/src/resources/course/course.repository.js b/src/resources/course/course.repository.js index 0cee771ba..932cc392f 100644 --- a/src/resources/course/course.repository.js +++ b/src/resources/course/course.repository.js @@ -319,7 +319,7 @@ const setStatus = async req => { resolve(id); } catch (err) { - console.error(err.message); + process.stdout.write(`COURSE - setStatus : ${err.message}\n`); reject(new Error(err)); } }); diff --git a/src/resources/course/v2/course.controller.js b/src/resources/course/v2/course.controller.js index f337f0bd4..7d19a7df0 100644 --- a/src/resources/course/v2/course.controller.js +++ b/src/resources/course/v2/course.controller.js @@ -33,7 +33,7 @@ export default class CourseController extends Controller { }); } catch (err) { // Return error response if something goes wrong - console.error(err.message); + process.stdout.write(`COURSE - setStatus : ${err.message}\n`); return res.status(500).json({ success: false, message: 'A server error occurred, please try again', @@ -52,7 +52,7 @@ export default class CourseController extends Controller { }); } catch (err) { // Return error response if something goes wrong - console.error(err.message); + process.stdout.write(`COURSE - getCourses : ${err.message}\n`); return res.status(500).json({ success: false, message: 'A server error occurred, please try again', diff --git a/src/resources/dataUseRegister/dataUseRegister.controller.js b/src/resources/dataUseRegister/dataUseRegister.controller.js index 03903dfe1..0c55b5007 100644 --- a/src/resources/dataUseRegister/dataUseRegister.controller.js +++ b/src/resources/dataUseRegister/dataUseRegister.controller.js @@ -14,6 +14,7 @@ import { filtersService } from '../filters/dependency'; import { DataUseRegister } from '../dataUseRegister/dataUseRegister.model'; import { isEmpty, isUndefined } from 'lodash'; import { UserModel } from '../user/user.model'; +import HttpExceptions from '../../exceptions/HttpExceptions'; const logCategory = 'dataUseRegister'; @@ -33,10 +34,7 @@ export default class DataUseRegisterController extends Controller { // If no id provided, it is a bad request if (!id) { - return res.status(400).json({ - success: false, - message: 'You must provide a dataUseRegister identifier', - }); + throw new HttpExceptions(`You must provide a dataUseRegister identifier`, 400); } // Find the dataUseRegister @@ -53,10 +51,7 @@ export default class DataUseRegisterController extends Controller { // Return if no dataUseRegister found if (!dataUseRegister) { - return res.status(404).json({ - success: false, - message: 'A dataUseRegister could not be found with the provided id', - }); + throw new HttpExceptions(`A dataUseRegister could not be found with the provided id`, 404); } // Reverse look up @@ -100,12 +95,7 @@ export default class DataUseRegisterController extends Controller { ...dataUseRegister, }); } catch (err) { - // Return error response if something goes wrong - console.error(err.message); - return res.status(500).json({ - success: false, - message: 'A server error occurred, please try again', - }); + throw new HttpExceptions(`A server error occurred, please try again :: ${err.message}`, 500); } } @@ -149,12 +139,7 @@ export default class DataUseRegisterController extends Controller { }); } } catch (err) { - // Return error response if something goes wrong - logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'A server error occurred, please try again', - }); + throw new HttpExceptions(`A server error occurred, please try again :: ${err.message}`, 500); } } @@ -210,12 +195,7 @@ export default class DataUseRegisterController extends Controller { success: true, }); } catch (err) { - // Return error response if something goes wrong - logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'A server error occurred, please try again', - }); + throw new HttpExceptions(`A server error occurred, please try again :: ${JSON.stringify(err.message)}`, 500); } } @@ -231,12 +211,7 @@ export default class DataUseRegisterController extends Controller { result, }); } catch (err) { - // Return error response if something goes wrong - logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'A server error occurred, please try again', - }); + throw new HttpExceptions(`A server error occurred, please try again :: ${JSON.stringify(err.message)}`, 500); } } @@ -248,12 +223,7 @@ export default class DataUseRegisterController extends Controller { return res.status(200).json({ success: true, result }); } catch (err) { - // Return error response if something goes wrong - logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'A server error occurred, please try again', - }); + throw new HttpExceptions(`A server error occurred, please try again :: ${err.message}`, 500); } } @@ -278,22 +248,25 @@ export default class DataUseRegisterController extends Controller { as: 'publisherDetails', }, }, - { - $lookup: { - from: 'tools', - localField: 'gatewayOutputsTools', - foreignField: 'id', - as: 'gatewayOutputsToolsInfo', - }, - }, - { - $lookup: { - from: 'tools', - localField: 'gatewayOutputsPapers', - foreignField: 'id', - as: 'gatewayOutputsPapersInfo', - }, - }, + // Removed for now, see comments on: + // https://hdruk.atlassian.net/browse/GAT-1932 + // + // { + // $lookup: { + // from: 'tools', + // localField: 'gatewayOutputsTools', + // foreignField: 'id', + // as: 'gatewayOutputsToolsInfo', + // }, + // }, + // { + // $lookup: { + // from: 'tools', + // localField: 'gatewayOutputsPapers', + // foreignField: 'id', + // as: 'gatewayOutputsPapersInfo', + // }, + // }, { $lookup: { from: 'users', @@ -350,16 +323,30 @@ export default class DataUseRegisterController extends Controller { aggregateQuery.unshift({ $match: { $text: { $search: searchString } } }); } - const result = await DataUseRegister.aggregate(aggregateQuery); + const results = await DataUseRegister.aggregate(aggregateQuery); + let newPayload = []; + + // Due to the excessive size of laySummary and publicBenefitStatement + // we have to truncate the content to avoid running out of memory + // + // TODO - Needs refactoring on the whole + results.forEach(result => { + if (result.laySummary) { + result.laySummary = result.laySummary.substring(0, 200); + result.laySummary += '...'; + } + + if (result.publicBenefitStatement) { + result.publicBenefitStatement = result.publicBenefitStatement.substring(0, 200) + result.publicBenefitStatement += '...'; + } - return res.status(200).json({ success: true, result }); + newPayload.push(result); + }); + + return res.status(200).json({ success: true, newPayload }); } catch (err) { - //Return error response if something goes wrong - logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'A server error occurred, please try again', - }); + throw new HttpExceptions(`A server error occurred, please try again :: ${err.message}`, 500); } } @@ -456,12 +443,7 @@ export default class DataUseRegisterController extends Controller { this.dataUseRegisterService.updateDataUseRegister(id, { counter }); return res.status(200).json({ success: true }); } catch (err) { - // Return error response if something goes wrong - logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'A server error occurred, please try again', - }); + throw new HttpExceptions(`A server error occurred, please try again :: ${err.message}`, 500); } } } diff --git a/src/resources/dataUseRegister/dataUseRegister.route.js b/src/resources/dataUseRegister/dataUseRegister.route.js index a2ff75cf1..fa2cfc520 100644 --- a/src/resources/dataUseRegister/dataUseRegister.route.js +++ b/src/resources/dataUseRegister/dataUseRegister.route.js @@ -15,7 +15,7 @@ router.get('/search', logger.logRequestMiddleware({ logCategory, action: 'Search dataUseRegisterController.searchDataUseRegisters(req, res) ); -// @route GET /api/v2/data-use-registers/id +// @route GET /api/v2/data-use-registers/:id // @desc Returns a dataUseRegister based on dataUseRegister ID provided // @access Public router.get('/:id', logger.logRequestMiddleware({ logCategory, action: 'Viewed dataUseRegister data' }), (req, res) => @@ -35,8 +35,9 @@ router.get( // @route PATCH /api/v2/data-use-registers/counter // @desc Updates the data use register counter for page views // @access Public -router.patch('/counter', logger.logRequestMiddleware({ logCategory, action: 'Data use counter update' }), (req, res) => - dataUseRegisterController.updateDataUseRegisterCounter(req, res) +router.patch('/counter', + logger.logRequestMiddleware({ logCategory, action: 'Data use counter update' }), + (req, res) => dataUseRegisterController.updateDataUseRegisterCounter(req, res) ); // @route PATCH /api/v2/data-use-registers/id @@ -54,8 +55,10 @@ router.patch( // @route POST /api/v2/data-use-registers/check // @desc Check the submitted data uses for duplicates and returns links to Gatway entities (datasets, users) // @access Public -router.post('/check', passport.authenticate('jwt'), logger.logRequestMiddleware({ logCategory, action: 'Check data uses' }), (req, res) => - dataUseRegisterController.checkDataUseRegister(req, res) +router.post('/check', + passport.authenticate('jwt'), + logger.logRequestMiddleware({ logCategory, action: 'Check data uses' }), + (req, res) => dataUseRegisterController.checkDataUseRegister(req, res) ); // @route POST /api/v2/data-use-registers/upload diff --git a/src/resources/datarequest/amendment/amendment.controller.js b/src/resources/datarequest/amendment/amendment.controller.js index 2347b26a3..0ff7288b0 100644 --- a/src/resources/datarequest/amendment/amendment.controller.js +++ b/src/resources/datarequest/amendment/amendment.controller.js @@ -4,7 +4,8 @@ import constants from '../../utilities/constants.util'; import datarequestUtil from '../utils/datarequest.util'; import teamController from '../../team/team.controller'; import Controller from '../../base/controller'; -import { logger } from '../../utilities/logger'; +import HttpExceptions from '../../../exceptions/HttpExceptions'; +import teamV3Util from '../../utilities/team.v3.util'; const logCategory = 'Data Access Request'; @@ -25,6 +26,7 @@ export default class AmendmentController extends Controller { const requestingUserId = parseInt(req.user.id); const requestingUserObjectId = req.user._id; let { questionId, questionSetId, mode, reason, answer } = req.body; + if (_.isEmpty(questionId) || _.isEmpty(questionSetId)) { return res.status(400).json({ success: false, @@ -35,7 +37,7 @@ export default class AmendmentController extends Controller { // 2. Retrieve DAR from database const accessRecord = await this.dataRequestService.getApplicationWithTeamById(id); if (!accessRecord) { - return res.status(404).json({ status: 'error', message: 'Application not found.' }); + throw new HttpExceptions(`Application not found.`, 404); } // 3. If application is not in review or submitted, amendments cannot be made @@ -43,18 +45,16 @@ export default class AmendmentController extends Controller { accessRecord.applicationStatus !== constants.applicationStatuses.SUBMITTED && accessRecord.applicationStatus !== constants.applicationStatuses.INREVIEW ) { - return res.status(400).json({ - success: false, - message: 'This application is not within a reviewable state and amendments cannot be made or requested at this time.', - }); + throw new HttpExceptions(`This application is not within a reviewable state and amendments cannot be made or requested at this time.`, 400); } // 4. Get the requesting users permission levels - let { authorised, userType } = datarequestUtil.getUserPermissionsForApplication( + let userType = datarequestUtil.getUserPermissionsForApplication( accessRecord.toObject(), requestingUserId, requestingUserObjectId ); + let authorised = true; // 5. Get the current iteration amendment party let validParty = false; @@ -92,22 +92,18 @@ export default class AmendmentController extends Controller { // 7. Return unauthorised message if the user did not have sufficient access for action requested if (!authorised) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); + throw new HttpExceptions(`Unauthorised`, 401); } // 8. Return bad request if the opposite party is editing the application if (!validParty) { - return res.status(400).json({ - status: 'failure', - message: 'You cannot make or request amendments to this application as the opposite party are currently responsible for it.', - }); + throw new HttpExceptions(`You cannot make or request amendments to this application as the opposite party are currently responsible for it.`, 400); } // 9. Save changes to database await accessRecord.save(async err => { if (err) { - console.error(err.message); - return res.status(500).json({ status: 'error', message: err.message }); + throw new HttpExceptions(err.message, 500); } else { // 10. Update json schema and question answers with modifications since original submission and retain previous version requested updates let accessRecordObj = accessRecord.toObject(); @@ -157,7 +153,7 @@ export default class AmendmentController extends Controller { } // 12. Append question actions depending on user type and application status - let userRole = activeParty === constants.userTypes.CUSTODIAN ? constants.roleTypes.MANAGER : ''; + let userRole = activeParty === constants.userTypes.CUSTODIAN ? constants.roleMemberTeam.CUST_DAR_MANAGER : ''; accessRecordObj.jsonSchema = datarequestUtil.injectQuestionActions( accessRecordObj.jsonSchema, userType, @@ -181,12 +177,8 @@ export default class AmendmentController extends Controller { } }); } catch (err) { - // Return error response if something goes wrong - logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred updating the application amendment', - }); + process.stdout.write(err.message); + throw new HttpExceptions(`An error occurred updating the application amendment : ${err.message}`, 500); } } @@ -202,17 +194,17 @@ export default class AmendmentController extends Controller { let accessRecord = await this.dataRequestService.getApplicationForUpdateRequest(id); if (!accessRecord) { - return res.status(404).json({ status: 'error', message: 'Application not found.' }); + throw new HttpExceptions(`Application not found.`, 404); } // 3. Check permissions of user is manager of associated team let authorised = false; if (_.has(accessRecord.toObject(), 'publisherObj.team')) { const { team } = accessRecord.publisherObj; - authorised = teamController.checkTeamPermissions(constants.roleTypes.MANAGER, team.toObject(), requestingUserObjectId); + authorised = teamV3Util.checkUserRolesByTeam([constants.roleMemberTeam.CUST_DAR_MANAGER], team.toObject(), requestingUserObjectId); } if (!authorised) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); + throw new HttpExceptions(`Unauthorised`, 401); } // 4. Ensure single datasets are mapped correctly into array (backward compatibility for single dataset applications) @@ -223,19 +215,13 @@ export default class AmendmentController extends Controller { // 5. Get the current iteration amendment party and return bad request if the opposite party is editing the application const activeParty = this.amendmentService.getAmendmentIterationParty(accessRecord); if (activeParty !== constants.userTypes.CUSTODIAN) { - return res.status(400).json({ - status: 'failure', - message: 'You cannot make or request amendments to this application as the applicant(s) are amending the current version.', - }); + throw new HttpExceptions(`You cannot make or request amendments to this application as the applicant(s) are amending the current version.`, 400); } // 6. Check some amendments exist to be submitted to the applicant(s) const { unansweredAmendments } = this.amendmentService.countAmendments(accessRecord, constants.userTypes.CUSTODIAN); if (unansweredAmendments === 0) { - return res.status(400).json({ - status: 'failure', - message: 'You cannot submit requested amendments as none have been requested in the current version', - }); + throw new HttpExceptions(`You cannot submit requested amendments as none have been requested in the current version`, 400); } // 7. Find current amendment iteration index @@ -247,8 +233,7 @@ export default class AmendmentController extends Controller { // 9. Save changes to database await accessRecord.save(async err => { if (err) { - console.error(err.message); - return res.status(500).json({ status: 'error', message: err.message }); + throw new HttpExceptions(err.message, 500); } else { // 10. Send update request notifications let fullAccessRecord = await this.dataRequestService.getApplicationById(id); @@ -263,12 +248,7 @@ export default class AmendmentController extends Controller { } }); } catch (err) { - // Return error response if something goes wrong - logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred attempting to submit the requested updates', - }); + throw new HttpExceptions(`An error occurred attempting to submit the requested updates : ${err.message}`, 500); } } } diff --git a/src/resources/datarequest/datarequest.controller.js b/src/resources/datarequest/datarequest.controller.js index 2f4cb6312..d9163217a 100644 --- a/src/resources/datarequest/datarequest.controller.js +++ b/src/resources/datarequest/datarequest.controller.js @@ -3,6 +3,7 @@ import moment from 'moment'; import mongoose from 'mongoose'; import teamController from '../team/team.controller'; +import teamV3Util from '../utilities/team.v3.util'; import datarequestUtil from './utils/datarequest.util'; import notificationBuilder from '../utilities/notificationBuilder'; import emailGenerator from '../utilities/emailGenerator.util'; @@ -17,6 +18,7 @@ import { UserModel } from '../user/user.model'; import { PublisherModel } from '../publisher/publisher.model'; import { dataUseRegisterController } from '../dataUseRegister/dependency'; import { publishMessageWithRetryToPubSub } from '../../services/google/PubSubWithRetryService'; +import HttpExceptions from '../../exceptions/HttpExceptions'; const logCategory = 'Data Access Request'; const bpmController = require('../bpmnworkflow/bpmnworkflow.controller'); @@ -83,12 +85,8 @@ export default class DataRequestController extends Controller { canViewSubmitted: true, }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred searching for user applications', - }); + throw new HttpExceptions(`An error occurred searching for user applications`, 500); } } @@ -107,7 +105,7 @@ export default class DataRequestController extends Controller { // 2. Find the matching record and include attached datasets records with publisher details and workflow details let accessRecord = await this.dataRequestService.getApplicationById(id); if (!accessRecord) { - return res.status(404).json({ status: 'error', message: 'The application could not be found.' }); + throw new HttpExceptions(`The application could not be found.`, 404); } // 3. If invalid version requested, return 404 @@ -116,7 +114,7 @@ export default class DataRequestController extends Controller { requestedVersion ); if (!isValidVersion) { - return res.status(404).json({ status: 'error', message: 'The requested application version could not be found.' }); + throw new HttpExceptions(`The requested application version could not be found.`, 404); } // 4. Get requested amendment iteration details @@ -126,14 +124,11 @@ export default class DataRequestController extends Controller { ); // 5. Check if requesting user is custodian member or applicant/contributor - const { authorised, userType } = datarequestUtil.getUserPermissionsForApplication( + const userType = datarequestUtil.getUserPermissionsForApplication( accessRecord, requestingUserId, requestingUserObjectId ); - if (!authorised) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); - } // 6. Set edit mode for applicants who have not yet submitted const { applicationStatus, jsonSchema, versionTree, applicationType } = accessRecord; @@ -148,7 +143,7 @@ export default class DataRequestController extends Controller { // 9. Get role type for requesting user, applicable for only Custodian users i.e. Manager/Reviewer role const userRole = - userType === constants.userTypes.APPLICANT ? '' : isManager ? constants.roleTypes.MANAGER : constants.roleTypes.REVIEWER; + userType === constants.userTypes.APPLICANT ? '' : isManager ? constants.roleMemberTeam.CUST_DAR_MANAGER : constants.roleMemberTeam.CUST_DAR_REVIEWER; // 10. Handle amendment type application loading for Custodian showing any changes in the major version if (applicationType === constants.submissionTypes.AMENDED && userType === constants.userTypes.CUSTODIAN) { @@ -222,12 +217,8 @@ export default class DataRequestController extends Controller { }, }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred opening this data access request application', - }); + throw new HttpExceptions(`An error occurred opening this data access request application`, 500); } } @@ -262,7 +253,7 @@ export default class DataRequestController extends Controller { return await this.getAccessRequestById(req, res); } else { if (_.isEmpty(datasets)) { - return res.status(500).json({ status: 'error', message: 'No datasets available.' }); + throw new HttpExceptions(`No datasets available.`, 500); } const { datasetfields: { publisher = '' }, @@ -321,12 +312,8 @@ export default class DataRequestController extends Controller { }, }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred opening a data access request application for the requested dataset(s)', - }); + throw new HttpExceptions(`An error occurred opening a data access request application for the requested dataset(s)`, 500); } } @@ -347,12 +334,8 @@ export default class DataRequestController extends Controller { }), }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred populating additional information for contributors.', - }); + throw new HttpExceptions(`An error occurred populating additional information for contributors.`, 500); } } @@ -377,14 +360,11 @@ export default class DataRequestController extends Controller { } // 3. Check user type and authentication to submit application - let { authorised, userType } = datarequestUtil.getUserPermissionsForApplication( + let userType = datarequestUtil.getUserPermissionsForApplication( accessRecord, requestingUserId, requestingUserObjectId ); - if (!authorised) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); - } // 4. Ensure single datasets are mapped correctly into array (backward compatibility for single dataset applications) if (_.isEmpty(accessRecord.datasets)) { @@ -427,10 +407,7 @@ export default class DataRequestController extends Controller { // 6. Ensure a valid submission is taking place if (_.isNil(accessRecord.applicationType)) { - return res.status(400).json({ - status: 'error', - message: 'Application cannot be submitted as it has reached a final decision status.', - }); + throw new HttpExceptions(`Application cannot be submitted as it has reached a final decision status.`, 400); } // 7. Save changes to db @@ -488,15 +465,10 @@ export default class DataRequestController extends Controller { } } - // 11. Return aplication and successful response return res.status(200).json({ status: 'success', data: savedAccessRecord }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred submitting the application', - }); + throw new HttpExceptions(`An error occurred submitting the application`, 500); } } @@ -518,7 +490,7 @@ export default class DataRequestController extends Controller { let accessRecord = await this.dataRequestService.getApplicationToUpdateById(id); // 4. Check access record if (!accessRecord) { - return res.status(404).json({ status: 'error', message: 'Data Access Request not found.' }); + throw new HttpExceptions(`Data Access Request not found.`, 404); } // 5. Update record object accessRecord = await this.dataRequestService.updateApplication(accessRecord, updateObj).catch(err => { @@ -580,12 +552,8 @@ export default class DataRequestController extends Controller { jsonSchema: dirtySchema ? accessRecord.jsonSchema : undefined, }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred updating the application', - }); + throw new HttpExceptions(`An error occurred updating the application`, 500); } } @@ -607,40 +575,33 @@ export default class DataRequestController extends Controller { // 3. Find the relevant data request application let accessRecord = await this.dataRequestService.getApplicationWithWorkflowById(id, { lean: false }); if (!accessRecord) { - return res.status(404).json({ status: 'error', message: 'Application not found.' }); + throw new HttpExceptions(`Application not found.`, 404); } // 4. Check if the user is permitted to perform update to application let isDirty = false, statusChange = false, contributorChange = false; - let { authorised, userType } = datarequestUtil.getUserPermissionsForApplication( + let userType = datarequestUtil.getUserPermissionsForApplication( accessRecord.toObject(), requestingUserId, requestingUserObjectId ); - if (!authorised) { - return res.status(401).json({ - status: 'error', - message: 'Unauthorised to perform this update.', - }); - } - let { authorIds: currentAuthors } = accessRecord; let newAuthors = []; // 5. Extract new application status and desc to save updates if (userType === constants.userTypes.CUSTODIAN) { // Only a custodian manager can set the final status of an application - authorised = false; const { team = {} } = accessRecord.publisherObj.toObject(); if (!_.isEmpty(team)) { - authorised = teamController.checkTeamPermissions(constants.roleTypes.MANAGER, team, requestingUserObjectId); - } - if (!authorised) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); + let authorised = teamV3Util.checkUserRolesByTeam([constants.roleMemberTeam.CUST_DAR_MANAGER], team, requestingUserObjectId); + if (!authorised) { + throw new HttpExceptions(`User not authorized to perform this action`,403); + } } + // Extract params from body ({ applicationStatus, applicationStatusDesc } = req.body); const finalStatuses = [ @@ -791,12 +752,8 @@ export default class DataRequestController extends Controller { data: accessRecord._doc, }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred updating the application', - }); + throw new HttpExceptions(`An error occurred updating the application`, 500); } } @@ -815,23 +772,20 @@ export default class DataRequestController extends Controller { const appToDelete = await this.dataRequestService.getApplicationWithTeamById(appIdToDelete, { lean: true }); // 3. Get the requesting users permission levels - let { authorised, userType } = datarequestUtil.getUserPermissionsForApplication( + let userType = datarequestUtil.getUserPermissionsForApplication( appToDelete, requestingUserId, requestingUserObjectId ); // 4. Return unauthorised message if the requesting user is not an applicant - if (!authorised || userType !== constants.userTypes.APPLICANT) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); + if (userType !== constants.userTypes.APPLICANT) { + throw new HttpExceptions(`Unauthorised`, 401); } // 5. If application is not in progress, actions cannot be performed if (appToDelete.applicationStatus !== constants.applicationStatuses.INPROGRESS) { - return res.status(400).json({ - success: false, - message: 'This application is no longer in pre-submission status and therefore this action cannot be performed', - }); + throw new HttpExceptions(`This application is no longer in pre-submission status and therefore this action cannot be performed`, 400); } // 6. Delete application @@ -846,12 +800,8 @@ export default class DataRequestController extends Controller { success: true, }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred deleting the existing application', - }); + throw new HttpExceptions(`An error occurred deleting the existing application`, 500); } } @@ -874,24 +824,24 @@ export default class DataRequestController extends Controller { let appToClone = await this.dataRequestService.getApplicationWithTeamById(id, { lean: true }); if (!appToClone) { - return res.status(404).json({ status: 'error', message: 'Application not found.' }); + throw new HttpExceptions(`Application not found.`, 404); } // 3. If invalid version requested to clone, return 404 const { isValidVersion, requestedMinorVersion } = this.dataRequestService.validateRequestedVersion(appToClone, requestedVersion); if (!isValidVersion) { - return res.status(404).json({ status: 'error', message: 'The requested application version could not be found.' }); + throw new HttpExceptions(`The requested application version could not be found.`, 404); } // 4. Get requested amendment iteration details const { versionIndex } = this.amendmentService.getAmendmentIterationDetailsByVersion(appToClone, requestedMinorVersion); // 5. Get the requesting users permission levels - let { authorised, userType } = datarequestUtil.getUserPermissionsForApplication(appToClone, requestingUserId, requestingUserObjectId); + let userType = datarequestUtil.getUserPermissionsForApplication(appToClone, requestingUserId, requestingUserObjectId); // 6. Return unauthorised message if the requesting user is not an applicant - if (!authorised || userType !== constants.userTypes.APPLICANT) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); + if (userType !== constants.userTypes.APPLICANT) { + throw new HttpExceptions(`Unauthorised`, 401); } // 7. Update question answers with modifications since original submission @@ -914,16 +864,16 @@ export default class DataRequestController extends Controller { const appToCloneInto = await this.dataRequestService.getApplicationWithTeamById(appIdToCloneInto, { lean: true }); // Ensure application to clone into was found if (!appToCloneInto) { - return res.status(404).json({ status: 'error', message: 'Application to clone into not found.' }); + throw new HttpExceptions(`Application to clone into not found.`, 404); } // Get permissions for application to clone into - let { authorised, userType } = datarequestUtil.getUserPermissionsForApplication( + let userType = datarequestUtil.getUserPermissionsForApplication( appToCloneInto, requestingUserId, requestingUserObjectId ); // Return unauthorised message if the requesting user is not authorised to the new application - if (!authorised || userType !== constants.userTypes.APPLICANT) { + if (userType !== constants.userTypes.APPLICANT) { return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); } clonedAccessRecord = await datarequestUtil.cloneIntoExistingApplication(appToClone, appToCloneInto); @@ -949,12 +899,8 @@ export default class DataRequestController extends Controller { accessRecord: clonedAccessRecord, }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred cloning the existing application', - }); + throw new HttpExceptions(`An error occurred cloning the existing application.`, 500); } } @@ -969,34 +915,29 @@ export default class DataRequestController extends Controller { const requestingUserObjectId = req.user._id; let { questionId, questionSetId, questionIds = [], mode, separatorText = '' } = req.body; if (_.isEmpty(questionId) || _.isEmpty(questionSetId)) { - return res.status(400).json({ - success: false, - message: 'You must supply the unique identifiers for the question to perform an action', - }); + throw new HttpExceptions(`You must supply the unique identifiers for the question to perform an action`, 400); } // 2. Retrieve DAR from database let accessRecord = await this.dataRequestService.getApplicationWithTeamById(id, { lean: false }); if (!accessRecord) { - return res.status(404).json({ status: 'error', message: 'Application not found.' }); + throw new HttpExceptions(`Application not found.`, 404); } // 3. If application is not in progress, actions cannot be performed if (accessRecord.applicationStatus !== constants.applicationStatuses.INPROGRESS) { - return res.status(400).json({ - success: false, - message: 'This application is no longer in pre-submission status and therefore this action cannot be performed', - }); + throw new HttpExceptions(`This application is no longer in pre-submission status and therefore this action cannot be performed`, 400); } // 4. Get the requesting users permission levels - let { authorised, userType } = datarequestUtil.getUserPermissionsForApplication( + let userType = datarequestUtil.getUserPermissionsForApplication( accessRecord.toObject(), requestingUserId, requestingUserObjectId ); // 5. Return unauthorised message if the requesting user is not an applicant - if (!authorised || userType !== constants.userTypes.APPLICANT) { + if (userType !== constants.userTypes.APPLICANT) { + throw new HttpExceptions(`Unauthorised`, 401); return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); } @@ -1015,30 +956,21 @@ export default class DataRequestController extends Controller { break; case constants.formActions.ADDREPEATABLEQUESTIONS: if (_.isEmpty(questionIds)) { - return res.status(400).json({ - success: false, - message: 'You must supply the question identifiers to duplicate when performing this action', - }); + throw new HttpExceptions(`You must supply the question identifiers to duplicate when performing this action`, 400); } const duplicateQuestions = dynamicForm.duplicateQuestions(questionSetId, questionIds, separatorText, jsonSchema); jsonSchema = dynamicForm.insertQuestions(questionSetId, questionId, duplicateQuestions, jsonSchema); break; case constants.formActions.REMOVEREPEATABLEQUESTIONS: if (_.isEmpty(questionIds)) { - return res.status(400).json({ - success: false, - message: 'You must supply the question identifiers to remove when performing this action', - }); + throw new HttpExceptions(`You must supply the question identifiers to remove when performing this action`, 400); } questionIds = [...questionIds, questionId]; jsonSchema = dynamicForm.removeQuestionReferences(questionSetId, questionIds, jsonSchema); questionAnswers = dynamicForm.removeQuestionAnswers(questionIds, questionAnswers); break; default: - return res.status(400).json({ - success: false, - message: 'You must supply a valid action to perform', - }); + throw new HttpExceptions(`You must supply a valid action to perform`, 400); } // 8. Update record @@ -1068,12 +1000,8 @@ export default class DataRequestController extends Controller { }, }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred updating the application amendment', - }); + throw new HttpExceptions(`An error occurred updating the application amendment`, 500); } } @@ -1092,31 +1020,29 @@ export default class DataRequestController extends Controller { // 2. Find the matching record and include attached datasets records with publisher details and workflow details let accessRecord = await this.dataRequestService.getApplicationById(id); if (!accessRecord) { - return res.status(404).json({ status: 'error', message: 'The application could not be found.' }); + throw new HttpExceptions(`The application could not be found.`, 404); } // 3. Check if requesting user is custodian member or applicant/contributor - const { authorised, userType } = datarequestUtil.getUserPermissionsForApplication( + const userType = datarequestUtil.getUserPermissionsForApplication( accessRecord, requestingUserId, requestingUserObjectId ); - if (!authorised || userType !== constants.userTypes.APPLICANT) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); + if (userType !== constants.userTypes.APPLICANT) { + throw new HttpExceptions(`Unauthorised`, 401); } // 4. If invalid version requested, return 404 const { isValidVersion, requestedMinorVersion } = this.dataRequestService.validateRequestedVersion(accessRecord, requestedVersion); if (!isValidVersion) { - return res.status(404).json({ status: 'error', message: 'The requested application version could not be found.' }); + throw new HttpExceptions(`The requested application version could not be found.`, 404); } // 5. Check version is the latest version const { isLatestMinorVersion } = this.amendmentService.getAmendmentIterationDetailsByVersion(accessRecord, requestedMinorVersion); if (!isLatestMinorVersion) { - return res - .status(400) - .json({ status: 'error', message: 'This action can only be performed against the latest version of an approved application' }); + throw new HttpExceptions(`This action can only be performed against the latest version of an approved application`, 400); } // 6. Check application is in correct status @@ -1125,9 +1051,7 @@ export default class DataRequestController extends Controller { applicationStatus !== constants.applicationStatuses.APPROVED && applicationStatus !== constants.applicationStatuses.APPROVEDWITHCONDITIONS ) { - return res - .status(400) - .json({ status: 'error', message: 'This action can only be performed against an application that has been approved' }); + throw new HttpExceptions(`This action can only be performed against an application that has been approved`, 400); } // 7. Update question answers with modifications since original submission (minor version updates) @@ -1139,7 +1063,7 @@ export default class DataRequestController extends Controller { }); if (!newAccessRecord) { - return res.status(400).json({ status: 'error', message: 'Creating application amendment failed' }); + throw new HttpExceptions(`Creating application amendment failed`, 400); } // 9. Get amended application (new major version) with all details populated @@ -1154,12 +1078,8 @@ export default class DataRequestController extends Controller { }, }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred opening this data access request application', - }); + throw new HttpExceptions(`An error occurred opening this data access request application`, 500); } } @@ -1181,17 +1101,14 @@ export default class DataRequestController extends Controller { // 4. Get access record let accessRecord = await this.dataRequestService.getApplicationWithTeamById(id, { lean: false }); if (!accessRecord) { - return res.status(404).json({ status: 'error', message: 'Application not found.' }); + throw new HttpExceptions(`Application not found.`, 404); } // 5. Check if requesting user is custodian member or applicant/contributor - let { authorised } = datarequestUtil.getUserPermissionsForApplication(accessRecord, requestingUserId, requestingUserObjectId); - // 6. Check authorisation - if (!authorised) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); - } + datarequestUtil.getUserPermissionsForApplication(accessRecord, requestingUserId, requestingUserObjectId); + // 7. Check files if (_.isEmpty(files)) { - return res.status(400).json({ status: 'error', message: 'No files to upload' }); + throw new HttpExceptions(`No files to upload`, 400); } // 8. Upload files const mediaFiles = await this.dataRequestService @@ -1202,12 +1119,8 @@ export default class DataRequestController extends Controller { // 9. return response return res.status(200).json({ status: 'success', mediaFiles }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred uploading the file to the application', - }); + throw new HttpExceptions(`An error occurred uploading the file to the application`, 500); } } @@ -1222,7 +1135,7 @@ export default class DataRequestController extends Controller { // 2. get AccessRecord const accessRecord = await this.dataRequestService.getFilesForApplicationById(id); if (!accessRecord) { - return res.status(404).json({ status: 'error', message: 'Application not found.' }); + throw new HttpExceptions(`Application not found.`, 404); } // 3. get file @@ -1232,12 +1145,8 @@ export default class DataRequestController extends Controller { // 4. Return successful response return res.status(200).json({ status: accessRecord.files[fileIndex].status }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred attempting to retrieve the status of an uploaded file', - }); + throw new HttpExceptions(`An error occurred attempting to retrieve the status of an uploaded file`, 500); } } @@ -1252,7 +1161,7 @@ export default class DataRequestController extends Controller { // 2. Get AccessRecord const accessRecord = await this.dataRequestService.getFilesForApplicationById(id, { lean: false }); if (!accessRecord) { - return res.status(404).json({ status: 'error', message: 'Application not found.' }); + throw new HttpExceptions(`Application not found.`, 404); } // 3. Find the file in the files array from db const mediaFile = @@ -1262,10 +1171,7 @@ export default class DataRequestController extends Controller { }) || {}; // 4. No file return if (_.isEmpty(mediaFile)) { - return res.status(400).json({ - status: 'error', - message: 'No file to download, please try again later', - }); + throw new HttpExceptions(`No file to download, please try again later`, 400); } // 6. get the name of the file let { name, fileId: dbFileId } = mediaFile; @@ -1275,12 +1181,8 @@ export default class DataRequestController extends Controller { // 8. send file back to user return res.status(200).sendFile(`${process.env.TMPDIR}${initialApplicationId}/${dbFileId}_${name}`); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred attempting to retrieve an uploaded file', - }); + throw new HttpExceptions(`An error occurred attempting to retrieve an uploaded file`, 500); } } @@ -1298,7 +1200,7 @@ export default class DataRequestController extends Controller { const accessRecord = await this.dataRequestService.getFilesForApplicationById(id); if (!accessRecord) { - return res.status(404).json({ status: 'error', message: 'Application not found.' }); + throw new HttpExceptions(`Application not found.`, 404); } //3. Check the status is valid @@ -1308,7 +1210,7 @@ export default class DataRequestController extends Controller { status !== fileStatus.ERROR && status !== fileStatus.QUARANTINED ) { - return res.status(400).json({ status: 'error', message: 'File status not valid' }); + throw new HttpExceptions(`File status not valid`, 400); } //4. Update all versions of application using version tree @@ -1318,12 +1220,8 @@ export default class DataRequestController extends Controller { success: true, }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred attempting to update the status of an uploaded file', - }); + throw new HttpExceptions(`An error occurred attempting to update the status of an uploaded file`, 500); } } @@ -1343,27 +1241,24 @@ export default class DataRequestController extends Controller { const accessRecord = await this.dataRequestService.getFilesForApplicationById(id, { lean: false }); if (!accessRecord) { - return res.status(404).json({ status: 'error', message: 'Application not found.' }); + throw new HttpExceptions(`Application not found.`, 404); } // 3. If application is not in progress, actions cannot be performed if (accessRecord.applicationStatus !== constants.applicationStatuses.INPROGRESS) { - return res.status(400).json({ - success: false, - message: 'This application is no longer in pre-submission status and therefore this action cannot be performed', - }); + throw new HttpExceptions(`This application is no longer in pre-submission status and therefore this action cannot be performed`, 400); } // 4. Get the requesting users permission levels - let { authorised, userType } = datarequestUtil.getUserPermissionsForApplication( + let userType = datarequestUtil.getUserPermissionsForApplication( accessRecord.toObject(), requestingUserId, requestingUserObjectId ); // 5. Return unauthorised message if the requesting user is not an applicant - if (!authorised || userType !== constants.userTypes.APPLICANT) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); + if (userType !== constants.userTypes.APPLICANT) { + throw new HttpExceptions(`Unauthorised`, 401); } // 6. Remove the file from the application @@ -1378,8 +1273,8 @@ export default class DataRequestController extends Controller { // 8. Return successful response return res.status(200).json({ status: 'success' }); } catch (err) { - console.error(err.message); - res.status(500).json({ status: 'error', message: err.message }); + process.stdout.write(`DATA REQUEST - updateAccessRequestDeleteFile : ${err.message}\n`); + throw new HttpExceptions(err.message, 500); } } @@ -1396,55 +1291,40 @@ export default class DataRequestController extends Controller { const requestingUserObjectId = req.user._id; const { workflowId = '' } = req.body; if (_.isEmpty(workflowId)) { - return res.status(400).json({ - success: false, - message: 'You must supply the unique identifier to assign a workflow to this application', - }); + throw new HttpExceptions(`You must supply the unique identifier to assign a workflow to this application`, 400); } // 2. Retrieve DAR from database let accessRecord = await this.dataRequestService.getApplicationWithTeamById(id, { lean: false }); if (!accessRecord) { - return res.status(404).json({ status: 'error', message: 'Application not found.' }); + throw new HttpExceptions(`Application not found.`, 404); } // 3. Check permissions of user is manager of associated team - let authorised = false; if (_.has(accessRecord.toObject(), 'publisherObj.team')) { let { team } = accessRecord.publisherObj; - authorised = teamController.checkTeamPermissions(constants.roleTypes.MANAGER, team.toObject(), requestingUserObjectId); - } - - // 4. Refuse access if not authorised - if (!authorised) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); + let authorised = teamV3Util.checkUserRolesByTeam([constants.roleMemberTeam.CUST_DAR_MANAGER], team.toObject(), requestingUserObjectId); + if (!authorised) { + throw new HttpExceptions(`User not authorized to perform this action`,403); + } } // 5. Check publisher allows workflows const { workflowEnabled = false } = accessRecord.publisherObj; if (!workflowEnabled) { - return res.status(400).json({ - success: false, - message: 'This custodian has not enabled workflows', - }); + throw new HttpExceptions(`This custodian has not enabled workflows`, 400); } // 6. Check no workflow already assigned const { workflowId: currentWorkflowId = '' } = accessRecord; if (!_.isEmpty(currentWorkflowId)) { - return res.status(400).json({ - success: false, - message: 'This application already has a workflow assigned', - }); + throw new HttpExceptions(`This application already has a workflow assigned`, 400); } // 7. Check application is in-review const { applicationStatus } = accessRecord; if (applicationStatus !== constants.applicationStatuses.INREVIEW) { - return res.status(400).json({ - success: false, - message: 'The application status must be set to in review to assign a workflow', - }); + throw new HttpExceptions(`The application status must be set to in review to assign a workflow`, 400); } // 8. Assign workflow and save changes to application @@ -1478,12 +1358,8 @@ export default class DataRequestController extends Controller { success: true, }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred assigning the workflow', - }); + throw new HttpExceptions(`An error occurred assigning the workflow`, 500); } } @@ -1499,38 +1375,29 @@ export default class DataRequestController extends Controller { // 2. Retrieve DAR from database let accessRecord = await this.dataRequestService.getApplicationWithWorkflowById(id, { lean: false }); if (!accessRecord) { - return res.status(404).json({ status: 'error', message: 'Application not found.' }); + throw new HttpExceptions(`Application not found.`, 404); } // 3. Check permissions of user is manager of associated team - let authorised = false; if (_.has(accessRecord.toObject(), 'publisherObj.team')) { const { team } = accessRecord.publisherObj; - authorised = teamController.checkTeamPermissions(constants.roleTypes.MANAGER, team.toObject(), requestingUserObjectId); - } - - // 4. Refuse access if not authorised - if (!authorised) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); + let authorised = teamV3Util.checkUserRolesByTeam([constants.roleMemberTeam.CUST_DAR_MANAGER], team.toObject(), requestingUserObjectId); + if (!authorised) { + throw new HttpExceptions(`User not authorized to perform this action`,403); + } } // 5. Check application is in review state const { applicationStatus } = accessRecord; if (applicationStatus !== constants.applicationStatuses.INREVIEW) { - return res.status(400).json({ - success: false, - message: 'The application status must be set to in review', - }); + throw new HttpExceptions(`The application status must be set to in review`, 400); } // 6. Check a workflow is assigned with valid steps const { workflow = {} } = accessRecord; const { steps = [] } = workflow; if (_.isEmpty(workflow) || _.isEmpty(steps)) { - return res.status(400).json({ - success: false, - message: 'A valid workflow has not been attached to this application', - }); + throw new HttpExceptions(`A valid workflow has not been attached to this application`, 400); } // 7. Get the attached active workflow step @@ -1538,10 +1405,7 @@ export default class DataRequestController extends Controller { return step.active === true; }); if (activeStepIndex === -1) { - return res.status(400).json({ - success: false, - message: 'There is no active step to override for this workflow', - }); + throw new HttpExceptions(`There is no active step to override for this workflow`, 400); } // 8. Update the step to be completed closing off end date/time @@ -1601,12 +1465,8 @@ export default class DataRequestController extends Controller { // 16. Return aplication and successful response return res.status(200).json({ status: 'success' }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred assigning the workflow', - }); + throw new HttpExceptions(`An error occurred assigning the workflow`, 500); } } @@ -1621,46 +1481,34 @@ export default class DataRequestController extends Controller { const requestingUserObjectId = req.user._id; const { approved, comments = '' } = req.body; if (_.isUndefined(approved) || _.isEmpty(comments)) { - return res.status(400).json({ - success: false, - message: 'You must supply the approved status with a reason', - }); + throw new HttpExceptions(`You must supply the approved status with a reason`, 400); } // 2. Retrieve DAR from database let accessRecord = await this.dataRequestService.getApplicationWithWorkflowById(id, { lean: false }); if (!accessRecord) { - return res.status(404).json({ status: 'error', message: 'Application not found.' }); + throw new HttpExceptions(`Application not found.`, 400); } // 3. Check permissions of user is reviewer of associated team - let authorised = false; if (_.has(accessRecord.toObject(), 'publisherObj.team')) { const { team } = accessRecord.publisherObj; - authorised = teamController.checkTeamPermissions(constants.roleTypes.REVIEWER, team.toObject(), requestingUserObjectId); - } - - // 4. Refuse access if not authorised - if (!authorised) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); + let authorised = teamV3Util.checkUserRolesByTeam([constants.roleMemberTeam.CUST_DAR_REVIEWER], team.toObject(), requestingUserObjectId); + if (!authorised) { + throw new HttpExceptions(`User not authorized to perform this action`,403); + } } // 5. Check application is in-review const { applicationStatus } = accessRecord; if (applicationStatus !== constants.applicationStatuses.INREVIEW) { - return res.status(400).json({ - success: false, - message: 'The application status must be set to in review to cast a vote', - }); + throw new HttpExceptions(`The application status must be set to in review to cast a vote`, 400); } // 6. Ensure a workflow has been attached to this application const { workflow } = accessRecord; if (!workflow) { - return res.status(400).json({ - success: false, - message: 'There is no workflow attached to this application in order to cast a vote', - }); + throw new HttpExceptions(`There is no workflow attached to this application in order to cast a vote`, 400); } // 7. Ensure the requesting user is expected to cast a vote @@ -1669,10 +1517,7 @@ export default class DataRequestController extends Controller { return step.active === true; }); if (!steps[activeStepIndex].reviewers.map(reviewer => reviewer._id.toString()).includes(requestingUserObjectId.toString())) { - return res.status(400).json({ - success: false, - message: 'You have not been assigned to vote on this review phase', - }); + throw new HttpExceptions(`You have not been assigned to vote on this review phase`, 400); } //8. Ensure the requesting user has not already voted @@ -1682,10 +1527,7 @@ export default class DataRequestController extends Controller { return rec.reviewer.equals(requestingUserObjectId); }); if (found) { - return res.status(400).json({ - success: false, - message: 'You have already voted on this review phase', - }); + throw new HttpExceptions(`You have already voted on this review phase`, 400); } } @@ -1770,12 +1612,8 @@ export default class DataRequestController extends Controller { // 17. Return aplication and successful response return res.status(200).json({ status: 'success', data: accessRecord._doc }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred assigning the workflow', - }); + throw new HttpExceptions(`An error occurred assigning the workflow`, 500); } } @@ -1791,28 +1629,22 @@ export default class DataRequestController extends Controller { // 2. Retrieve DAR from database let accessRecord = await this.dataRequestService.getApplicationWithTeamById(id, { lean: false }); if (!accessRecord) { - return res.status(404).json({ status: 'error', message: 'Application not found.' }); + throw new HttpExceptions(`Application not found.`, 404); } // 3. Check permissions of user is reviewer of associated team - let authorised = false; if (_.has(accessRecord.toObject(), 'publisherObj.team')) { const { team } = accessRecord.publisherObj; - authorised = teamController.checkTeamPermissions(constants.roleTypes.MANAGER, team.toObject(), requestingUserObjectId); - } - - // 4. Refuse access if not authorised - if (!authorised) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); + let authorised = teamV3Util.checkUserRolesByTeam([constants.roleMemberTeam.CUST_DAR_MANAGER], team.toObject(), requestingUserObjectId); + if (!authorised) { + throw new HttpExceptions(`User not authorized to perform this action`,403); + } } // 5. Check application is in submitted state const { applicationStatus } = accessRecord; if (applicationStatus !== constants.applicationStatuses.SUBMITTED) { - return res.status(400).json({ - success: false, - message: 'The application status must be set to submitted to start a review', - }); + throw new HttpExceptions(`The application status must be set to submitted to start a review`, 400); } // 6. Update application to 'in review' @@ -1857,12 +1689,8 @@ export default class DataRequestController extends Controller { // 12. Return aplication and successful response return res.status(200).json({ status: 'success' }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred assigning the workflow', - }); + throw new HttpExceptions(`An error occurred assigning the workflow`, 500); } } @@ -1877,14 +1705,11 @@ export default class DataRequestController extends Controller { // 2. Retrieve DAR from database const accessRecord = await this.dataRequestService.getApplicationWithWorkflowById(id); if (!accessRecord) { - return res.status(404).json({ status: 'error', message: 'Application not found.' }); + throw new HttpExceptions(`Application not found.`, 404); } const { workflow } = accessRecord; if (_.isEmpty(workflow)) { - return res.status(400).json({ - status: 'error', - message: 'There is no workflow attached to this application.', - }); + throw new HttpExceptions(`There is no workflow attached to this application.`, 400); } const activeStepIndex = workflow.steps.findIndex(step => { return step.active === true; @@ -1902,12 +1727,8 @@ export default class DataRequestController extends Controller { } return res.status(200).json({ status: 'success' }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred triggering notifications for workflow review deadlines', - }); + throw new HttpExceptions(`An error occurred triggering notifications for workflow review deadlines`, 500); } } @@ -1928,26 +1749,23 @@ export default class DataRequestController extends Controller { const accessRecord = await this.dataRequestService.getApplicationWithTeamById(id, { lean: true }); if (!accessRecord) { - return res.status(404).json({ status: 'error', message: 'Application not found.' }); + throw new HttpExceptions(`Application not found.`, 404); } // 3. If application is not in progress, actions cannot be performed if (accessRecord.applicationStatus !== constants.applicationStatuses.INPROGRESS) { - return res.status(400).json({ - success: false, - message: 'This application is no longer in pre-submission status and therefore this action cannot be performed', - }); + throw new HttpExceptions(`This application is no longer in pre-submission status and therefore this action cannot be performed`, 400); } // 4. Get the requesting users permission levels - let { authorised, userType } = datarequestUtil.getUserPermissionsForApplication( + let userType = datarequestUtil.getUserPermissionsForApplication( accessRecord, requestingUserId, requestingUserObjectId ); // 5. Return unauthorised message if the requesting user is not an applicant - if (!authorised || userType !== constants.userTypes.APPLICANT) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); + if (userType !== constants.userTypes.APPLICANT) { + throw new HttpExceptions(`Unauthorised`, 401); } // 6. Send notification to the authorised user @@ -1955,12 +1773,8 @@ export default class DataRequestController extends Controller { return res.status(200).json({ status: 'success' }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred emailing the application', - }); + throw new HttpExceptions(`An error occurred emailing the application`, 500); } } @@ -2030,7 +1844,7 @@ export default class DataRequestController extends Controller { switch (type) { case constants.notificationTypes.INPROGRESS: - custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, 'All'); + custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, constants.roleMemberTeam.CUST_DAR_MANAGER); custodianManagersIds = custodianManagers.map(user => user.id); notificationTeam = accessRecord.publisherObj.team.notifications; if (notificationTeam.length && notificationTeam[0].optIn) { @@ -2070,7 +1884,7 @@ export default class DataRequestController extends Controller { // 1. Create notifications // Custodian manager and current step reviewer notifications // Retrieve all custodian manager user Ids and active step reviewers - custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, 'All'); + custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, constants.roleMemberTeam.CUST_DAR_MANAGER); let activeStep = this.workflowService.getActiveWorkflowStep(workflow); stepReviewers = this.workflowService.getStepReviewers(activeStep); // Create custodian notification @@ -2142,7 +1956,7 @@ export default class DataRequestController extends Controller { // Custodian notification if (_.has(accessRecord.datasets[0], 'publisher.team.users') && accessRecord.datasets[0].publisher.allowAccessRequestManagement) { // Retrieve all custodian user Ids to generate notifications - custodianManagers = teamController.getTeamMembersByRole(accessRecord.datasets[0].publisher.team, 'All'); + custodianManagers = teamController.getTeamMembersByRole(accessRecord.datasets[0].publisher.team, constants.roleMemberTeam.CUST_DAR_MANAGER); // check if publisher.team has email notifications custodianUserIds = custodianManagers.map(user => user.id); await notificationBuilder.triggerNotificationMessage( @@ -2162,7 +1976,7 @@ export default class DataRequestController extends Controller { } } else if (_.has(accessRecord, 'publisherObj') && accessRecord.publisherObj.allowAccessRequestManagement) { // Retrieve all custodian user Ids to generate notifications - custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, 'All'); + custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, constants.roleMemberTeam.CUST_DAR_MANAGER); // check if publisher.team has email notifications custodianUserIds = custodianManagers.map(user => user.id); await notificationBuilder.triggerNotificationMessage( @@ -2275,7 +2089,7 @@ export default class DataRequestController extends Controller { // Custodian notification if (_.has(accessRecord.datasets[0], 'publisher.team.users')) { // Retrieve all custodian user Ids to generate notifications - custodianManagers = teamController.getTeamMembersByRole(accessRecord.datasets[0].publisher.team, 'All'); + custodianManagers = teamController.getTeamMembersByRole(accessRecord.datasets[0].publisher.team, constants.roleMemberTeam.CUST_DAR_MANAGER); custodianUserIds = custodianManagers.map(user => user.id); await notificationBuilder.triggerNotificationMessage( custodianUserIds, @@ -2293,7 +2107,7 @@ export default class DataRequestController extends Controller { } } else if (_.has(accessRecord, 'publisherObj') && accessRecord.publisherObj.allowAccessRequestManagement) { // Retrieve all custodian user Ids to generate notifications - custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, 'All'); + custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, constants.roleMemberTeam.CUST_DAR_MANAGER); custodianUserIds = custodianManagers.map(user => user.id); await notificationBuilder.triggerNotificationMessage( @@ -2512,7 +2326,7 @@ export default class DataRequestController extends Controller { break; case constants.notificationTypes.FINALDECISIONREQUIRED: // 1. Get managers for publisher - custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, 'All'); + custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, constants.roleMemberTeam.CUST_DAR_MANAGER); custodianManagersIds = custodianManagers.map(user => user.id); // 2. Create manager notifications @@ -2550,7 +2364,7 @@ export default class DataRequestController extends Controller { ); break; case constants.notificationTypes.DEADLINEWARNING: - custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, 'All'); + custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, constants.roleMemberTeam.CUST_DAR_MANAGER); custodianManagersIds = custodianManagers.map(user => user.id); notificationTeam = accessRecord.publisherObj.team.notifications; if (notificationTeam.length && notificationTeam[0].optIn) { @@ -2593,7 +2407,7 @@ export default class DataRequestController extends Controller { break; case constants.notificationTypes.DEADLINEPASSED: // 1. Get all managers - custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, 'All'); + custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, constants.roleMemberTeam.CUST_DAR_MANAGER); custodianManagersIds = custodianManagers.map(user => user.id); // 2. Combine managers and reviewers remaining let deadlinePassedUserIds = [...remainingReviewerUserIds, ...custodianManagersIds]; @@ -2640,7 +2454,7 @@ export default class DataRequestController extends Controller { break; case constants.notificationTypes.WORKFLOWASSIGNED: // 1. Get managers for publisher - custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team.toObject(), 'All'); + custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team.toObject(), constants.roleMemberTeam.CUST_DAR_MANAGER); // 2. Get managerIds for notifications custodianManagersIds = custodianManagers.map(user => user.id); // 3. deconstruct and set options for notifications and email @@ -2778,7 +2592,7 @@ export default class DataRequestController extends Controller { // Custodian notification if (_.has(accessRecord.datasets[0], 'publisher.team.users')) { // Retrieve all custodian user Ids to generate notifications - custodianManagers = teamController.getTeamMembersByRole(accessRecord.datasets[0].publisher.team, 'All'); + custodianManagers = teamController.getTeamMembersByRole(accessRecord.datasets[0].publisher.team, constants.roleMemberTeam.CUST_DAR_MANAGER); custodianUserIds = custodianManagers.map(user => user.id); await notificationBuilder.triggerNotificationMessage( custodianUserIds, @@ -2788,7 +2602,7 @@ export default class DataRequestController extends Controller { ); } else if (_.has(accessRecord, 'publisherObj')) { // Retrieve all custodian user Ids to generate notifications - custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, 'All'); + custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, constants.roleMemberTeam.CUST_DAR_MANAGER); custodianUserIds = custodianManagers.map(user => user.id); await notificationBuilder.triggerNotificationMessage( custodianUserIds, @@ -2879,7 +2693,7 @@ export default class DataRequestController extends Controller { break; case constants.notificationTypes.MESSAGESENT: if (userType === constants.userTypes.APPLICANT) { - const custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, 'All'); + const custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, constants.roleMemberTeam.CUST_DAR_MANAGER); const custodianManagersIds = custodianManagers.map(user => user.id); notificationTeam = accessRecord.publisherObj.team.notifications; @@ -2914,7 +2728,7 @@ export default class DataRequestController extends Controller { false ); } else if (userType === constants.userTypes.CUSTODIAN) { - const custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, 'All'); + const custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, constants.roleMemberTeam.CUST_DAR_MANAGER); const custodianManagersIds = custodianManagers.map(user => user.id); notificationTeam = accessRecord.publisherObj.team.notifications; @@ -2968,16 +2782,16 @@ export default class DataRequestController extends Controller { let accessRecord = await this.dataRequestService.getApplicationById(id); if (!accessRecord) { - return res.status(404).json({ status: 'error', message: 'The application could not be found.' }); + throw new HttpExceptions(`The application could not be found.`, 404); } - const { authorised, userType } = datarequestUtil.getUserPermissionsForApplication( + const userType = datarequestUtil.getUserPermissionsForApplication( accessRecord, requestingUserId, requestingUserObjectId ); - if (!authorised || userType !== constants.userTypes.APPLICANT) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); + if (userType !== constants.userTypes.APPLICANT) { + throw new HttpExceptions(`Unauthorised`, 401); } await this.dataRequestService.shareApplication(accessRecord).catch(err => { @@ -2989,10 +2803,7 @@ export default class DataRequestController extends Controller { }); } catch (err) { logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred updating the application', - }); + throw new HttpExceptions(`An error occurred updating the application`, 500); } } @@ -3008,26 +2819,24 @@ export default class DataRequestController extends Controller { let accessRecord = await this.dataRequestService.getApplicationById(id); if (!accessRecord) { - return res.status(404).json({ status: 'error', message: 'The application could not be found.' }); + throw new HttpExceptions(`The application could not be found.`, 404); } - const { authorised, userType } = datarequestUtil.getUserPermissionsForApplication( + const userType = datarequestUtil.getUserPermissionsForApplication( accessRecord, requestingUserId, requestingUserObjectId ); - if (!authorised) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); - } else if ( + if ( userType === constants.userTypes.APPLICANT && ![constants.DARMessageTypes.DARNOTESAPPLICANT, constants.DARMessageTypes.DARMESSAGE].includes(messageType) ) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); + throw new HttpExceptions(`Unauthorised`, 401); } else if ( userType === constants.userTypes.CUSTODIAN && ![constants.DARMessageTypes.DARNOTESCUSTODIAN, constants.DARMessageTypes.DARMESSAGE].includes(messageType) ) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); + throw new HttpExceptions(`Unauthorised`, 401); } const topic = await this.topicService.getTopicForDAR(id, questionId || panelId, messageType); @@ -3050,10 +2859,7 @@ export default class DataRequestController extends Controller { }); } catch (err) { logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred updating the application', - }); + throw new HttpExceptions(`An error occurred updating the application`, 500); } } @@ -3070,27 +2876,25 @@ export default class DataRequestController extends Controller { let accessRecord = await this.dataRequestService.getApplicationWithTeamById(id, { lean: true }); if (!accessRecord) { - return res.status(404).json({ status: 'error', message: 'The application could not be found.' }); + throw new HttpExceptions(`The application could not be found.`, 404); } - const { authorised, userType } = datarequestUtil.getUserPermissionsForApplication( + const userType = datarequestUtil.getUserPermissionsForApplication( accessRecord, requestingUserId, requestingUserObjectId ); - if (!authorised) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); - } else if ( + if ( userType === constants.userTypes.APPLICANT && ![constants.DARMessageTypes.DARNOTESAPPLICANT, constants.DARMessageTypes.DARMESSAGE].includes(messageType) ) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); + throw new HttpExceptions(`Unauthorised`, 401); } else if ( userType === constants.userTypes.CUSTODIAN && ![constants.DARMessageTypes.DARNOTESCUSTODIAN, constants.DARMessageTypes.DARMESSAGE].includes(messageType) ) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); + throw new HttpExceptions(`Unauthorised`, 401); } let topic = await this.topicService.getTopicForDAR(id, questionId || panel.panelId, messageType); @@ -3181,10 +2985,7 @@ export default class DataRequestController extends Controller { }); } catch (err) { logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred updating the application', - }); + throw new HttpExceptions(`An error occurred updating the application`, 500); } } } diff --git a/src/resources/datarequest/datarequest.model.js b/src/resources/datarequest/datarequest.model.js index 6a82bd2f4..f1415257a 100644 --- a/src/resources/datarequest/datarequest.model.js +++ b/src/resources/datarequest/datarequest.model.js @@ -103,6 +103,7 @@ const DataRequestSchema = new Schema( originId: { type: Schema.Types.ObjectId, ref: 'data_request' }, versionTree: { type: Object, default: {} }, isShared: { type: Boolean, default: false }, + publishedForm: { type: Boolean, default: false }, }, { timestamps: true, diff --git a/src/resources/datarequest/schema/datarequest.schema.controller.js b/src/resources/datarequest/schema/datarequest.schema.controller.js index dd7add232..6c31f3f47 100644 --- a/src/resources/datarequest/schema/datarequest.schema.controller.js +++ b/src/resources/datarequest/schema/datarequest.schema.controller.js @@ -36,7 +36,7 @@ export default class DatarequestschemaController extends Controller { }); } catch (err) { // Return error response if something goes wrong - console.error(err.message); + process.stdout.write(`DATA REQUEST - getDatarequestschema : ${err.message}\n`); return res.status(500).json({ success: false, message: 'A server error occurred, please try again', diff --git a/src/resources/datarequest/schema/datarequest.schemas.route.js b/src/resources/datarequest/schema/datarequest.schemas.route.js index 4cf5698ee..3a300a5f0 100644 --- a/src/resources/datarequest/schema/datarequest.schemas.route.js +++ b/src/resources/datarequest/schema/datarequest.schemas.route.js @@ -105,6 +105,6 @@ async function archiveOtherVersions(id, dataSetId, status) { ); } } catch (err) { - console.error(err.message); + process.stdout.write(`DATA REQUEST - archiveOtherVersions : ${err.message}\n`); } } diff --git a/src/resources/datarequest/utils/datarequest.util.js b/src/resources/datarequest/utils/datarequest.util.js index b6020cdd5..81579611f 100644 --- a/src/resources/datarequest/utils/datarequest.util.js +++ b/src/resources/datarequest/utils/datarequest.util.js @@ -1,6 +1,6 @@ import { has, isEmpty, isNil } from 'lodash'; import constants from '../../utilities/constants.util'; -import teamController from '../../team/team.controller'; +import teamV3Util from '../../utilities/team.v3.util'; import moment from 'moment'; import { DataRequestSchemaModel } from '../schema/datarequest.schemas.model'; import dynamicForm from '../../utilities/dynamicForms/dynamicForm.util'; @@ -12,7 +12,7 @@ const injectQuestionActions = (jsonSchema, userType, applicationStatus, role = ' userType === constants.userTypes.CUSTODIAN && applicationStatus === constants.applicationStatuses.INREVIEW && activeParty === constants.userTypes.CUSTODIAN && - role === constants.roleTypes.MANAGER && + role === constants.roleMemberTeam.CUST_DAR_MANAGER && isLatestMinorVersion ) return { @@ -33,36 +33,48 @@ const injectQuestionActions = (jsonSchema, userType, applicationStatus, role = ' }; const getUserPermissionsForApplication = (application, userId, _id) => { - try { - let authorised = false, - isTeamMember = false, - userType = ''; - // Return default unauthorised with no user type if incorrect params passed - if (!application || !userId || !_id) { - return { authorised, userType }; - } - // Check if the user is a custodian team member and assign permissions if so - if (has(application, 'datasets') && has(application.datasets[0], 'publisher.team')) { - isTeamMember = teamController.checkTeamPermissions('', application.datasets[0].publisher.team, _id); - } else if (has(application, 'publisherObj.team')) { - isTeamMember = teamController.checkTeamPermissions('', application.publisherObj.team, _id); - } - if (isTeamMember && (application.applicationStatus !== constants.applicationStatuses.INPROGRESS || application.isShared)) { - userType = constants.userTypes.CUSTODIAN; + + let authorised = false, + userType = '', + isTeamMember = false; + + if (!application || !userId || !_id) { + throw new HttpExceptions(`User not authorized to perform this action`,403); + } + + // constants.roleMemberTeam.CUST_DAR_MANAGER + if (has(application, 'datasets') && has(application.datasets[0], 'publisher.team')) { + isTeamMember = teamV3Util.checkUserRolesByTeam( + [], + application.datasets[0].publisher.team, + _id + ); + } else if (has(application, 'publisherObj.team')) { + isTeamMember = teamV3Util.checkUserRolesByTeam( + [], + application.publisherObj.team, + _id + ); + } + + if (isTeamMember && (application.applicationStatus !== constants.applicationStatuses.INPROGRESS || application.isShared)) { + userType = constants.userTypes.CUSTODIAN; + authorised = true; + } + + // If user is not authenticated as a custodian, check if they are an author or the main applicant + if (application.applicationStatus === constants.applicationStatuses.INPROGRESS || isEmpty(userType)) { + if (application.userId === userId || (application.authorIds && application.authorIds.includes(userId))) { + userType = constants.userTypes.APPLICANT; authorised = true; } - // If user is not authenticated as a custodian, check if they are an author or the main applicant - if (application.applicationStatus === constants.applicationStatuses.INPROGRESS || isEmpty(userType)) { - if (application.userId === userId || (application.authorIds && application.authorIds.includes(userId))) { - userType = constants.userTypes.APPLICANT; - authorised = true; - } - } - return { authorised, userType }; - } catch (err) { - console.error(err.message); - return { authorised: false, userType: '' }; } + + if (authorised) { + return userType; + } + + throw new HttpExceptions(`User not authorized to perform this action`, 403); }; const extractApplicantNames = questionAnswers => { diff --git a/src/resources/dataset/__mocks__/datasets.js b/src/resources/dataset/__mocks__/datasets.js index c1e9f4ef0..4dacb5c01 100644 --- a/src/resources/dataset/__mocks__/datasets.js +++ b/src/resources/dataset/__mocks__/datasets.js @@ -205,7 +205,7 @@ export const v2DatasetsStub = [ pid: 'b67f0edd-fed2-4d68-a25f-d225759aa3b0', id: 'dfb21b3b-7fd9-40c4-892e-810edd6dfc25', version: '0.0.1', - identifier: 'https://web.www.healthdatagateway.org/dataset/dfb21b3b-7fd9-40c4-892e-810edd6dfc25', + identifier: 'https://web.old.healthdatagateway.org/dataset/dfb21b3b-7fd9-40c4-892e-810edd6dfc25', revisions: { '0.0.1': 'dfb21b3b-7fd9-40c4-892e-810edd6dfc25', latest: 'dfb21b3b-7fd9-40c4-892e-810edd6dfc25', @@ -252,7 +252,7 @@ export const v2DatasetsStub = [ pid: 'b67f0edd-fed2-4d68-a25f-d225759aa3b0', id: 'dfb21b3b-7fd9-40c4-892e-810edd6dfc25', version: '0.0.1', - identifier: 'https://web.www.healthdatagateway.org/dataset/dfb21b3b-7fd9-40c4-892e-810edd6dfc25', + identifier: 'https://web.old.healthdatagateway.org/dataset/dfb21b3b-7fd9-40c4-892e-810edd6dfc25', revisions: { '0.0.1': 'dfb21b3b-7fd9-40c4-892e-810edd6dfc25', latest: 'dfb21b3b-7fd9-40c4-892e-810edd6dfc25', diff --git a/src/resources/dataset/dataset.controller.js b/src/resources/dataset/dataset.controller.js index e8fbf9901..f480dae01 100644 --- a/src/resources/dataset/dataset.controller.js +++ b/src/resources/dataset/dataset.controller.js @@ -28,7 +28,7 @@ export default class DatasetController extends Controller { }); } catch (err) { // Return error response if something goes wrong - console.error(err.message); + process.stdout.write(`DATA SET - getDataset : ${err.message}\n`); return res.status(500).json({ success: false, message: 'A server error occurred, please try again', @@ -48,7 +48,7 @@ export default class DatasetController extends Controller { }); } catch (err) { // Return error response if something goes wrong - console.error(err.message); + process.stdout.write(`DATA SET - getDatasets : ${err.message}\n`); return res.status(500).json({ success: false, message: 'A server error occurred, please try again', diff --git a/src/resources/dataset/dataset.entity.js b/src/resources/dataset/dataset.entity.js index 2edc0815d..cd262fc9e 100644 --- a/src/resources/dataset/dataset.entity.js +++ b/src/resources/dataset/dataset.entity.js @@ -44,13 +44,13 @@ export default class DatasetClass extends Entity { const transformedObject = this.transformTo(transformer, { strict: false }); // Manually update identifier URL link - transformedObject.dataset.identifier = `https://web.www.healthdatagateway.org/dataset/${this.datasetid}`; + transformedObject.dataset.identifier = `https://web.old.healthdatagateway.org/dataset/${this.datasetid}`; // Append static schema details for v2 transformedObject.dataset['@schema'] = { type: `Dataset`, version: `2.0.0`, - url: `https://raw.githubusercontent.com/HDRUK/schemata/master/schema/dataset/latest/dataset.schema.json`, + url: `https://raw.githubusercontent.com/HDRUK/schemata/master/schema/dataset/2.1.0/dataset.schema.json`, } // Return v2 object diff --git a/src/resources/dataset/v1/dataset.route.js b/src/resources/dataset/v1/dataset.route.js index 63a09df5e..6893bb979 100644 --- a/src/resources/dataset/v1/dataset.route.js +++ b/src/resources/dataset/v1/dataset.route.js @@ -7,7 +7,6 @@ import escape from 'escape-html'; import { Course } from '../../course/course.model'; import { DataUseRegister } from '../../dataUseRegister/dataUseRegister.model'; import { filtersService } from '../../filters/dependency'; -import * as Sentry from '@sentry/node'; const router = express.Router(); const rateLimit = require('express-rate-limit'); @@ -17,8 +16,6 @@ const datasetLimiter = rateLimit({ message: 'Too many calls have been made to this api from this IP, please try again after an hour', }); -const readEnv = process.env.ENV || 'prod'; - router.post('/', async (req, res) => { try { // Check to see if header is in json format @@ -46,10 +43,7 @@ router.post('/', async (req, res) => { // Return response indicating job has started (do not await async import) return res.status(200).json({ success: true, message: 'Caching started' }); } catch (err) { - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.captureException(err); - } - console.error(err.message); + process.stdout.write(`DATASET - Caching failed : ${err.message}\n`); return res.status(500).json({ success: false, message: 'Caching failed' }); } }); @@ -78,10 +72,7 @@ router.post('/updateServices', async (req, res) => { return res.status(200).json({ success: true, message: 'Services Update started' }); } catch (err) { - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.captureException(err); - } - console.error(err.message); + process.stdout.write(`DATASET - Services update failed : ${err.message}\n`); return res.status(500).json({ success: false, message: 'Services update failed' }); } }); diff --git a/src/resources/dataset/v1/dataset.service.js b/src/resources/dataset/v1/dataset.service.js index 3d28c2bef..e03b836d2 100644 --- a/src/resources/dataset/v1/dataset.service.js +++ b/src/resources/dataset/v1/dataset.service.js @@ -1,7 +1,6 @@ import { Data } from '../../tool/data.model'; import { MetricsData } from '../../stats/metrics.model'; import axios from 'axios'; -import * as Sentry from '@sentry/node'; import { v4 as uuidv4 } from 'uuid'; import { filtersService } from '../../filters/dependency'; import { PublisherModel } from '../../publisher/publisher.model'; @@ -16,8 +15,6 @@ let metadataQualityList = [], datasetsMDCIDs = [], counter = 0; -const readEnv = process.env.ENV || 'prod'; - export async function updateExternalDatasetServices(services) { for (let service of services) { if (service === 'phenotype') { @@ -26,34 +23,18 @@ export async function updateExternalDatasetServices(services) { timeout: 10000, }) .catch(err => { - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.addBreadcrumb({ - category: 'Caching', - message: 'Unable to get metadata quality value ' + err.message, - level: Sentry.Severity.Error, - }); - Sentry.captureException(err); - } - console.error('Unable to get metadata quality value ' + err.message); + process.stdout.write(`DATASET - Unable to get metadata quality value : ${err.message}`); }); for (const pid in phenotypesList.data) { await Data.updateMany({ pid: pid }, { $set: { 'datasetfields.phenotypes': phenotypesList.data[pid] } }); - console.log(`PID is ${pid} and number of phenotypes is ${phenotypesList.data[pid].length}`); + process.stdout.write(`PID is ${pid} and number of phenotypes is ${phenotypesList.data[pid].length}`); } } else if (service === 'dataUtility') { const dataUtilityList = await axios .get('https://raw.githubusercontent.com/HDRUK/datasets/master/reports/data_utility.json', { timeout: 10000 }) .catch(err => { - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.addBreadcrumb({ - category: 'Caching', - message: 'Unable to get data utility ' + err.message, - level: Sentry.Severity.Error, - }); - Sentry.captureException(err); - } - console.error('Unable to get data utility ' + err.message); + process.stdout.write(`DATASET - Unable to get data utility : ${err.message}`); }); for (const dataUtility of dataUtilityList.data) { @@ -70,7 +51,7 @@ export async function updateExternalDatasetServices(services) { await dataset.save(); } // log details - // console.log(`DatasetID is ${dataUtility.id} and metadata richness is ${dataUtility.metadata_richness}`); + // process.stdout.write(`DatasetID is ${dataUtility.id} and metadata richness is ${dataUtility.metadata_richness}`); } } } @@ -92,7 +73,7 @@ export async function importCatalogues(cataloguesToImport, override = false, lim } const isValid = validateCatalogueParams(metadataCatalogues[catalogue]); if (!isValid) { - console.error('Catalogue failed to run due to incorrect or incomplete parameters'); + process.stdout.write(`Catalogue failed to run due to incorrect or incomplete parameters`); continue; } const { metadataUrl, dataModelExportRoute, username, password, source, instanceType } = metadataCatalogues[catalogue]; @@ -181,7 +162,7 @@ function initialiseImporter() { async function importMetadataFromCatalogue(baseUri, dataModelExportRoute, source, { instanceType, credentials, override = false, limit }) { const startCacheTime = Date.now(); - console.log( + process.stdout.write( `Starting metadata import for ${source} on ${instanceType} at ${Date()} with base URI ${baseUri}, override:${override}, limit:${ limit || 'all' }` @@ -199,21 +180,13 @@ async function importMetadataFromCatalogue(baseUri, dataModelExportRoute, source await logoutCatalogue(baseUri); await loginCatalogue(baseUri, credentials); await loadDatasets(baseUri, dataModelExportRoute, datasetsMDCList.items, datasetsMDCList.count, source, limit).catch(err => { - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.addBreadcrumb({ - category: 'Caching', - message: `Unable to complete the metadata import for ${source} ${err.message}`, - level: Sentry.Severity.Error, - }); - Sentry.captureException(err); - } - console.error(`Unable to complete the metadata import for ${source} ${err.message}`); + process.stdout.write(`DATASET - Unable to complete the metadata import for ${source} ${err.message}`); }); await logoutCatalogue(baseUri); await archiveMissingDatasets(source); const totalCacheTime = ((Date.now() - startCacheTime) / 1000).toFixed(3); - console.log(`Run Completed for ${source} at ${Date()} - Run took ${totalCacheTime}s`); + process.stdout.write(`Run Completed for ${source} at ${Date()} - Run took ${totalCacheTime}s`); } async function loadDatasets(baseUri, dataModelExportRoute, datasetsToImport, datasetsToImportCount, source, limit) { @@ -223,7 +196,7 @@ async function loadDatasets(baseUri, dataModelExportRoute, datasetsToImport, dat } for (const datasetMDC of datasetsToImport) { counter++; - console.log(`Starting ${counter} of ${datasetsToImportCount} datasets (${datasetMDC.id})`); + process.stdout.write(`Starting ${counter} of ${datasetsToImportCount} datasets (${datasetMDC.id})`); let datasetHDR = await Data.findOne({ datasetid: datasetMDC.id }); datasetsMDCIDs.push({ datasetid: datasetMDC.id }); @@ -240,46 +213,22 @@ async function loadDatasets(baseUri, dataModelExportRoute, datasetsToImport, dat timeout: 60000, }) .catch(err => { - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.addBreadcrumb({ - category: 'Caching', - message: 'Unable to get dataset JSON ' + err.message, - level: Sentry.Severity.Error, - }); - Sentry.captureException(err); - } - console.error('Unable to get metadata JSON ' + err.message); + process.stdout.write(`DATASET - Unable to get metadata JSON : ${err.message}`); }); const elapsedTime = ((Date.now() - startImportTime) / 1000).toFixed(3); - console.log(`Time taken to import JSON ${elapsedTime} (${datasetMDC.id})`); + process.stdout.write(`Time taken to import JSON ${elapsedTime} (${datasetMDC.id})`); const metadataSchemaCall = axios //Paul - Remove and populate gateway side .get(`${baseUri}/api/profiles/uk.ac.hdrukgateway/HdrUkProfilePluginService/schema.org/${datasetMDC.id}`, { timeout: 10000, }) .catch(err => { - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.addBreadcrumb({ - category: 'Caching', - message: 'Unable to get metadata schema ' + err.message, - level: Sentry.Severity.Error, - }); - Sentry.captureException(err); - } - console.error('Unable to get metadata schema ' + err.message); + process.stdout.write(`DATASET - Unable to get metadata schema : ${err.message}`); }); const versionLinksCall = axios.get(`${baseUri}/api/catalogueItems/${datasetMDC.id}/semanticLinks`, { timeout: 10000 }).catch(err => { - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.addBreadcrumb({ - category: 'Caching', - message: 'Unable to get version links ' + err.message, - level: Sentry.Severity.Error, - }); - Sentry.captureException(err); - } - console.error('Unable to get version links ' + err.message); + process.stdout.write(`DATASET - Unable to get version links : ${err.message}`); }); const [metadataSchema, versionLinks] = await axios.all([metadataSchemaCall, versionLinksCall]); @@ -404,7 +353,7 @@ async function loadDatasets(baseUri, dataModelExportRoute, datasetsToImport, dat datasetv2: datasetv2Object, } ); - console.log(`Dataset Editted (${datasetMDC.id})`); + process.stdout.write(`Dataset Editted (${datasetMDC.id})`); } else { //Add let uuid = uuidv4(); @@ -488,10 +437,10 @@ async function loadDatasets(baseUri, dataModelExportRoute, datasetsToImport, dat data.datasetfields.phenotypes = phenotypes; data.datasetv2 = datasetv2Object; await data.save(); - console.log(`Dataset Added (${datasetMDC.id})`); + process.stdout.write(`Dataset Added (${datasetMDC.id})`); } - console.log(`Finished ${counter} of ${datasetsToImportCount} datasets (${datasetMDC.id})`); + process.stdout.write(`Finished ${counter} of ${datasetsToImportCount} datasets (${datasetMDC.id})`); } } @@ -505,15 +454,7 @@ async function getDataUtilityExport() { return await axios .get('https://raw.githubusercontent.com/HDRUK/datasets/master/reports/data_utility.json', { timeout: 10000 }) .catch(err => { - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.addBreadcrumb({ - category: 'Caching', - message: 'Unable to get data utility ' + err.message, - level: Sentry.Severity.Error, - }); - Sentry.captureException(err); - } - console.error('Unable to get data utility ' + err.message); + process.stdout.write(`DATASET - Unable to get data utility : ${err.message}`); }); } @@ -527,15 +468,7 @@ async function getPhenotypesExport() { return await axios .get('https://raw.githubusercontent.com/spiros/hdr-caliber-phenome-portal/master/_data/dataset2phenotypes.json', { timeout: 10000 }) .catch(err => { - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.addBreadcrumb({ - category: 'Caching', - message: 'Unable to get metadata quality value ' + err.message, - level: Sentry.Severity.Error, - }); - Sentry.captureException(err); - } - console.error('Unable to get metadata quality value ' + err.message); + process.stdout.write(`DATASET - Unable to get metadata quality value : ${err.message}`); }); } @@ -549,15 +482,7 @@ async function getMetadataQualityExport() { return await axios .get('https://raw.githubusercontent.com/HDRUK/datasets/master/reports/metadata_quality.json', { timeout: 10000 }) .catch(err => { - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.addBreadcrumb({ - category: 'Caching', - message: 'Unable to get metadata quality value ' + err.message, - level: Sentry.Severity.Error, - }); - Sentry.captureException(err); - } - console.error('Unable to get metadata quality value ' + err.message); + process.stdout.write(`DATASET Unable to get metadata quality value ${err.message}`); }); } @@ -569,14 +494,7 @@ async function getDataModels(baseUri) { resolve(response.data); }) .catch(err => { - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.addBreadcrumb({ - category: 'Caching', - message: 'The caching run has failed because it was unable to get a count from the MDC', - level: Sentry.Severity.Fatal, - }); - Sentry.captureException(err); - } + process.stdout.write(`DATASET - The caching run has failed because it was unable to get a count from the MDC : ${err.message}\n`); reject(err); }); }).catch(() => { @@ -589,16 +507,9 @@ async function checkDifferentialValid(incomingMetadataCount, source, override) { const datasetsHDRCount = await Data.countDocuments({ type: 'dataset', activeflag: 'active', source }); if ((incomingMetadataCount / datasetsHDRCount) * 100 < 90 && !override) { - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.addBreadcrumb({ - category: 'Caching', - message: `The caching run has failed because the counts from the MDC (${incomingMetadataCount}) where ${ - 100 - (incomingMetadataCount / datasetsHDRCount) * 100 - }% lower than the number stored in the DB (${datasetsHDRCount})`, - level: Sentry.Severity.Fatal, - }); - Sentry.captureException(); - } + process.stdout.write(`DATASET - checkDifferentialValid : The caching run has failed because the counts from the MDC (${incomingMetadataCount}) where ${ + 100 - (incomingMetadataCount / datasetsHDRCount) * 100 + }% lower than the number stored in the DB (${datasetsHDRCount})\n`); return false; } return true; @@ -611,7 +522,7 @@ async function getDataAccessRequestCustodians() { async function logoutCatalogue(baseUri) { await axios.post(`${baseUri}/api/authentication/logout`, { withCredentials: true, timeout: 10000 }).catch(err => { - console.error(`Error when trying to logout of the MDC - ${err.message}`); + process.stdout.write(`DATASET - Error when trying to logout of the MDC : ${err.message}`); }); } diff --git a/src/resources/discourse/discourse.route.js b/src/resources/discourse/discourse.route.js index 5d5da3637..072a91624 100644 --- a/src/resources/discourse/discourse.route.js +++ b/src/resources/discourse/discourse.route.js @@ -39,7 +39,7 @@ router.get('/topic/:topicId', async (req, res) => { return res.status(500).json({ success: false, error: error.message }); }); } catch (err) { - console.error(err.message); + process.stdout.write(`DISCOURSE - GET TOPIC: ${err.message}`); return res.status(500).json({ success: false, error: 'Error retrieving the topic, please try again later...' }); } }); @@ -67,7 +67,7 @@ router.get('/user/topic/:topicId', passport.authenticate('jwt'), utils.checkIsIn return res.status(500).json({ success: false, error: error.message }); }); } catch (err) { - console.error(err.message); + process.stdout.write(`DISCOURSE - GET TOPIC: ${err.message}`); return res.status(500).json({ success: false, error: 'Error retrieving the topic, please try again later...' }); } }); @@ -99,7 +99,7 @@ router.put('/tool/:toolId', passport.authenticate('jwt'), utils.checkIsInRole(RO return res.status(500).json({ success: false, error: error.message }); }); } catch (err) { - console.error(err.message); + process.stdout.write(`DISCOURSE - PUT TOPIC: ${err.message}`); return res.status(500).json({ success: false, error: 'Error creating the topic, please try again later...' }); } }); @@ -167,7 +167,7 @@ router.post('/user/posts', passport.authenticate('jwt'), utils.checkIsInRole(ROL return res.json({ success: true, topic }); } } catch (err) { - console.error(err.message); + process.stdout.write(`DISCOURSE - POSTS: ${err.message}`); return res.status(500).json({ success: false, error: 'Error creating the topic, please try again later...' }); } }); @@ -194,7 +194,7 @@ router.put('/user/posts/:postId', passport.authenticate('jwt'), utils.checkIsInR // 5. Return the topic data return res.json({ success: true, topic }); } catch (err) { - console.error(err.message); + process.stdout.write(`DISCOURSE - PUT POSTS: ${err.message}`); return res.status(500).json({ success: false, error: 'Error editing the post, please try again later...' }); } }); @@ -218,7 +218,7 @@ router.delete('/user/posts/:postId', passport.authenticate('jwt'), utils.checkIs return res.status(500).json({ success: false, error: err.message }); }); } catch (err) { - console.error(err.message); + process.stdout.write(`DISCOURSE - DELETE POSTS: ${err.message}`); return res.status(500).json({ success: false, error: 'Error deleting the topic, please try again later...' }); } }); diff --git a/src/resources/discourse/discourse.service.js b/src/resources/discourse/discourse.service.js index 2981bbf2f..dba6969d5 100644 --- a/src/resources/discourse/discourse.service.js +++ b/src/resources/discourse/discourse.service.js @@ -42,7 +42,7 @@ export async function getDiscourseTopic(topicId, user) { posts: posts, }; } catch (err) { - console.error(err.message); + process.stdout.write(`DISCOURSE - getDiscourseTopic : ${err.message}`); } } @@ -109,7 +109,7 @@ export async function createDiscourseTopic(tool) { } } } catch (err) { - console.error(err.message); + process.stdout.write(`DISCOURSE - createDiscourseTopic : ${err.message}`); } } @@ -140,7 +140,7 @@ export async function createDiscoursePost(topicId, comment, user) { try { await axios.post(`${process.env.DISCOURSE_URL}/posts.json`, payload, config); } catch (err) { - console.error(err.message); + process.stdout.write(`DISCOURSE - createDiscoursePost : ${err.message}`); } } @@ -176,7 +176,7 @@ export async function updateDiscoursePost(postId, comment, user) { // 4. Return the post data return post; } catch (err) { - console.error(err.message); + process.stdout.write(`DISCOURSE - updateDiscoursePost : ${err.message}`); } } @@ -208,7 +208,7 @@ export async function deleteDiscoursePost(postId, user) { try { await axios.delete(`${process.env.DISCOURSE_URL}/posts/${postId}`, config); } catch (err) { - console.error(err.message); + process.stdout.write(`DISCOURSE - deleteDiscoursePost : ${err.message}`); } } @@ -248,7 +248,7 @@ async function createUser({ id, email, username }) { // 6. Return the new user object from Discourse return res.data; } catch (err) { - console.error(err.message); + process.stdout.write(`DISCOURSE - createUser : ${err.message}`); } } @@ -286,7 +286,7 @@ async function generateAPIKey(discourseUsername) { // 3. Return key return key; } catch (err) { - console.error(err.message); + process.stdout.write(`DISCOURSE - generateAPIKey : ${err.message}`); return ''; } } @@ -326,7 +326,7 @@ async function getCredentials(user, strict) { // 6. Update MongoDb to contain users Discourse credentials await UserModel.findOneAndUpdate({ id: { $eq: id } }, { $set: { discourseUsername, discourseKey } }); } catch (err) { - console.error(err.message); + process.stdout.write(`DISCOURSE - getCredentials : ${err.message}`); } // 3. If user has username but no API key, generate new one } else if (_.isEmpty(discourseKey)) { @@ -336,7 +336,7 @@ async function getCredentials(user, strict) { // 5. Update MongoDb to contain users Discourse credentials await UserModel.findOneAndUpdate({ id: { $eq: id } }, { $set: { discourseUsername, discourseKey } }); } catch (err) { - console.error(err.message); + process.stdout.write(`DISCOURSE - getCredentials : ${err.message}`); } } // Return identification payload of registered Discourse user diff --git a/src/resources/filters/filters.controller.js b/src/resources/filters/filters.controller.js index 7f5cc2ba5..8607e3f5d 100644 --- a/src/resources/filters/filters.controller.js +++ b/src/resources/filters/filters.controller.js @@ -33,7 +33,7 @@ export default class FiltersController extends Controller { }); } catch (err) { // Return error response if something goes wrong - console.error(err.message); + process.stdout.write(`DISCOURSE - getFilters : ${err.message}`); return res.status(500).json({ success: false, message: 'A server error occurred, please try again', diff --git a/src/resources/filters/filters.entity.js b/src/resources/filters/filters.entity.js index 88d86e58a..22ef77462 100644 --- a/src/resources/filters/filters.entity.js +++ b/src/resources/filters/filters.entity.js @@ -12,7 +12,7 @@ export default class FiltersClass extends Entity { mapDto() { if (!this.id) { - console.error('Failed to load filters'); + process.stdout.write(`Failed to load filters`); return; } diff --git a/src/resources/filters/filters.repository.js b/src/resources/filters/filters.repository.js index 38081fafc..05d03f553 100644 --- a/src/resources/filters/filters.repository.js +++ b/src/resources/filters/filters.repository.js @@ -15,7 +15,7 @@ export default class FiltersRepository extends Repository { async updateFilterSet(filters, type) { await Filters.findOneAndUpdate({ id: type }, { keys: filters }, { upsert: true }, err => { if (err) { - console.error(err.message); + process.stdout.write(`FILTERS - updateFilterSet : ${err.message}\n`); } }); } diff --git a/src/resources/filters/filters.service.js b/src/resources/filters/filters.service.js index 87491fa86..2033e0906 100644 --- a/src/resources/filters/filters.service.js +++ b/src/resources/filters/filters.service.js @@ -183,8 +183,8 @@ export default class FiltersService { Object.keys(filters).forEach(filterKey => { // 9. Set filter values to title case (all except publisher) / upper case (publisher) and remove white space if (filterKey === 'publisher') { - filters[filterKey] = filters[filterKey].map(value => - value.includes('>') ? value.split(' > ')[1].toString().toUpperCase().trim() : value.toString().toUpperCase().trim() + filters[filterKey] = filters[filterKey].map(value => + value.includes('>') ? value.split('>')[1].toString().toUpperCase().trim() : value.toString().toUpperCase().trim() ); } else { filters[filterKey] = filters[filterKey].map(value => (filterKey === 'spatial') ? value.toString().trim() : helper.toTitleCase(value.toString().trim())); diff --git a/src/resources/help/help.router.js b/src/resources/help/help.router.js index a76d8d3b4..2b339885f 100644 --- a/src/resources/help/help.router.js +++ b/src/resources/help/help.router.js @@ -20,7 +20,7 @@ router.get('/:category', async (req, res) => { // 4. Return help data in response return res.status(200).json({ success: true, help }); } catch (err) { - console.error(err.message); + process.stdout.write(`HELP ROUTER - GET CATEGORY : ${err.message}\n`); return res.status(500).json({ success: false, message: 'An error occurred searching for help data', diff --git a/src/resources/linkchecker/linkchecker.router.js b/src/resources/linkchecker/linkchecker.router.js index 2cb2f4e9b..08c71996f 100644 --- a/src/resources/linkchecker/linkchecker.router.js +++ b/src/resources/linkchecker/linkchecker.router.js @@ -1,5 +1,4 @@ import express from 'express'; -import * as Sentry from '@sentry/node'; import { getObjectResult } from './linkchecker.repository'; import { getUserByUserId } from '../user/user.repository'; import { Data } from '../tool/data.model'; @@ -8,7 +7,6 @@ import _ from 'lodash'; const sgMail = require('@sendgrid/mail'); const hdrukEmail = `enquiry@healthdatagateway.org`; -const readEnv = process.env.ENV || 'prod'; const axios = require('axios'); const router = express.Router(); @@ -104,7 +102,7 @@ router.post('/', async (req, res) => { if (checkUser[0].emailNotifications === true) { let msg = { to: user.email, - from: `${hdrukEmail}`, + from: hdrukEmail, subject: `Updates required for links in ${item.name}.`, html: `${user.firstname} ${user.lastname},

Please review your ${item.type} "${item.name}" here: ${resourceLink}. This ${item.type} contains stale links which require updating. @@ -112,14 +110,7 @@ router.post('/', async (req, res) => { }; await sgMail.send(msg, false, err => { - if (err && (readEnv === 'test' || readEnv === 'prod')) { - Sentry.addBreadcrumb({ - category: 'SendGrid', - message: 'Sending email failed', - level: Sentry.Severity.Warning, - }); - Sentry.captureException(err); - } + process.stdout.write(`LINKCHECKER - sendEmailToUsers: error`); }); } } diff --git a/src/resources/message/message.controller.js b/src/resources/message/message.controller.js index c88145982..c210382cf 100644 --- a/src/resources/message/message.controller.js +++ b/src/resources/message/message.controller.js @@ -48,13 +48,13 @@ module.exports = { ({ publisher = '' } = tools[0]); if (_.isEmpty(publisher)) { - console.error(`No publisher associated to this dataset`); + process.stdout.write(`No publisher associated to this dataset\n`); return res.status(500).json({ success: false, message: 'No publisher associated to this dataset' }); } // 5. get team ({ team = [] } = publisher); if (_.isEmpty(team)) { - console.error(`No team associated to publisher, cannot message`); + process.stdout.write(`No team associated to publisher, cannot message\n`); return res.status(500).json({ success: false, message: 'No team associated to publisher, cannot message' }); } // 6. Set user type (if found in team, they are custodian) @@ -131,16 +131,25 @@ module.exports = { [constants.teamNotificationTypes.DATAACCESSREQUEST] ); if (!_.isEmpty(subscribedMembersByType)) { + // build cleaner array of memberIds from subscribedMembersByType if (topicObj.topicMessages !== undefined) { - const memberIds = [...subscribedMembersByType.map(m => m.memberid.toString()), ...topicObj.createdBy._id.toString()]; + const memberIds = [...subscribedMembersByType.map(m => { + if (m.roles.includes('custodian.dar.manager')) { + return m.memberid.toString(); + } + }), ...topicObj.createdBy._id.toString()].filter(elem => elem); // returns array of objects [{email: 'email@email.com '}] for members in subscribed emails users is list of full user object const { memberEmails } = teamController.getMemberDetails([...memberIds], [...messageRecipients]); messageRecipients = [...teamNotificationEmails, ...memberEmails]; } else { - const memberIds = [...subscribedMembersByType.map(m => m.memberid.toString())].filter( + const memberIds = [...subscribedMembersByType.map(m => { + if (m.roles.includes('custodian.dar.manager')) { + return m.memberid.toString(); + } + })].filter( ele => ele !== topicObj.createdBy.toString() - ); + ).filter(elem => elem); const creatorObjectId = topicObj.createdBy.toString(); // returns array of objects [{email: 'email@email.com '}] for members in subscribed emails users is list of full user object const { memberEmails } = teamController.getMemberDetails([...memberIds], [...messageRecipients]); @@ -229,7 +238,7 @@ module.exports = { return res.status(201).json({ success: true, messageObj }); } catch (err) { - console.error(err.message); + process.stdout.write(`MESSAGE - createMessage : ${err.message}\n`); return res.status(500).json(err.message); } }, @@ -261,7 +270,7 @@ module.exports = { // 8. Return successful response return res.status(204).json({ success: true }); } catch (err) { - console.error(err.message); + process.stdout.write(`MESSAGE - deleteMessage : ${err.message}\n`); return res.status(500).json(err.message); } }, @@ -296,7 +305,7 @@ module.exports = { // 6. Return success no content return res.status(204).json({ success: true }); } catch (err) { - console.error(err.message); + process.stdout.write(`MESSAGE - updateMessage : ${err.message}\n`); return res.status(500).json(err.message); } }, @@ -322,7 +331,7 @@ module.exports = { // 3. Return the number of unread messages return res.status(200).json({ success: true, count: unreadMessageCount }); } catch (err) { - console.error(err.message); + process.stdout.write(`MESSAGE - getUnreadMessageCount : ${err.message}\n`); return res.status(500).json(err.message); } }, diff --git a/src/resources/message/v3/message.controller.js b/src/resources/message/v3/message.controller.js new file mode 100644 index 000000000..37088e072 --- /dev/null +++ b/src/resources/message/v3/message.controller.js @@ -0,0 +1,331 @@ +import _ from 'lodash'; +import mongoose from 'mongoose'; + +import { activityLogService } from '../../activitylog/dependency'; +import constants from '../../utilities/constants.util'; +import { Data as ToolModel } from '../../tool/data.model'; +import { dataRequestService } from '../../datarequest/dependency'; +import { MessagesModel } from './../message.model'; +import { ROLES } from '../../user/user.roles'; +import { TopicModel } from '../../topic/topic.model'; +import teamController from '../../team/team.controller'; +import { sendNotification, sendPubSubMessage } from './notification'; + +const topicController = require('../../topic/topic.controller'); + + +class MessageController +{ + limitRows = 50; + roles = [ROLES.Admin.toLowerCase(), ROLES.Creator.toLowerCase()]; + + constructor() {} + + async createMessage(req, res) { + try { + const { _id: createdBy, firstname, lastname, isServiceAccount = false } = req.user; + let { messageType = 'message', topic = '', messageDescription, relatedObjectIds, firstMessage } = req.body; + let topicObj = {}; + let team, publisher, userType; + let tools = {}; + + const userRole = req.user.role.toLowerCase(); + + if (!this.checkUserRole(userRole)) { + process.stdout.write(`MESSAGE - createMessage : the user role is not Admin or Creator`); + res.status(500).json(`An error occurred - user does not have the right roles`); + } + + // 1. If the message type is 'message' and topic id is empty + if (messageType === 'message') { + // 2. Find the related object(s) in MongoDb and include team data to update topic recipients in case teams have changed + tools = await ToolModel.find() + .where('_id') + .in(relatedObjectIds) + .populate({ + path: 'publisher', + populate: { + path: 'team', + select: 'members notifications', + populate: { + path: 'users', + }, + }, + }); + + if (_.isEmpty(tools)) return undefined; + + ({ publisher = '' } = tools[0]); + + if (_.isEmpty(publisher)) { + process.stdout.write(`No publisher associated to this dataset\n`); + return res.status(500).json({ success: false, message: 'No publisher associated to this dataset' }); + } + + ({ team = [] } = publisher); + if (_.isEmpty(team)) { + process.stdout.write(`No team associated to publisher, cannot message\n`); + return res.status(500).json({ success: false, message: 'No team associated to publisher, cannot message' }); + } + + userType = teamController.checkTeamPermissions('', team.toObject(), req.user._id) + ? constants.userTypes.CUSTODIAN + : constants.userTypes.APPLICANT; + if (_.isEmpty(topic)) { + topicObj = await topicController.buildTopic({ createdBy, relatedObjectIds }); + if (!topicObj) return res.status(500).json({ success: false, message: 'Could not save topic to database.' }); + topic = topicObj._id; + } else { + topicObj = await topicController.findTopic(topic, createdBy); + if (!topicObj) return res.status(404).json({ success: false, message: 'The topic specified could not be found' }); + topicObj.recipients = await topicController.buildRecipients(team, topicObj.createdBy); + await topicObj.save(); + } + + if (!topicObj.linkedDataAccessApplication) { + const accessRecord = await dataRequestService.linkRelatedApplicationByMessageContext( + topicObj._id, + req.user.id, + topicObj.datasets.map(dataset => dataset.datasetId), + constants.applicationStatuses.INPROGRESS + ); + if (accessRecord) { + topicObj.linkedDataAccessApplication = accessRecord._id; + await topicObj.save(); + } + } + } + + const message = await MessagesModel.create({ + messageID: parseInt(Math.random().toString().replace('0.', '')), + messageObjectID: parseInt(Math.random().toString().replace('0.', '')), + messageDescription, + firstMessage, + topic, + createdBy, + messageType, + readBy: [new mongoose.Types.ObjectId(createdBy)], + ...(userType && { userType }), + }); + + if (!message) return res.status(500).json({ success: false, message: 'Could not save message to database.' }); + + if (messageType === 'message') { + await sendNotification(topicObj, team); + await sendPubSubMessage(tools, topicObj._id, message, req.body.firstMessage, isServiceAccount); + } + // 19. Return successful response with message data + const messageObj = message.toObject(); + messageObj.createdByName = { firstname, lastname }; + messageObj.createdBy = { _id: createdBy, firstname, lastname }; + + // 20. Update activity log if there is a linked DAR + if (topicObj.linkedDataAccessApplication) { + activityLogService.logActivity(constants.activityLogEvents.data_access_request.PRESUBMISSION_MESSAGE, { + messages: [messageObj], + applicationId: topicObj.linkedDataAccessApplication, + publisher: publisher.name, + }); + } + + return res.status(201).json({ success: true, messageObj }); + } catch (err) { + process.stdout.write(`MESSAGE - createMessage : ${err.message}\n`); + return res.status(500).json(err.message); + } + } + + async getCountUnreadMessagesByPersonId(req, res) { + const personId = parseInt(req.params.personId) || ''; + const userRole = req.user.role.toLowerCase(); + + if (!this.checkUserRole(userRole)) { + process.stdout.write(`MESSAGE - getCountUnreadMessagesByPersonId : the user role is not Admin or Creator`); + res.status(500).json(`An error occurred - user does not have the right roles`); + } + + const pipeline = this.messagesAggregatePipelineCount(userRole, personId); + const messages = await this.getDataMessages(pipeline); + + const countUnreadMessages = messages.filter(element => element.isRead !== 'false').length; + + return res.status(200).json({ countUnreadMessages }); + } + + async getMessagesByPersonId(req, res) { + const personId = parseInt(req.params.personId) || ''; + const userRole = req.user.role.toLowerCase(); + + if (!this.checkUserRole(userRole)) { + process.stdout.write(`MESSAGE - getMessagesByPersonId : the user role is not Admin or Creator`); + res.status(500).json(`An error occurred - user does not have the right roles`); + } + + const pipeline = this.messagesAggregatePipelineByPersonId(userRole, personId); + const messages = await this.getDataMessages(pipeline); + + return res.status(200).json({ success: true, data: messages }); + } + + async getUnreadMessageCount(req, res){ + try { + const { _id: userId } = req.user; + let unreadMessageCount = 0; + + const topics = await TopicModel.find({ + recipients: { $elemMatch: { $eq: userId } }, + status: 'active', + }); + + topics.forEach(topic => { + topic.topicMessages.forEach(message => { + if (!message.readBy.includes(userId)) { + unreadMessageCount++; + } + }); + }); + + return res.status(200).json({ success: true, count: unreadMessageCount }); + } catch (err) { + process.stdout.write(`MESSAGE - getUnreadMessageCount : ${err.message}\n`); + return res.status(500).json({ message: err.message }); + } + } + + async deleteMessage(req, res) { + try { + const { id } = req.params; + const { _id: userId } = req.user; + + const userRole = req.user.role.toLowerCase(); + + if (!this.checkUserRole(userRole)) { + process.stdout.write(`MESSAGE - createMessage : the user role is not Admin or Creator`); + res.status(500).json(`An error occurred - user does not have the right roles`); + } + + if (!id) { + return res.status(404).json({ success: false, message: 'Message Id not found.' }); + } + + const message = await MessagesModel.findOne({ _id: id }); + + if (!message) { + return res.status(404).json({ success: false, message: `Message not found for ${id}` }); + } + + if (message.createdBy.toString() !== userId.toString()) { + return res.status(401).json({ success: false, message: 'Not authorised to delete this message' }); + } + + await MessagesModel.remove({ _id: id }); + + const messagesCount = await MessagesModel.count({ topic: message.topic }); + + if (!messagesCount) { + await TopicModel.remove({ _id: new mongoose.Types.ObjectId(message.topic) }); + } + + return res.status(204).json({ success: true }); + } catch (err) { + process.stdout.write(`MESSAGE - deleteMessage : ${err.message}\n`); + return res.status(500).json({ message: err.message }); + } + } + + async updateMessage(req, res) { + try { + let { _id: userId } = req.user; + let { messageId, isRead, messageDescription = '', messageType = '' } = req.body; + + const userRole = req.user.role.toLowerCase(); + + if (!this.checkUserRole(userRole)) { + process.stdout.write(`MESSAGE - createMessage : the user role is not Admin or Creator`); + res.status(500).json(`An error occurred - user does not have the right roles`); + } + + if (!messageId) return res.status(404).json({ success: false, message: 'Message Id not found.' }); + + const message = await MessagesModel.findOne({ _id: messageId }); + + if (!message) { + return res.status(404).json({ success: false, message: 'Message not found.' }); + } + + if (isRead && !message.readBy.includes(userId.toString())) { + message.readBy.push(userId); + } + + if (isRead) { + message.isRead = isRead; + } + + if (!_.isEmpty(messageDescription)) { + message.messageDescription = messageDescription; + } + + if (!_.isEmpty(messageType)) { + message.messageType = messageType; + } + + await message.save(); + + return res.status(204).json({ success: true }); + } catch (err) { + process.stdout.write(`MESSAGE - updateMessage : ${err.message}\n`); + return res.status(500).json({ message: err.message }); + } + } + + async getDataMessages(pipeline) { + try { + const statement = MessagesModel.aggregate(pipeline).limit(this.limitRows); + return await statement.exec(); + } catch (err) { + process.stdout.write(`MessageController.getDataReviews : ${err.message}`); + throw new Error(`An error occurred : ${err.message}`); + } + } + + messagesAggregatePipelineByPersonId(role, personId) { + let query = []; + + if (role === ROLES.Admin.toLowerCase()) { + query.push({ "$match": { "$and": [{ "$or": [{ "messageTo": personId }, { "messageTo": 0 }] }] } }); + } + + if (role === ROLES.Creator.toLowerCase()) { + query.push({ "$match": { "$and": [{ "messageTo": personId }] } }); + } + + query.push({ "$sort": { "createdDate": -1 } }); + query.push({ "$lookup": { "from": "tools", "localField": "messageObjectID", "foreignField": "id", "as": "tool" } }); + query.push({ "$lookup": { "from": "course", "localField": "messageObjectID", "foreignField": "id", "as": "course" } }); + + return query; + } + + messagesAggregatePipelineCount(role, personId) { + let query = []; + + if (role === ROLES.Admin.toLowerCase()) { + query.push({ "$match": { "$and": [{ "$or": [{ "messageTo": personId }, { "messageTo": 0 }] }] } }); + } + + if (role === ROLES.Creator.toLowerCase()) { + query.push({ "$match": { "$and": [{ "messageTo": personId }] } }); + } + + query.push({ "$sort": { "createdDate": -1 } }); + query.push({ "$lookup": { "from": "tools", "localField": "messageObjectID", "foreignField": "id", "as": "tool" } }); + + return query; + } + + checkUserRole(userRole) { + return (this.roles.indexOf(userRole) > -1) + } +} + +module.exports = new MessageController(); \ No newline at end of file diff --git a/src/resources/message/v3/message.route.js b/src/resources/message/v3/message.route.js new file mode 100644 index 000000000..1b27d581d --- /dev/null +++ b/src/resources/message/v3/message.route.js @@ -0,0 +1,36 @@ +import express from 'express'; +import passport from 'passport'; +const MessageController = require('./message.controller'); +const router = express.Router(); + +// @router GET /api/v3/messages/:personId +// @desc get messages by person id for users.role === Admin or users.role === Creator +// @access Private +router.get('/:personId', passport.authenticate('jwt'), (req, res) => MessageController.getMessagesByPersonId(req, res)); + +// @router GET /api/v3/messages/:personId/unread/count +// @desc count number of unread messages by person id for users.role === Admin or users.role === Creator +// @access Private +router.get('/:personId/unread/count', passport.authenticate('jwt'), (req, res) => MessageController.getCountUnreadMessagesByPersonId(req, res)); + +// @route GET api/v3/messages/unread/count +// @desc GET the number of unread messages for a user +// @access Private +router.get('/unread/count', passport.authenticate('jwt'), (req, res) => MessageController.getUnreadMessageCount(req, res)); + +// @route POST api/v3/messages +// @desc POST a message - create an message/enquire; user need to be Admin or Creator +// @access Private +router.post('/', passport.authenticate('jwt'), (req, res) => MessageController.createMessage(req, res)); + +// @route PUT api/v3/messages +// @desc PUT Update a message; user need to be Admin or Creator +// @access Private +router.put('/', passport.authenticate('jwt'), (req, res) => MessageController.updateMessage(req, res)); + +// @route DELETE api/v3/messages/:id +// @desc DELETE Delete a message; user need to be Admin or Creator +// @access Private +router.delete('/:id', passport.authenticate('jwt'), (req, res) => MessageController.deleteMessage(req, res)); + +module.exports = router; \ No newline at end of file diff --git a/src/resources/message/v3/notification.js b/src/resources/message/v3/notification.js new file mode 100644 index 000000000..49ee5e871 --- /dev/null +++ b/src/resources/message/v3/notification.js @@ -0,0 +1,120 @@ +import _ from 'lodash'; + +import constants from '../../utilities/constants.util'; +import emailGenerator from '../../utilities/emailGenerator.util'; +import { publishMessageWithRetryToPubSub } from '../../../services/google/PubSubWithRetryService'; +import { PublisherModel } from '../../publisher/publisher.model'; +import teamController from '../../team/team.controller'; +import { UserModel } from '../../user/user.model'; + +const { ObjectId } = require('mongodb'); + +export const sendNotification = async (topicObj, team) => { + let optIn, subscribedEmails, messageCreatorRecipient; + // 16. Find recipients who have opted in to email updates and exclude the requesting user + let messageRecipients = await UserModel.find({ _id: { $in: topicObj.recipients } }); + + if (!_.isEmpty(team) || !_.isNil(team)) { + // 17. team all users for notificationType + generic email + // Retrieve notifications for the team based on type return {notificationType, subscribedEmails, optIn} + let teamNotifications = teamController.getTeamNotificationByType(team, constants.teamNotificationTypes.DATAACCESSREQUEST); + // only deconstruct if team notifications object returns - safeguard code + if (!_.isEmpty(teamNotifications)) { + // Get teamNotification emails if optIn true + ({ optIn = false, subscribedEmails = [] } = teamNotifications); + // check subscribedEmails and optIn send back emails or blank [] + let teamNotificationEmails = teamController.getTeamNotificationEmails(optIn, subscribedEmails); + // get users from team.members with notification type and optedIn only + const subscribedMembersByType = teamController.filterMembersByNoticationTypesOptIn( + [...team.members], + [constants.teamNotificationTypes.DATAACCESSREQUEST] + ); + if (!_.isEmpty(subscribedMembersByType)) { + // build cleaner array of memberIds from subscribedMembersByType + if (topicObj.topicMessages !== undefined) { + const memberIds = [...subscribedMembersByType.map(m => m.memberid.toString()), ...topicObj.createdBy._id.toString()]; + // returns array of objects [{email: 'email@email.com '}] for members in subscribed emails users is list of full user object + const { memberEmails } = teamController.getMemberDetails([...memberIds], [...messageRecipients]); + messageRecipients = [...teamNotificationEmails, ...memberEmails]; + } else { + const memberIds = [...subscribedMembersByType.map(m => m.memberid.toString())].filter( + ele => ele !== topicObj.createdBy.toString() + ); + const creatorObjectId = topicObj.createdBy.toString(); + // returns array of objects [{email: 'email@email.com '}] for members in subscribed emails users is list of full user object + const { memberEmails } = teamController.getMemberDetails([...memberIds], [...messageRecipients]); + const creatorEmail = await UserModel.findById(creatorObjectId); + messageCreatorRecipient = [{ email: creatorEmail.email }]; + messageRecipients = [...teamNotificationEmails, ...memberEmails]; + } + } else { + // only if not membersByType but has a team email setup + messageRecipients = [...messageRecipients, ...teamNotificationEmails]; + } + } + } + + // Create object to pass through email data + let options = { + firstMessage, + firstname, + lastname, + messageDescription, + openMessagesLink: process.env.homeURL + '/search?search=&tab=Datasets&openUserMessages=true', + }; + // Create email body content + let html = emailGenerator.generateMessageNotification(options); + + // 18. Send email + emailGenerator.sendEmail( + messageRecipients, + constants.hdrukEmail, + `You have received a new message on the HDR UK Innovation Gateway`, + html, + false + ); + + if (messageCreatorRecipient) { + let htmlCreator = emailGenerator.generateMessageCreatorNotification(options); + + emailGenerator.sendEmail( + messageCreatorRecipient, + constants.hdrukEmail, + `You have received a new message on the HDR UK Innovation Gateway`, + htmlCreator, + false + ); + } +} + +export const sendPubSubMessage = async (tools, topicObjId, message, firstMessag, isServiceAccount) => { + try { + const cacheEnabled = parseInt(process.env.CACHE_ENABLED) || 0; + + if (cacheEnabled && !isServiceAccount) { + let publisherDetails = await PublisherModel.findOne({ _id: ObjectId(tools[0].publisher._id) }).lean(); + + if (publisherDetails['dar-integration'] && publisherDetails['dar-integration']['enabled']) { + const pubSubMessage = { + id: '', + type: 'enquiry', + publisherInfo: { + id: publisherDetails._id, + name: publisherDetails.name, + }, + details: { + topicId: topicObjId, + messageId: message.messageID, + createdDate: message.createdDate, + questionBank: firstMessage, + }, + darIntegration: publisherDetails['dar-integration'], + }; + await publishMessageWithRetryToPubSub(process.env.PUBSUB_TOPIC_ENQUIRY, JSON.stringify(pubSubMessage)); + } + } + } catch (err) { + process.stdout.write(`MESSAGE - createMessage - send PubSub Message : ${err.message}\n`); + return res.status(500).json(err.message); + } +} \ No newline at end of file diff --git a/src/resources/metadata/metadata.route.js b/src/resources/metadata/metadata.route.js new file mode 100644 index 000000000..cc9e4596c --- /dev/null +++ b/src/resources/metadata/metadata.route.js @@ -0,0 +1,22 @@ +import express from 'express'; +import passport from 'passport'; + +import { utils } from '../auth'; +import { ROLES } from '../user/user.roles'; + +import datasetonboardingUtil from '../../utils/datasetonboarding.util'; + +const router = express.Router({ mergeParams: true }); + +router.post('/scoring', passport.authenticate('jwt'), utils.checkIsInRole(ROLES.Admin), async (req, res) => { + const { dataset } = req.body; + + if (!dataset) { + res.json({ success: false, error: 'Dataset object must be supplied and contain all required data', status: 400 }); + } + + const verdict = await datasetonboardingUtil.buildMetadataQuality(dataset, dataset.datasetv2, dataset.pid); + res.json({ success: true, data: verdict, status: 200 }); +}); + +module.exports = router; \ No newline at end of file diff --git a/src/resources/paper/paper.controller.js b/src/resources/paper/paper.controller.js index 9fd61b739..fada56b93 100644 --- a/src/resources/paper/paper.controller.js +++ b/src/resources/paper/paper.controller.js @@ -33,7 +33,7 @@ export default class PaperController extends Controller { }); } catch (err) { // Return error response if something goes wrong - console.error(err.message); + process.stdout.write(`PAPER - getPaper : ${err.message}\n`); return res.status(500).json({ success: false, message: 'A server error occurred, please try again', @@ -52,7 +52,7 @@ export default class PaperController extends Controller { }); } catch (err) { // Return error response if something goes wrong - console.error(err.message); + process.stdout.write(`PAPER - getPaper : ${err.message}\n`); return res.status(500).json({ success: false, message: 'A server error occurred, please try again', diff --git a/src/resources/paper/v1/paper.route.js b/src/resources/paper/v1/paper.route.js index 4d083956d..915006c64 100644 --- a/src/resources/paper/v1/paper.route.js +++ b/src/resources/paper/v1/paper.route.js @@ -71,7 +71,7 @@ router.post('/validate', passport.authenticate('jwt'), async (req, res) => { // 5. Otherwise return valid return res.status(200).json({ success: true }); } catch (err) { - console.error(err.message); + process.stdout.write(`PAPER - validate : ${err.message}\n`); return res.status(500).json({ success: false, error: 'Paper link validation failed' }); } }); diff --git a/src/resources/person/person.route.js b/src/resources/person/person.route.js index 564ffca42..b2138a8da 100644 --- a/src/resources/person/person.route.js +++ b/src/resources/person/person.route.js @@ -164,7 +164,7 @@ router.get('/profile/:id', async (req, res) => { let data = [person]; return res.json({ success: true, data: data }); } catch (err) { - console.error(err.message); + process.stdout.write(`PERSON - GET PROFILE : ${err.message}\n`); return res.json({ success: false, error: err.message }); } }); diff --git a/src/resources/project/__mocks__/projects.js b/src/resources/project/__mocks__/projects.js index 910063305..8f06d30e4 100644 --- a/src/resources/project/__mocks__/projects.js +++ b/src/resources/project/__mocks__/projects.js @@ -186,7 +186,7 @@ export const projectsStub = [ "id": 7635028705590892, "type": "project", "name": "How can NCS healthcare data be connected with wastewater surveillance of COVID-19 in a privacy-preserving fashion to inform epidemiological models and democratise data access?", - "link": "https://web.www.healthdatagateway.org/project/7635028705590892", + "link": "https://web.old.healthdatagateway.org/project/7635028705590892", "description": "Wastewater-based epidemiology (WBE), i.e. the monitoring of public health using samples collected from sewerage systems, offers the unique opportunity to make healthcare data widely available without compromising the privacy of individuals: by their very nature, sewerage systems aggregate the signal from thousands of people, diminishing the ability to identify any one individual. In addition to ongoing work modelling the relationship between case numbers and wastewater data, this project will deliver five additional benefits:\n* provide data on COVID-19 related hospital admissions and symptoms as a complementary signal to refine WBE modelling with higher spatiotemporal resolution than is currently available.\n* develop standards for exchanging WBE data that support automatic validation, increasing our confidence in the data.\nprovide consistent methods to aggregate healthcare data to catchment areas across England, Scotland, and Wales.\n* develop sound, privacy-preserving aggregation methods for sensitive data in the context of WBE that could help address future public health concerns, e.g. influenza outbreaks or illicit drug consumption.\n* subject to privacy due diligence, generate data products that can be shared publicly, aligning with the government’s open data strategy and allowing a broader audience to participate in WBE.\n", "resultsInsights": "", "activeflag": "active", diff --git a/src/resources/project/project.controller.js b/src/resources/project/project.controller.js index c96eb2ad5..fc6234c7f 100644 --- a/src/resources/project/project.controller.js +++ b/src/resources/project/project.controller.js @@ -33,7 +33,7 @@ export default class ProjectController extends Controller { }); } catch (err) { // Return error response if something goes wrong - console.error(err.message); + process.stdout.write(`PROJECT - GET PROJECT : ${err.message}\n`); return res.status(500).json({ success: false, message: 'A server error occurred, please try again', @@ -52,7 +52,7 @@ export default class ProjectController extends Controller { }); } catch (err) { // Return error response if something goes wrong - console.error(err.message); + process.stdout.write(`PROJECT - GET PROJECTS : ${err.message}\n`); return res.status(500).json({ success: false, message: 'A server error occurred, please try again', diff --git a/src/resources/publisher/publisher.controller.js b/src/resources/publisher/publisher.controller.js index 7b3c5cb50..856e8336f 100644 --- a/src/resources/publisher/publisher.controller.js +++ b/src/resources/publisher/publisher.controller.js @@ -3,6 +3,8 @@ import constants from '../utilities/constants.util'; import teamController from '../team/team.controller'; import Controller from '../base/controller'; import { logger } from '../utilities/logger'; +import teamV3Util from '../utilities/team.v3.util'; +import HttpExceptions from '../../exceptions/HttpExceptions'; const logCategory = 'Publisher'; @@ -17,7 +19,6 @@ export default class PublisherController extends Controller { async getPublisher(req, res) { try { - // 1. Get the publisher from the database const { id } = req.params; const publisher = await this.publisherService.getPublisher(id).catch(err => { logger.logError(err, logCategory); @@ -28,15 +29,11 @@ export default class PublisherController extends Controller { publisher: { dataRequestModalContent: {}, allowsMessaging: false }, }); } - // 2. Return publisher + return res.status(200).json({ success: true, publisher }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred fetching the custodian details', - }); + throw new HttpExceptions(`An error occurred fetching the custodian details`, 500); } } @@ -47,45 +44,36 @@ export default class PublisherController extends Controller { async getPublisherDatasets(req, res) { try { - // 1. Get the datasets for the publisher from the database const { id } = req.params; let datasets = await this.publisherService.getPublisherDatasets(id).catch(err => { logger.logError(err, logCategory); }); - // 2. Return publisher datasets + return res.status(200).json({ success: true, datasets }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred searching for custodian datasets', - }); + throw new HttpExceptions(`An error occurred searching for custodian datasets`, 500); } } async getPublisherDataAccessRequests(req, res) { try { - // 1. Deconstruct the request const { _id: requestingUserId } = req.user; const { id } = req.params; - // 2. Lookup publisher team const options = { lean: true, populate: [{ path: 'team' }, { path: 'members' }] }; const publisher = await this.publisherService.getPublisher(id, options).catch(err => { logger.logError(err, logCategory); }); if (!publisher) { - return res.status(404).json({ success: false }); + throw new HttpExceptions(`Not Found`, 404); } - // 3. Check the requesting user is a member of the custodian team - const isAuthenticated = teamController.checkTeamPermissions('', publisher.team, requestingUserId); - if (!isAuthenticated) return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); - + //Check if current user is a manager - const isManager = teamController.checkTeamPermissions(constants.roleTypes.MANAGER, publisher.team, requestingUserId); + const isManager = teamV3Util.checkUserRolesByTeam([constants.roleMemberTeam.CUST_DAR_MANAGER], publisher.team, requestingUserId); // 4. Find all applications for current team member view + // if I am custodian.dar.manager, reviewer const applications = await this.publisherService.getPublisherDataAccessRequests(id, requestingUserId, isManager).catch(err => { logger.logError(err, logCategory); }); @@ -109,21 +97,16 @@ export default class PublisherController extends Controller { .sort((a, b) => b.updatedAt - a.updatedAt); const avgDecisionTime = this.dataRequestService.calculateAvgDecisionTime(applications); - // 6. Return all applications + return res.status(200).json({ success: true, data: modifiedApplications, avgDecisionTime, canViewSubmitted: isManager }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred searching for custodian applications', - }); + throw new HttpExceptions(`An error occurred searching for custodian applications`, 500); } } async getPublisherWorkflows(req, res) { try { - // 1. Get the workflow from the database including the team members to check authorisation const { id } = req.params; let workflows = await this.workflowService.getWorkflowsByPublisher(id).catch(err => { logger.logError(err, logCategory); @@ -144,20 +127,16 @@ export default class PublisherController extends Controller { const { publisher: { team }, } = workflows[0]; - const authorised = teamController.checkTeamPermissions(constants.roleTypes.MANAGER, team, requestingUserId); - // 4. If not return unauthorised + + let authorised = teamV3Util.checkUserRolesByTeam([constants.roleMemberTeam.CUST_DAR_MANAGER], team, requestingUserId); if (!authorised) { - return res.status(401).json({ success: false }); + throw new HttpExceptions(`User not authorized to perform this action`,403); } - // 5. Return payload + return res.status(200).json({ success: true, workflows }); } catch (err) { - // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'An error occurred searching for custodian workflows', - }); + throw new HttpExceptions(`An error occurred searching for custodian workflows`, 500); } } @@ -167,10 +146,7 @@ export default class PublisherController extends Controller { return res.status(200).json({ success: true }); }); } catch (err) { - return res.status(500).json({ - success: false, - message: 'An error occurred updating data use widget settings', - }); + throw new HttpExceptions(`An error occurred updating data use widget settings`, 500); } } @@ -180,10 +156,7 @@ export default class PublisherController extends Controller { return res.status(200).json({ success: true }); }); } catch (err) { - return res.status(500).json({ - success: false, - message: 'An error occurred updating data request modal content', - }); + throw new HttpExceptions(`An error occurred updating data request modal content`, 500); } } @@ -193,10 +166,7 @@ export default class PublisherController extends Controller { return res.status(200).json({ success: true }); }); } catch (err) { - return res.status(500).json({ - success: false, - message: 'An error occurred updating the question bank settings', - }); + throw new HttpExceptions(`An error occurred updating the question bank settings`, 500); } } } diff --git a/src/resources/publisher/publisher.route.js b/src/resources/publisher/publisher.route.js index ef6dbd440..428a2da35 100644 --- a/src/resources/publisher/publisher.route.js +++ b/src/resources/publisher/publisher.route.js @@ -59,7 +59,7 @@ router.get( // @route PATCH /api/publishers/:id/dataUseWidget // @desc Update data use widget settings (terms and conditions) // @access Public -router.patch('/:id/dataUseWidget', passport.authenticate('jwt'), utils.userIsTeamManager(), (req, res) => +router.patch('/:id/dataUseWidget', passport.authenticate('jwt'), (req, res) => publisherController.updateDataUseWidget(req, res) ); router.patch('/dataRequestModalContent/:id', passport.authenticate('jwt'), utils.userIsTeamManager(), (req, res) => diff --git a/src/resources/questionbank/questionbank.controller.js b/src/resources/questionbank/questionbank.controller.js index f6caff38b..2494421e9 100644 --- a/src/resources/questionbank/questionbank.controller.js +++ b/src/resources/questionbank/questionbank.controller.js @@ -1,5 +1,6 @@ import Controller from '../base/controller'; import { logger } from '../utilities/logger'; +import HttpExceptions from '../../exceptions/HttpExceptions'; const logCategory = 'questionbank'; @@ -21,11 +22,8 @@ export default class QuestionbankController extends Controller { }); } catch (err) { // Return error response if something goes wrong - console.error(err.message); - return res.status(500).json({ - success: false, - message: 'A server error occurred, please try again', - }); + process.stdout.write(`QUESTIONBANK - GET QUESTIONBANK : ${err.message}\n`); + throw new HttpExceptions(`A server error occurred, please try again`, 500); } } @@ -43,10 +41,7 @@ export default class QuestionbankController extends Controller { } catch (err) { // Return error response if something goes wrong logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'A server error occurred, please try again', - }); + throw new HttpExceptions(`A server error occurred, please try again`, 500); } } @@ -59,10 +54,7 @@ export default class QuestionbankController extends Controller { return res.status(200).json({ success: true, result: newRequestSchema }); } catch (err) { logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'A server error occurred, please try again', - }); + throw new HttpExceptions(`A server error occurred, please try again`, 500); } } @@ -76,10 +68,7 @@ export default class QuestionbankController extends Controller { return res.status(200).json({ success: true }); } catch (err) { logger.logError(err, logCategory); - return res.status(500).json({ - success: false, - message: 'Error removing the schema updates, please try again', - }); + throw new HttpExceptions(`A server error occurred, please try again`, 500); } } } diff --git a/src/resources/questionbank/questionbank.route.js b/src/resources/questionbank/questionbank.route.js index 54dd3dd9b..acc9d1b67 100644 --- a/src/resources/questionbank/questionbank.route.js +++ b/src/resources/questionbank/questionbank.route.js @@ -7,6 +7,7 @@ import { datarequestschemaService } from './../datarequest/schema/dependency'; import { logger } from '../utilities/logger'; import { isUserMemberOfTeamById, isUserMemberOfTeamByName } from '../auth/utils'; import constants from '../utilities/constants.util'; +import HttpExceptions from '../../exceptions/HttpExceptions'; const router = express.Router(); const questionbankController = new QuestionbankController(questionbankService); @@ -15,7 +16,9 @@ const logCategory = 'questionbank'; const validateViewRequest = (req, res, next) => { const { publisherId } = req.params; - if (isUndefined(publisherId)) return res.status(400).json({ success: false, message: 'You must provide a valid publisher Id' }); + if (isUndefined(publisherId)) { + throw new HttpExceptions(`You must provide a valid publisher Id`, 400); + } next(); }; @@ -28,10 +31,7 @@ const authorizeViewRequest = (req, res, next) => { const isAdminUser = requestingUser.teams.map(team => team.type).includes(constants.teamTypes.ADMIN); if (!authorised && !isAdminUser) { - return res.status(401).json({ - success: false, - message: 'You are not authorised to perform this action', - }); + throw new HttpExceptions(`You are not authorised to perform this action`, 401); } next(); @@ -40,7 +40,9 @@ const authorizeViewRequest = (req, res, next) => { const validatePostRequest = (req, res, next) => { const { schemaId } = req.params; - if (isUndefined(schemaId)) return res.status(400).json({ success: false, message: 'You must provide a valid data request schema Id' }); + if (isUndefined(schemaId)) { + throw new HttpExceptions(`You must provide a valid data request schema Id`, 400); + } next(); }; @@ -52,20 +54,14 @@ const authorizePostRequest = async (req, res, next) => { const dataRequestSchema = await datarequestschemaService.getDatarequestschemaById(schemaId); if (isEmpty(dataRequestSchema)) { - return res.status(404).json({ - success: false, - message: 'The requested data request schema could not be found', - }); + throw new HttpExceptions(`The requested data request schema could not be found`, 404); } const authorised = isUserMemberOfTeamByName(requestingUser, dataRequestSchema.publisher); const isAdminUser = requestingUser.teams.map(team => team.type).includes(constants.teamTypes.ADMIN); if (!authorised && !isAdminUser) { - return res.status(401).json({ - success: false, - message: 'You are not authorised to perform this action', - }); + throw new HttpExceptions(`You are not authorised to perform this action`, 401); } req.body.dataRequestSchema = dataRequestSchema; diff --git a/src/resources/questionbank/questionbank.service.js b/src/resources/questionbank/questionbank.service.js index 6bc93e8dc..2ef580437 100644 --- a/src/resources/questionbank/questionbank.service.js +++ b/src/resources/questionbank/questionbank.service.js @@ -1,4 +1,3 @@ -import { Console } from '@sentry/node/dist/integrations'; import { isEmpty, has } from 'lodash'; export default class QuestionbankService { diff --git a/src/resources/tool/review.model.js b/src/resources/review/review.model.js similarity index 100% rename from src/resources/tool/review.model.js rename to src/resources/review/review.model.js diff --git a/src/resources/tool/review.route.js b/src/resources/review/v1/review.route.js similarity index 69% rename from src/resources/tool/review.route.js rename to src/resources/review/v1/review.route.js index a2a90878d..352532cb7 100644 --- a/src/resources/tool/review.route.js +++ b/src/resources/review/v1/review.route.js @@ -1,18 +1,15 @@ import express from 'express'; -import { ROLES } from '../user/user.roles'; -import { Reviews } from './review.model'; +import { ROLES } from '../../user/user.roles'; +import { Reviews } from '../review.model'; import passport from 'passport'; -import { utils } from '../auth'; -import helper from '../utilities/helper.util'; +import { utils } from '../../auth'; +import helper from '../../utilities/helper.util'; const router = express.Router(); -/** - * {get} /accountsearch Search tools - * - * Return list of tools, this can be with filters or/and search criteria. This will also include pagination on results. - * The free word search criteria can be improved on with node modules that specialize with searching i.e. js-search - */ +// @router GET /api/v1/reviews/admin/pending +// @desc get reviews in pending for user.role = Admin +// @access Public router.get('/admin/pending', passport.authenticate('jwt'), utils.checkIsInRole(ROLES.Admin), async (req, res) => { var r = Reviews.aggregate([ { $lookup: { from: 'tools', localField: 'reviewerID', foreignField: 'id', as: 'person' } }, @@ -28,12 +25,9 @@ router.get('/admin/pending', passport.authenticate('jwt'), utils.checkIsInRole(R }); }); -/** - * {get} /accountsearch Search tools - * - * Return list of tools, this can be with filters or/and search criteria. This will also include pagination on results. - * The free word search criteria can be improved on with node modules that specialize with searching i.e. js-search - */ +// @router GET /api/v1/reviews/pending +// @desc get reviews in pending for user.role = Creator by reviewerID +// @access Public router.get('/pending', passport.authenticate('jwt'), utils.checkIsInRole(ROLES.Creator), async (req, res) => { var idString = ''; @@ -59,12 +53,9 @@ router.get('/pending', passport.authenticate('jwt'), utils.checkIsInRole(ROLES.C }); }); -/** - * {get} /accountsearch Search tools - * - * Return list of tools, this can be with filters or/and search criteria. This will also include pagination on results. - * The free word search criteria can be improved on with node modules that specialize with searching i.e. js-search - */ +// @router GET /api/v1/reviews +// @desc find reviews by reviewID +// @access Public router.get('/', async (req, res) => { var reviewIDString = ''; diff --git a/src/resources/review/v3/email.js b/src/resources/review/v3/email.js new file mode 100644 index 000000000..0b4a8bb06 --- /dev/null +++ b/src/resources/review/v3/email.js @@ -0,0 +1,93 @@ +import { Data } from '../../tool/data.model'; +import { UserModel } from '../../user/user.model'; +import emailGenerator from '../../utilities/emailGenerator.util'; + +export const sendEmailNotifications = async (review, activeflag) => { + const tool = await Data.findOne({ id: review.toolID }); + const reviewer = await UserModel.findOne({ id: review.reviewerID }); + const toolLink = process.env.homeURL + '/tool/' + tool.id; + const hdrukEmail = process.env.HDRUK_ENQUIRY_EMAIL || `enquiry@healthdatagateway.org`; + + const statement = UserModel.aggregate([ + { $match: { $or: [{ role: 'Admin' }, { id: { $in: tool.authors } }] } }, + { + $lookup: { + from: 'tools', + localField: 'id', + foreignField: 'id', + as: 'tool', + }, + }, + { $match: { 'tool.emailNotifications': true } }, + { + $project: { + _id: 1, + firstname: 1, + lastname: 1, + email: 1, + role: 1, + 'tool.emailNotifications': 1, + }, + }, + ]); + + statement.exec((err, emailRecipients) => { + if (err) { + return new Error({ success: false, error: err }); + } + + let subject; + if (activeflag === 'active') { + subject = `A review has been added to the ${tool.type} ${tool.name}`; + } else if (activeflag === 'rejected') { + subject = `A review on the ${tool.type} ${tool.name} has been rejected`; + } else if (activeflag === 'archive') { + subject = `A review on the ${tool.type} ${tool.name} has been archived`; + } + + let html = `
+
+ + + + + + + + + + + + + + +
+ ${subject} +
+

+ ${ + activeflag === 'active' + ? `${reviewer.firstname} ${reviewer.lastname} gave a ${review.rating}-star review to the tool ${tool.name}.` + : activeflag === 'rejected' + ? `A ${review.rating}-star review from ${reviewer.firstname} ${reviewer.lastname} on the ${tool.type} ${tool.name} has been rejected.` + : activeflag === 'archive' + ? `A ${review.rating}-star review from ${reviewer.firstname} ${reviewer.lastname} on the ${tool.type} ${tool.name} has been archived.` + : `` + } +

+
+ View ${tool.type} +
+
+
`; + + emailGenerator.sendEmail(emailRecipients, `${hdrukEmail}`, subject, html, false); + }); +} \ No newline at end of file diff --git a/src/resources/review/v3/notification.js b/src/resources/review/v3/notification.js new file mode 100644 index 000000000..58dc4b2a4 --- /dev/null +++ b/src/resources/review/v3/notification.js @@ -0,0 +1,37 @@ +import { Data } from '../../tool/data.model'; +import { UserModel } from '../../user/user.model'; +import { MessagesModel } from '../../message/message.model'; + +export const storeNotificationMessages = async (review) => { + const tool = await Data.findOne({ id: review.toolID }); + const reviewer = await UserModel.findOne({ id: review.reviewerID }); + const toolLink = process.env.homeURL + '/tool/' + review.toolID + '/' + tool.name; + const messageId = parseInt(Math.random().toString().replace('0.', '')); + + let message = new MessagesModel(); + message.messageID = messageId; + message.messageTo = 0; + message.messageObjectID = review.toolID; + message.messageType = 'review'; + message.messageSent = Date.now(); + message.isRead = false; + message.messageDescription = `${reviewer.firstname} ${reviewer.lastname} gave a ${review.rating}-star review to your tool ${tool.name} ${toolLink}`; + + await message.save(async err => { + if (err) { + return new Error({ success: false, error: err }); + } + }); + + const authors = tool.authors; + authors.forEach(async author => { + message.messageTo = author; + await message.save(async err => { + if (err) { + return new Error({ success: false, error: err }); + } + }); + }); + + return { success: true, id: messageId }; +} \ No newline at end of file diff --git a/src/resources/review/v3/review.controller.js b/src/resources/review/v3/review.controller.js new file mode 100644 index 000000000..e26e0a9f7 --- /dev/null +++ b/src/resources/review/v3/review.controller.js @@ -0,0 +1,76 @@ +import { Reviews } from '../review.model'; +import { storeNotificationMessages } from './notification'; +import { sendEmailNotifications } from './email'; + +class ReviewController { + constructor() {} + + async getReviews(req, res) { + const reviewId = parseInt(req.params.reviewId) || ''; + + const data = await this.getDataReviews(reviewId); + + return res.status(200).json({ + 'success': true, + data + }); + } + + async updateStateReviews(req, res) { + const reviewId = parseInt(req.params.reviewId); + const { activeflag } = req.body; + + if (!activeflag) { + process.stdout.write(`ReviewController.updateReviews : activeflag missing`); + throw new Error(`An error occurred : activeflag missing`); + } + const statusReview = activeflag === 'approve' ? 'active' : activeflag; + + const review = await this.updateStateDataReviews({ reviewID: reviewId }, { activeflag: statusReview }); + + await storeNotificationMessages(review); + // Send email notififcation of approval to authors and admins who have opted in + await sendEmailNotifications(review, activeflag); + + return res.status(200).json({ + 'success': true + }); + } + + async updateStateDataReviews(filter, update) { + try { + return await Reviews.findOneAndUpdate(filter, update, { + new: true + }); + } catch (err) { + process.stdout.write(`ReviewController.updateDataReviews : ${err.message}`); + throw new Error(`An error occurred : ${err.message}`); + } + } + + async getDataReviews(reviewId) { + try { + const pipeline = this.reviewAggregatePipeline(reviewId); + const statement = Reviews.aggregate(pipeline); + return await statement.exec(); + } catch (err) { + process.stdout.write(`ReviewController.getDataReviews : ${err.message}`); + throw new Error(`An error occurred : ${err.message}`); + } + } + + reviewAggregatePipeline(reviewId) { + let query = []; + + if (reviewId) { + query.push({ $match: { 'reviewID': reviewId } }); + } + query.push({ "$lookup": { "from": "tools", "localField": "reviewerID", "foreignField": "id", "as": "person" } }); + query.push({ "$lookup": { "from": "tools", "localField": "toolID", "foreignField": "id", "as": "tool" } }); + + return query; + } + +} + +module.exports = new ReviewController(); diff --git a/src/resources/review/v3/review.route.js b/src/resources/review/v3/review.route.js new file mode 100644 index 000000000..cdeee88d5 --- /dev/null +++ b/src/resources/review/v3/review.route.js @@ -0,0 +1,20 @@ +import express from 'express'; +import passport from 'passport'; + +const ReviewController = require('./review.controller'); + +const router = express.Router(); + +// @router GET /api/v3/reviews/:reviewId? +// @desc get all reviews or find reviews by reviewId +// @access Private +router.get('/:reviewId?', passport.authenticate('jwt'), (req, res) => ReviewController.getReviews(req, res)); + + +// @router PATCH /api/v3/reviews/:reviewId +// @bodyParam {string} activeflag can be: active/approve (approve will be converted in active), reject, archive +// @desc update active flag by reviewId +// @access Private +router.patch('/:reviewId', passport.authenticate('jwt'), (req, res) => ReviewController.updateStateReviews(req, res)); + +module.exports = router; diff --git a/src/resources/search/search.repository.js b/src/resources/search/search.repository.js index 5f6a78f51..6361e2064 100644 --- a/src/resources/search/search.repository.js +++ b/src/resources/search/search.repository.js @@ -17,6 +17,7 @@ import moment from 'moment'; import helperUtil from '../utilities/helper.util'; export async function getObjectResult(type, searchAll, searchQuery, startIndex, maxResults, sort, authorID, form) { + let collection = Data; if (type === 'course') { collection = Course; @@ -278,6 +279,33 @@ export async function getObjectResult(type, searchAll, searchQuery, startIndex, as: 'relatedResourcesCourses', }, }, + { + $lookup: { + from: 'datauseregisters', + let: { + pid: '$pid', + }, + pipeline: [ + { $unwind: '$relatedObjects' }, + { + $match: { + $expr: { + $and: [ + { + $eq: ['$relatedObjects.pid', '$$pid'], + }, + { + $eq: ['$activeflag', 'active'], + }, + ], + }, + }, + }, + { $group: { _id: null, count: { $sum: 1 } } }, + ], + as: 'relatedResourcesDataUseRegister', + }, + }, { $project: { _id: 0, @@ -336,7 +364,7 @@ export async function getObjectResult(type, searchAll, searchQuery, startIndex, 'datasetfields.metadataquality.weighted_error_percent': 1, 'datasetfields.metadataquality.weighted_completeness_percent': 1, - latestUpdate: '$timestamps.updated', + latestUpdate: '$updatedAt', relatedresources: { $add: [ { @@ -353,11 +381,19 @@ export async function getObjectResult(type, searchAll, searchQuery, startIndex, else: { $first: '$relatedResourcesCourses.count' }, }, }, + { + $cond: { + if: { $eq: [{ $size: '$relatedResourcesDataUseRegister' }, 0] }, + then: 0, + else: { $first: '$relatedResourcesDataUseRegister.count' }, + }, + }, ], }, }, }, ]; + } else { queryObject = [ { $match: newSearchQuery }, @@ -493,14 +529,14 @@ export async function getObjectResult(type, searchAll, searchQuery, startIndex, const searchResults = type === 'dataUseRegister' ? await collection.aggregate(queryObject).catch(err => { - console.log(err); + process.stdout.write(`${err.message}\n`); }) : await collection .aggregate(queryObject) .skip(parseInt(startIndex)) .limit(parseInt(maxResults)) .catch(err => { - console.log(err); + process.stdout.write(`${err.message}\n`); }); return { data: searchResults }; @@ -939,7 +975,7 @@ export function getObjectFilters(searchQueryStart, queryParams, type) { } } } catch (err) { - console.error(err.message); + process.stdout.write(`SEARCH - GET OBJECT FILTERS : ${err.message}\n`); } } return searchQuery; diff --git a/src/resources/spatialfilter/LocationController.js b/src/resources/spatialfilter/LocationController.js index a7ef7f0e0..905cd15e1 100644 --- a/src/resources/spatialfilter/LocationController.js +++ b/src/resources/spatialfilter/LocationController.js @@ -11,7 +11,7 @@ class LocationController extends BaseController { async getData(req, res) { const { filter } = req.params; const table = `${process.env.BIG_QUERY_PROJECT_ID}.${process.env.BIG_QUERY_DATABASE}.${process.env.BIG_QUERY_TABLE}`; - console.log(table); + process.stdout.write(`LocationController - getData : ${table}\n`); const statement = `SELECT name, country, level_one, level_two, level_three FROM \`${table}\` WHERE lower(\`name\`) LIKE "%${filter.toLowerCase()}%" diff --git a/src/resources/stats/stats.router.js b/src/resources/stats/stats.router.js index fa2430f95..8718f92aa 100644 --- a/src/resources/stats/stats.router.js +++ b/src/resources/stats/stats.router.js @@ -502,7 +502,7 @@ router.get('', async (req, res) => { } } } catch (err) { - console.error(err.message); + process.stdout.write(`STATS - GET STATS : ${err.message}\n`); return res.json({ success: false, error: err.message }); } }); diff --git a/src/resources/stats/v1/stats.route.js b/src/resources/stats/v1/stats.route.js index 4e339078e..c46e5ca4a 100644 --- a/src/resources/stats/v1/stats.route.js +++ b/src/resources/stats/v1/stats.route.js @@ -90,7 +90,7 @@ router.get('', logger.logRequestMiddleware({ logCategory, action: 'Viewed stats' } return result; } catch (err) { - console.error(err.message); + process.stdout.write(`STATS : ${err.message}\n`); return res.json({ success: false, error: err.message }); } }); diff --git a/src/resources/team/team.controller.js b/src/resources/team/team.controller.js index 6a5080269..8fc3a49fd 100644 --- a/src/resources/team/team.controller.js +++ b/src/resources/team/team.controller.js @@ -33,7 +33,7 @@ const getTeamById = async (req, res) => { // 4. Return team return res.status(200).json({ success: true, team }); } catch (err) { - console.error(err.message); + process.stdout.write(`TEAM - getTeamById : ${err.message}\n`); return res.status(500).json(err.message); } }; @@ -41,7 +41,6 @@ const getTeamById = async (req, res) => { // GET api/v1/teams/:id/members const getTeamMembers = async (req, res) => { try { - // 1. Get the team from the database const team = await TeamModel.findOne({ _id: req.params.id }).populate({ path: 'users', populate: { @@ -67,7 +66,7 @@ const getTeamMembers = async (req, res) => { // 6. Return team members return res.status(200).json({ success: true, members: users }); } catch (err) { - console.error(err.message); + process.stdout.write(`TEAM - getTeamMembers : ${err.message}\n`); return res.status(500).json(err.message); } }; @@ -82,8 +81,9 @@ const formatTeamUsers = team => { id, _id, email, - additionalInfo: { organisation, bio, showOrganisation, showBio }, + additionalInfo, } = user; + let { organisation = '', bio = '', showOrganisation = true, showBio = true } = { ...additionalInfo }; let userMember = team.members.find(el => el.memberid.toString() === user._id.toString()); let { roles = [] } = userMember; return { @@ -146,7 +146,7 @@ const addTeamMembers = async (req, res) => { // 9. Save members handling error callback if validation fails team.save(async err => { if (err) { - console.error(err.message); + process.stdout.write(`TEAM - addTeamMembers : ${err.message}\n`); return res.status(400).json({ success: false, message: err.message, @@ -173,7 +173,7 @@ const addTeamMembers = async (req, res) => { } }); } catch (err) { - console.error(err.message); + process.stdout.write(`TEAM - addTeamMembers : ${err.message}\n`); return res.status(400).json({ success: false, message: 'You must supply a valid team identifier', @@ -226,7 +226,7 @@ const getTeamNotifications = async (req, res) => { // 8. return 200 success return res.status(200).json(notifications); } catch (err) { - console.error(err.message); + process.stdout.write(`TEAM - getTeamNotifications : ${err.message}\n`); return res.status(500).json({ success: false, message: 'An error occurred retrieving team notifications', @@ -435,7 +435,7 @@ const updateNotifications = async (req, res) => { // 13. return 201 with new team return res.status(201).json(team); } catch (err) { - console.error(err.message); + process.stdout.write(`TEAM - updateNotifications : ${err.message}\n`); return res.status(500).json({ success: false, message: 'An error occurred updating team notifications', @@ -463,11 +463,11 @@ const updateNotificationMessages = async (req, res) => { return res.status(201).json(); }) .catch(err => { - console.log(err); + process.stdout.write(`TEAM - updateNotificationMessages : ${err.message}\n`); res.status(500).json({ success: false, message: err.message }); }); } catch (err) { - console.error(err.message); + process.stdout.write(`TEAM - updateNotificationMessages : ${err.message}\n`); return res.status(500).json({ success: false, message: 'An error occurred updating notification messages', @@ -529,7 +529,7 @@ const deleteTeamMember = async (req, res) => { team.members = updatedMembers; team.save(function (err) { if (err) { - console.error(err.message); + process.stdout.write(`TEAM - deleteTeamMember : ${err.message}\n`); return res.status(400).json({ success: false, message: err.message, @@ -545,7 +545,7 @@ const deleteTeamMember = async (req, res) => { } }); } catch (err) { - console.error(err.message); + process.stdout.write(`TEAM - deleteTeamMember : ${err.message}\n`); res.status(500).json({ status: 'error', message: err.message }); } }; @@ -592,7 +592,7 @@ const getTeamsList = async (req, res) => { // 4. Return team return res.status(200).json({ success: true, teams }); } catch (err) { - console.error(err.message); + process.stdout.write(`TEAM - getTeamsList : ${err.message}\n`); return res.status(500).json(err.message); } }; @@ -665,23 +665,23 @@ const addTeam = async (req, res) => { timeout: 60000, }) .then(async res => { - console.log(`public flag res: ${res}`); + process.stdout.write(`TEAM - public flag res: ${res}`); }) .catch(err => { - console.error('Error when making folder public on the MDC - ' + err.message); + process.stdout.write(`TEAM - Error when making folder public on the MDC : ${err.message}\n`); }); }) .catch(err => { - console.error('Error when trying to create new folder on the MDC - ' + err.message); + process.stdout.write(`TEAM - Error when trying to create new folder on the MDC : ${err.message}\n`); }); }) .catch(err => { - console.error('Error when trying to login to MDC - ' + err.message); + process.stdout.write(`TEAM - Error when trying to login to MDC : ${err.message}\n`); }); // 7. Log out of MDC await axios.post(metadataCatalogueLink + `/api/authentication/logout`, { withCredentials: true, timeout: 5000 }).catch(err => { - console.error('Error when trying to logout of the MDC - ' + err.message); + process.stdout.write(`TEAM - Error when trying to logout of the MDC : ${err.message}\n`); }); // 8. If a MDC folder with the name already exists return unsuccessful @@ -727,7 +727,7 @@ const addTeam = async (req, res) => { return res.status(200).json(newPublisher); } catch (err) { - console.error(err.message); + process.stdout.write(`TEAM - addTeam : ${err.message}\n`); return res.status(500).json({ success: false, message: 'Error', @@ -746,7 +746,7 @@ async function getManagerInfo(managerId, teamManagerIds, recipients) { ).exec(); teamManagerIds.push({ - roles: ['manager'], + roles: [constants.roleMemberTeam.CUST_TEAM_ADMIN], memberid: ObjectId(managerInfo._id.toString()), }); @@ -846,7 +846,7 @@ const editTeam = async (req, res) => { } ) .catch(err => { - console.error('Error when trying to update metdata on the MDC - ' + err.message); + process.stdout.write(`TEAM - Error when trying to update metdata on the MDC : ${err.message}\n`); }); } @@ -862,7 +862,7 @@ const editTeam = async (req, res) => { } ) .catch(err => { - console.error('Error when trying to update metdata on the MDC - ' + err.message); + process.stdout.write(`TEAM - Error when trying to update metdata on the MDC : ${err.message}\n`); }); } @@ -878,22 +878,22 @@ const editTeam = async (req, res) => { } ) .catch(err => { - console.error('Error when trying to update metdata on the MDC - ' + err.message); + process.stdout.write(`TEAM - Error when trying to update metdata on the MDC : ${err.message}\n`); }); } }) .catch(err => { - console.error('Error when trying to get the metdata from the MDC - ' + err.message); + process.stdout.write(`TEAM - Error when trying to get the metdata from the MDC : ${err.message}\n`); }); } }) .catch(err => { - console.error('Error when trying to login to MDC - ' + err.message); + process.stdout.write(`TEAM - Error when trying to login to MDC : ${err.message}\n`); }); // 12. Log out of MDC await axios.post(metadataCatalogueLink + `/api/authentication/logout`, { withCredentials: true, timeout: 5000 }).catch(err => { - console.error('Error when trying to logout of the MDC - ' + err.message); + process.stdout.write(`TEAM - Error when trying to logout of the MDC : ${err.message}\n`); }); //13. Update datasets if name or member change @@ -919,7 +919,7 @@ const editTeam = async (req, res) => { return res.status(200).json({ success: true }); } catch (err) { - console.error(err.message); + process.stdout.write(`TEAM - editTeam : ${err.message}\n`); return res.status(500).json(err.message); } }; diff --git a/src/resources/team/team.model.js b/src/resources/team/team.model.js index 520eaa522..cb2d611ac 100644 --- a/src/resources/team/team.model.js +++ b/src/resources/team/team.model.js @@ -12,7 +12,21 @@ const TeamSchema = new Schema( { _id: false, memberid: { type: Schema.Types.ObjectId, ref: 'User', required: true }, - roles: { type: [String], enum: ['reviewer', 'manager', 'metadata_editor'], required: true }, + roles: { + type: [String], + enum: [ + 'reviewer', + 'manager', + 'metadata_editor', + 'custodian.team.admin', + 'custodian.metadata.manager', + 'custodian.dar.manager', + 'admin_dataset', + 'admin_data_use', + 'editor' + ], + required: true + }, dateCreated: Date, dateUpdated: Date, notifications: [ diff --git a/src/resources/team/v3/team.controller.js b/src/resources/team/v3/team.controller.js new file mode 100644 index 000000000..9eb21c6e2 --- /dev/null +++ b/src/resources/team/v3/team.controller.js @@ -0,0 +1,499 @@ +// import { getDBTeamMembers } from './team.database'; + +import _, { isEmpty, has, difference, includes, isNull } from 'lodash'; +import TeamService from './team.service'; +import teamV3Util from '../../utilities/team.v3.util'; +import constants from '../../utilities/constants.util'; +import HttpExceptions from '../../../exceptions/HttpExceptions'; +import { UserModel } from '../../user/user.model'; +import { TeamModel } from '../team.model'; +import { LoggingService } from '../../../services'; +import emailGenerator from '../../utilities/emailGenerator.util'; +import notificationBuilder from '../../utilities/notificationBuilder'; +import emailTeam from './util/emailTeam'; + +class TeamController extends TeamService { + _logger; + + constructor() { + super(); + + this._logger = new LoggingService(); + } + + async getTeamMembers(req, res) { + const teamId = req.params.teamid; + const currentUserId = req.user._id; + const allowPerms = req.allowPerms || []; + + await this.checkUserAuth(teamId, currentUserId, allowPerms); + + const team = await this.getMembersByTeamId(teamId); + + let members = teamV3Util.formatTeamMembers(team); + + this.sendLogInGoogle({ + action: 'getTeamMembers', + input: { + teamId, + currentUserId, + }, + output: members, + }); + + res.status(200).json({ + members, + }); + } + + async deleteTeamMember(req, res) { + const teamId = req.params.teamid; + const deleteUserId = req.params.memberid; + const userObj = req.user; + const currentUserId = req.user._id; + const allowPerms = req.allowPerms || []; + + await this.checkUserAuth(teamId, currentUserId, allowPerms); + + const team = await this.getTeamByTeamId(teamId); + + let { members = [], users = [] } = team; + + let updatedMembers = [...members].filter(mem => mem.memberid.toString() !== deleteUserId.toString()); + if (members.length === updatedMembers.length) { + throw new Error(`The user requested for deletion is not a member of this team.`); + } + + teamV3Util.checkIfExistAdminRole( + updatedMembers, + [ + constants.roleMemberTeam.CUST_TEAM_ADMIN, + constants.roleMemberTeam.CUST_DAR_MANAGER, + constants.roleMemberTeam.CUST_MD_MANAGER + ] + ); + + this.sendLogInGoogle({ + action: 'deleteTeamMember', + input: { + teamId, + memberid: deleteUserId, + currentUserId, + }, + output: 'success' + }); + + team.members = updatedMembers; + let teamClone = team; + try { + team.save(async err => { + if (err) { + throw new HttpExceptions(err.message); + } else { + await this.updateTeamMemberMessage(teamId, deleteUserId, currentUserId.toString(), '', null, teamClone, true); + + return res.status(204).json({ + success: true, + }); + } + }); + + } catch (e) { + throw new HttpExceptions(e.message); + } + } + + async addTeamMember(req, res) { + const teamId = req.params.teamid; + const currentUserId = req.user._id; + const { memberId, roles = [] } = req.body; + const allowPerms = req.allowPerms || []; + + await this.checkUserAuth(teamId, currentUserId, allowPerms); + + const team = await this.getTeamByTeamId(teamId); + + let { members } = team; + + let checkIfExistMember = members.find(item => item.memberid.toString() === memberId.toString()); + if (checkIfExistMember) { + throw new HttpExceptions(`Member already exists`, 409); + } + + let newMembers = { + roles: roles, + memberid: memberId, + notifications: [] + }; + + this.sendLogInGoogle({ + action: 'addTeamMember', + input: { + teamId, + currentUserId, + body: req.body, + }, + output: newMembers, + }); + + team.members = team.members.concat(newMembers); + let teamClone = team; + team.save(async err => { + if (err) { + throw new HttpExceptions(err.message); + } else { + for (const role of roles) { + await this.updateTeamMemberMessage(teamId, memberId[0], currentUserId.toString(), role, true, teamClone); + } + + const updatedTeam = await this.getMembersByTeamId(teamId); + let users = teamV3Util.formatTeamMembers(updatedTeam); + + return res.status(201).json({ + success: true, + members: users, + }); + } + }); + } + + async updateTeamMember(req, res) { + const teamId = req.params.teamid; + const updateUserId = req.params.memberid; + const currentUserId = req.user._id; + const role = req.body; + const allowPerms = req.allowPerms || []; + let tempRole = ''; + + if (Object.keys(role).length === 0) { + throw new HttpExceptions(`No Roles`, 400); + } + + let tempKeyRole = Object.keys(role)[0] || ''; + let tempValueRole = Object.values(role)[0] || ''; + + const currUserRoles = await this.checkUserAuth(teamId, currentUserId, allowPerms); + + const team = await this.getTeamByTeamId(teamId); + + let { members } = team; + + let checkIfExistMember = members.find(item => item.memberid.toString() === updateUserId.toString()); + if (!checkIfExistMember) { + throw new HttpExceptions(`The member does not exist in the team`, 409); + } + + const approvedRoles = teamV3Util.listOfRolesAllowed(currUserRoles, constants.rolesAcceptedByRoles); + teamV3Util.checkAllowNewRoles([tempKeyRole], approvedRoles); + team.members.map(member => { + if (member.memberid.toString() === updateUserId.toString()) { + tempRole = member.roles; + if (tempValueRole) { + tempRole.push(tempKeyRole); + } else { + tempRole = tempRole.filter(item => item !== tempKeyRole); + } + member.roles = [...new Set(tempRole)]; + } + }); + + teamV3Util.checkIfExistAdminRole( + team.members, + [ + constants.roleMemberTeam.CUST_TEAM_ADMIN, + constants.roleMemberTeam.CUST_DAR_MANAGER, + constants.roleMemberTeam.CUST_MD_MANAGER + ] + ); + + let teamClone = team; + + try { + team.save(async err => { + if (err) { + throw new HttpExceptions(err.message); + } else { + let updatedTeam = await this.getMembersByTeamId(teamId); + let users = teamV3Util.formatTeamMembers(updatedTeam); + + await this.updateTeamMemberMessage(teamId, updateUserId, currentUserId, tempKeyRole, tempValueRole, teamClone); + + this.sendLogInGoogle({ + action: 'updateTeamMember', + input: { + teamId, + memberid: updateUserId, + currentUserId, + body: req.body, + }, + output: users, + }); + + return res.json({ + success: true, + members: users, + }); + + } + }); + + } catch (e) { + throw new HttpExceptions(e.message); + } + } + + async updateTeamMemberMessage(teamId, userId, currentUserId, permission, permissionStatus, team, deleteUser = false) { + const userDetails = await UserModel.findOne({ _id: userId }); + const userName = `${userDetails.firstname.toUpperCase()} ${userDetails.lastname.toUpperCase()}`; + const currentUserDetails = await UserModel.findOne({ _id: currentUserId }); + const currentUserName = `${currentUserDetails.firstname.toUpperCase()} ${currentUserDetails.lastname.toUpperCase()}`; + const publisherName = team.publisher.name.toUpperCase(); + const subjectEmail = emailTeam.subjectEmail(publisherName, currentUserName, permission, permissionStatus, deleteUser); + const bodyEmail = emailTeam.bodyEmail(publisherName, currentUserName, userName, permission, permissionStatus, teamId, team, deleteUser); + + await emailGenerator.sendEmailOne(userDetails.email, constants.hdrukEmail, subjectEmail, bodyEmail); + } + + async getTeamNotifications(req, res) { + const teamId = req.params.teamid; + const currentUserId = req.user._id; + const allowPerms = req.allowPerms || []; + + try { + await this.checkUserAuth(teamId, currentUserId, allowPerms); + + const team = await this.getTeamByTeamIdSimple(teamId); + if (!team) { + throw new HttpExceptions(e.message, 404); + } + + const { + user: { _id }, + } = req; + + let { members } = team; + let authorised = false; + + if (members) { + authorised = members.some(el => el.memberid.toString() === _id.toString()); + } + + if (!authorised) { + throw new HttpExceptions(`You must provide valid authentication credentials to access this resource.`, 401); + } + + let member = [...members].find(el => el.memberid.toString() === _id.toString()); + + const teamNotifications = teamV3Util.formatTeamNotifications(team); + + let notifications = { + memberNotifications: member.notifications ? member.notifications : [], + teamNotifications, + }; + + return res.status(200).json(notifications); + } catch (err) { + process.stdout.write(err.message); + throw new HttpExceptions(`An error occurred retrieving team notifications : ${err.message}`, 500); + } + } + + async updateNotifications(req, res) { + const teamId = req.params.teamid; + const currentUserId = req.user._id; + const allowPerms = req.allowPerms || []; + try { + await this.checkUserAuth(teamId, currentUserId, allowPerms); + + const team = await this.getTeamByTeamId(teamId); + + const { + user: { _id }, + body: data, + } = req; + + let { members, users, notifications } = team; + let authorised = false; + + if (members) { + authorised = [...members].some(el => el.memberid.toString() === _id.toString()); + } + + if (!authorised) return res.status(401).json({ success: false }); + + let member = [...members].find(el => el.memberid.toString() === _id.toString()); + + let isManager = true; + + let { memberNotifications = [], teamNotifications = [] } = data; + + let missingOptIns = {}; + + if (!isEmpty(memberNotifications) && !isEmpty(teamNotifications)) { + missingOptIns = teamV3Util.findMissingOptIns(memberNotifications, teamNotifications); + } + + if (!isEmpty(missingOptIns)) return res.status(400).json({ success: false, message: missingOptIns }); + + if (isManager) { + const optedOutTeamNotifications = Object.values([...teamNotifications]).filter(notification => !notification.optIn) || []; + if (!isEmpty(optedOutTeamNotifications)) { + optedOutTeamNotifications.forEach(teamNotification => { + let { notificationType } = teamNotification; + members.forEach(member => { + let { notifications = [] } = member; + if (!isEmpty(notifications)) { + let notificationIndex = notifications.findIndex(n => n.notificationType.toUpperCase() === notificationType.toUpperCase()); + if (!notifications[notificationIndex].optIn) { + notifications[notificationIndex].optIn = true; + notifications[notificationIndex].message = constants.teamNotificationMessages[notificationType.toUpperCase()]; + } + } + member.notifications = notifications; + }); + }); + } + + if (!isEmpty(notifications)) { + let manager = [...users].find(user => user._id.toString() === member.memberid.toString()); + + [...notifications].forEach(dbNotification => { + let { notificationType } = dbNotification; + const notificationPayload = + [...teamNotifications].find(n => n.notificationType.toUpperCase() === notificationType.toUpperCase()) || {}; + if (!isEmpty(notificationPayload)) { + let { subscribedEmails: dbSubscribedEmails, optIn: dbOptIn } = dbNotification; + let { subscribedEmails: payLoadSubscribedEmails, optIn: payLoadOptIn } = notificationPayload; + const removedEmails = difference([...dbSubscribedEmails], [...payLoadSubscribedEmails]) || []; + const addedEmails = difference([...payLoadSubscribedEmails], [...dbSubscribedEmails]) || []; + const subscribedMembersByType = teamV3Util.filterMembersByNoticationTypes([...members], [notificationType]); + if (!isEmpty(subscribedMembersByType)) { + const memberIds = [...subscribedMembersByType].map(m => m.memberid.toString()); + const { memberEmails, userIds } = teamV3Util.getMemberDetails([...memberIds], [...users]); + let html = ''; + let options = { + managerName: `${manager.firstname} ${manager.lastname}`, + notificationRemoved: false, + disabled: false, + header: '', + emailAddresses: [], + }; + if (!isEmpty(removedEmails) || (dbOptIn && !payLoadOptIn)) { + options = { + ...options, + notificationRemoved: true, + disabled: !payLoadOptIn ? true : false, + header: `A manager for ${team.publisher ? team.publisher.name : 'a team'} has ${dbOptIn && !payLoadOptIn ? 'disabled all' : 'removed a' + } generic team email address(es)`, + emailAddresses: dbOptIn && !payLoadOptIn ? payLoadSubscribedEmails : removedEmails, + publisherId: team.publisher._id.toString(), + }; + html = emailGenerator.generateTeamNotificationEmail(options); + emailGenerator.sendEmail( + memberEmails, + constants.hdrukEmail, + `A manager for ${team.publisher ? team.publisher.name : 'a team'} has ${dbOptIn && !payLoadOptIn ? 'disabled all' : 'removed a' + } generic team email address(es)`, + html, + true + ); + + notificationBuilder.triggerNotificationMessage( + [...userIds], + `A manager for ${team.publisher ? team.publisher.name : 'a team'} has ${dbOptIn && !payLoadOptIn ? 'disabled all' : 'removed a' + } generic team email address(es)`, + 'team', + team.publisher ? team.publisher.name : 'Undefined' + ); + } + + if (!isEmpty(addedEmails) || (!dbOptIn && payLoadOptIn)) { + options = { + ...options, + notificationRemoved: false, + header: `A manager for ${team.publisher ? team.publisher.name : 'a team'} has ${!dbOptIn && payLoadOptIn ? 'enabled all' : 'added a' + } generic team email address(es)`, + emailAddresses: payLoadSubscribedEmails, + publisherId: team.publisher._id.toString(), + }; + html = emailGenerator.generateTeamNotificationEmail(options); + emailGenerator.sendEmail( + memberEmails, + constants.hdrukEmail, + `A manager for ${team.publisher ? team.publisher.name : 'a team'} has ${!dbOptIn && payLoadOptIn ? 'enabled all' : 'added a' + } generic team email address(es)`, + html, + true + ); + + notificationBuilder.triggerNotificationMessage( + [...userIds], + `A manager for ${team.publisher ? team.publisher.name : 'a team'} has ${!dbOptIn && payLoadOptIn ? 'enabled all' : 'added a' + } generic team email address(es)`, + 'team', + team.publisher ? team.publisher.name : 'Undefined' + ); + } + } + } + }); + } + team.notifications = teamNotifications; + } + member.notifications = memberNotifications; + await team.save(); + return res.status(201).json(team); + } catch (err) { + process.stdout.write(err.message); + throw new HttpExceptions(`An error occurred updating notification messages : ${err.message}`, 500); + } + } + + async updateNotificationMessages(req, res) { + const teamId = req.params.teamid; + const currentUserId = req.user._id; + const allowPerms = req.allowPerms || []; + + try { + await this.checkUserAuth(teamId, currentUserId, allowPerms); + + const { + user: { _id }, + } = req; + await TeamModel.update( + { _id: teamId }, + { $set: { 'members.$[m].notifications.$[].message': '' } }, + { arrayFilters: [{ 'm.memberid': _id }], multi: true } + ) + .then(resp => { + return res.status(201).json(); + }) + .catch(err => { + process.stdout.write(err.message); + throw new HttpExceptions(`An error occurred updating notification messages : ${err.message}`, 500); + }); + } catch (err) { + process.stdout.write(err.message); + throw new HttpExceptions(`An error occurred updating notification messages : ${err.message}`, 500); + } + } + + async checkUserAuth(teamId, userId, allowPerms) { + const currUserRolesFromTeamPublisher = await this.getPermsByUserIdFromTeamPublisher(teamId, userId); + const currUserRolesFromTeamAdmin = await this.getPermsByUserIdFromTeamAdmin(userId); + const currUserRolesExists = [...currUserRolesFromTeamPublisher, ...currUserRolesFromTeamAdmin]; + const currUserRolesUnique = [...new Set(currUserRolesExists)]; + teamV3Util.checkingUserAuthorization(allowPerms, currUserRolesUnique); + + return currUserRolesUnique; + } + + sendLogInGoogle(message) { + const loggingEnabled = parseInt(process.env.LOGGING_LOG_ENABLED) || 0; + if (loggingEnabled) { + this._logger.sendDataInLogging(JSON.stringify(message), 'INFO'); + } + } +} + +module.exports = new TeamController(); diff --git a/src/resources/team/v3/team.route.js b/src/resources/team/v3/team.route.js new file mode 100644 index 000000000..89efac8ed --- /dev/null +++ b/src/resources/team/v3/team.route.js @@ -0,0 +1,92 @@ +import express from 'express'; +import passport from 'passport'; +import { checkAccessToTeamMiddleware } from '../../../middlewares/checkAccessTeamMiddleware'; +import constants from '../../utilities/constants.util'; +const TeamController = require('./team.controller'); + +const router = express.Router(); + +// @route GET api/v3/teams/:teamid/members +// @desc GET all team members for team +// @access Private +router.get( + '/:teamid/members', + passport.authenticate('jwt'), + checkAccessToTeamMiddleware([]), + (req, res) => TeamController.getTeamMembers(req, res) +); + +// @route DELETE api/v3/teams/:teamid/members/:memberid +// @desc DELETE team member from the team +// @access Private +router.delete( + '/:teamid/members/:memberid', + passport.authenticate('jwt'), + checkAccessToTeamMiddleware([ + constants.roleMemberTeam.CUST_TEAM_ADMIN + ]), + (req, res) => TeamController.deleteTeamMember(req, res), +); + +// @route POST api/v3/teams/:teamid/members +// @desc POST add new team member +// @access Private +router.post( + '/:teamid/members', + passport.authenticate('jwt'), + checkAccessToTeamMiddleware([ + constants.roleMemberTeam.CUST_TEAM_ADMIN, + constants.roleMemberTeam.CUST_DAR_MANAGER, + constants.roleMemberTeam.CUST_MD_MANAGER + ]), + (req, res) => TeamController.addTeamMember(req, res), +); + +// @route PATCH api/v3/teams/:teamid/members/:memberid +// @desc PATCH add new team member +// @access Private +router.patch( + '/:teamid/members/:memberid', + passport.authenticate('jwt'), + checkAccessToTeamMiddleware([ + constants.roleMemberTeam.CUST_TEAM_ADMIN, + constants.roleMemberTeam.CUST_DAR_MANAGER, + constants.roleMemberTeam.CUST_MD_MANAGER + ]), + (req, res) => TeamController.updateTeamMember(req, res), +); + +// @route GET api/v3/teams/:teamid/notifications +// @desc Get team notifications +// @access Private +router.get( + '/:teamid/notifications', + passport.authenticate('jwt'), + (req, res) => TeamController.getTeamNotifications(req, res), +); + +// @route PUT api/v3/teams/:teamid/notifications +// @desc Update notifications +// @access Private +router.put( + '/:teamid/notifications', + passport.authenticate('jwt'), + checkAccessToTeamMiddleware([ + constants.roleMemberTeam.CUST_TEAM_ADMIN + ]), + (req, res) => TeamController.updateNotifications(req, res), +); + +// @route PUT api/v3/teams/:teamid/notifications/messages +// @desc Update notifications +// @access Private +router.put( + '/:teamid/notifications/messages', + passport.authenticate('jwt'), + checkAccessToTeamMiddleware([ + constants.roleMemberTeam.CUST_TEAM_ADMIN + ]), + (req, res) => TeamController.updateNotificationMessages(req, res), +); + +module.exports = router; \ No newline at end of file diff --git a/src/resources/team/v3/team.service.js b/src/resources/team/v3/team.service.js new file mode 100644 index 000000000..2256043a6 --- /dev/null +++ b/src/resources/team/v3/team.service.js @@ -0,0 +1,100 @@ +import HttpExceptions from "../../../exceptions/HttpExceptions"; +import { TeamModel } from "../team.model"; + +export default class TeamService { + constructor() {} + + async getMembersByTeamId(teamId) { + try { + const team = await TeamModel.findOne({ _id: teamId }).populate({ + path: 'users', + populate: { + path: 'additionalInfo', + select: 'organisation bio showOrganisation showBio news', + }, + }); + + if (!team) { + throw new Error(`Team not Found`); + } + + return team; + } catch (e) { + process.stdout.write(`TeamController.getTeamMembers : ${e.message}\n`); + throw new HttpExceptions(e.message); + } + } + + async getPermsByUserIdFromTeamPublisher(teamId, userId) { + try { + const team = await TeamModel.findOne({ _id: teamId, type: 'publisher' }); + + if (!team) { + return []; + } + + let userRoles = team.members.find(member => member.memberid.toString() === userId.toString()); + + return userRoles ? userRoles.roles : []; + } catch (e) { + process.stdout.write(`TeamController.getTeamMembers : ${e.message}\n`); + throw new HttpExceptions(e.message); + } + } + + async getPermsByUserIdFromTeamAdmin(userId) { + try { + const team = await TeamModel.findOne({ type: 'admin' }); + + if (!team) { + return []; + } + + let userRoles = team.members.find(member => member.memberid.toString() === userId.toString()); + + return userRoles ? userRoles.roles : []; + } catch (e) { + process.stdout.write(`TeamController.getTeamMembers : ${e.message}\n`); + throw new HttpExceptions(e.message); + } + } + + async getTeamByTeamId(teamId) { + try { + const team = await TeamModel + .findOne({ _id: teamId }) + .populate([ + { path: 'users' }, + { + path: 'publisher', + select: 'name' + } + ]); + + if (!team) { + throw new HttpExceptions(`Team not Found`); + } + + return team; + } catch (e) { + process.stdout.write(`TeamController.getTeamByTeamId : ${e.message}\n`); + throw new HttpExceptions(e.message); + } + } + + async getTeamByTeamIdSimple(teamId) { + try { + const team = await TeamModel + .findOne({ _id: teamId }); + + if (!team) { + throw new Error(`Team not Found`); + } + + return team; + } catch (e) { + process.stdout.write(`TeamController.getTeamByTeamIdSimple : ${e.message}\n`); + throw new HttpExceptions(e.message); + } + } +} diff --git a/src/resources/team/v3/util/emailTeam.js b/src/resources/team/v3/util/emailTeam.js new file mode 100644 index 000000000..d9ee07ab5 --- /dev/null +++ b/src/resources/team/v3/util/emailTeam.js @@ -0,0 +1,657 @@ +import HttpExceptions from "../../../../exceptions/HttpExceptions"; +import constants from "../../../utilities/constants.util"; + +const subjectEmail = (teamName = '', userName = '', role, status, deleteUser) => { + let subject = ''; + let publisherName = ''; + + if (teamName.search('>') === -1) { + publisherName = teamName.trim(); + } else { + publisherName = teamName.split('>')[1].trim(); + } + + switch (role) { + case 'custodian.team.admin': + if (status) { + subject = `${userName} has added you to the ${publisherName} publishing team on the Gateway as a Team Admin`; + } else { + subject = `You have been removed as a Team Admin for the ${publisherName} team on the Gateway.`; + } + break; + case 'custodian.metadata.manager': + if (status) { + subject = `${userName} has added you to the ${publisherName} publishing team on the Gateway as a Metadata Manager`; + } else { + subject = `You have been removed as a Metadata Manager for the ${publisherName} team on the Gateway`; + } + break; + case 'metadata_editor': + if (status) { + subject = `${userName} has added you to the ${publisherName} publishing team on the Gateway as a Metadata Editor`; + } else { + subject = `You have been removed as a Metadata Editor for the ${publisherName} team on the Gateway.`; + } + break; + case 'custodian.dar.manager': + if (status) { + subject = `${userName} has added you to the ${publisherName} publishing team on the Gateway as a Data Access Manager`; + } else { + subject = `You have been removed as a Data Access Manager for the ${publisherName} team on the Gateway.`; + } + break; + case 'reviewer': + if (status) { + subject = `${userName} has added you to the ${publisherName} publishing team on the Gateway as a Reviewer`; + } else { + subject = `You have been removed as a Reviewer for the ${publisherName} team on the Gateway.`; + } + break; + default: + if (deleteUser) { + subject = `You have been removed as a user from the ${publisherName} team on the Gateway.`; + } + break; + } + + return subject; +} + +const bodyEmail = (teamName = '', currentUserName = '', userName = '', role, status, teamId, team, deleteUser) => { + const urlHdrukLogoEmail = 'https://storage.googleapis.com/public_files_dev/hdruk_logo_email.jpg'; + const urlHdrukHeaderEmail = 'https://storage.googleapis.com/public_files_dev/hdruk_header_email.jpg'; + + let topBodyEmail = ''; + let middleBodyEmail = ''; + let footerBodyEmail = ''; + let bodyEmail = ''; + + let publisherName = ''; + + if (teamName.search('>') === -1) { + publisherName = teamName.trim(); + } else { + publisherName = teamName.split('>')[1].trim(); + } + const urlDatasetsTeam = `${process.env.homeURL}/search?search=&datasetpublisher=${publisherName}&datasetSort=latest&tab=Datasets`; + const urlDataAccessRequestTeam = `${process.env.homeURL}/account?tab=dataaccessrequests&teamType=team&teamId=${teamId}`; + const urlManageTeam = `${process.env.homeURL}/account?tab=teamManagement&teamType=team&teamId=${teamId}&subTab=members`; + + const teamAdmin = _generateTeamAdmin(team); + + topBodyEmail = ` + + + + + + +
+ + + + + `; + + switch (role) { + case 'custodian.team.admin': + if (status) { + middleBodyEmail = ` + + + + + + + + + + + + + `; + } else { + middleBodyEmail = ` + + + + + + + + + + + + + `; + } + break; + case 'custodian.metadata.manager': + if (status) { + middleBodyEmail = ` + + + + + + + + + + + + + `; + } else { + middleBodyEmail = ` + + + + + + + + + + + + + `; + } + break; + case 'metadata_editor': + if (status) { + middleBodyEmail = ` + + + + + + + + + + + + + `; + } else { + middleBodyEmail = ` + + + + + + + + + + + + + `; + } + break; + case 'custodian.dar.manager': + if (status) { + middleBodyEmail = ` + + + + + + + + + + + + + `; + } else { + middleBodyEmail = ` + + + + + + + + + + + + + `; + } + break; + case 'reviewer': + if (status) { + middleBodyEmail = ` + + + + + + + + + + + + + `; + } else { + middleBodyEmail = ` + + + + + + + + + + + + + `; + } + break; + default: + if (deleteUser) { + middleBodyEmail = ` + + + + + + + + + + `; + } + break; + } + + let currentYear = new Date().getFullYear(); + footerBodyEmail = ` + + + + + + +
+ Health Data Gateway +
+ + + Custodian Team Admin has been assigned + + +
+ ${currentUserName} has added you to the ${publisherName} publishing team on the Gateway as a Team Admin. +
+ You can now add, remove, and change the roles of other members of the ${publisherName} team. +
+ Manage team +
+ + + Custodian Team Admin has been removed + + +
+ You have been removed as a Team Admin for the ${publisherName} team on the Gateway. +
+ You can no longer:
+
    +
  • Add roles of other members of the ${publisherName} team.
  • +
  • Remove roles of other members of the ${publisherName} team.
  • +
  • Change the roles of other members of the ${publisherName} team.
  • +
+
+ For more information, please contact a Team Admin for your team:
+ ${teamAdmin} +
+ + + Metadata Manager has been assigned + + +
+ ${currentUserName} has added you the ${publisherName} publishing team on the Gateway as a Metadata Manager. +
+ You can now:
+
    +
  • Onboard and manage information about datasets uploaded by the ${publisherName} team.
  • +
  • Add and remove other team members with editor permissions.
  • +
+
+ View Datasets +
+ + + Metadata Manager has been removed + + +
+ You have been removed as a Metadata Manager for the ${publisherName} team on the Gateway. +
+ You can no longer:
+
    +
  • Onboard and manage information about datasets uploaded by the ${publisherName} team.
  • +
  • Add and remove other team members with editor permissions.
  • +
+
+ For more information, please contact a Team Admin for your team:
+ ${teamAdmin} +
+ + + Metadata Editor has been assigned + + +
+ ${currentUserName} has added you the ${publisherName} publishing team on the Gateway as a Metadata Editor. +
+ You can now:
+
    +
  • Onboard information about datasets uploaded by the ${publisherName} team.
  • +
  • Manage information about datasets uploaded by the ${publisherName} team.
  • +
+
+ View Datasets +
+ + + Metadata Editor has been removed + + +
+ You have been removed as a Metadata Editor for the ${publisherName} team on the Gateway. +
+ You can no longer Onboard and manage information about datasets uploaded by the ${publisherName} team. +
+ For more information, please contact a Team Admin for your team:
+ ${teamAdmin} +
+ + + DAR Manager has been assigned + + +
+ ${currentUserName} has added you the ${publisherName} publishing team on the Gateway as a Data Access Manager. +
+ You can now:
+
    +
  • Manage data access requests through the Gateway for the ${publisherName} team.
  • +
  • You can create and assign workflows, process applications, and communicate with applicants through the Gateway.
  • +
  • You can also add and remove other team members, and assign sections of the data access review workflow to them.
  • +
+
+ View data access requests +
+ + + DAR Manager has been removed + + +
+ You have been removed as a Data Access Manager for the ${publisherName} team on the Gateway. +
+ You can no longer:
+
    +
  • Manage data access requests through the Gateway for the ${publisherName} team.
  • +
  • Create and assign workflows, process applications, and communicate with applicants through the Gateway.
  • +
  • Add and remove other team members.
  • +
  • Assign sections of the data access review workflow to them.
  • +
+
+ For more information, please contact a Team Admin for your team:
+ ${teamAdmin} +
+ + + DAR Reviewer has been assigned + + +
+ ${currentUserName} has added you to the ${publisherName} publishing team on the Gateway as a Reviewer. +
+ You can now:
+
    +
  • Review sections of a data access request that have been assigned to you by a Data Access Manager for the ${publisherName} team.
  • +
  • You can process applications and communicate with applicants through the Gateway.
  • +
+
+ View data access requests +
+ + + DAR Reviewer has been removed + + +
+ You have been removed as a Reviewer Manager for the ${publisherName} team on the Gateway. +
+ You can no longer:
+
    +
  • Review sections of a data access request that have been assigned to you by a Data Access Manager for the ${publisherName} team.
  • +
  • Process applications and communicate with applicants through the Gateway.
  • +
+
+ For more information, please contact a Team Admin for your team:
+ ${teamAdmin} +
+ + + User has been removed + + +
+ You have been removed as a user from the ${publisherName} team on the Gateway. +
+ For more information, please contact a Team Admin for your team:
+ ${teamAdmin} +
+ www.healthdatagateway.org +
+ ŠHDR UK ${currentYear}. All rights reserved. +
+
+ `; + + bodyEmail = `${topBodyEmail}${middleBodyEmail}${footerBodyEmail}`; + + return bodyEmail; +} + +const _generateTeamAdmin = (team) => { + const { members, users } = team; + const adminRole = [ + constants.roleMemberTeam.CUST_TEAM_ADMIN, + ]; + let adminMemberIds = []; + let adminMemberNames = []; + + members.map(member => { + if (member.roles.some(mem => adminRole.includes(mem))) { + return adminMemberIds.push(member.memberid.toString()); + } + }); + + users.map(user => { + let userId = user._id.toString(); + if (adminMemberIds.includes(userId)) { + return adminMemberNames.push(`${user.firstname} ${user.lastname}`); + } + }); + + return _generateTableTeamAdmin(adminMemberNames); +} + +const _generateTableTeamAdmin = (teamNames) => { + if (teamNames.length === 0) { + return ''; + } + + let top = ``; + let body = ``; + let tmp = ``; + + teamNames.map(team => { + tmp = ` + + `; + body = body + tmp; + }); + + let bottom = `
${team}
`; + + return `${top}${body}${bottom}`; +} + +export default { + subjectEmail, + bodyEmail, +} \ No newline at end of file diff --git a/src/resources/tool/counter.route.js b/src/resources/tool/counter.route.js index eda04cd68..18e1fc5fa 100644 --- a/src/resources/tool/counter.route.js +++ b/src/resources/tool/counter.route.js @@ -7,12 +7,12 @@ router.post('/update', async (req, res) => { const { id, counter } = req.body; if (isNaN(id)) { - Data.findOneAndUpdate({ datasetid: { $eq: id } }, { counter }, err => { + Data.findOneAndUpdate({ datasetid: { $eq: id } }, { $set: { counter: counter } }, { timestamps: false }, err => { if (err) return res.json({ success: false, error: err }); return res.json({ success: true }); }); } else { - Data.findOneAndUpdate({ id: { $eq: id } }, { counter }, err => { + Data.findOneAndUpdate({ id: { $eq: id } }, { $set: { counter: counter } }, { timestamps: false }, err => { if (err) return res.json({ success: false, error: err }); return res.json({ success: true }); }); diff --git a/src/resources/tool/data.repository.js b/src/resources/tool/data.repository.js index 4e47130d4..5d423bfc1 100644 --- a/src/resources/tool/data.repository.js +++ b/src/resources/tool/data.repository.js @@ -50,7 +50,7 @@ const addTool = async (req, res) => { data.journalYear = inputSanitizer.removeNonBreakingSpaces(journalYear); data.description = inputSanitizer.removeNonBreakingSpaces(description); data.resultsInsights = inputSanitizer.removeNonBreakingSpaces(resultsInsights); - console.log(req.body); + if (categories && typeof categories !== 'undefined') data.categories.category = inputSanitizer.removeNonBreakingSpaces(categories.category); data.license = inputSanitizer.removeNonBreakingSpaces(license); diff --git a/src/resources/tool/v1/tool.route.js b/src/resources/tool/v1/tool.route.js index 9b9473485..d202c34e3 100644 --- a/src/resources/tool/v1/tool.route.js +++ b/src/resources/tool/v1/tool.route.js @@ -1,7 +1,7 @@ /* eslint-disable no-undef */ import express from 'express'; import { ROLES } from '../../user/user.roles'; -import { Reviews } from '../review.model'; +import { Reviews } from '../../review/review.model'; import { Data } from '../data.model'; import { Course } from '../../course/course.model'; import { DataUseRegister } from '../../dataUseRegister/dataUseRegister.model'; @@ -393,7 +393,7 @@ router.get('/:type/tag', passport.authenticate('jwt'), async (req, res) => { // 4. Return projects return res.status(200).json({ success: true, entities }); } catch (err) { - console.error(err.message); + process.stdout.write(`TOOL : ${err.message}\n`); return res.status(500).json({ success: false, message: 'An error occurred searching for tools by tag', diff --git a/src/resources/tool/v2/tool.controller.js b/src/resources/tool/v2/tool.controller.js index 77554ceff..b0cb44614 100644 --- a/src/resources/tool/v2/tool.controller.js +++ b/src/resources/tool/v2/tool.controller.js @@ -33,7 +33,7 @@ export default class ToolController extends Controller { }); } catch (err) { // Return error response if something goes wrong - console.error(err.message); + process.stdout.write(`TOOL - getTool : ${err.message}\n`); return res.status(500).json({ success: false, message: 'A server error occurred, please try again', @@ -52,7 +52,7 @@ export default class ToolController extends Controller { }); } catch (err) { // Return error response if something goes wrong - console.error(err.message); + process.stdout.write(`TOOL - getTools : ${err.message}\n`); return res.status(500).json({ success: false, message: 'A server error occurred, please try again', diff --git a/src/resources/topic/topic.controller.js b/src/resources/topic/topic.controller.js index a3a40ab43..31bcc67cb 100644 --- a/src/resources/topic/topic.controller.js +++ b/src/resources/topic/topic.controller.js @@ -6,15 +6,15 @@ module.exports = { buildRecipients: async (team, createdBy) => { // 1. Cause error if no members found if (_.isNull(team)) { - console.error('A topic cannot be created without a receiving team'); + process.stdout.write(`A topic cannot be created without a receiving team\n`); return []; } let { members } = team; if (_.isNull(members || members.length === 0)) { - console.error('A topic cannot be created with only the creating user'); + process.stdout.write(`A topic cannot be created with only the creating user\n`); return []; } - let recipients = members.filter(mem => mem.roles.includes('manager') || mem.roles.includes('reviewer')).map(m => m.memberid); + let recipients = members.filter(mem => mem.roles.includes('manager') || mem.roles.includes('custodian.dar.manager')).map(m => m.memberid); // 2. Return team recipients plus the user that created the message recipients = [...recipients, createdBy]; return recipients; @@ -29,7 +29,7 @@ module.exports = { const { createdBy, relatedObjectIds } = context; // 1. Topic cannot be created without related object i.e. data/project/tool/paper if (_.isEmpty(relatedObjectIds)) { - console.error('No related object Id passed to build topic'); + process.stdout.write(`No related object Id passed to build topic\n`); return undefined; } // 2. Find the related object(s) in MongoDb and include team data @@ -39,7 +39,7 @@ module.exports = { .populate({ path: 'publisher', populate: { path: 'team' } }); // 3. Return undefined if no object exists if (_.isEmpty(tools)) { - console.error(`Failed to find related tool(s) with objectId(s): ${relatedObjectIds.join(', ')}`); + process.stdout.write(`Failed to find related tool(s) with objectId(s): ${relatedObjectIds.join(', ')}\n`); return undefined; } // 4. Iterate through each tool @@ -68,17 +68,17 @@ module.exports = { // 7. Get recipients for topic/message using the first tool (same team exists as each publisher is the same) let { publisher = '' } = tools[0]; if (_.isEmpty(publisher)) { - console.error(`No publisher associated to this dataset`); + process.stdout.write(`No publisher associated to this dataset\n`); return undefined; } let { team = [] } = publisher; if (_.isEmpty(team)) { - console.error(`No team associated to publisher, cannot message`); + process.stdout.write(`No team associated to publisher, cannot message\n`); return undefined; } const recipients = await module.exports.buildRecipients(team, createdBy); if (_.isEmpty(recipients)) { - console.error('A topic cannot be created without recipients'); + process.stdout.write(`A topic cannot be created without recipients\n`); return undefined; } // Future extension could be to iterate through tools at this point to generate a topic for each publisher @@ -98,7 +98,7 @@ module.exports = { // 9. Return created object return topic; } catch (err) { - console.error(err.message); + process.stdout.write(`TOPIC - buildTopic : ${err.message}\n`); return undefined; } }, @@ -123,7 +123,7 @@ module.exports = { return topic; } catch (err) { - console.error(err.message); + process.stdout.write(`TOPIC - findTopic : ${err.message}\n`); return undefined; } }, @@ -137,7 +137,7 @@ module.exports = { return res.status(201).json({ success: true, topic }); } catch (err) { - console.error(err.message); + process.stdout.write(`TOPIC - createTopic : ${err.message}\n`); return res.status(500).json(err.message); } }, @@ -149,7 +149,7 @@ module.exports = { TopicModel.findByIdAndUpdate(id, { isDeleted: true, status: 'closed', expiryDate: Date.now() }, { new: true }); return res.status(204).json({ success: true }); } catch (err) { - console.error(err.message); + process.stdout.write(`TOPIC - deleteTopic : ${err.message}\n`); return res.status(500).json(err.message); } }, @@ -186,7 +186,7 @@ module.exports = { ); return res.status(200).json({ success: true, topics }); } catch (err) { - console.error(err.message); + process.stdout.write(`TOPIC - getTopics : ${err.message}\n`); return res.status(500).json(err.message); } }, @@ -211,7 +211,7 @@ module.exports = { // 5. Return original topic so unread messages are displayed correctly return res.status(200).json({ success: true, topic: dispatchTopic }); } catch (err) { - console.error(err.message); + process.stdout.write(`TOPIC - getTopicById : ${err.message}\n`); return res.status(500).json(err.message); } }, diff --git a/src/resources/user/user.register.route.js b/src/resources/user/user.register.route.js index 75f31c8e5..0b8833225 100644 --- a/src/resources/user/user.register.route.js +++ b/src/resources/user/user.register.route.js @@ -105,7 +105,7 @@ router.post('/', async (req, res) => { const [loginErr, token] = await to(login(req, user)); if (loginErr) { - console.error(loginErr); + process.stdout.write(`Authentication error\n`); return res.status(500).json({ success: false, data: 'Authentication error!' }); } diff --git a/src/resources/user/user.route.js b/src/resources/user/user.route.js index 316c3113b..827344910 100644 --- a/src/resources/user/user.route.js +++ b/src/resources/user/user.route.js @@ -141,7 +141,7 @@ router.patch('/advancedSearch/roles/:id', passport.authenticate('jwt'), utils.ch // serviceAccount // }); // } catch (err) { -// console.error(err.message); +// process.stdout.write(`USER - create service account: ${err.message}\n`); // return res.status(500).json(err); // } // }); diff --git a/src/resources/utilities/__mocks__/checkAllowNewRoles.mock.js b/src/resources/utilities/__mocks__/checkAllowNewRoles.mock.js new file mode 100644 index 000000000..209b18efc --- /dev/null +++ b/src/resources/utilities/__mocks__/checkAllowNewRoles.mock.js @@ -0,0 +1,26 @@ +const mockUserUpdateRoles = [ + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" +]; +const mockUserUpdateRolesFalse = [ + "reviewer", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" +]; +const mockAllowedRoles = [ + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.metadata.editor", + "custodian.developer", + "custodian.dar.manager", + "custodian.dar.reviewer", + "custodian.dur.manager" +]; + +export { + mockUserUpdateRoles, + mockUserUpdateRolesFalse, + mockAllowedRoles, +} \ No newline at end of file diff --git a/src/resources/utilities/__mocks__/checkIfAdmin.mock.js b/src/resources/utilities/__mocks__/checkIfAdmin.mock.js new file mode 100644 index 000000000..ea923d914 --- /dev/null +++ b/src/resources/utilities/__mocks__/checkIfAdmin.mock.js @@ -0,0 +1,1274 @@ +const mockUser = { + "feedback": false, + "news": false, + "isServiceAccount": false, + "advancedSearchRoles": [], + "_id": "61f91d232e175937b960e213", + "id": 42412943236984630, + "providerId": "100742128864395249791", + "provider": "google", + "firstname": "Dan", + "lastname": "Nita", + "email": "dan.nita.hdruk@gmail.com", + "role": "Admin", + "createdAt": "2022-02-01T11:44:35.584Z", + "updatedAt": "2022-11-29T10:26:06.318Z", + "__v": 0, + "redirectURL": "/search?search=&tab=Datasets", + "discourseKey": "12e3760a22942a100ea08036b93837a01cf615988fc7d312e50d733d9367d861", + "discourseUsername": "dan.nita", + "teams": [ + { + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [ + { + "optIn": true, + "_id": "606ddfb9130e8f3f3d431801", + "notificationType": "dataAccessRequest", + "message": "" + } + ] + }, + { + "roles": [ + "manager", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "notifications": [], + "memberid": "61825367cefce1bfe5c9ba7c" + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [ + { + "optIn": true, + "_id": "62837ba8a35a2205b0f0a0b6", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [ + { + "optIn": true, + "_id": "628cc003cc879f3d43852b8f", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d945d9fb5b536d1520c618", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61ddbbcdd05e7f703fc3190d", + "notifications": [] + } + ], + "type": "publisher", + "publisher": { + "_id": "5f8992a97150a1b050be0712", + "name": "ALLIANCE > PUBLIC HEALTH SCOTLAND" + } + }, + { + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [ + { + "optIn": true, + "_id": "606ddfb9130e8f3f3d431801", + "notificationType": "dataAccessRequest", + "message": "" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "613f2c40e8592f8d3add7add", + "notifications": [] + }, + { + "roles": [ + "manager", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "notifications": [], + "memberid": "61825367cefce1bfe5c9ba7c" + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61d489c85a45342ce44b0bc9", + "notifications": [] + }, + { + "roles": [ + "metadata_editor" + ], + "memberid": "61d489c85a45342ce44b0bc9", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d945d9fb5b536d1520c618", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623467f6ce42aab1cfc29021", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61ddbbcdd05e7f703fc3190d", + "notifications": [] + } + ], + "type": "publisher", + "publisher": { + "_id": "5f3f98068af2ef61552e1d75", + "name": "ALLIANCE > SAIL" + } + }, + { + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61128e7ef7ff9cee652532b4", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "613f2c40e8592f8d3add7add", + "notifications": [] + }, + { + "roles": [ + "manager", + "reviewer", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61825367cefce1bfe5c9ba7c", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [ + { + "optIn": true, + "_id": "623c81f5aa033e0e643d6c6a", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "618008d49566b41988d1be02", + "notifications": [ + { + "optIn": true, + "_id": "62c40c6ab1079d7d4ee1c608", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "notifications": [], + "memberid": "61b9c46dfbd7e9f3aa270ac1" + }, + { + "roles": [ + "manager", + "reviewer", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "6193e832536f42aa8f976fdc", + "notifications": [ + { + "optIn": true, + "_id": "623c515eaa033e6d0d3d6473", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62349f5f767db5d3408b4007", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [ + { + "optIn": true, + "_id": "62839a8883f55d40d3d20cf4", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62e79d3a892f2fbc28bc233b", + "notifications": [ + { + "optIn": true, + "_id": "62ebe58bd7595507ba81d689", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d691a49901cef16d8da801", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d5e29ad375f198868e4dc7", + "notifications": [ + { + "optIn": true, + "_id": "62ed1f7e7d1af36e6d280b8e", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "632c325de4e074719a8c13de", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "62f502413e9bf5e82256d63b", + "notifications": [] + }, + { + "roles": [ + "manager", + "reviewer", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ef9c4ebb9796854174cbf94", + "notifications": [] + }, + { + "roles": [ + "reviewer", + "metadata_editor", + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61dd491e4d274e7eac3b848e", + "notifications": [] + }, + { + "roles": [ + "reviewer", + "metadata_editor", + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61ddbbcdd05e7f703fc3190d", + "notifications": [] + } + ], + "type": "publisher", + "publisher": { + "_id": "5f7b1a2bce9f65e6ed83e7da", + "name": "OTHER > HEALTH DATA RESEARCH UK" + } + }, + { + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [ + { + "optIn": true, + "_id": "606ddfb9130e8f3f3d431801", + "notificationType": "dataAccessRequest", + "message": "" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5f8563c6fa9a256698a9fafb", + "notifications": [ + { + "optIn": true, + "_id": "606ddfb9130e8f3f3d431801", + "notificationType": "dataAccessRequest", + "message": "" + } + ] + }, + { + "roles": [ + "manager", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "notifications": [], + "memberid": "61825367cefce1bfe5c9ba7c" + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61d489c85a45342ce44b0bc9", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "notifications": [], + "memberid": "61854c1e46f52ed51754bb24" + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "60191005c13ca825e4c6cadd", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d945d9fb5b536d1520c618", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61ddbbcdd05e7f703fc3190d", + "notifications": [] + } + ], + "type": "publisher", + "publisher": { + "_id": "5f89662f7150a1b050be0710", + "name": "ALLIANCE > HEALTH AND SOCIAL CARE NORTHERN IRELAND" + } + }, + { + "members": [ + { + "roles": [ + "admin_dataset" + ], + "memberid": "5e725692c9a581a0dd2bd84b", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "5e726049c9a58131cd2bd874", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "611a58bb9f17737532ad25c0", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "6139c0d1e6b81112ad5e0312", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "618008d49566b41988d1be02", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "61825367cefce1bfe5c9ba7c", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "6167edbd5306ac30d5b1da14", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "619261978f832546e0b0edfd", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "619501876b8724ad01077af6", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "61978834a87688d4e67770fa", + "notifications": [] + }, + { + "roles": [ + "admin_data_use" + ], + "memberid": "619ba1e87d2fd8db23885ce9", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "61d489c85a45342ce44b0bc9", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "61b9c46dfbd7e9f3aa270ac1", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "613a0399e6b8113f0a5e0ccc", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "61a5fda2b2d8db3617d85b4b", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "61854c1e46f52ed51754bb24", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "62349a67767db5d3408b4001", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "61e93915826d995980cca3dc", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "62e79d3a892f2fbc28bc233b", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "6308bfd1d2ff69e6c13427e7", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "626a72ba4524adcf224b769b", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [] + } + ], + "type": "admin", + "publisher": null + }, + { + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61825367cefce1bfe5c9ba7c", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "61d489c85a45342ce44b0bc9", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "611a58bb9f17737532ad25c0", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "618008d49566b41988d1be02", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "613f2c40e8592f8d3add7add", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62349f5f767db5d3408b4007", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61ddbbcdd05e7f703fc3190d", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62e79d3a892f2fbc28bc233b", + "notifications": [ + { + "optIn": true, + "_id": "62f2335e062fcd05b9e8d2cf", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d691a49901cef16d8da801", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d5e29ad375f198868e4dc7", + "notifications": [ + { + "optIn": true, + "_id": "62f241e8f15b29ce1c24fd96", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62f502413e9bf5e82256d63b", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "62c80ec7970e8a07797c23c7", + "notifications": [] + }, + { + "roles": [ + "metadata_editor" + ], + "memberid": "6318aab2f051c6375ec53b7e", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "632c325de4e074719a8c13de", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "628f9e65b089fa694655d168", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61ddbd0628f427fb9473287b", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "5fc8e2b7c386587231140f85", + "notifications": [] + }, + { + "roles": [ + "metadata_editor" + ], + "memberid": "5f7c2bcd504d5e7cda30b3ea", + "notifications": [] + }, + { + "roles": [ + "metadata_editor" + ], + "memberid": "5e851759b9bbd5ecd9f65a39", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "634c518998e119341680d558", + "notifications": [] + }, + { + "roles": [ + "metadata_editor" + ], + "memberid": "61a5fda2b2d8db3617d85b4b", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + } + ], + "type": "publisher", + "publisher": { + "_id": "61e57fd3012bda94e0e8b9c6", + "name": "OTHER > Priti Test Team" + } + }, + { + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "611a58bb9f17737532ad25c0", + "notifications": [ + { + "optIn": true, + "_id": "62389e82e5c72465f718013f", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "61ddbd0628f427fb9473287b", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "618008d49566b41988d1be02", + "notifications": [] + }, + { + "roles": [ + "metadata_editor" + ], + "memberid": "613a0399e6b8113f0a5e0ccc", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "6139c0d1e6b81112ad5e0312", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "61cc3966928507fe0f1b9325", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [ + { + "optIn": true, + "_id": "6255a87db588c01f5250a3ce", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "613f2c40e8592f8d3add7add", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [] + } + ], + "type": "publisher", + "publisher": { + "_id": "62024195f0e15612a4e16979", + "name": "OTHER > Test1" + } + } + ] +}; + +const mockRole = [ + "admin_dataset" +]; +const mockRoleEmpty = []; + +export { + mockUser, + mockRole, + mockRoleEmpty, +} \ No newline at end of file diff --git a/src/resources/utilities/__mocks__/checkIfExistAdminRole.mock.js b/src/resources/utilities/__mocks__/checkIfExistAdminRole.mock.js new file mode 100644 index 000000000..9cb105d60 --- /dev/null +++ b/src/resources/utilities/__mocks__/checkIfExistAdminRole.mock.js @@ -0,0 +1,49 @@ +const mockMembersTrue = [ + { + "roles": [ + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61128e7ef7ff9cee652532b4", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "613f2c40e8592f8d3add7add", + "notifications": [] + }, + { + "roles": [ + "manager", + "reviewer", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61825367cefce1bfe5c9ba7c", + "notifications": [] + } +]; +const mockMembersFalse = [ + { + "roles": [ + "editor" + ], + "memberid": "61128e7ef7ff9cee652532b4", + "notifications": [] + } +]; +const mockRolesAuth = ["custodian.team.admin","custodian.dar.manager","custodian.metadata.manager"]; + +export { + mockMembersTrue, + mockMembersFalse, + mockRolesAuth +} diff --git a/src/resources/utilities/__mocks__/checkTeamV3Permissions.mock.js b/src/resources/utilities/__mocks__/checkTeamV3Permissions.mock.js new file mode 100644 index 000000000..332534712 --- /dev/null +++ b/src/resources/utilities/__mocks__/checkTeamV3Permissions.mock.js @@ -0,0 +1,380 @@ +const mockRole = ""; +const mockRoleManager = "manager"; + +const mockTeam = { + "active": true, + "_id": "619658d8a87688acd2775721", + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "6167edbd5306ac30d5b1da14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61a519eb06447b0e9b6ae3fd", + "notifications": [] + }, + { + "roles": [ + "manager", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "notifications": [], + "memberid": "61825367cefce1bfe5c9ba7c" + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d945d9fb5b536d1520c618", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [] + } + ], + "notifications": [], + "type": "publisher", + "createdAt": "2021-11-18T13:44:56.935Z", + "updatedAt": "2022-07-22T09:14:00.954Z", + "__v": 3, + "users": [ + { + "feedback": false, + "news": false, + "isServiceAccount": false, + "advancedSearchRoles": [], + "_id": "6167edbd5306ac30d5b1da14", + "id": 3201332329402178, + "providerId": "113237134008791776027", + "provider": "google", + "firstname": "Varsha", + "lastname": "Khodiyar", + "email": "varsha.khodiyar@hdruk.ac.uk", + "role": "Creator", + "createdAt": "2021-10-14T08:43:41.185Z", + "updatedAt": "2021-10-14T08:44:06.191Z", + "__v": 0, + "redirectURL": "/search?search=&loginReferrer=https%3A%2F%2Fuat.healthdatagateway.org%2F&tab=Datasets", + "discourseKey": "90f50e560155b1ffac50927b25dea45648babda0ef34ab53bc09bd0ec9ca55f4", + "discourseUsername": "varsha.khodiyar", + "additionalInfo": { + "emailNotifications": true, + "showOrganisation": true, + "id": 3201332329402178, + "bio": "", + "link": "", + "orcid": "https://orcid.org/", + "activeflag": "active", + "terms": true, + "organisation": "Health Data Research UK", + "showBio": true + } + }, + { + "feedback": false, + "news": false, + "isServiceAccount": false, + "advancedSearchRoles": [], + "_id": "61a519eb06447b0e9b6ae3fd", + "id": 4539054910544198, + "providerId": "660a0c9a-e529-4785-9b00-f46ea0f8ffb7", + "provider": "azure", + "firstname": "Louis", + "lastname": "Grandjean", + "role": "Creator", + "createdAt": "2021-11-29T18:20:27.801Z", + "updatedAt": "2021-11-29T19:28:24.512Z", + "__v": 0, + "redirectURL": "/search?search=&tab=Datasets", + "discourseKey": "a069e40d8d254c7730fb9a273a8d3e81c8d7c5509bce4ced6644442bf7baa203", + "discourseUsername": "louis.grandjean", + "email": "Louis.grandjean@gosh.nhs.uk", + "additionalInfo": { + "emailNotifications": true, + "showOrganisation": true, + "id": 4539054910544198, + "bio": "Associate Professor of Infectious Diseases at UCL and Honorary Consultant Paediatric Infectious Diseases at Great Ormond Street Hospital", + "link": "", + "orcid": "https://orcid.org/", + "activeflag": "active", + "terms": true, + "organisation": "", + "showBio": true + } + }, + { + "feedback": true, + "news": true, + "isServiceAccount": false, + "advancedSearchRoles": [ + "GENERAL_ACCESS", + "SYSTEM_ADMIN" + ], + "_id": "61825367cefce1bfe5c9ba7c", + "id": 1214329286003002, + "providerId": "103353525483349097009", + "provider": "google", + "firstname": "Priti", + "lastname": "Pampatwar", + "email": "priti.pampatwar@hdruk.ac.uk", + "role": "Admin", + "createdAt": "2021-11-03T09:16:23.610Z", + "updatedAt": "2021-11-08T13:15:12.514Z", + "__v": 0, + "redirectURL": "/search?search=&loginReferrer=http%3A%2F%2Fuat.healthdatagateway.org%2F&tab=Datasets", + "discourseKey": "8ebd0df738d8027c90ed7b7b2d88e6329aaeb708d26a7a7ec5bfdfdc9830f104", + "discourseUsername": "priti.pampatwar1", + "additionalInfo": { + "emailNotifications": true, + "showOrganisation": true, + "id": 1214329286003002, + "bio": "Need access to UAT to explore Gateway features for analysis. thanks", + "link": "", + "orcid": "https://orcid.org/", + "activeflag": "active", + "terms": true, + "organisation": "HDRUK", + "showBio": true + } + }, + { + "feedback": false, + "news": false, + "isServiceAccount": false, + "advancedSearchRoles": [ + "GENERAL_ACCESS", + "SYSTEM_ADMIN" + ], + "_id": "623483baff441ae7fec9fd43", + "id": 5992307808590300, + "providerId": "110650484151346440783", + "provider": "google", + "firstname": "Hdr", + "lastname": "GatewayAdmin", + "email": "hdrgatea@gmail.com", + "role": "Admin", + "createdAt": "2022-03-18T13:06:02.463Z", + "updatedAt": "2022-11-30T14:21:04.554Z", + "__v": 0, + "redirectURL": "/search?search=&datasetSort=latest&loginReferrer=https%3A%2F%2Fuat.healthdatagateway.org%2F&tab=Datasets", + "discourseKey": "1a487eb79808d4037497e999794bf43787a6ab4c2ae8691e7ed86be388aeb57e", + "discourseUsername": "hdr.gatewayadmin", + "acceptedAdvancedSearchTerms": true, + "additionalInfo": { + "emailNotifications": true, + "showOrganisation": true, + "id": 5992307808590300, + "bio": "", + "link": "", + "orcid": "https://orcid.org/", + "activeflag": "active", + "terms": true, + "organisation": "", + "showBio": true + } + }, + { + "feedback": true, + "news": true, + "isServiceAccount": false, + "advancedSearchRoles": [], + "_id": "62d945d9fb5b536d1520c618", + "id": 9829759310154118, + "providerId": "103891864579295967212", + "provider": "google", + "firstname": "Yemi", + "lastname": "Aiyeola", + "email": "yemiayat@gmail.com", + "role": "Creator", + "createdAt": "2022-07-21T12:26:01.756Z", + "updatedAt": "2022-07-22T06:51:10.112Z", + "__v": 0, + "redirectURL": "/completeRegistration/9829759310154118", + "discourseKey": "", + "discourseUsername": null, + "additionalInfo": { + "emailNotifications": true, + "showOrganisation": true, + "id": 9829759310154118, + "bio": "Test account", + "link": "", + "orcid": "https://orcid.org/", + "activeflag": "active", + "terms": true, + "organisation": "HDR UK", + "showBio": true + } + }, + { + "feedback": true, + "news": true, + "isServiceAccount": false, + "advancedSearchRoles": [], + "_id": "5ec2a116b293e07eb48afe14", + "id": 45222846999444660, + "providerId": "111308516852033336265", + "provider": "google", + "firstname": "Clara", + "lastname": "Fennessy", + "email": "clara.fennessy@hdruk.ac.uk", + "role": "Admin", + "__v": 0, + "redirectURL": "/search?search=COVID-19&type=all&tab=Projects&toolcategory=&programminglanguage=&features=&topics=&license=&sampleavailability=&keywords=&publisher=&ageband=&geographiccover=", + "updatedAt": "2022-01-17T10:03:53.640Z", + "createdAt": "2020-09-04T00:00:00.000Z", + "additionalInfo": { + "emailNotifications": true, + "showOrganisation": true, + "id": 45222846999444660, + "bio": "HDR UK", + "link": "", + "orcid": "", + "activeflag": "active", + "organisation": "", + "showBio": true, + "terms": true + } + }, + { + "feedback": false, + "news": false, + "isServiceAccount": false, + "advancedSearchRoles": [ + "GENERAL_ACCESS", + "SYSTEM_ADMIN" + ], + "_id": "62384f08e5c7245adf17f0fd", + "id": 6644721675822300, + "providerId": "116978061621110601234", + "provider": "google", + "firstname": "Vijayalakshmi", + "lastname": "Shanmugam", + "email": "vijisrisan@gmail.com", + "role": "Creator", + "createdAt": "2022-03-21T10:10:16.388Z", + "updatedAt": "2022-11-15T11:31:21.426Z", + "__v": 0, + "redirectURL": "/search?search=&datasetSort=latest&tab=Datasets", + "discourseKey": "", + "discourseUsername": null, + "additionalInfo": { + "emailNotifications": true, + "showOrganisation": true, + "id": 6644721675822300, + "bio": "", + "link": "", + "orcid": "https://orcid.org/", + "activeflag": "active", + "terms": true, + "organisation": "", + "showBio": true + } + }, + { + "feedback": false, + "news": false, + "isServiceAccount": false, + "advancedSearchRoles": [ + "GENERAL_ACCESS", + "SYSTEM_ADMIN" + ], + "_id": "62028ae4d62405c442fd383f", + "id": 37447512737860000, + "providerId": "108928523103518483536", + "provider": "google", + "firstname": "Gateway", + "lastname": "Custodian", + "email": "custodianhdr01@gmail.com", + "role": "Admin", + "createdAt": "2022-02-08T15:23:16.406Z", + "updatedAt": "2022-11-30T13:47:30.946Z", + "__v": 0, + "redirectURL": "/search?search=&datasetSort=latest&loginReferrer=https%3A%2F%2Fuat.healthdatagateway.org%2F&tab=Datasets", + "discourseKey": "8860b4f92315a4b54ee1f9fed1078e88d119a9260b27b2d33b7995003d3e792a", + "discourseUsername": "gateway.custodian", + "acceptedAdvancedSearchTerms": true, + "additionalInfo": { + "emailNotifications": true, + "showOrganisation": true, + "id": 37447512737860000, + "bio": "", + "link": "", + "orcid": "https://orcid.org/", + "activeflag": "active", + "terms": true, + "organisation": "", + "showBio": true + } + } + ] +}; + +const mockTeamEmpty = {}; +const mockUserIdNotInList = "61f91d232e175937b960e213"; +const mockUserIdInList = "6167edbd5306ac30d5b1da14"; +const mockUserIdInListNoManager = "62028ae4d62405c442fd383f"; + + + export { + mockRole, + mockRoleManager, + mockTeam, + mockTeamEmpty, + mockUserIdNotInList, + mockUserIdInList, + mockUserIdInListNoManager, + } \ No newline at end of file diff --git a/src/resources/utilities/__mocks__/checkUserAuthorization.mock.js b/src/resources/utilities/__mocks__/checkUserAuthorization.mock.js new file mode 100644 index 000000000..c895143ec --- /dev/null +++ b/src/resources/utilities/__mocks__/checkUserAuthorization.mock.js @@ -0,0 +1,1365 @@ +const currUserId = '61f91d232e175937b960e213'; +const permission = 'manager'; + +const mockTeam = { + "active": true, + "_id": "63bbebf8ec565a91c474cd1b", + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "6308bfd1d2ff69e6c13427e7", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "5e8c3823e63e5d83ac27c347", + "notifications": [] + } + ], + "notifications": [], + "type": "publisher", + "createdAt": "2023-01-09T10:27:04.376Z", + "updatedAt": "2023-01-23T13:59:48.253Z", + "__v": 25, + "users": [ + { + "feedback": false, + "news": false, + "isServiceAccount": false, + "advancedSearchRoles": [], + "_id": "6308bfd1d2ff69e6c13427e7", + "id": 6531262197049297, + "providerId": "102868775144293907483", + "provider": "google", + "firstname": "kymme", + "lastname": "hayley", + "email": "kymme@hdruk.dev", + "role": "Admin", + "createdAt": "2022-08-26T12:42:57.116Z", + "updatedAt": "2023-01-06T16:08:52.920Z", + "__v": 0, + "redirectURL": "/search?search=&tab=Datasets", + "discourseKey": "fa596dcd0486d6919c9ee98db5eb00429341d266e275c3c5d8b95b21ff27b89f", + "discourseUsername": "kymme.hayley", + "additionalInfo": { + "emailNotifications": true, + "showOrganisation": true, + "id": 6531262197049297, + "bio": "", + "link": "", + "orcid": "https://orcid.org/", + "activeflag": "active", + "terms": true, + "organisation": "HDR UK", + "showBio": true + } + }, + { + "feedback": false, + "news": false, + "isServiceAccount": false, + "advancedSearchRoles": [], + "_id": "5e8c3823e63e5d83ac27c347", + "id": 5890232553870074, + "providerId": "102167422686846649659", + "provider": "google", + "firstname": "Ciara", + "lastname": "Ward", + "email": "ciara.ward@paconsulting.com", + "password": null, + "role": "Creator", + "__v": 0, + "discourseKey": "a23c62c2f9b06f0873a567522ac585a288af9fa8ec7b62eeec68baebef1cdf10", + "discourseUsername": "ciara.ward", + "updatedAt": "2021-05-12T09:51:49.573Z", + "createdAt": "2020-09-04T00:00:00.000Z", + "additionalInfo": { + "emailNotifications": true, + "showOrganisation": false, + "id": 5890232553870074, + "activeflag": "active", + "bio": "", + "link": "", + "orcid": "https://orcid.org/undefined", + "organisation": "", + "terms": true, + "showBio": true + } + } + ] +}; +const mockUsers = { + "feedback": false, + "news": false, + "isServiceAccount": false, + "advancedSearchRoles": [], + "_id": "61f91d232e175937b960e213", + "id": 42412943236984630, + "providerId": "100742128864395249791", + "provider": "google", + "firstname": "Dan", + "lastname": "Nita", + "email": "dan.nita.hdruk@gmail.com", + "role": "Admin", + "createdAt": "2022-02-01T11:44:35.584Z", + "updatedAt": "2022-11-29T10:26:06.318Z", + "__v": 0, + "redirectURL": "/search?search=&tab=Datasets", + "discourseKey": "12e3760a22942a100ea08036b93837a01cf615988fc7d312e50d733d9367d861", + "discourseUsername": "dan.nita", + "teams": [ + { + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [ + { + "optIn": true, + "_id": "606ddfb9130e8f3f3d431801", + "notificationType": "dataAccessRequest", + "message": "" + } + ] + }, + { + "roles": [ + "manager", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "notifications": [], + "memberid": "61825367cefce1bfe5c9ba7c" + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [ + { + "optIn": true, + "_id": "62837ba8a35a2205b0f0a0b6", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [ + { + "optIn": true, + "_id": "628cc003cc879f3d43852b8f", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d945d9fb5b536d1520c618", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61ddbbcdd05e7f703fc3190d", + "notifications": [] + } + ], + "type": "publisher", + "publisher": { + "_id": "5f8992a97150a1b050be0712", + "name": "ALLIANCE > PUBLIC HEALTH SCOTLAND" + } + }, + { + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [ + { + "optIn": true, + "_id": "606ddfb9130e8f3f3d431801", + "notificationType": "dataAccessRequest", + "message": "" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "613f2c40e8592f8d3add7add", + "notifications": [] + }, + { + "roles": [ + "manager", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "notifications": [], + "memberid": "61825367cefce1bfe5c9ba7c" + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61d489c85a45342ce44b0bc9", + "notifications": [] + }, + { + "roles": [ + "metadata_editor" + ], + "memberid": "61d489c85a45342ce44b0bc9", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d945d9fb5b536d1520c618", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623467f6ce42aab1cfc29021", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [] + }, + { + "roles": [ + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61ddbbcdd05e7f703fc3190d", + "notifications": [] + } + ], + "type": "publisher", + "publisher": { + "_id": "5f3f98068af2ef61552e1d75", + "name": "ALLIANCE > SAIL" + } + }, + { + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61128e7ef7ff9cee652532b4", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "613f2c40e8592f8d3add7add", + "notifications": [] + }, + { + "roles": [ + "manager", + "reviewer", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61825367cefce1bfe5c9ba7c", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [ + { + "optIn": true, + "_id": "623c81f5aa033e0e643d6c6a", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "618008d49566b41988d1be02", + "notifications": [ + { + "optIn": true, + "_id": "62c40c6ab1079d7d4ee1c608", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "notifications": [], + "memberid": "61b9c46dfbd7e9f3aa270ac1" + }, + { + "roles": [ + "manager", + "reviewer", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "6193e832536f42aa8f976fdc", + "notifications": [ + { + "optIn": true, + "_id": "623c515eaa033e6d0d3d6473", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62349f5f767db5d3408b4007", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [ + { + "optIn": true, + "_id": "62839a8883f55d40d3d20cf4", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62e79d3a892f2fbc28bc233b", + "notifications": [ + { + "optIn": true, + "_id": "62ebe58bd7595507ba81d689", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d691a49901cef16d8da801", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d5e29ad375f198868e4dc7", + "notifications": [ + { + "optIn": true, + "_id": "62ed1f7e7d1af36e6d280b8e", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "632c325de4e074719a8c13de", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "62f502413e9bf5e82256d63b", + "notifications": [] + }, + { + "roles": [ + "manager", + "reviewer", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ef9c4ebb9796854174cbf94", + "notifications": [] + }, + { + "roles": [ + "reviewer", + "metadata_editor", + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61dd491e4d274e7eac3b848e", + "notifications": [] + }, + { + "roles": [ + "reviewer", + "metadata_editor", + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61ddbbcdd05e7f703fc3190d", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "6308bfd1d2ff69e6c13427e7", + "notifications": [] + } + ], + "type": "publisher", + "publisher": { + "_id": "5f7b1a2bce9f65e6ed83e7da", + "name": "OTHER > HEALTH DATA RESEARCH UK" + } + }, + { + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [ + { + "optIn": true, + "_id": "606ddfb9130e8f3f3d431801", + "notificationType": "dataAccessRequest", + "message": "" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5f8563c6fa9a256698a9fafb", + "notifications": [ + { + "optIn": true, + "_id": "606ddfb9130e8f3f3d431801", + "notificationType": "dataAccessRequest", + "message": "" + } + ] + }, + { + "roles": [ + "manager", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "notifications": [], + "memberid": "61825367cefce1bfe5c9ba7c" + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61d489c85a45342ce44b0bc9", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "notifications": [], + "memberid": "61854c1e46f52ed51754bb24" + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "60191005c13ca825e4c6cadd", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d945d9fb5b536d1520c618", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61ddbbcdd05e7f703fc3190d", + "notifications": [] + } + ], + "type": "publisher", + "publisher": { + "_id": "5f89662f7150a1b050be0710", + "name": "ALLIANCE > HEALTH AND SOCIAL CARE NORTHERN IRELAND" + } + }, + { + "members": [ + { + "roles": [ + "admin_dataset" + ], + "memberid": "5e725692c9a581a0dd2bd84b", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "5e726049c9a58131cd2bd874", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "611a58bb9f17737532ad25c0", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "6139c0d1e6b81112ad5e0312", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "618008d49566b41988d1be02", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "61825367cefce1bfe5c9ba7c", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "6167edbd5306ac30d5b1da14", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "619261978f832546e0b0edfd", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "619501876b8724ad01077af6", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "61978834a87688d4e67770fa", + "notifications": [] + }, + { + "roles": [ + "admin_data_use" + ], + "memberid": "619ba1e87d2fd8db23885ce9", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "61d489c85a45342ce44b0bc9", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "61b9c46dfbd7e9f3aa270ac1", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "613a0399e6b8113f0a5e0ccc", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "61a5fda2b2d8db3617d85b4b", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "61854c1e46f52ed51754bb24", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "62349a67767db5d3408b4001", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "61e93915826d995980cca3dc", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "62e79d3a892f2fbc28bc233b", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "6308bfd1d2ff69e6c13427e7", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "626a72ba4524adcf224b769b", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [] + } + ], + "type": "admin", + "publisher": null + }, + { + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61825367cefce1bfe5c9ba7c", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "61d489c85a45342ce44b0bc9", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "611a58bb9f17737532ad25c0", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "618008d49566b41988d1be02", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "613f2c40e8592f8d3add7add", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62349f5f767db5d3408b4007", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61ddbbcdd05e7f703fc3190d", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62e79d3a892f2fbc28bc233b", + "notifications": [ + { + "optIn": true, + "_id": "62f2335e062fcd05b9e8d2cf", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d691a49901cef16d8da801", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d5e29ad375f198868e4dc7", + "notifications": [ + { + "optIn": true, + "_id": "62f241e8f15b29ce1c24fd96", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62f502413e9bf5e82256d63b", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "62c80ec7970e8a07797c23c7", + "notifications": [] + }, + { + "roles": [ + "metadata_editor" + ], + "memberid": "6318aab2f051c6375ec53b7e", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "632c325de4e074719a8c13de", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61ddbd0628f427fb9473287b", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "5fc8e2b7c386587231140f85", + "notifications": [] + }, + { + "roles": [ + "metadata_editor" + ], + "memberid": "5f7c2bcd504d5e7cda30b3ea", + "notifications": [] + }, + { + "roles": [ + "metadata_editor" + ], + "memberid": "5e851759b9bbd5ecd9f65a39", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "634c518998e119341680d558", + "notifications": [] + }, + { + "roles": [ + "metadata_editor" + ], + "memberid": "61a5fda2b2d8db3617d85b4b", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + } + ], + "type": "publisher", + "publisher": { + "_id": "61e57fd3012bda94e0e8b9c6", + "name": "OTHER > Priti Test Team" + } + }, + { + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "611a58bb9f17737532ad25c0", + "notifications": [ + { + "optIn": true, + "_id": "62389e82e5c72465f718013f", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "61ddbd0628f427fb9473287b", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "618008d49566b41988d1be02", + "notifications": [] + }, + { + "roles": [ + "metadata_editor" + ], + "memberid": "613a0399e6b8113f0a5e0ccc", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "6139c0d1e6b81112ad5e0312", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "61cc3966928507fe0f1b9325", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [ + { + "optIn": true, + "_id": "6255a87db588c01f5250a3ce", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "613f2c40e8592f8d3add7add", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [] + } + ], + "type": "publisher", + "publisher": { + "_id": "62024195f0e15612a4e16979", + "name": "OTHER > Test1" + } + } + ] +}; + +export { + currUserId, + permission, + mockTeam, + mockUsers, +} \ No newline at end of file diff --git a/src/resources/utilities/__mocks__/checkUserRolesByTeam.mock.js b/src/resources/utilities/__mocks__/checkUserRolesByTeam.mock.js new file mode 100644 index 000000000..9bb27341d --- /dev/null +++ b/src/resources/utilities/__mocks__/checkUserRolesByTeam.mock.js @@ -0,0 +1,156 @@ +const mockArrayCheckRolesEmptyRole = []; +const mockArrayCheckRolesOneRole = ["custodian.dar.manager"]; +const mockArrayCheckRolesMultiRole = ["custodian.dar.manager", "reviewer"]; +const mockArrayCheckRolesManagerRole = ["manager"]; +const mockTeam = { + "_id": "5f3f98068af2ef61552e1d75", + "active": true, + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [ + { + "optIn": true, + "_id": "606ddfb9130e8f3f3d431801", + "notificationType": "dataAccessRequest", + "message": "" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "613f2c40e8592f8d3add7add", + "notifications": [] + }, + { + "roles": [ + "manager", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "notifications": [], + "memberid": "61825367cefce1bfe5c9ba7c" + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61d489c85a45342ce44b0bc9", + "notifications": [] + }, + { + "roles": [ + "metadata_editor" + ], + "memberid": "61d489c85a45342ce44b0bc9", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d945d9fb5b536d1520c618", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623467f6ce42aab1cfc29021", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [] + }, + { + "roles": [ + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61ddbbcdd05e7f703fc3190d", + "notifications": [] + } + ], + "type": "publisher", + "notifications": [ + { + "notificationType": "dataAccessRequest", + "optIn": false, + "subscribedEmails": [ + "nita.dan2@gmail.com" + ], + "_id": "62e79fb6892f2f85d9bc2555" + } + ], + "__v": 11, + "updatedAt": "2022-12-09T15:05:33.512Z" +}; +const mockUserId = "61f91d232e175937b960e213"; + +export { + mockArrayCheckRolesEmptyRole, + mockArrayCheckRolesOneRole, + mockArrayCheckRolesMultiRole, + mockArrayCheckRolesManagerRole, + mockTeam, + mockUserId, +} \ No newline at end of file diff --git a/src/resources/utilities/__mocks__/checkingUserAuthorization.mock.js b/src/resources/utilities/__mocks__/checkingUserAuthorization.mock.js new file mode 100644 index 000000000..c9e5aa55f --- /dev/null +++ b/src/resources/utilities/__mocks__/checkingUserAuthorization.mock.js @@ -0,0 +1,20 @@ +const mockArrayRolesAllow = [ + "custodian.team.admin", +]; +const mockArrayRolesNotAllow = [ + "reviewer", +]; +const mockArrayCurrentUserRoles = [ + "admin_dataset", + "admin_data_use", + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager", +]; + +export { + mockArrayRolesAllow, + mockArrayRolesNotAllow, + mockArrayCurrentUserRoles +} \ No newline at end of file diff --git a/src/resources/utilities/__mocks__/formatTeamMembers.mock.js b/src/resources/utilities/__mocks__/formatTeamMembers.mock.js new file mode 100644 index 000000000..c1a94c8d8 --- /dev/null +++ b/src/resources/utilities/__mocks__/formatTeamMembers.mock.js @@ -0,0 +1,449 @@ +const mockTeam = { + "active": true, + "_id": "619658d8a87688acd2775721", + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "6167edbd5306ac30d5b1da14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61a519eb06447b0e9b6ae3fd", + "notifications": [] + }, + { + "roles": [ + "manager", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "notifications": [], + "memberid": "61825367cefce1bfe5c9ba7c" + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d945d9fb5b536d1520c618", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [] + } + ], + "notifications": [], + "type": "publisher", + "createdAt": "2021-11-18T13:44:56.935Z", + "updatedAt": "2022-07-22T09:14:00.954Z", + "__v": 3, + "users": [ + { + "feedback": false, + "news": false, + "isServiceAccount": false, + "advancedSearchRoles": [], + "_id": "6167edbd5306ac30d5b1da14", + "id": 3201332329402178, + "providerId": "113237134008791776027", + "provider": "google", + "firstname": "Varsha", + "lastname": "Khodiyar", + "email": "varsha.khodiyar@hdruk.ac.uk", + "role": "Creator", + "createdAt": "2021-10-14T08:43:41.185Z", + "updatedAt": "2021-10-14T08:44:06.191Z", + "__v": 0, + "redirectURL": "/search?search=&loginReferrer=https%3A%2F%2Fuat.healthdatagateway.org%2F&tab=Datasets", + "discourseKey": "90f50e560155b1ffac50927b25dea45648babda0ef34ab53bc09bd0ec9ca55f4", + "discourseUsername": "varsha.khodiyar", + "additionalInfo": { + "emailNotifications": true, + "showOrganisation": true, + "id": 3201332329402178, + "bio": "", + "link": "", + "orcid": "https://orcid.org/", + "activeflag": "active", + "terms": true, + "organisation": "Health Data Research UK", + "showBio": true + } + }, + { + "feedback": false, + "news": false, + "isServiceAccount": false, + "advancedSearchRoles": [], + "_id": "61a519eb06447b0e9b6ae3fd", + "id": 4539054910544198, + "providerId": "660a0c9a-e529-4785-9b00-f46ea0f8ffb7", + "provider": "azure", + "firstname": "Louis", + "lastname": "Grandjean", + "role": "Creator", + "createdAt": "2021-11-29T18:20:27.801Z", + "updatedAt": "2021-11-29T19:28:24.512Z", + "__v": 0, + "redirectURL": "/search?search=&tab=Datasets", + "discourseKey": "a069e40d8d254c7730fb9a273a8d3e81c8d7c5509bce4ced6644442bf7baa203", + "discourseUsername": "louis.grandjean", + "email": "Louis.grandjean@gosh.nhs.uk", + "additionalInfo": { + "emailNotifications": true, + "showOrganisation": true, + "id": 4539054910544198, + "bio": "Associate Professor of Infectious Diseases at UCL and Honorary Consultant Paediatric Infectious Diseases at Great Ormond Street Hospital", + "link": "", + "orcid": "https://orcid.org/", + "activeflag": "active", + "terms": true, + "organisation": "", + "showBio": true + } + }, + { + "feedback": true, + "news": true, + "isServiceAccount": false, + "advancedSearchRoles": [ + "GENERAL_ACCESS", + "SYSTEM_ADMIN" + ], + "_id": "61825367cefce1bfe5c9ba7c", + "id": 1214329286003002, + "providerId": "103353525483349097009", + "provider": "google", + "firstname": "Priti", + "lastname": "Pampatwar", + "email": "priti.pampatwar@hdruk.ac.uk", + "role": "Admin", + "createdAt": "2021-11-03T09:16:23.610Z", + "updatedAt": "2021-11-08T13:15:12.514Z", + "__v": 0, + "redirectURL": "/search?search=&loginReferrer=http%3A%2F%2Fuat.healthdatagateway.org%2F&tab=Datasets", + "discourseKey": "8ebd0df738d8027c90ed7b7b2d88e6329aaeb708d26a7a7ec5bfdfdc9830f104", + "discourseUsername": "priti.pampatwar1", + "additionalInfo": { + "emailNotifications": true, + "showOrganisation": true, + "id": 1214329286003002, + "bio": "Need access to UAT to explore Gateway features for analysis. thanks", + "link": "", + "orcid": "https://orcid.org/", + "activeflag": "active", + "terms": true, + "organisation": "HDRUK", + "showBio": true + } + }, + { + "feedback": false, + "news": false, + "isServiceAccount": false, + "advancedSearchRoles": [ + "GENERAL_ACCESS", + "SYSTEM_ADMIN" + ], + "_id": "623483baff441ae7fec9fd43", + "id": 5992307808590300, + "providerId": "110650484151346440783", + "provider": "google", + "firstname": "Hdr", + "lastname": "GatewayAdmin", + "email": "hdrgatea@gmail.com", + "role": "Admin", + "createdAt": "2022-03-18T13:06:02.463Z", + "updatedAt": "2022-11-30T14:21:04.554Z", + "__v": 0, + "redirectURL": "/search?search=&datasetSort=latest&loginReferrer=https%3A%2F%2Fuat.healthdatagateway.org%2F&tab=Datasets", + "discourseKey": "1a487eb79808d4037497e999794bf43787a6ab4c2ae8691e7ed86be388aeb57e", + "discourseUsername": "hdr.gatewayadmin", + "acceptedAdvancedSearchTerms": true, + "additionalInfo": { + "emailNotifications": true, + "showOrganisation": true, + "id": 5992307808590300, + "bio": "", + "link": "", + "orcid": "https://orcid.org/", + "activeflag": "active", + "terms": true, + "organisation": "", + "showBio": true + } + }, + { + "feedback": true, + "news": true, + "isServiceAccount": false, + "advancedSearchRoles": [], + "_id": "62d945d9fb5b536d1520c618", + "id": 9829759310154118, + "providerId": "103891864579295967212", + "provider": "google", + "firstname": "Yemi", + "lastname": "Aiyeola", + "email": "yemiayat@gmail.com", + "role": "Creator", + "createdAt": "2022-07-21T12:26:01.756Z", + "updatedAt": "2022-07-22T06:51:10.112Z", + "__v": 0, + "redirectURL": "/completeRegistration/9829759310154118", + "discourseKey": "", + "discourseUsername": null, + "additionalInfo": { + "emailNotifications": true, + "showOrganisation": true, + "id": 9829759310154118, + "bio": "Test account", + "link": "", + "orcid": "https://orcid.org/", + "activeflag": "active", + "terms": true, + "organisation": "HDR UK", + "showBio": true + } + }, + { + "feedback": true, + "news": true, + "isServiceAccount": false, + "advancedSearchRoles": [], + "_id": "5ec2a116b293e07eb48afe14", + "id": 45222846999444660, + "providerId": "111308516852033336265", + "provider": "google", + "firstname": "Clara", + "lastname": "Fennessy", + "email": "clara.fennessy@hdruk.ac.uk", + "role": "Admin", + "__v": 0, + "redirectURL": "/search?search=COVID-19&type=all&tab=Projects&toolcategory=&programminglanguage=&features=&topics=&license=&sampleavailability=&keywords=&publisher=&ageband=&geographiccover=", + "updatedAt": "2022-01-17T10:03:53.640Z", + "createdAt": "2020-09-04T00:00:00.000Z", + "additionalInfo": { + "emailNotifications": true, + "showOrganisation": true, + "id": 45222846999444660, + "bio": "HDR UK", + "link": "", + "orcid": "", + "activeflag": "active", + "organisation": "", + "showBio": true, + "terms": true + } + }, + { + "feedback": false, + "news": false, + "isServiceAccount": false, + "advancedSearchRoles": [ + "GENERAL_ACCESS", + "SYSTEM_ADMIN" + ], + "_id": "62384f08e5c7245adf17f0fd", + "id": 6644721675822300, + "providerId": "116978061621110601234", + "provider": "google", + "firstname": "Vijayalakshmi", + "lastname": "Shanmugam", + "email": "vijisrisan@gmail.com", + "role": "Creator", + "createdAt": "2022-03-21T10:10:16.388Z", + "updatedAt": "2022-11-15T11:31:21.426Z", + "__v": 0, + "redirectURL": "/search?search=&datasetSort=latest&tab=Datasets", + "discourseKey": "", + "discourseUsername": null, + "additionalInfo": { + "emailNotifications": true, + "showOrganisation": true, + "id": 6644721675822300, + "bio": "", + "link": "", + "orcid": "https://orcid.org/", + "activeflag": "active", + "terms": true, + "organisation": "", + "showBio": true + } + }, + { + "feedback": false, + "news": false, + "isServiceAccount": false, + "advancedSearchRoles": [ + "GENERAL_ACCESS", + "SYSTEM_ADMIN" + ], + "_id": "62028ae4d62405c442fd383f", + "id": 37447512737860000, + "providerId": "108928523103518483536", + "provider": "google", + "firstname": "Gateway", + "lastname": "Custodian", + "email": "custodianhdr01@gmail.com", + "role": "Admin", + "createdAt": "2022-02-08T15:23:16.406Z", + "updatedAt": "2022-11-30T13:47:30.946Z", + "__v": 0, + "redirectURL": "/search?search=&datasetSort=latest&loginReferrer=https%3A%2F%2Fuat.healthdatagateway.org%2F&tab=Datasets", + "discourseKey": "8860b4f92315a4b54ee1f9fed1078e88d119a9260b27b2d33b7995003d3e792a", + "discourseUsername": "gateway.custodian", + "acceptedAdvancedSearchTerms": true, + "additionalInfo": { + "emailNotifications": true, + "showOrganisation": true, + "id": 37447512737860000, + "bio": "", + "link": "", + "orcid": "https://orcid.org/", + "activeflag": "active", + "terms": true, + "organisation": "", + "showBio": true + } + } + ] +}; +const mockResponse = [ + { + firstname: 'Varsha', + lastname: 'Khodiyar', + id: 3201332329402178, + userId: '6167edbd5306ac30d5b1da14', + email: 'varsha.khodiyar@hdruk.ac.uk', + roles: ["manager","custodian.team.admin","custodian.metadata.manager","custodian.dar.manager"], + organisation: 'Health Data Research UK', + bio: '' + }, + { + firstname: 'Louis', + lastname: 'Grandjean', + id: 4539054910544198, + userId: '61a519eb06447b0e9b6ae3fd', + email: 'Louis.grandjean@gosh.nhs.uk', + roles: ["manager","custodian.team.admin","custodian.metadata.manager","custodian.dar.manager"], + organisation: '', + bio: 'Associate Professor of Infectious Diseases at UCL and Honorary Consultant Paediatric Infectious Diseases at Great Ormond Street Hospital' + }, + { + firstname: 'Priti', + lastname: 'Pampatwar', + id: 1214329286003002, + userId: '61825367cefce1bfe5c9ba7c', + email: 'priti.pampatwar@hdruk.ac.uk', + roles: ["manager","metadata_editor","custodian.team.admin","custodian.metadata.manager","custodian.dar.manager"], + organisation: 'HDRUK', + bio: 'Need access to UAT to explore Gateway features for analysis. thanks' + }, + { + firstname: 'Hdr', + lastname: 'GatewayAdmin', + id: 5992307808590300, + userId: '623483baff441ae7fec9fd43', + email: 'hdrgatea@gmail.com', + roles: ["manager","custodian.team.admin","custodian.metadata.manager","custodian.dar.manager"], + organisation: '', + bio: '' + }, + { + firstname: 'Yemi', + lastname: 'Aiyeola', + id: 9829759310154118, + userId: '62d945d9fb5b536d1520c618', + email: 'yemiayat@gmail.com', + roles: ["manager","custodian.team.admin","custodian.metadata.manager","custodian.dar.manager"], + organisation: 'HDR UK', + bio: 'Test account' + }, + { + firstname: 'Clara', + lastname: 'Fennessy', + id: 45222846999444660, + userId: '5ec2a116b293e07eb48afe14', + email: 'clara.fennessy@hdruk.ac.uk', + roles: ["manager","custodian.team.admin","custodian.metadata.manager","custodian.dar.manager"], + organisation: '', + bio: 'HDR UK' + }, + { + firstname: 'Vijayalakshmi', + lastname: 'Shanmugam', + id: 6644721675822300, + userId: '62384f08e5c7245adf17f0fd', + email: 'vijisrisan@gmail.com', + roles: ["manager","custodian.team.admin","custodian.metadata.manager","custodian.dar.manager"], + organisation: '', + bio: '' + }, + { + firstname: 'Gateway', + lastname: 'Custodian', + id: 37447512737860000, + userId: '62028ae4d62405c442fd383f', + email: 'custodianhdr01@gmail.com', + roles: ["manager","custodian.team.admin","custodian.metadata.manager","custodian.dar.manager"], + organisation: '', + bio: '' + } +]; + +export { + mockTeam, + mockResponse, +} \ No newline at end of file diff --git a/src/resources/utilities/__mocks__/formatTeamNotifications.mock.js b/src/resources/utilities/__mocks__/formatTeamNotifications.mock.js new file mode 100644 index 000000000..6ca317103 --- /dev/null +++ b/src/resources/utilities/__mocks__/formatTeamNotifications.mock.js @@ -0,0 +1,170 @@ +const mockTeam = { + "active": true, + "_id": "5f7b1a2bce9f65e6ed83e7da", + "members": [ + { + "roles": [ + "manager", + "reviewer", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61825367cefce1bfe5c9ba7c", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62349f5f767db5d3408b4007", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d691a49901cef16d8da801", + "notifications": [] + }, + { + "roles": [ + "reviewer", + "custodian.dar.manager", + "custodian.team.admin", + "metadata_editor" + ], + "memberid": "632c325de4e074719a8c13de", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "reviewer", + "metadata_editor" + ], + "memberid": "62f502413e9bf5e82256d63b", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61ddbbcdd05e7f703fc3190d", + "notifications": [] + }, + { + "roles": [ + "custodian.team.admin", + "admin_dataset" + ], + "memberid": "63d2bfcbd4663faea8745ae6", + "notifications": [] + }, + { + "roles": [ + "custodian.team.admin", + "custodian.dar.manager" + ], + "memberid": "6308bfd1d2ff69e6c13427e7", + "notifications": [] + }, + { + "roles": [ + "custodian.dar.manager", + "reviewer", + "custodian.team.admin" + ], + "memberid": "63d27a30a5959c9bfc72caa2", + "notifications": [] + }, + { + "roles": [ + "custodian.metadata.manager", + "custodian.dar.manager", + "reviewer" + ], + "memberid": "63d3cb845487686dad9552ea", + "notifications": [] + }, + { + "roles": [ + "manager" + ], + "memberid": "634c518998e119341680d558", + "notifications": [] + }, + { + "roles": [ + "reviewer", + "custodian.dar.manager", + "custodian.team.admin", + "custodian.metadata.manager" + ], + "memberid": "63dd1225a87ca70692fcddcf", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [] + } + ], + "type": "publisher", + "notifications": [ + { + "notificationType": "dataAccessRequest", + "optIn": true, + "subscribedEmails": [ + "vijisrisan@gmail.com", + "hello@gmail.com" + ], + "_id": "6384dcce285c42274308a947" + } + ], + "__v": 369, + "createdAt": "2020-12-11T10:46:22.406Z", + "updatedAt": "2023-02-13T13:19:02.636Z" +}; +const mockResponse = [ + { + "notificationType": "dataAccessRequest", + "optIn": true, + "subscribedEmails": [ + { + "value": "vijisrisan@gmail.com", + "error": "" + }, + { + "value": "hello@gmail.com", + "error": "" + } + ] + } +]; + +export { + mockTeam, + mockResponse, +} \ No newline at end of file diff --git a/src/resources/utilities/__mocks__/getAllRolesForApproverUser.mock.js b/src/resources/utilities/__mocks__/getAllRolesForApproverUser.mock.js new file mode 100644 index 000000000..c25035a3e --- /dev/null +++ b/src/resources/utilities/__mocks__/getAllRolesForApproverUser.mock.js @@ -0,0 +1,1254 @@ +const mockTeam = [ + { + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [ + { + "optIn": true, + "_id": "606ddfb9130e8f3f3d431801", + "notificationType": "dataAccessRequest", + "message": "" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "613f2c40e8592f8d3add7add", + "notifications": [] + }, + { + "roles": [ + "manager", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "notifications": [], + "memberid": "61825367cefce1bfe5c9ba7c" + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61d489c85a45342ce44b0bc9", + "notifications": [] + }, + { + "roles": [ + "metadata_editor" + ], + "memberid": "61d489c85a45342ce44b0bc9", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d945d9fb5b536d1520c618", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623467f6ce42aab1cfc29021", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [] + }, + { + "roles": [ + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61ddbbcdd05e7f703fc3190d", + "notifications": [] + } + ], + "type": "publisher", + "publisher": { + "_id": "5f3f98068af2ef61552e1d75", + "name": "ALLIANCE > SAIL" + } + }, + { + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [ + { + "optIn": true, + "_id": "606ddfb9130e8f3f3d431801", + "notificationType": "dataAccessRequest", + "message": "" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5f8563c6fa9a256698a9fafb", + "notifications": [ + { + "optIn": true, + "_id": "606ddfb9130e8f3f3d431801", + "notificationType": "dataAccessRequest", + "message": "" + } + ] + }, + { + "roles": [ + "manager", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "notifications": [], + "memberid": "61825367cefce1bfe5c9ba7c" + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61d489c85a45342ce44b0bc9", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "notifications": [], + "memberid": "61854c1e46f52ed51754bb24" + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "60191005c13ca825e4c6cadd", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d945d9fb5b536d1520c618", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61ddbbcdd05e7f703fc3190d", + "notifications": [] + } + ], + "type": "publisher", + "publisher": { + "_id": "5f89662f7150a1b050be0710", + "name": "ALLIANCE > HEALTH AND SOCIAL CARE NORTHERN IRELAND" + } + }, + { + "members": [ + { + "roles": [ + "admin_dataset" + ], + "memberid": "5e725692c9a581a0dd2bd84b", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "5e726049c9a58131cd2bd874", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "611a58bb9f17737532ad25c0", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "6139c0d1e6b81112ad5e0312", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "618008d49566b41988d1be02", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "61825367cefce1bfe5c9ba7c", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "6167edbd5306ac30d5b1da14", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "619261978f832546e0b0edfd", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "619501876b8724ad01077af6", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "61978834a87688d4e67770fa", + "notifications": [] + }, + { + "roles": [ + "admin_data_use" + ], + "memberid": "619ba1e87d2fd8db23885ce9", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "61d489c85a45342ce44b0bc9", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "61b9c46dfbd7e9f3aa270ac1", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "613a0399e6b8113f0a5e0ccc", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "61a5fda2b2d8db3617d85b4b", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "61854c1e46f52ed51754bb24", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "62349a67767db5d3408b4001", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [] + }, + { + "roles": [ + "admin_dataset" + ], + "memberid": "61e93915826d995980cca3dc", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "62e79d3a892f2fbc28bc233b", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use" + ], + "memberid": "6308bfd1d2ff69e6c13427e7", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "626a72ba4524adcf224b769b", + "notifications": [] + }, + { + "roles": [ + "admin_dataset", + "admin_data_use", + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [] + } + ], + "type": "admin", + "publisher": null + }, + { + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "611a58bb9f17737532ad25c0", + "notifications": [ + { + "optIn": true, + "_id": "62389e82e5c72465f718013f", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "61ddbd0628f427fb9473287b", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "618008d49566b41988d1be02", + "notifications": [] + }, + { + "roles": [ + "metadata_editor" + ], + "memberid": "613a0399e6b8113f0a5e0ccc", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "6139c0d1e6b81112ad5e0312", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "61cc3966928507fe0f1b9325", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [ + { + "optIn": true, + "_id": "6255a87db588c01f5250a3ce", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "613f2c40e8592f8d3add7add", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [] + } + ], + "type": "publisher", + "publisher": { + "_id": "62024195f0e15612a4e16979", + "name": "OTHER > Test1" + } + }, + { + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [ + { + "optIn": true, + "_id": "606ddfb9130e8f3f3d431801", + "notificationType": "dataAccessRequest", + "message": "" + } + ] + }, + { + "roles": [ + "manager", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "notifications": [], + "memberid": "61825367cefce1bfe5c9ba7c" + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [ + { + "optIn": true, + "_id": "62837ba8a35a2205b0f0a0b6", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [ + { + "optIn": true, + "_id": "628cc003cc879f3d43852b8f", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d945d9fb5b536d1520c618", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61ddbbcdd05e7f703fc3190d", + "notifications": [] + } + ], + "type": "publisher", + "publisher": { + "_id": "5f8992a97150a1b050be0712", + "name": "ALLIANCE > PUBLIC HEALTH SCOTLAND" + } + }, + { + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61128e7ef7ff9cee652532b4", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "613f2c40e8592f8d3add7add", + "notifications": [] + }, + { + "roles": [ + "manager", + "reviewer", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61825367cefce1bfe5c9ba7c", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [ + { + "optIn": true, + "_id": "623c81f5aa033e0e643d6c6a", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "618008d49566b41988d1be02", + "notifications": [ + { + "optIn": true, + "_id": "62c40c6ab1079d7d4ee1c608", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "notifications": [], + "memberid": "61b9c46dfbd7e9f3aa270ac1" + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62349f5f767db5d3408b4007", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [ + { + "optIn": true, + "_id": "62839a8883f55d40d3d20cf4", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62e79d3a892f2fbc28bc233b", + "notifications": [ + { + "optIn": true, + "_id": "62ebe58bd7595507ba81d689", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d691a49901cef16d8da801", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d5e29ad375f198868e4dc7", + "notifications": [ + { + "optIn": true, + "_id": "62ed1f7e7d1af36e6d280b8e", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "632c325de4e074719a8c13de", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "62f502413e9bf5e82256d63b", + "notifications": [] + }, + { + "roles": [ + "manager", + "reviewer", + "metadata_editor", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ef9c4ebb9796854174cbf94", + "notifications": [] + }, + { + "roles": [ + "custodian.metadata.manager" + ], + "memberid": "61dd491e4d274e7eac3b848e", + "notifications": [] + }, + { + "roles": [ + "reviewer", + "metadata_editor", + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61ddbbcdd05e7f703fc3190d", + "notifications": [] + }, + { + "roles": [ + "custodian.team.admin" + ], + "memberid": "63d2bfcbd4663faea8745ae6", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "6308bfd1d2ff69e6c13427e7", + "notifications": [] + }, + { + "roles": [ + "custodian.dar.manager" + ], + "memberid": "63d27a30a5959c9bfc72caa2", + "notifications": [] + }, + { + "roles": [ + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "63d3cb845487686dad9552ea", + "notifications": [] + } + ], + "type": "publisher", + "publisher": { + "_id": "5f7b1a2bce9f65e6ed83e7da", + "name": "OTHER > HEALTH DATA RESEARCH UK" + } + }, + { + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61825367cefce1bfe5c9ba7c", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "61d489c85a45342ce44b0bc9", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "623483baff441ae7fec9fd43", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "611a58bb9f17737532ad25c0", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "618008d49566b41988d1be02", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61f91d232e175937b960e213", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62028ae4d62405c442fd383f", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "613f2c40e8592f8d3add7add", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62349f5f767db5d3408b4007", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61ddbbcdd05e7f703fc3190d", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62e79d3a892f2fbc28bc233b", + "notifications": [ + { + "optIn": true, + "_id": "62f2335e062fcd05b9e8d2cf", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d691a49901cef16d8da801", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62d5e29ad375f198868e4dc7", + "notifications": [ + { + "optIn": true, + "_id": "62f241e8f15b29ce1c24fd96", + "notificationType": "dataAccessRequest" + } + ] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62f502413e9bf5e82256d63b", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "62c80ec7970e8a07797c23c7", + "notifications": [] + }, + { + "roles": [ + "metadata_editor" + ], + "memberid": "6318aab2f051c6375ec53b7e", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "632c325de4e074719a8c13de", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "61ddbd0628f427fb9473287b", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "5fc8e2b7c386587231140f85", + "notifications": [] + }, + { + "roles": [ + "metadata_editor" + ], + "memberid": "5f7c2bcd504d5e7cda30b3ea", + "notifications": [] + }, + { + "roles": [ + "metadata_editor" + ], + "memberid": "5e851759b9bbd5ecd9f65a39", + "notifications": [] + }, + { + "roles": [ + "reviewer" + ], + "memberid": "634c518998e119341680d55a", + "notifications": [] + }, + { + "roles": [ + "metadata_editor" + ], + "memberid": "61a5fda2b2d8db3617d85b4b", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "5ec2a116b293e07eb48afe14", + "notifications": [] + }, + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "62384f08e5c7245adf17f0fd", + "notifications": [] + } + ], + "type": "publisher", + "publisher": { + "_id": "61e57fd3012bda94e0e8b9c6", + "name": "OTHER > Priti Test Team" + } + } +]; +const mockTeamId = "63bbebf8ec565a91c474cd1b"; +const mockUserId = "61f91d232e175937b960e213"; +const mockResponse = ["admin_dataset","admin_data_use","manager","custodian.team.admin","custodian.metadata.manager","custodian.dar.manager"] + +export { + mockTeam, + mockTeamId, + mockUserId, + mockResponse, +}; \ No newline at end of file diff --git a/src/resources/utilities/__mocks__/getTeamName.mock.js b/src/resources/utilities/__mocks__/getTeamName.mock.js new file mode 100644 index 000000000..05f0e9a1d --- /dev/null +++ b/src/resources/utilities/__mocks__/getTeamName.mock.js @@ -0,0 +1,136 @@ +const mockTeamWithPublisher = { + "active": true, + "_id": "63bbebf8ec565a91c474cd1b", + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "6308bfd1d2ff69e6c13427e7", + "notifications": [] + } + ], + "notifications": [], + "type": "publisher", + "createdAt": "2023-01-09T10:27:04.376Z", + "updatedAt": "2023-01-23T13:59:48.253Z", + "__v": 25, + "publisher": { + "_id": "63bbebf8ec565a91c474cd1b", + "name": "ALLIANCE > Test40" + }, + "users": [ + { + "feedback": false, + "news": false, + "isServiceAccount": false, + "advancedSearchRoles": [], + "_id": "6308bfd1d2ff69e6c13427e7", + "id": 6531262197049297, + "providerId": "102868775144293907483", + "provider": "google", + "firstname": "kymme", + "lastname": "hayley", + "email": "kymme@hdruk.dev", + "role": "Admin", + "createdAt": "2022-08-26T12:42:57.116Z", + "updatedAt": "2023-01-06T16:08:52.920Z", + "__v": 0, + "redirectURL": "/search?search=&tab=Datasets", + "discourseKey": "fa596dcd0486d6919c9ee98db5eb00429341d266e275c3c5d8b95b21ff27b89f", + "discourseUsername": "kymme.hayley" + }, + { + "feedback": false, + "news": false, + "isServiceAccount": false, + "advancedSearchRoles": [], + "_id": "5e8c3823e63e5d83ac27c347", + "id": 5890232553870074, + "providerId": "102167422686846649659", + "provider": "google", + "firstname": "Ciara", + "lastname": "Ward", + "email": "ciara.ward@paconsulting.com", + "password": null, + "role": "Creator", + "__v": 0, + "discourseKey": "a23c62c2f9b06f0873a567522ac585a288af9fa8ec7b62eeec68baebef1cdf10", + "discourseUsername": "ciara.ward", + "updatedAt": "2021-05-12T09:51:49.573Z", + "createdAt": "2020-09-04T00:00:00.000Z" + } + ] +}; + +const mockTeamWithoutPublisher = { + "active": true, + "_id": "63bbebf8ec565a91c474cd1b", + "members": [ + { + "roles": [ + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" + ], + "memberid": "6308bfd1d2ff69e6c13427e7", + "notifications": [] + } + ], + "notifications": [], + "type": "publisher", + "createdAt": "2023-01-09T10:27:04.376Z", + "updatedAt": "2023-01-23T13:59:48.253Z", + "__v": 25, + "users": [ + { + "feedback": false, + "news": false, + "isServiceAccount": false, + "advancedSearchRoles": [], + "_id": "6308bfd1d2ff69e6c13427e7", + "id": 6531262197049297, + "providerId": "102868775144293907483", + "provider": "google", + "firstname": "kymme", + "lastname": "hayley", + "email": "kymme@hdruk.dev", + "role": "Admin", + "createdAt": "2022-08-26T12:42:57.116Z", + "updatedAt": "2023-01-06T16:08:52.920Z", + "__v": 0, + "redirectURL": "/search?search=&tab=Datasets", + "discourseKey": "fa596dcd0486d6919c9ee98db5eb00429341d266e275c3c5d8b95b21ff27b89f", + "discourseUsername": "kymme.hayley" + }, + { + "feedback": false, + "news": false, + "isServiceAccount": false, + "advancedSearchRoles": [], + "_id": "5e8c3823e63e5d83ac27c347", + "id": 5890232553870074, + "providerId": "102167422686846649659", + "provider": "google", + "firstname": "Ciara", + "lastname": "Ward", + "email": "ciara.ward@paconsulting.com", + "password": null, + "role": "Creator", + "__v": 0, + "discourseKey": "a23c62c2f9b06f0873a567522ac585a288af9fa8ec7b62eeec68baebef1cdf10", + "discourseUsername": "ciara.ward", + "updatedAt": "2021-05-12T09:51:49.573Z", + "createdAt": "2020-09-04T00:00:00.000Z" + } + ] +}; + +export { + mockTeamWithPublisher, + mockTeamWithoutPublisher, +} \ No newline at end of file diff --git a/src/resources/utilities/__mocks__/listOfRolesAllowed.mock.js b/src/resources/utilities/__mocks__/listOfRolesAllowed.mock.js new file mode 100644 index 000000000..4b9c8998c --- /dev/null +++ b/src/resources/utilities/__mocks__/listOfRolesAllowed.mock.js @@ -0,0 +1,46 @@ +const mockUserRoles = [ + "admin_dataset", + "admin_data_use", + "manager", + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.dar.manager" +]; +const mockRolesAcceptedByRoles = { + "custodian.team.admin": [ + "custodian.team.admin", + "custodian.metadata.manager", + "custodian.metadata.editor", + "custodian.developer", + "custodian.dar.manager", + "custodian.dar.reviewer", + "custodian.dur.manager" + ], + "custodian.metadata.manager": [ + "custodian.metadata.manager", + "custodian.metadata.editor" + ], + "custodian.metadata.editor": [ + "custodian.metadata.editor" + ], + "custodian.developer": [ + "custodian.developer" + ], + "custodian.dar.manager": [ + "custodian.dar.manager", + "custodian.dar.reviewer" + ], + "custodian.dar.reviewer": [ + "custodian.dar.reviewer" + ], + "custodian.dur.manager": [ + "custodian.dur.manager" + ] +}; +const mockResponse = ["custodian.team.admin","custodian.metadata.manager","custodian.metadata.editor","custodian.developer","custodian.dar.manager","custodian.dar.reviewer","custodian.dur.manager"]; + +export { + mockUserRoles, + mockRolesAcceptedByRoles, + mockResponse, +} \ No newline at end of file diff --git a/src/resources/utilities/__tests__/checkAllowNewRoles.test.js b/src/resources/utilities/__tests__/checkAllowNewRoles.test.js new file mode 100644 index 000000000..528d135dd --- /dev/null +++ b/src/resources/utilities/__tests__/checkAllowNewRoles.test.js @@ -0,0 +1,19 @@ +import teamV3Util from '../team.v3.util'; +import { mockUserUpdateRoles, mockUserUpdateRolesFalse, mockAllowedRoles } from '../__mocks__/checkAllowNewRoles.mock'; +import HttpExceptions from '../../../exceptions/HttpExceptions'; + +describe('checkAllowNewRoles test', () => { + it('should return true', () => { + let response = teamV3Util.checkAllowNewRoles(mockUserUpdateRoles, mockAllowedRoles); + expect(typeof response).toBe('boolean'); + expect(response).toBe(true); + }); + + it('should return an exception', () => { + try { + teamV3Util.checkAllowNewRoles(mockUserUpdateRolesFalse, mockAllowedRoles); + } catch (error) { + expect(error).toBeInstanceOf(HttpExceptions); + } + }); +}); \ No newline at end of file diff --git a/src/resources/utilities/__tests__/checkIfAdmin.test.js b/src/resources/utilities/__tests__/checkIfAdmin.test.js new file mode 100644 index 000000000..e4cd7d8f8 --- /dev/null +++ b/src/resources/utilities/__tests__/checkIfAdmin.test.js @@ -0,0 +1,18 @@ +import teamV3Util from '../team.v3.util'; +import { + mockUser, + mockRole, + mockRoleEmpty +} from '../__mocks__/checkIfAdmin.mock'; + +describe("test checkIfAdmin", () => { + it("should return true", () => { + let response = teamV3Util.checkIfAdmin(mockUser, mockRole); + expect(response).toBe(true); + }); + + it("should return false", () => { + let response = teamV3Util.checkIfAdmin(mockUser, mockRoleEmpty); + expect(response).toBe(false); + }); +}); \ No newline at end of file diff --git a/src/resources/utilities/__tests__/checkIfExistAdminRole.test.js b/src/resources/utilities/__tests__/checkIfExistAdminRole.test.js new file mode 100644 index 000000000..a2eb3cc00 --- /dev/null +++ b/src/resources/utilities/__tests__/checkIfExistAdminRole.test.js @@ -0,0 +1,23 @@ +import teamV3Util from '../team.v3.util'; +import { + mockMembersTrue, + mockMembersFalse, + mockRolesAuth, +} from '../__mocks__/checkIfExistAdminRole.mock'; +import HttpExceptions from '../../../exceptions/HttpExceptions'; + +describe('checkIfExistAdminRole test', () => { + it('should return true', () => { + let response = teamV3Util.checkIfExistAdminRole(mockMembersTrue, mockRolesAuth); + expect(typeof response).toBe('boolean'); + expect(response).toBe(true); + }); + + it('should return an exception', () => { + try { + teamV3Util.checkIfExistAdminRole(mockMembersFalse, mockRolesAuth); + } catch (error) { + expect(error).toBeInstanceOf(HttpExceptions); + } + }); +}); \ No newline at end of file diff --git a/src/resources/utilities/__tests__/checkTeamV3Permissions.test.js b/src/resources/utilities/__tests__/checkTeamV3Permissions.test.js new file mode 100644 index 000000000..61d68cb1c --- /dev/null +++ b/src/resources/utilities/__tests__/checkTeamV3Permissions.test.js @@ -0,0 +1,32 @@ +import teamV3Util from '../team.v3.util'; +import { + mockRole, + mockRoleManager, + mockTeam, + mockTeamEmpty, + mockUserIdNotInList, + mockUserIdInList, + mockUserIdInListNoManager +} from '../__mocks__/checkTeamV3Permissions.mock'; + +describe("test for checkTeamV3Permissions", () => { + it("checkTeamV3Permissions with userid in team should return true", () => { + let response = teamV3Util.checkTeamV3Permissions(mockRole, mockTeam, mockUserIdInList); + expect(response).toBe(true); + }); + + it("checkTeamV3Permissions with userid not in team should return true", () => { + let response = teamV3Util.checkTeamV3Permissions(mockRole, mockTeam, mockUserIdInList); + expect(response).toBe(true); + }); + + it("checkTeamV3Permissions for user without manager permisions should return false", () => { + let response = teamV3Util.checkTeamV3Permissions(mockRoleManager, mockTeam, mockUserIdInListNoManager); + expect(response).toBe(false); + }); + + it("checkTeamV3Permissions with empty team should return false", () => { + let response = teamV3Util.checkTeamV3Permissions(mockRole, mockTeamEmpty, mockUserIdNotInList); + expect(response).toBe(false); + }); +}); \ No newline at end of file diff --git a/src/resources/utilities/__tests__/checkUserAuthorization.test.js b/src/resources/utilities/__tests__/checkUserAuthorization.test.js new file mode 100644 index 000000000..32d5de5d1 --- /dev/null +++ b/src/resources/utilities/__tests__/checkUserAuthorization.test.js @@ -0,0 +1,14 @@ +import teamV3Util from '../team.v3.util'; +import { + currUserId, + permission, + mockTeam, + mockUsers, +} from '../__mocks__/checkUserAuthorization.mock'; + +describe("test checkUserAuthorization", () => { + it("should return true", () => { + let response = teamV3Util.checkUserAuthorization(currUserId, permission, mockTeam, mockUsers); + expect(response).toBe(true); + }); +}); \ No newline at end of file diff --git a/src/resources/utilities/__tests__/checkUserRolesByTeam.test.js b/src/resources/utilities/__tests__/checkUserRolesByTeam.test.js new file mode 100644 index 000000000..135a6e613 --- /dev/null +++ b/src/resources/utilities/__tests__/checkUserRolesByTeam.test.js @@ -0,0 +1,38 @@ +import teamV3Util from '../team.v3.util'; +import { + mockArrayCheckRolesEmptyRole, + mockArrayCheckRolesOneRole, + mockArrayCheckRolesMultiRole, + mockArrayCheckRolesManagerRole, + mockTeam, + mockUserId, +} from '../__mocks__/checkUserRolesByTeam.mock'; +import HttpExceptions from '../../../exceptions/HttpExceptions'; + +describe('checkUserRolesByTeam test', () => { + it('should return true for no role request', () => { + let response = teamV3Util.checkUserRolesByTeam(mockArrayCheckRolesEmptyRole, mockTeam, mockUserId); + expect(typeof response).toBe('boolean'); + expect(response).toBe(true); + }); + + it('should return true for one role request', () => { + let response = teamV3Util.checkUserRolesByTeam(mockArrayCheckRolesOneRole, mockTeam, mockUserId); + expect(typeof response).toBe('boolean'); + expect(response).toBe(true); + }); + + it('should return true for multi role request', () => { + let response = teamV3Util.checkUserRolesByTeam(mockArrayCheckRolesMultiRole, mockTeam, mockUserId); + expect(typeof response).toBe('boolean'); + expect(response).toBe(true); + }); + + it('should return an exception for manager role', () => { + try { + teamV3Util.checkUserRolesByTeam(mockArrayCheckRolesManagerRole, mockTeam, mockUserId); + } catch (error) { + expect(response).toBe(false); + } + }); +}); \ No newline at end of file diff --git a/src/resources/utilities/__tests__/checkingUserAuthorization.test.js b/src/resources/utilities/__tests__/checkingUserAuthorization.test.js new file mode 100644 index 000000000..fa57b5337 --- /dev/null +++ b/src/resources/utilities/__tests__/checkingUserAuthorization.test.js @@ -0,0 +1,19 @@ +import teamV3Util from '../team.v3.util'; +import { mockArrayRolesAllow, mockArrayRolesNotAllow, mockArrayCurrentUserRoles } from '../__mocks__/checkingUserAuthorization.mock'; +import HttpExceptions from '../../../exceptions/HttpExceptions'; + +describe('checkingUserAuthorization test', () => { + it('should return true', () => { + let response = teamV3Util.checkingUserAuthorization(mockArrayRolesAllow, mockArrayCurrentUserRoles); + expect(typeof response).toBe('boolean'); + expect(response).toBe(true); + }); + + it('should return an exception', () => { + try { + teamV3Util.checkingUserAuthorization(mockArrayRolesNotAllow, mockArrayCurrentUserRoles); + } catch (error) { + expect(error).toBeInstanceOf(HttpExceptions); + } + }); +}); \ No newline at end of file diff --git a/src/resources/utilities/__tests__/formatTeamMembers.test.js b/src/resources/utilities/__tests__/formatTeamMembers.test.js new file mode 100644 index 000000000..8faebfac6 --- /dev/null +++ b/src/resources/utilities/__tests__/formatTeamMembers.test.js @@ -0,0 +1,14 @@ +import teamV3Util from '../team.v3.util'; +import { mockTeam, mockResponse } from '../__mocks__/formatTeamMembers.mock'; + +describe("formatTeamMembers test", () => { + it("should return empty object for empty input", () => { + let response = teamV3Util.formatTeamMembers({}); + expect(response).toMatchObject({}); + }); + + it("should return response as expected", () => { + let response = teamV3Util.formatTeamMembers(mockTeam); + expect(response).toMatchObject(mockResponse); + }); +}); \ No newline at end of file diff --git a/src/resources/utilities/__tests__/formatTeamNotifications.test.js b/src/resources/utilities/__tests__/formatTeamNotifications.test.js new file mode 100644 index 000000000..0c5f50287 --- /dev/null +++ b/src/resources/utilities/__tests__/formatTeamNotifications.test.js @@ -0,0 +1,13 @@ +import teamV3Util from '../team.v3.util'; +import { + mockTeam, + mockResponse, +} from '../__mocks__/formatTeamNotifications.mock'; + +describe("test formatTeamNotifications", () => { + it("should return return response", () => { + let response = teamV3Util.formatTeamNotifications(mockTeam); + expect(typeof response).toBe('object') + expect(response).toEqual(expect.arrayContaining(mockResponse)); + }); +}); \ No newline at end of file diff --git a/src/resources/utilities/__tests__/getAllRolesForApproverUser.test.js b/src/resources/utilities/__tests__/getAllRolesForApproverUser.test.js new file mode 100644 index 000000000..bc274b4a4 --- /dev/null +++ b/src/resources/utilities/__tests__/getAllRolesForApproverUser.test.js @@ -0,0 +1,10 @@ +import teamV3Util from '../team.v3.util'; +import { mockTeam, mockTeamId, mockUserId, mockResponse } from '../__mocks__/getAllRolesForApproverUser.mock'; + +describe('getAllRolesForApproverUser test', () => { + it('should return array', () => { + let response = teamV3Util.getAllRolesForApproverUser(mockTeam, mockTeamId, mockUserId); + expect(typeof response).toBe('object') + expect(response).toEqual(expect.arrayContaining(mockResponse)); + }); +}); \ No newline at end of file diff --git a/src/resources/utilities/__tests__/getTeamName.test.js b/src/resources/utilities/__tests__/getTeamName.test.js new file mode 100644 index 000000000..e4127fc41 --- /dev/null +++ b/src/resources/utilities/__tests__/getTeamName.test.js @@ -0,0 +1,16 @@ +import teamV3Util from '../team.v3.util'; +import { mockTeamWithPublisher, mockTeamWithoutPublisher } from '../__mocks__/getTeamName.mock'; + +describe('getTeamName test', () => { + it('should return a string who contain the publisher name', () => { + let response = teamV3Util.getTeamName(mockTeamWithPublisher); + expect(typeof response).toBe('string') + expect(response).toContain('ALLIANCE > Test40'); + }); + + it('should return a string who contain a generic publisher name', () => { + let response = teamV3Util.getTeamName(mockTeamWithoutPublisher); + expect(typeof response).toBe('string') + expect(response).toContain('No team name'); + }); +}); \ No newline at end of file diff --git a/src/resources/utilities/__tests__/listOfRolesAllowed.test.js b/src/resources/utilities/__tests__/listOfRolesAllowed.test.js new file mode 100644 index 000000000..e00a86440 --- /dev/null +++ b/src/resources/utilities/__tests__/listOfRolesAllowed.test.js @@ -0,0 +1,10 @@ +import teamV3Util from '../team.v3.util'; +import { mockUserRoles, mockRolesAcceptedByRoles, mockResponse } from '../__mocks__/listOfRolesAllowed.mock'; + +describe('listOfRolesAllowed test', () => { + it('should return array', () => { + let response = teamV3Util.listOfRolesAllowed(mockUserRoles, mockRolesAcceptedByRoles); + expect(typeof response).toBe('object') + expect(response).toEqual(expect.arrayContaining(mockResponse)); + }); +}); \ No newline at end of file diff --git a/src/resources/utilities/constants.util.js b/src/resources/utilities/constants.util.js index 956ac3388..f62a876ad 100644 --- a/src/resources/utilities/constants.util.js +++ b/src/resources/utilities/constants.util.js @@ -345,6 +345,41 @@ const _searchDataTypes = { Datauses: 'dataUseRegister', }; +// update - iam 2 + +const _roleMemberTeam = { + CUST_TEAM_ADMIN: 'custodian.team.admin', + CUST_MD_MANAGER: 'custodian.metadata.manager', + CUST_MD_EDITOR: 'metadata_editor', + CUST_DAR_MANAGER: 'custodian.dar.manager', + CUST_DAR_REVIEWER: 'reviewer', +}; + +const _rolesAcceptedByRoles = { + 'custodian.team.admin': [ + 'custodian.team.admin', + 'custodian.metadata.manager', + 'metadata_editor', + 'custodian.dar.manager', + 'manager', + 'reviewer', + ], + 'custodian.metadata.manager': [ + 'custodian.metadata.manager', + 'metadata_editor', + ], + 'metadata_editor': [ + 'metadata_editor', + ], + 'custodian.dar.manager': [ + 'custodian.dar.manager', + 'reviewer', + ], + 'reviewer': [ + 'reviewer', + ], +}; + export default { userTypes: _userTypes, enquiryFormId: _enquiryFormId, @@ -380,4 +415,6 @@ export default { dataUseRegisterStatus: _dataUseRegisterStatus, dataUseRegisterNotifications: _dataUseRegisterNotifications, searchDataTypes: _searchDataTypes, + roleMemberTeam: _roleMemberTeam, + rolesAcceptedByRoles: _rolesAcceptedByRoles, }; diff --git a/src/resources/utilities/emailGenerator.util.js b/src/resources/utilities/emailGenerator.util.js index cfd68d197..591cbf24e 100644 --- a/src/resources/utilities/emailGenerator.util.js +++ b/src/resources/utilities/emailGenerator.util.js @@ -4,12 +4,10 @@ import moment from 'moment'; import { UserModel } from '../user/user.model'; import helper from '../utilities/helper.util'; import constants from '../utilities/constants.util'; -import * as Sentry from '@sentry/node'; import wordTemplateBuilder from '../utilities/wordTemplateBuilder.util'; const fs = require('fs'); const nodemailer = require('nodemailer'); -const readEnv = process.env.ENV || 'production'; let parent, qsId; let questionList = []; @@ -648,7 +646,7 @@ const _displayDARLink = accessId => { const _displayActivityLogLink = (accessId, publisher) => { if (!accessId) return ''; - const activityLogLink = `${process.env.homeURL}/account?tab=dataaccessrequests&team=${publisher}&id=${accessId}`; + const activityLogLink = `${process.env.homeURL}/account?tab=dataaccessrequests&teamType=team&teamId=${publisher}&id=${accessId}`; return `View activity log`; }; @@ -660,7 +658,7 @@ const _displayDataUseRegisterLink = dataUseId => { }; const _displayDataUseRegisterDashboardLink = () => { - const dataUseLink = `${process.env.homeURL}/account?tab=datause&team=admin`; + const dataUseLink = `${process.env.homeURL}/account?tab=datause&teamType=admin`; return `View all data uses for review `; }; @@ -1747,7 +1745,7 @@ const _generateRemovedFromTeam = options => { }; const _displayViewEmailNotifications = publisherId => { - let link = `${process.env.homeURL}/account?tab=teamManagement&innertab=notifications&team=${publisherId}`; + let link = `${process.env.homeURL}/account?tab=teamManagement&innertab=notifications&teamType=team&teamId=${publisherId}`; return ` @@ -2090,7 +2088,7 @@ const _generateMetadataOnboardingApproved = options => { ${commentHTML} @@ -2149,7 +2147,7 @@ const _generateMetadataOnboardingRejected = options => { ${commentHTML} @@ -2217,7 +2215,7 @@ const _generateMetadataOnboardingDuplicated = options => { @@ -2574,23 +2572,26 @@ const _sendEmail = async (to, from, subject, html, allowUnsubscribe = true, atta try { await transporter.sendMail(message, (error, info) => { if (error) { - return console.log(error); + return process.stdout.write(`sendMail : ${error.message}`); } - console.log('Email sent: ' + info.response); + process.stdout.write(`Email sent: ${info.response}`); }); } catch (error) { - console.error(error.response.body); - Sentry.addBreadcrumb({ - category: 'SendGrid', - message: 'Sending email failed', - level: Sentry.Severity.Warning, - }); - Sentry.captureException(error); + process.stdout.write(`EMAIL GENERATOR - _sendEmail : ${error.message}\n`); } } }; -const _sendEmailSmtp = async message => { +const _sendEmailOne = async (to, from, subject, body, attachments = []) => { + let message = { + to: to, + from: from, + subject: subject, + html: body, + attachments, + }; + + // 4. Send email try { await transporter.sendMail(message, (error, info) => { if (error) { @@ -2600,12 +2601,19 @@ const _sendEmailSmtp = async message => { }); } catch (error) { console.error(error.response.body); - Sentry.addBreadcrumb({ - category: 'SendGrid', - message: 'Sending email failed', - level: Sentry.Severity.Warning, + } +}; + +const _sendEmailSmtp = async message => { + try { + await transporter.sendMail(message, (error, info) => { + if (error) { + return process.stdout.write(`${error.message}\n`); + } + process.stdout.write(`Email sent: ${info.response}`); }); - Sentry.captureException(error); + } catch (error) { + process.stdout.write(`EMAIL GENERATOR - _sendEmailSmtp : ${error.message}\n`); } }; @@ -2702,6 +2710,7 @@ const _deleteWordAttachmentTempFiles = async () => { export default { //General sendEmail: _sendEmail, + sendEmailOne: _sendEmailOne, sendIntroEmail: _sendIntroEmail, generateEmailFooter: _generateEmailFooter, generateAttachment: _generateAttachment, diff --git a/src/resources/utilities/ga4gh.utils.js b/src/resources/utilities/ga4gh.utils.js index 739c5bea8..0cbf507b1 100644 --- a/src/resources/utilities/ga4gh.utils.js +++ b/src/resources/utilities/ga4gh.utils.js @@ -69,7 +69,7 @@ const _buildGa4ghVisas = async user => { type: 'ControlledAccessGrants', asserted: dar.dateFinalStatus.getTime(), //date DAR was approved value: dar.pids.map(pid => { - return 'https://web.www.healthdatagateway.org/dataset/' + pid; + return 'https://web.old.healthdatagateway.org/dataset/' + pid; }), //URL to each dataset that they have been approved for source: 'https://www.healthdatagateway.org', by: 'dac', diff --git a/src/resources/utilities/logger.js b/src/resources/utilities/logger.js index 3b937197d..0be172e2f 100644 --- a/src/resources/utilities/logger.js +++ b/src/resources/utilities/logger.js @@ -1,8 +1,5 @@ -import * as Sentry from '@sentry/node'; import constants from './constants.util'; -const readEnv = process.env.ENV || 'prod'; - const logRequestMiddleware = options => { return (req, res, next) => { const { logCategory, action } = options; @@ -13,26 +10,13 @@ const logRequestMiddleware = options => { const logSystemActivity = options => { const { category = 'Action not categorised', action = 'Action not described' } = options; - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.addBreadcrumb({ - category, - message: action, - level: Sentry.Severity.Info, - }); - } + process.stdout.write(`logSystemActivity : action ${action}, category ${category}`); // Save to database }; const logUserActivity = (user, category, type, context) => { const { action } = context; - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.addBreadcrumb({ - category, - message: action, - level: Sentry.Severity.Info, - }); - } - console.log(`${action}`); + process.stdout.write(`logUserActivity - action: ${action}`); // Log date/time // Log action // Log if user was logged in @@ -41,14 +25,7 @@ const logUserActivity = (user, category, type, context) => { }; const logError = (err, category) => { - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.captureException(err, { - tags: { - area: category, - }, - }); - } - console.error(`The following error occurred: ${err.message}`); + process.stdout.write(`The following error occurred: ${err.message}`); }; export const logger = { diff --git a/src/resources/utilities/notificationBuilder.js b/src/resources/utilities/notificationBuilder.js index 5bb081ad4..f5f0f120b 100644 --- a/src/resources/utilities/notificationBuilder.js +++ b/src/resources/utilities/notificationBuilder.js @@ -3,6 +3,7 @@ import { MessagesModel } from '../message/message.model'; const triggerNotificationMessage = (messageRecipients, messageDescription, messageType, messageObjectID, publisherName = '') => { messageRecipients.forEach(async recipient => { let messageID = parseInt(Math.random().toString().replace('0.', '')); + let message = new MessagesModel({ messageType, messageSent: Date.now(), @@ -17,7 +18,7 @@ const triggerNotificationMessage = (messageRecipients, messageDescription, messa }); await message.save(async err => { if (err) { - console.error(`Failed to save ${messageType} message with error : ${err.message}`); + process.stdout.write(`NOTIFICATION BUILDER - Failed to save ${messageType} message with error : ${err.message}\n`); } }); }); diff --git a/src/resources/utilities/team.v3.util.js b/src/resources/utilities/team.v3.util.js new file mode 100644 index 000000000..15bb2f9f8 --- /dev/null +++ b/src/resources/utilities/team.v3.util.js @@ -0,0 +1,420 @@ +import _, { isEmpty, has, includes, isNull, filter, some } from 'lodash'; +import constants from './constants.util'; +// import emailGenerator from '../../utilities/emailGenerator.util'; +import emailGenerator from './emailGenerator.util'; +import notificationBuilder from './notificationBuilder'; +import HttpExceptions from '../../exceptions/HttpExceptions'; +import { TeamModel } from '../team/team.model'; + +/** + * Check a users permission levels for a team + * + * @param {enum} role The role required for the action + * @param {object} team The team object containing its members + * @param {objectId} userId The userId to check the permissions for + */ +const checkTeamV3Permissions = (role, team, userId) => { + if (has(team, 'members')) { + let { members } = team; + let userMember = members.find(el => el.memberid.toString() === userId.toString()); + + if (userMember) { + let { roles = [] } = userMember; + if (roles.includes(role) || roles.includes(constants.roleTypes.MANAGER) || role === '') { + return true; + } + } + } + return false; +}; + +/** + * Check if admin + * + * @param {object} user The user object + * @param {array} adminRoles The adminRoles to check + */ +const checkIfAdmin = (user, adminRoles) => { + let { teams } = user; + if (teams) { + teams = teams.map(team => { + let { publisher, type, members } = team; + let member = members.find(member => { + return member.memberid.toString() === user._id.toString(); + }); + let { roles } = member; + return { ...publisher, type, roles }; + }); + } + const isAdmin = teams.filter(team => team.type === constants.teamTypes.ADMIN); + if (!isEmpty(isAdmin)) { + if (isAdmin[0].roles.some(role => adminRoles.includes(role))) { + return true; + } + } + + return false; +}; + +/** + * format output team members + * + * @param {object} team The team object + */ +const formatTeamMembers = team => { + let { users = [] } = team; + users = users.map(user => { + if (user.id) { + let { + firstname, + lastname, + id, + _id, + email, + additionalInfo: { organisation, bio, showOrganisation, showBio }, + } = user; + let userMember = team.members.find(el => el.memberid.toString() === user._id.toString()); + let { roles = [] } = userMember; + return { + firstname, + lastname, + id, + userId: _id.toString(), + email, + roles, + organisation: showOrganisation ? organisation : '', + bio: showBio ? bio : '', + }; + } + }); + return users.filter(user => { + return user; + }); +}; + +const createTeamNotifications = async (type, context, team, user, publisherId) => { + let teamName; + if (type !== 'TeamAdded') { + teamName = getTeamName(team); + } + let options = {}; + let html = ''; + + switch (type) { + case constants.notificationTypes.MEMBERREMOVED: + // 1. Get user removed + const { removedUser } = context; + // 2. Create user notifications + notificationBuilder.triggerNotificationMessage( + [removedUser.id], + `You have been removed from the team ${teamName}`, + 'team unlinked', + teamName + ); + // 3. Create email + options = { + teamName, + }; + html = emailGenerator.generateRemovedFromTeam(options); + await emailGenerator.sendEmail([removedUser], constants.hdrukEmail, `You have been removed from the team ${teamName}`, html, false); + break; + case constants.notificationTypes.MEMBERADDED: + // 1. Get users added + const { newUsers } = context; + const newUserIds = newUsers.map(user => user.id); + // 2. Create user notifications + notificationBuilder.triggerNotificationMessage( + newUserIds, + `You have been added to the team ${teamName} on the HDR UK Innovation Gateway`, + 'team', + teamName + ); + // 3. Create email for reviewers + options = { + teamName, + role: constants.roleTypes.REVIEWER, + }; + html = emailGenerator.generateAddedToTeam(options); + await emailGenerator.sendEmail( + newUsers, + constants.hdrukEmail, + `You have been added as a reviewer to the team ${teamName} on the HDR UK Innovation Gateway`, + html, + false + ); + // 4. Create email for managers + options = { + teamName, + role: constants.roleTypes.MANAGER, + }; + html = emailGenerator.generateAddedToTeam(options); + await emailGenerator.sendEmail( + newUsers, + constants.hdrukEmail, + `You have been added as a manager to the team ${teamName} on the HDR UK Innovation Gateway`, + html, + false + ); + break; + case constants.notificationTypes.TEAMADDED: + const { recipients } = context; + const recipientIds = recipients.map(recipient => recipient.id); + //1. Create notifications + notificationBuilder.triggerNotificationMessage( + recipientIds, + `You have been assigned as a team manger to the team ${team}`, + 'team added', + team, + publisherId + ); + //2. Create email + options = { + team, + }; + html = emailGenerator.generateNewTeamManagers(options); + await emailGenerator.sendEmail( + recipients, + constants.hdrukEmail, + `You have been assigned as a team manger to the team ${team}`, + html, + false + ); + break; + case constants.notificationTypes.MEMBERROLECHANGED: + break; + } +}; + +/** + * Extract the name of a team from MongoDb object + * + * @param {object} team The team object containing its name or linked object containing name e.g. publisher + */ +const getTeamName = team => { + if (has(team, 'publisher') && !isNull(team.publisher)) { + let { + publisher: { name }, + } = team; + return name; + } else { + return 'No team name'; + } +}; + +const checkUserAuthorization = (currUserId, permission, team, users) => { + let authorised = checkTeamV3Permissions(permission, team, currUserId); + if (!authorised) { + authorised = checkIfAdmin(users, [constants.roleTypes.ADMIN_DATASET]); + } + if (!authorised) { + throw new HttpExceptions(`Not enough permissions. User is not authorized to perform this action.`, 403); + } + + return true; +}; + +const checkingUserAuthorization = (arrayRolesAllow, arrayCurrentUserRoles) => { + let arrayRolesAllowLength = arrayRolesAllow.length; + if (!arrayRolesAllowLength) { + return true; + } + const allow = arrayCurrentUserRoles.filter(element => arrayRolesAllow.includes(element)).length; + + if (!allow) { + throw new HttpExceptions(`Not enough permissions. User is not authorized to perform this action.`, 403); + } + + return true; +}; + +const checkIfLastManager = (members, deleteUserId) => { + let managerCount = members.filter(mem => mem.roles.includes('manager') && mem.memberid.toString() !== deleteUserId).length; + if (managerCount === 0) { + throw new HttpExceptions(`You cannot delete the last manager in the team.`); + } +}; + +const checkIfExistAdminRole = (members, roles) => { + let checkingMemberRoles; + let checkingMembers = members.map(member => { + checkingMemberRoles = member.roles.filter(role => roles.includes(role)).length; + if (checkingMemberRoles) { + return member.memberid; + } + }); + + const filteredArray = _.compact(checkingMembers).length; + + if (!filteredArray) { + throw new HttpExceptions(`The user requested for deletion is not a member of this team.`); + } + + return true; +}; + +const getAllRolesForApproverUser = (team, teamId, userId) => { + let arrayRoles = []; + + team.map(publisher => { + if (publisher && publisher.type === constants.teamTypes.ADMIN) { + publisher.members.map(member => { + if (member.memberid.toString() === userId.toString()) { + arrayRoles = [...arrayRoles, ...member.roles]; + } + }); + } + + if (publisher && publisher.type === 'publisher' && publisher.publisher._id.toString() === teamId.toString()) { + publisher.members.map(member => { + if (member.memberid.toString() === userId.toString()) { + arrayRoles = [...arrayRoles, ...member.roles]; + } + }); + } + }); + + return [...new Set(arrayRoles)]; +}; + +const listOfRolesAllowed = (userRoles, rolesAcceptedByRoles) => { + let allowedRoles = []; + + userRoles.map(uRole => { + if (rolesAcceptedByRoles[uRole]) { + rolesAcceptedByRoles[uRole].forEach(element => allowedRoles.push(element)); + } + }); + + return [...new Set(allowedRoles)]; +}; + +const checkAllowNewRoles = (userUpdateRoles, allowedRoles) => { + userUpdateRoles.forEach(uRole => { + if (!allowedRoles.includes(uRole)) { + throw new HttpExceptions(`Adding the \'${uRole}\' role is not allowed`, 403); + } + }); + + return true; +}; + +const checkUserRolesByTeam = (arrayCheckRoles, team, userId) => { + if (has(team, 'members')) { + let { members } = team; + + let userMember = members.find(el => el.memberid.toString() === userId.toString()); + + if (userMember) { + let { roles = [] } = userMember; + + if (arrayCheckRoles.length === 0) { + return true; + } + + const check = roles.filter(element => arrayCheckRoles.includes(element)).length; + if (check) { + return true; + } + + if (roles.includes(constants.roleMemberTeam.CUST_DAR_MANAGER)) { + return true; + } + } + } + + return false; +}; + +const formatTeamNotifications = team => { + let { notifications = [] } = team; + if (!isEmpty(notifications)) { + return [...notifications].reduce((arr, notification) => { + let teamNotificationEmails = []; + let { notificationType = '', optIn = false, subscribedEmails = [] } = notification; + + if (!isEmpty(subscribedEmails)) teamNotificationEmails = [...subscribedEmails].map(email => ({ value: email, error: '' })); + else teamNotificationEmails = [{ value: '', error: '' }]; + + let formattedNotification = { + notificationType, + optIn, + subscribedEmails: teamNotificationEmails, + }; + + arr = [...arr, formattedNotification]; + + return arr; + }, []); + } else { + return []; + } +}; + +const findMissingOptIns = (memberNotifications, teamNotifications) => { + return [...memberNotifications].reduce((neededOptIns, memberNotification) => { + let { notificationType: memberNotificationType, optIn: memberOptIn } = memberNotification; + // find the matching notification type within the teams notification + let teamNotification = + [...teamNotifications].find(teamNotification => teamNotification.notificationType === memberNotificationType) || {}; + // if the team has the same notification type test + if (!isEmpty(teamNotification)) { + let { notificationType, optIn: teamOptIn, subscribedEmails } = teamNotification; + // if both are turned off build and return new error + if ((!teamOptIn && !memberOptIn) || (!memberOptIn && subscribedEmails.length <= 0)) { + neededOptIns = { + ...neededOptIns, + [`${notificationType}`]: `Notifications must be enabled for ${constants.teamNotificationTypesHuman[notificationType]}`, + }; + } + } + return neededOptIns; + }, {}); +}; + +const filterMembersByNoticationTypes = (members, notificationTypes) => { + return filter(members, member => { + return some(member.notifications, notification => { + return includes(notificationTypes, notification.notificationType); + }); + }); +}; + +const getMemberDetails = (memberIds = [], users = []) => { + if (!isEmpty(memberIds) && !isEmpty(users)) { + return [...users].reduce( + (arr, user) => { + let { email, id, _id } = user; + if (memberIds.includes(_id.toString())) { + arr['memberEmails'].push({ email }); + arr['userIds'].push({ id }); + } + return { + memberEmails: arr['memberEmails'], + userIds: arr['userIds'], + }; + }, + { memberEmails: [], userIds: [] } + ); + } + return []; +}; + +export default { + checkTeamV3Permissions, + checkIfAdmin, + formatTeamMembers, + createTeamNotifications, + getTeamName, + checkUserAuthorization, + checkingUserAuthorization, + checkIfLastManager, + checkIfExistAdminRole, + getAllRolesForApproverUser, + listOfRolesAllowed, + checkAllowNewRoles, + checkUserRolesByTeam, + formatTeamNotifications, + findMissingOptIns, + filterMembersByNoticationTypes, + getMemberDetails, +}; diff --git a/src/resources/workflow/workflow.controller.js b/src/resources/workflow/workflow.controller.js index a48b6b7c6..681412571 100644 --- a/src/resources/workflow/workflow.controller.js +++ b/src/resources/workflow/workflow.controller.js @@ -4,10 +4,11 @@ import Mongoose from 'mongoose'; import { PublisherModel } from '../publisher/publisher.model'; import { DataRequestModel } from '../datarequest/datarequest.model'; import { WorkflowModel } from './workflow.model'; -import teamController from '../team/team.controller'; +import teamV3Util from '../utilities/team.v3.util'; import helper from '../utilities/helper.util'; import constants from '../utilities/constants.util'; import Controller from '../base/controller'; +import HttpExceptions from '../../exceptions/HttpExceptions'; export default class WorkflowController extends Controller { constructor(workflowService) { @@ -41,15 +42,15 @@ export default class WorkflowController extends Controller { }, ]); if (!workflow) { - return res.status(404).json({ success: false }); + throw new HttpExceptions(`Workflow not found`, 404); } // 2. Check the requesting user is a manager of the custodian team let { _id: userId } = req.user; - let authorised = teamController.checkTeamPermissions(constants.roleTypes.MANAGER, workflow.publisher.team.toObject(), userId); - // 3. If not return unauthorised + let authorised = teamV3Util.checkUserRolesByTeam([constants.roleMemberTeam.CUST_DAR_MANAGER], workflow.publisher.team.toObject(), userId); if (!authorised) { - return res.status(401).json({ success: false }); + throw new HttpExceptions(`User not authorized to perform this action`,403); } + // 4. Build workflow response let { active, _id, id, workflowName, version, steps, applications = [] } = workflow.toObject(); applications = applications.map(app => { @@ -77,11 +78,8 @@ export default class WorkflowController extends Controller { }, }); } catch (err) { - console.error(err.message); - return res.status(500).json({ - success: false, - message: 'An error occurred searching for the specified workflow', - }); + process.stdout.write(`WORKFLOW - getWorkflowById : ${err.message}\n`); + throw new HttpExceptions(`An error occurred searching for the specified workflow`, 500); } } @@ -91,10 +89,7 @@ export default class WorkflowController extends Controller { // 1. Look at the payload for the publisher passed const { workflowName = '', publisher = '', steps = [] } = req.body; if (_.isEmpty(workflowName.trim()) || _.isEmpty(publisher.trim()) || _.isEmpty(steps)) { - return res.status(400).json({ - success: false, - message: 'You must supply a workflow name, publisher, and at least one step definition to create a workflow', - }); + throw new HttpExceptions(`You must supply a workflow name, publisher, and at least one step definition to create a workflow`, 400); } // 2. Look up publisher and team const publisherObj = await PublisherModel.findOne({ //lgtm [js/sql-injection] @@ -108,18 +103,13 @@ export default class WorkflowController extends Controller { }); if (!publisherObj) { - return res.status(400).json({ - success: false, - message: 'You must supply a valid publisher to create the workflow against', - }); + throw new HttpExceptions(`You must supply a valid publisher to create the workflow against`, 400); } - // 3. Check the requesting user is a manager of the custodian team - let authorised = teamController.checkTeamPermissions(constants.roleTypes.MANAGER, publisherObj.team.toObject(), userId); - - // 4. Refuse access if not authorised + let authorised = teamV3Util.checkUserRolesByTeam([constants.roleMemberTeam.CUST_DAR_MANAGER], publisherObj.team.toObject(), userId); if (!authorised) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); + throw new HttpExceptions(`User not authorized to perform this action`,403); } + // 5. Create new workflow model const id = helper.generatedNumericId(); // 6. set workflow obj for saving @@ -133,10 +123,7 @@ export default class WorkflowController extends Controller { // 7. save new workflow to db workflow = await workflow.save().catch(err => { if (err) { - return res.status(400).json({ - success: false, - message: err.message, - }); + throw new HttpExceptions(`ERROR SAVE WORKFLOW: ${err.message}`, 400); } }); // 8. populate the workflow with the needed fields for our new notification and email @@ -158,11 +145,8 @@ export default class WorkflowController extends Controller { workflow: detailedWorkflow, }); } catch (err) { - console.error(err.message); - return res.status(500).json({ - success: false, - message: 'An error occurred creating the workflow', - }); + process.stdout.write(`WORKFLOW - createWorkflow : ${err.message}\n`); + throw new HttpExceptions(`An error occurred creating the workflow`, 500); } } @@ -182,24 +166,20 @@ export default class WorkflowController extends Controller { }, }); if (!workflow) { - return res.status(404).json({ success: false }); + throw new HttpExceptions(`Workflow not Found`, 404); } - // 2. Check the requesting user is a manager of the custodian team - let authorised = teamController.checkTeamPermissions(constants.roleTypes.MANAGER, workflow.publisher.team.toObject(), userId); - // 3. Refuse access if not authorised + let authorised = teamV3Util.checkUserRolesByTeam([constants.roleMemberTeam.CUST_DAR_MANAGER], workflow.publisher.team.toObject(), userId); if (!authorised) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); + throw new HttpExceptions(`User not authorized to perform this action`,403); } + // 4. Ensure there are no in-review DARs with this workflow const applications = await DataRequestModel.countDocuments({ workflowId, applicationStatus: 'inReview', }); if (applications > 0) { - return res.status(400).json({ - success: false, - message: 'A workflow which is attached to applications currently in review cannot be edited', - }); + throw new HttpExceptions(`A workflow which is attached to applications currently in review cannot be edited`, 400); } // 5. Edit workflow const { workflowName = '', publisher = '', steps = [] } = req.body; @@ -216,10 +196,7 @@ export default class WorkflowController extends Controller { if (isDirty) { workflow = await workflow.save().catch(err => { if (err) { - return res.status(400).json({ - success: false, - message: err.message, - }); + throw new HttpExceptions(`ERROR SAVE WORKFLOW: ${err.message}`, 400); } }); @@ -233,10 +210,7 @@ export default class WorkflowController extends Controller { }, }); if (!publisherObj) { - return res.status(400).json({ - success: false, - message: 'You must supply a valid publisher to create the workflow against', - }); + throw new HttpExceptions(`You must supply a valid publisher to create the workflow against`, 400); } const detailedWorkflow = await WorkflowModel.findById(workflow._id).populate({ path: 'steps.reviewers', @@ -259,11 +233,8 @@ export default class WorkflowController extends Controller { }); } } catch (err) { - console.error(err.message); - return res.status(500).json({ - success: false, - message: 'An error occurred editing the workflow', - }); + process.stdout.write(`WORKFLOW - updateWorkflow : ${err.message}\n`); + throw new HttpExceptions(`An error occurred editing the workflow`, 500); } } @@ -285,24 +256,20 @@ export default class WorkflowController extends Controller { const { workflowName = '', publisher = {}, steps = [] } = workflow; if (!workflow) { - return res.status(404).json({ success: false }); + throw new HttpExceptions(`Workflow not Found`, 404); } - // 2. Check the requesting user is a manager of the custodian team - let authorised = teamController.checkTeamPermissions(constants.roleTypes.MANAGER, workflow.publisher.team.toObject(), userId); - // 3. Refuse access if not authorised + let authorised = teamV3Util.checkUserRolesByTeam([constants.roleMemberTeam.CUST_DAR_MANAGER], workflow.publisher.team.toObject(), userId); if (!authorised) { - return res.status(401).json({ status: 'failure', message: 'Unauthorised' }); + throw new HttpExceptions(`User not authorized to perform this action`,403); } + // 4. Ensure there are no in-review DARs with this workflow const applications = await DataRequestModel.countDocuments({ workflowId, applicationStatus: 'inReview', }); if (applications > 0) { - return res.status(400).json({ - success: false, - message: 'A workflow which is attached to applications currently in review cannot be deleted', - }); + throw new HttpExceptions(`A workflow which is attached to applications currently in review cannot be deleted`, 400); } const detailedWorkflow = await WorkflowModel.findById(workflowId).populate({ path: 'steps.reviewers', @@ -312,11 +279,8 @@ export default class WorkflowController extends Controller { // 5. Delete workflow WorkflowModel.deleteOne({ _id: workflowId }, function (err) { if (err) { - console.error(err.message); - return res.status(400).json({ - success: false, - message: 'An error occurred deleting the workflow', - }); + process.stdout.write(`WORKFLOW - deleteOne : ${err.message}\n`); + throw new HttpExceptions(`An error occurred deleting the workflow`, 400); } }); const publisherObj = await PublisherModel.findOne({ @@ -329,10 +293,7 @@ export default class WorkflowController extends Controller { }, }); if (!publisherObj) { - return res.status(400).json({ - success: false, - message: 'You must supply a valid publisher to create the workflow against', - }); + throw new HttpExceptions(`You must supply a valid publisher to create the workflow against`, 400); } let context = { publisherObj: publisherObj.team.toObject(), @@ -345,11 +306,8 @@ export default class WorkflowController extends Controller { success: true, }); } catch (err) { - console.error(err.message); - return res.status(500).json({ - success: false, - message: 'An error occurred deleting the workflow', - }); + process.stdout.write(`WORKFLOW - deleteWorkflow : ${err.message}\n`); + throw new HttpExceptions(`An error occurred deleting the workflow`, 500); } } } diff --git a/src/resources/workflow/workflow.service.js b/src/resources/workflow/workflow.service.js index 3fc4120c7..0d6d86f55 100644 --- a/src/resources/workflow/workflow.service.js +++ b/src/resources/workflow/workflow.service.js @@ -5,6 +5,7 @@ import teamController from '../team/team.controller'; import constants from '../utilities/constants.util'; import emailGenerator from '../utilities/emailGenerator.util'; import notificationBuilder from '../utilities/notificationBuilder'; +import teamV3Util from '../utilities/team.v3.util'; const bpmController = require('../bpmnworkflow/bpmnworkflow.controller'); @@ -22,7 +23,8 @@ export default class WorkflowService { // Check if the current user can override the current step if (has(accessRecord, 'publisherObj.team')) { const { team } = accessRecord.publisherObj; - isManager = teamController.checkTeamPermissions(constants.roleTypes.MANAGER, team, requestingUserObjectId); + // isManager = teamController.checkTeamPermissions(constants.roleTypes.MANAGER, team, requestingUserObjectId); + isManager = teamV3Util.checkUserRolesByTeam([constants.roleMemberTeam.CUST_DAR_MANAGER], team, requestingUserObjectId); // Set the workflow override capability if there is an active step and user is a manager if (!isEmpty(workflow)) { workflow.canOverrideStep = !workflow.isCompleted && isManager; @@ -141,7 +143,7 @@ export default class WorkflowService { getReviewManagers(team, requestingUserId) { const { members = [], users = [] } = team; const managers = members.filter(mem => { - return mem.roles.includes('manager'); + return mem.roles.includes(constants.roleMemberTeam.CUST_DAR_MANAGER); }); return users .filter(user => managers.some(manager => manager.memberid.toString() === user._id.toString())) @@ -162,14 +164,15 @@ export default class WorkflowService { // deconstruct context let { publisherObj, workflow = {}, actioner = '' } = context; - custodianManagers = teamController.getTeamMembersByRole(publisherObj, 'All'); - if (publisherObj.notifications[0].optIn) { + custodianManagers = teamController.getTeamMembersByRole(publisherObj, constants.roleMemberTeam.CUST_DAR_MANAGER); + if (has(publisherObj.notifications[0], 'optIn') && publisherObj.notifications[0].optIn) { publisherObj.notifications[0].subscribedEmails.map(teamEmail => { custodianManagers.push({ email: teamEmail }); }); } managerUserIds = custodianManagers.map(user => user.id); - let { workflowName = 'Workflow Title', _id, steps, createdAt } = workflow; + let { workflowName = 'Workflow Title', _id, steps, createdAt, publisher } = workflow; + const action = type.replace('Workflow', '').toLowerCase(); options = { actioner, @@ -189,7 +192,8 @@ export default class WorkflowService { managerUserIds, `A new workflow of ${workflowName} has been created`, 'workflow', - _id + _id, + publisher ); // 5. Generate the email html = await emailGenerator.generateWorkflowActionEmail(options); @@ -204,7 +208,8 @@ export default class WorkflowService { managerUserIds, `A workflow of ${workflowName} has been updated`, 'workflow', - _id + _id, + publisher ); // 5. Generate the email html = await emailGenerator.generateWorkflowActionEmail(options); @@ -219,7 +224,8 @@ export default class WorkflowService { managerUserIds, `A workflow of ${workflowName} has been deleted`, 'workflow', - _id + _id, + publisher ); // 5. Generate the email html = await emailGenerator.generateWorkflowActionEmail(options); diff --git a/src/services/cachePubSub/cachePubSubClient.js b/src/services/cachePubSub/cachePubSubClient.js index 215b7c1e9..246c4d486 100644 --- a/src/services/cachePubSub/cachePubSubClient.js +++ b/src/services/cachePubSub/cachePubSubClient.js @@ -11,14 +11,14 @@ export const publishMessageToChannel = async (channel, message) => { await client.connect(); } - client.on("connect", () => console.log("Redis cache is ready")); - client.on("error", (err) => console.log('Redis Client Error', err)); - client.on('ready', () => console.log('redis is running')); + client.on("connect", () => process.stdout.write(`Redis cache is ready`)); + client.on("error", (err) => process.stdout.write(`Redis Client Error : ${err.message}`)); + client.on('ready', () => process.stdout.write(`redis is running`)); await client.publish(channel, message); } catch (e) { - console.log(e); + process.stdout.write(`Redis Create Client Error : ${e.message}`); throw new Error(e.message); } } \ No newline at end of file diff --git a/src/services/datasetonboarding.service.js b/src/services/datasetonboarding.service.js index 545801489..6da1f77d0 100644 --- a/src/services/datasetonboarding.service.js +++ b/src/services/datasetonboarding.service.js @@ -209,7 +209,7 @@ export default class DatasetOnboardingService { ], }, }, - ]).exec(); + ]).allowDiskUse(true).exec(); const versionedDatasets = datasets[0].datasets.length > 0 ? datasets[0].datasets : []; diff --git a/src/services/google/PubSubService.js b/src/services/google/PubSubService.js index 9cd4789f5..f47042c51 100644 --- a/src/services/google/PubSubService.js +++ b/src/services/google/PubSubService.js @@ -15,9 +15,9 @@ export const publishMessageToPubSub = async (topicName, message) => { try { const messageId = pubSubClient.topic(topicName).publishMessage({data: dataBuffer}); - console.log(`Message ${messageId} published.`); + process.stdout.write(`Message ${messageId} published.`); } catch (error) { - console.error(error); + process.stdout.write(`publishMessageToPubSub : ${error.message}`); throw new Error(`Received error while publishing a message to PubSub`); } }; \ No newline at end of file diff --git a/src/services/google/PubSubWithRetryService.js b/src/services/google/PubSubWithRetryService.js index da06ddc73..d18dca054 100644 --- a/src/services/google/PubSubWithRetryService.js +++ b/src/services/google/PubSubWithRetryService.js @@ -74,5 +74,5 @@ export const publishMessageWithRetryToPubSub = async (topicName, message) => { const [response] = await publisherClient.publish(request, { retry: retrySettings, }); - console.log(`Message ${response.messageIds} published.`); + process.stdout.write(`Message ${response.messageIds} published.`); } \ No newline at end of file diff --git a/src/services/httpClient/httpClient.js b/src/services/httpClient/httpClient.js index c18ebb0d8..ea69afc88 100644 --- a/src/services/httpClient/httpClient.js +++ b/src/services/httpClient/httpClient.js @@ -24,7 +24,7 @@ class HttpClient { return response; } catch (err) { - console.error(err); + process.stdout.write(`HTTPCLIENT - POST : ${err.message}\n`); throw new Error(err.message); } } @@ -44,7 +44,7 @@ class HttpClient { return response; } catch (err) { - console.error(err); + process.stdout.write(`HTTPCLIENT - PUT : ${err.message}\n`); throw new Error(err.message); } } @@ -64,7 +64,7 @@ class HttpClient { return response; } catch (err) { - console.error(err); + process.stdout.write(`HTTPCLIENT - DELETE : ${err.message}\n`); throw new Error(err.message); } } diff --git a/src/services/hubspot/hubspot.js b/src/services/hubspot/hubspot.js index d1709e951..6e261f4ff 100644 --- a/src/services/hubspot/hubspot.js +++ b/src/services/hubspot/hubspot.js @@ -1,5 +1,4 @@ import { Client, NumberOfRetries } from '@hubspot/api-client'; -import * as Sentry from '@sentry/node'; import { isEmpty, get, isNil, isNull } from 'lodash'; import { UserModel } from '../../resources/user/user.model'; @@ -10,9 +9,8 @@ import { logger } from '../../resources/utilities/logger'; // Default service params const apiKey = process.env.HUBSPOT_API_KEY; const logCategory = 'Hubspot Integration'; -const readEnv = process.env.ENV || 'prod'; let hubspotClient; -if (apiKey) hubspotClient = new Client({ apiKey, numberOfApiCallRetries: NumberOfRetries.Three }); +if (apiKey) hubspotClient = new Client({ accessToken: apiKey, numberOfApiCallRetries: NumberOfRetries.Three }); /** * Sync A Single Gateway User With Hubspot @@ -140,15 +138,6 @@ const createContact = async gatewayUser => { const syncAllContacts = async () => { if (apiKey) { try { - // Track attempted sync in Sentry using log - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.addBreadcrumb({ - category: 'Hubspot', - message: `Syncing Gateway users with Hubspot contacts`, - level: Sentry.Severity.Log, - }); - } - // Batch import subscription changes from Hubspot await batchImportFromHubspot(); diff --git a/src/services/hubspot/hubspot.route.js b/src/services/hubspot/hubspot.route.js index ef9f92c23..4542c118f 100644 --- a/src/services/hubspot/hubspot.route.js +++ b/src/services/hubspot/hubspot.route.js @@ -1,8 +1,6 @@ import express from 'express'; -import * as Sentry from '@sentry/node'; import hubspotConnector from './hubspot'; const router = express.Router(); -const readEnv = process.env.ENV || 'prod'; // @router POST /api/v1/hubspot/sync // @desc Performs a two-way sync of contact details including communication opt in preferences between HubSpot and the Gateway database @@ -29,10 +27,7 @@ router.post('/sync', async (req, res) => { // Return response indicating job has started (do not await async import) return res.status(200).json({ success: true, message: 'Sync started' }); } catch (err) { - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.captureException(err); - } - console.error(err.message); + process.stdout.write(`HUBSPOT - SYNC : ${err.message}\n`); return res.status(500).json({ success: false, message: 'Sync failed' }); } }); diff --git a/src/services/mailchimp/mailchimp.js b/src/services/mailchimp/mailchimp.js index cc5b1d5be..a342ab031 100644 --- a/src/services/mailchimp/mailchimp.js +++ b/src/services/mailchimp/mailchimp.js @@ -1,5 +1,4 @@ import Mailchimp from 'mailchimp-api-v3'; -import * as Sentry from '@sentry/node'; import Crypto from 'crypto'; import constants from '../../resources/utilities/constants.util'; import { UserModel } from '../../resources/user/user.model'; @@ -13,7 +12,6 @@ let mailchimp; if (apiKey) mailchimp = new Mailchimp(apiKey); const tags = ['Gateway User']; const defaultSubscriptionStatus = constants.mailchimpSubscriptionStatuses.SUBSCRIBED; -const readEnv = process.env.ENV || 'prod'; /** * Create MailChimp Subscription Subscriber @@ -37,21 +35,10 @@ const addSubscriptionMember = async (subscriptionId, user, status) => { LNAME: lastname, }, }; - // 2. Track attempted update in Sentry using log - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.addBreadcrumb({ - category: 'MailChimp', - message: `Adding subscription for user: ${id} to subscription: ${subscriptionId}`, - level: Sentry.Severity.Log, - }); - } // 3. POST to MailChimp Marketing API to add the Gateway user to the MailChimp subscription members const md5email = Crypto.createHash('md5').update(email).digest('hex'); await mailchimp.put(`lists/${subscriptionId}/members/${md5email}`, body).catch(err => { - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.captureException(err); - } - console.error(`Message: ${err.message} Errors: ${JSON.stringify(err.errors)}`); + process.stdout.write(`MAILCHIP - addSubscriptionMember : ${id} - ${err.message}\n`); }); } }; @@ -104,22 +91,10 @@ const updateSubscriptionMembers = async (subscriptionId, members) => { skip_duplicate_check: true, update_existing: true, }; - // 4. Track attempted updates in Sentry using log - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.addBreadcrumb({ - category: 'MailChimp', - message: `Updating subscribed for members: ${members.map( - member => `${member.userId} to ${member.status}` - )} against subscription: ${subscriptionId}`, - level: Sentry.Severity.Log, - }); - } + // 5. POST to MailChimp Marketing API to update member statuses await mailchimp.post(`lists/${subscriptionId}`, body).catch(err => { - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.captureException(err); - } - console.error(`Message: ${err.message} Errors: ${JSON.stringify(err.errors)}`); + process.stdout.write(`MAILCHIP - updateSubscriptionMembers : ${err.message}\n`); }); } } @@ -134,22 +109,11 @@ const updateSubscriptionMembers = async (subscriptionId, members) => { */ const syncSubscriptionMembers = async subscriptionId => { if (apiKey) { - // 1. Track attempted sync in Sentry using log - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.addBreadcrumb({ - category: 'MailChimp', - message: `Syncing users for subscription: ${subscriptionId}`, - level: Sentry.Severity.Log, - }); - } // 2. Get total member count to anticipate chunking required to process all contacts const { stats: { member_count: subscribedCount, unsubscribe_count: unsubscribedCount }, } = await mailchimp.get(`lists/${subscriptionId}?fields=stats.member_count,stats.unsubscribe_count`).catch(err => { - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.captureException(err); - } - console.error(`Message: ${err.message} Errors: ${JSON.stringify(err.errors)}`); + process.stdout.write(`MAILCHIP - syncSubscriptionMembers : ${err.message}\n`); }); const memberCount = subscribedCount + unsubscribedCount; // 3. Batch update database to sync MailChimp to reflect users unsubscribed/subscribed externally diff --git a/src/services/mailchimp/mailchimp.route.js b/src/services/mailchimp/mailchimp.route.js index 7d3e451e5..f1b521c21 100644 --- a/src/services/mailchimp/mailchimp.route.js +++ b/src/services/mailchimp/mailchimp.route.js @@ -1,8 +1,6 @@ import express from 'express'; -import * as Sentry from '@sentry/node'; import mailchimpConnector from './mailchimp'; const router = express.Router(); -const readEnv = process.env.ENV || 'prod'; // @router GET /api/v1/mailchimp/:subscriptionId/sync // @desc Performs a two-way sync of opt in preferences between MailChimp and the Gateway database @@ -32,10 +30,7 @@ router.post('/sync', async (req, res) => { // Return response indicating job has started (do not await async import) return res.status(200).json({ success: true, message: 'Sync started' }); } catch (err) { - if (readEnv === 'test' || readEnv === 'prod') { - Sentry.captureException(err); - } - console.error(err.message); + process.stdout.write(`MAILCHIP - sync : ${err.message}\n`); return res.status(500).json({ success: false, message: 'Sync failed' }); } }); diff --git a/src/utils/datasetonboarding.util.js b/src/utils/datasetonboarding.util.js index 01d676ed9..a66d8d58d 100644 --- a/src/utils/datasetonboarding.util.js +++ b/src/utils/datasetonboarding.util.js @@ -68,16 +68,16 @@ const getUserPermissionsForDataset = async (id, user, publisherId) => { } if (!isEmpty(publisherTeam)) { - if (publisherTeam.roles.find(role => role.includes(constants.roleTypes.METADATA_EDITOR))) { - return { authorised: true, userType: constants.roleTypes.METADATA_EDITOR }; - } else if (publisherTeam.roles.find(role => role.includes(constants.roleTypes.MANAGER))) { - return { authorised: true, userType: constants.roleTypes.MANAGER }; + if (publisherTeam.roles.find(role => role.includes(constants.roleMemberTeam.CUST_MD_MANAGER))) { + return { authorised: true, userType: constants.roleMemberTeam.CUST_MD_MANAGER }; + } else if (publisherTeam.roles.find(role => role.includes(constants.roleMemberTeam.CUST_MD_EDITOR))) { + return { authorised: true, userType: constants.roleMemberTeam.CUST_MD_EDITOR }; } } return { authorised, userType }; } catch (error) { - console.error(error); + process.stdout.write(`DATASETONBOARDING - getUserPermissionsForDataset : ${error.message}\n`); return { authorised: false, userType: '' }; } }; @@ -335,7 +335,7 @@ const updateDataset = async (dataset, updateObj) => { // 2. If application is in progress, update initial question answers if (activeflag === constants.datasetStatuses.DRAFT || activeflag === constants.applicationStatuses.INREVIEW) { await Data.findByIdAndUpdate(_id, updateObj, { new: true }).catch(err => { - console.error(err); + process.stdout.write(`DATASETONBOARDING - Error updateDataset\n`); throw err; }); return dataset; @@ -758,7 +758,7 @@ const buildMetadataQuality = async (dataset, v2Object, pid) => { let rawdata = fs.readFileSync(__dirname + '/schema.json'); schema = JSON.parse(rawdata); - const ajv = new Ajv({ strict: false, allErrors: true }); + const ajv = new Ajv({ strict: false, allErrors: false }); addFormats(ajv); const validate = ajv.compile(schema); validate(cleanV2Object); @@ -932,25 +932,29 @@ const createNotifications = async (type, context) => { const isFederated = !_.isUndefined(team.publisher.federation) && team.publisher.federation.active; for (let member of team.members) { - teamMembers.push(member.memberid); + if ((Array.isArray(member.roles) && member.roles.some(role => ['manager', 'metadata_editor'].includes(role))) + || (typeof member.roles === 'string' && ['manager', 'metadata_editor'].includes(member.roles))) { + teamMembers.push(member.memberid); + } } teamMembersDetails = await UserModel.find({ _id: { $in: teamMembers } }) .populate('additionalInfo') .lean(); - for (let member of team.members) { - if (member.roles.some(role => ['manager', 'metadata_editor'].includes(role))) teamMembers.push(member.memberid); + for (let member of teamMembersDetails) { + teamMembersIds.push(member.id); } + // 2. Create user notifications - notificationBuilder.triggerNotificationMessage( - teamMembersIds, - context.datasetVersion !== '1.0.0' - ? `Your dataset version for "${context.name}" has been reviewed and rejected` - : `A dataset "${context.name}" has been reviewed and rejected`, - 'dataset rejected', - context.datasetv2.summary.publisher.identifier - ); + // notificationBuilder.triggerNotificationMessage( + // teamMembersIds, + // context.datasetVersion !== '1.0.0' + // ? `Your dataset version for "${context.name}" has been reviewed and rejected` + // : `A dataset "${context.name}" has been reviewed and rejected`, + // 'dataset rejected', + // context.datasetv2.summary.publisher.identifier + // ); // 3. Create email options = { name: context.name, diff --git a/src/utils/schema.json b/src/utils/schema.json index 4db807584..470c9384b 100644 --- a/src/utils/schema.json +++ b/src/utils/schema.json @@ -13,7 +13,7 @@ "title": "Dataset identifier", "$comment": "http://purl.org/dc/terms/identifier", "examples": [ - ["226fb3f1-4471-400a-8c39-2b66d46a39b6", "https://web.www.healthdatagateway.org/dataset/226fb3f1-4471-400a-8c39-2b66d46a39b6"] + ["226fb3f1-4471-400a-8c39-2b66d46a39b6", "https://web.old.healthdatagateway.org/dataset/226fb3f1-4471-400a-8c39-2b66d46a39b6"] ], "description": "System dataset identifier", "anyOf": [ diff --git a/test/routes.test.js b/test/routes.wrong.js similarity index 100% rename from test/routes.test.js rename to test/routes.wrong.js
@@ -2041,7 +2039,7 @@ const _generateMetadataOnboardingSumbitted = options => {
- View datasets pending approval + View datasets pending approval
- View dataset dashboard + View dataset dashboard
- View dataset dashboard + View dataset dashboard
- View dataset dashboard + View dataset dashboard