From dddb36c39f61713cd55513583b2e74dd0c13de0e Mon Sep 17 00:00:00 2001 From: Marcus Aspin Date: Thu, 2 Jan 2025 13:59:11 +0000 Subject: [PATCH] PI-2692 Initial screens to list and view notifications (#5) --- .github/workflows/pipeline.yml | 11 +- assets/scss/local.scss | 17 + .../values.yaml | 19 +- helm_deploy/values-dev.yaml | 1 - helm_deploy/values-preprod.yaml | 1 - helm_deploy/values-prod.yaml | 1 - integration_tests/e2e/list.cy.ts | 75 ++ integration_tests/e2e/notification.cy.ts | 12 + integration_tests/pages/index.ts | 2 +- package-lock.json | 721 +++++++++++++----- package.json | 16 +- .../notifications-node-client/index.d.ts | 120 +++ server/config.ts | 4 + server/middleware/asyncMiddleware.ts | 7 - .../authorisationMiddleware.test.ts | 15 +- server/middleware/authorisationMiddleware.ts | 16 +- server/routes/index.test.ts | 40 - server/routes/index.ts | 177 ++++- server/services/auditService.test.ts | 4 +- server/services/auditService.ts | 6 +- server/utils/nunjucksSetup.ts | 8 +- server/utils/url.ts | 17 + server/utils/utils.ts | 18 + server/views/pages/index.njk | 11 - server/views/pages/list.njk | 116 +++ server/views/pages/notification.njk | 46 ++ server/views/partials/header.njk | 2 +- wiremock/mappings/notifications-api.json | 187 +++++ 28 files changed, 1372 insertions(+), 298 deletions(-) create mode 100644 integration_tests/e2e/list.cy.ts create mode 100644 integration_tests/e2e/notification.cy.ts create mode 100644 server/@types/notifications-node-client/index.d.ts delete mode 100644 server/middleware/asyncMiddleware.ts delete mode 100644 server/routes/index.test.ts create mode 100644 server/utils/url.ts delete mode 100644 server/views/pages/index.njk create mode 100644 server/views/pages/list.njk create mode 100644 server/views/pages/notification.njk create mode 100644 wiremock/mappings/notifications-api.json diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index d574b6a..5993b63 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -9,11 +9,6 @@ on: required: false default: '' type: string - push: - description: Push docker image to registry flag - required: true - default: false - type: boolean permissions: contents: read @@ -56,7 +51,7 @@ jobs: run: | cp .env.example .env npm run wiremock & - npm run start & + npm run dev-server & timeout 10 sh -c 'until curl -s localhost:3000; do sleep 1; done' - name: Run tests run: npm run int-test @@ -79,13 +74,13 @@ jobs: environment: ${{ matrix.environment }} docker_build: - if: github.ref_name == 'main' + if: github.ref_name == 'main' || github.event_name == 'workflow_dispatch' uses: ministryofjustice/hmpps-github-actions/.github/workflows/docker_build.yml@v2 with: docker_registry: 'ghcr.io' registry_org: 'ministryofjustice' additional_docker_tag: ${{ inputs.additional_docker_tag }} - push: ${{ inputs.push || true }} + push: true docker_multiplatform: true deploy_dev: diff --git a/assets/scss/local.scss b/assets/scss/local.scss index 35cba2b..62274a8 100644 --- a/assets/scss/local.scss +++ b/assets/scss/local.scss @@ -1,3 +1,20 @@ .govuk-main-wrapper { min-height: 600px; } + +.govuk-\!-width-auto { + width: auto !important; +} + +.secondary-text { + color: $govuk-secondary-text-colour; + @include govuk-font-size($size: 16); +} + +.moj-filter__header { + display: none; +} + +.moj-filter__options .moj-datepicker input { + max-width: none; +} diff --git a/helm_deploy/hmpps-appointment-reminders-ui/values.yaml b/helm_deploy/hmpps-appointment-reminders-ui/values.yaml index e6b94cf..9fc6ff1 100644 --- a/helm_deploy/hmpps-appointment-reminders-ui/values.yaml +++ b/helm_deploy/hmpps-appointment-reminders-ui/values.yaml @@ -30,29 +30,32 @@ generic-service: REDIS_TLS_ENABLED: 'true' TOKEN_VERIFICATION_ENABLED: 'true' AUDIT_SQS_REGION: 'eu-west-2' - AUDIT_SERVICE_NAME: 'hmpps-appointment-reminders-ui' # Your audit service name + AUDIT_SERVICE_NAME: 'hmpps-appointment-reminders-ui' + AUDIT_ENABLED: 'true' # Pre-existing kubernetes secrets to load as environment variables in the deployment. namespace_secrets: - hmpps-appointment-reminders-ui: + hmpps-auth: AUTH_CODE_CLIENT_ID: 'AUTH_CODE_CLIENT_ID' AUTH_CODE_CLIENT_SECRET: 'AUTH_CODE_CLIENT_SECRET' CLIENT_CREDS_CLIENT_ID: 'CLIENT_CREDS_CLIENT_ID' CLIENT_CREDS_CLIENT_SECRET: 'CLIENT_CREDS_CLIENT_SECRET' - SESSION_SECRET: 'SESSION_SECRET' + govuk-notify: + NOTIFY_API_KEY: 'API_KEY' elasticache-redis: + SESSION_SECRET: 'session_secret' REDIS_HOST: 'primary_endpoint_address' REDIS_AUTH_TOKEN: 'auth_token' application-insights: APPLICATIONINSIGHTS_CONNECTION_STRING: "APPLICATIONINSIGHTS_CONNECTION_STRING" - # This secret will need to be created in your namespace (note it isn't in hmpps-templates-dev) - # IRSA configuration in your namespace will also be required, and then uncomment serviceAccountName above. - # sqs-hmpps-audit-secret: - # AUDIT_SQS_QUEUE_URL: 'sqs_queue_url' + sqs-hmpps-audit-secret: + AUDIT_SQS_QUEUE_URL: 'sqs_queue_url' allowlist: groups: - - internal + - digital_staff_and_mojo + - unilink_staff + - probation generic-prometheus-alerts: targetApplication: hmpps-appointment-reminders-ui diff --git a/helm_deploy/values-dev.yaml b/helm_deploy/values-dev.yaml index aa3bc4e..60742b9 100644 --- a/helm_deploy/values-dev.yaml +++ b/helm_deploy/values-dev.yaml @@ -12,7 +12,6 @@ generic-service: HMPPS_AUTH_URL: "https://sign-in-dev.hmpps.service.justice.gov.uk/auth" TOKEN_VERIFICATION_API_URL: "https://token-verification-api-dev.prison.service.justice.gov.uk" ENVIRONMENT_NAME: DEV - AUDIT_ENABLED: "false" generic-prometheus-alerts: alertSeverity: probation-integration-notifications diff --git a/helm_deploy/values-preprod.yaml b/helm_deploy/values-preprod.yaml index f4a3ede..16e264a 100644 --- a/helm_deploy/values-preprod.yaml +++ b/helm_deploy/values-preprod.yaml @@ -12,7 +12,6 @@ generic-service: HMPPS_AUTH_URL: "https://sign-in-preprod.hmpps.service.justice.gov.uk/auth" TOKEN_VERIFICATION_API_URL: "https://token-verification-api-preprod.prison.service.justice.gov.uk" ENVIRONMENT_NAME: PRE-PRODUCTION - AUDIT_ENABLED: "false" generic-prometheus-alerts: alertSeverity: probation-integration-notifications diff --git a/helm_deploy/values-prod.yaml b/helm_deploy/values-prod.yaml index cc684d2..b5f6bbe 100644 --- a/helm_deploy/values-prod.yaml +++ b/helm_deploy/values-prod.yaml @@ -9,7 +9,6 @@ generic-service: INGRESS_URL: "https://appointment-reminders.hmpps.service.justice.gov.uk" HMPPS_AUTH_URL: "https://sign-in.hmpps.service.justice.gov.uk/auth" TOKEN_VERIFICATION_API_URL: "https://token-verification-api.prison.service.justice.gov.uk" - AUDIT_ENABLED: "false" generic-prometheus-alerts: alertSeverity: probation-integration-notifications diff --git a/integration_tests/e2e/list.cy.ts b/integration_tests/e2e/list.cy.ts new file mode 100644 index 0000000..794c678 --- /dev/null +++ b/integration_tests/e2e/list.cy.ts @@ -0,0 +1,75 @@ +import Page from '../pages/page' +import IndexPage from '../pages' + +context('List page', () => { + it('can filter results', () => { + cy.visit('/') + Page.verifyOnPage(IndexPage) + + // Starts with 2 results + cy.get('#result-count').should('contain.text', 'Showing 2 results.') + cy.get('table tbody tr.govuk-table__row').should('have.length', 2) + + // Filter on template + status + cy.get('#template').click() + cy.get('#status').click() + cy.get('#apply-filters-button').click() + + // Now has 1 result, and 2 filters applied + cy.get('#result-count').should('contain.text', 'Showing 1 result.') + cy.get('table tbody tr.govuk-table__row').should('have.length', 1) + cy.get('.moj-filter__selected').should('exist').should('contain.text', 'Selected filters') + cy.get('.moj-filter__tag').should('have.length', 2) + + // Remove first filter + cy.get('.moj-filter__tag').first().click() + cy.get('#result-count').should('contain.text', 'Showing 2 results.') + cy.get('table tbody tr.govuk-table__row').should('have.length', 2) + cy.get('.moj-filter__selected').should('exist').should('contain.text', 'Selected filters') + cy.get('.moj-filter__tag').should('have.length', 1) + + // Clear all filters + cy.get('.moj-filter__heading-action a').click() + cy.get('.moj-filter__selected').should('not.exist') + cy.get('.moj-filter__tag').should('not.exist') + }) + + it('can search for keywords from message body', () => { + cy.visit('/') + + cy.get('#keywords').type('test2') + cy.get('#apply-filters-button').click() + + cy.get('#keywords').should('have.value', 'test2') + cy.get('#result-count').should('contain.text', 'Showing 1 result.') + cy.get('table tbody tr.govuk-table__row').should('have.length', 1).should('contain.text', 'Dear Test2') + }) + + it('can search for CRN', () => { + cy.visit('/') + + cy.get('#keywords').type('A000001') + cy.get('#apply-filters-button').click() + + cy.get('#keywords').should('have.value', 'A000001') + cy.get('#result-count').should('contain.text', 'Showing 1 result.') + cy.get('table tbody tr.govuk-table__row').should('have.length', 1).should('contain.text', 'A000001') + }) + + it('can search for phone number', () => { + cy.visit('/') + + cy.get('#keywords').type('07700900000') + cy.get('#apply-filters-button').click() + + cy.get('#keywords').should('have.value', '07700900000') + cy.get('#result-count').should('contain.text', 'Showing 1 result.') + cy.get('table tbody tr.govuk-table__row').should('have.length', 1).should('contain.text', '07700900000') + }) + + it('can navigate to notification screen', () => { + cy.visit('/') + cy.get('table tbody tr.govuk-table__row a').first().click() + cy.get('h1').should('have.text', 'Text message') + }) +}) diff --git a/integration_tests/e2e/notification.cy.ts b/integration_tests/e2e/notification.cy.ts new file mode 100644 index 0000000..194a586 --- /dev/null +++ b/integration_tests/e2e/notification.cy.ts @@ -0,0 +1,12 @@ +context('Notification page', () => { + it('displays message', () => { + cy.visit('/notification/00000000-0000-0000-0000-000000000001') + cy.get('body').should('contain.text', "'Unpaid Work Appointment Reminder' was sent to 07700900000.") + }) + + it('can navigate back to list screen', () => { + cy.visit('/notification/00000000-0000-0000-0000-000000000001') + cy.get('a.govuk-back-link').click() + cy.get('h1').should('have.text', 'Sent reminders') + }) +}) diff --git a/integration_tests/pages/index.ts b/integration_tests/pages/index.ts index 4de3d1c..070a747 100644 --- a/integration_tests/pages/index.ts +++ b/integration_tests/pages/index.ts @@ -2,7 +2,7 @@ import Page, { PageElement } from './page' export default class IndexPage extends Page { constructor() { - super('This site is under construction...') + super('Sent reminders') } headerUserName = (): PageElement => cy.get('[data-qa=header-user-name]') diff --git a/package-lock.json b/package-lock.json index cb96e85..2f2a5a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,13 @@ "license": "MIT", "dependencies": { "@aws-sdk/client-sqs": "^3.709.0", + "@js-joda/core": "^5.6.3", + "@js-joda/locale": "^4.14.1", + "@js-joda/locale_en": "^4.14.0", + "@json2csv/plainjs": "^7.0.6", "@ministryofjustice/frontend": "^3.2.1", "@ministryofjustice/hmpps-monitoring": "^0.0.1-beta.2", + "@types/qs": "^6.9.17", "agentkeepalive": "^4.5.0", "applicationinsights": "^2.9.6", "body-parser": "^1.20.3", @@ -21,13 +26,14 @@ "connect-flash": "^0.1.1", "connect-redis": "^8.0.1", "csrf-sync": "^4.0.3", - "express": "^4.21.2", + "express": "^5.0.1", "express-session": "^1.18.1", "govuk-frontend": "^5.7.1", "helmet": "^8.0.0", "http-errors": "^2.0.0", "jwt-decode": "^4.0.0", "nocache": "^4.0.0", + "notifications-node-client": "^8.2.1", "nunjucks": "^3.2.4", "passport": "^0.7.0", "passport-oauth2": "^1.8.0", @@ -2100,7 +2106,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -2118,7 +2123,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -2131,14 +2135,12 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -2156,7 +2158,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -2709,6 +2710,60 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@js-joda/core": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-5.6.3.tgz", + "integrity": "sha512-T1rRxzdqkEXcou0ZprN1q9yDRlvzCPLqmlNt5IIsGBzoEVgLCCYrKEwc84+TvsXuAc95VAZwtWD2zVsKPY4bcA==", + "license": "BSD-3-Clause" + }, + "node_modules/@js-joda/locale": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@js-joda/locale/-/locale-4.14.1.tgz", + "integrity": "sha512-YXn8NfxFDQeKRm/P4/iRnE77Y5EjYJd+4yzIJ277YDPrCZohcAv/z7o7lrVmfSsn+oTigRs2RapMTeKA8kVD8A==", + "license": "BSD-3-Clause", + "peerDependencies": { + "@js-joda/core": ">=3.2.0", + "@js-joda/timezone": "^2.3.0", + "cldr-data": "*", + "cldrjs": "^0.5.4" + } + }, + "node_modules/@js-joda/locale_en": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@js-joda/locale_en/-/locale_en-4.14.0.tgz", + "integrity": "sha512-gczCGYajB0DDiVFFLpNY6pNNpHZNXWH1TNigIeXJmwJ6YcQKug+/243oOObB7zW2W4iPaU5O8Bow86mFEMtCsw==", + "license": "BSD-3-Clause", + "peerDependencies": { + "@js-joda/core": ">=3.2.0", + "@js-joda/timezone": "^2.3.0" + } + }, + "node_modules/@js-joda/timezone": { + "version": "2.21.1", + "resolved": "https://registry.npmjs.org/@js-joda/timezone/-/timezone-2.21.1.tgz", + "integrity": "sha512-QOWH24q+6Z0bvYjuiQ5rNlJMWTbogsQkgL1EV35n0jy9msh2I9bgxwryGAFE/N6w8FWQJR+8p4/mIT7+nAb43g==", + "license": "BSD-3-Clause", + "peer": true, + "peerDependencies": { + "@js-joda/core": ">=1.11.0" + } + }, + "node_modules/@json2csv/formatters": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@json2csv/formatters/-/formatters-7.0.6.tgz", + "integrity": "sha512-hjIk1H1TR4ydU5ntIENEPgoMGW+Q7mJ+537sDFDbsk+Y3EPl2i4NfFVjw0NJRgT+ihm8X30M67mA8AS6jPidSA==", + "license": "MIT" + }, + "node_modules/@json2csv/plainjs": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@json2csv/plainjs/-/plainjs-7.0.6.tgz", + "integrity": "sha512-4Md7RPDCSYpmW1HWIpWBOqCd4vWfIqm53S3e/uzQ62iGi7L3r34fK/8nhOMEe+/eVfCx8+gdSCt1d74SlacQHw==", + "license": "MIT", + "dependencies": { + "@json2csv/formatters": "^7.0.6", + "@streamparser/json": "^0.0.20" + } + }, "node_modules/@microsoft/applicationinsights-web-snippet": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-snippet/-/applicationinsights-web-snippet-1.0.1.tgz", @@ -3236,7 +3291,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "license": "MIT", "optional": true, "peer": true, @@ -3949,6 +4003,12 @@ "node": ">=16.0.0" } }, + "node_modules/@streamparser/json": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@streamparser/json/-/json-0.0.20.tgz", + "integrity": "sha512-VqAAkydywPpkw63WQhPVKCD3SdwXuihCUVZbbiY3SfSTGQyHmwRoq27y4dmJdZuJwd5JIlQoMPyGvMbUPY0RKQ==", + "license": "MIT" + }, "node_modules/@tsconfig/node22": { "version": "22.0.0", "resolved": "https://registry.npmjs.org/@tsconfig/node22/-/node22-22.0.0.tgz", @@ -4256,7 +4316,6 @@ "version": "6.9.17", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", - "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { @@ -4577,23 +4636,42 @@ "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==", "license": "MIT" }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC", + "peer": true + }, "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", + "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.53.0" }, "engines": { "node": ">= 0.6" } }, "node_modules/accepts/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -4728,7 +4806,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4738,7 +4815,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -4853,9 +4929,9 @@ } }, "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", + "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==", "license": "MIT" }, "node_modules/array-includes": { @@ -5133,6 +5209,23 @@ "dev": true, "license": "MIT" }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -5263,7 +5356,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "devOptional": true, "license": "MIT" }, "node_modules/base64-js": { @@ -5382,7 +5474,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -5502,7 +5593,6 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, "license": "MIT", "engines": { "node": "*" @@ -5512,7 +5602,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/buffer-from": { @@ -5744,6 +5833,105 @@ "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", "license": "MIT" }, + "node_modules/cldr-data": { + "version": "36.0.2", + "resolved": "https://registry.npmjs.org/cldr-data/-/cldr-data-36.0.2.tgz", + "integrity": "sha512-JaZY8l0LuJNqc8USVrFPH0KWkYCRxYUB34ALr3TZFHOXDS3R4x5M49d/NTp8Cbucjh2l1Xk2cl9yif1RdWBGBw==", + "hasInstallScript": true, + "peer": true, + "dependencies": { + "cldr-data-downloader": "1.0.0-1", + "glob": "10.3.12" + } + }, + "node_modules/cldr-data-downloader": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cldr-data-downloader/-/cldr-data-downloader-1.1.0.tgz", + "integrity": "sha512-xg1GKFP4FOe4GEDkANb8ATz67e1tqJ6GGaRMTYJNNgRwr/9WL+qvlDU4nW9/Iw8gA6NISEfd/+XFNOFkuimaOQ==", + "peer": true, + "dependencies": { + "axios": "^1.7.2", + "mkdirp": "^1.0.4", + "nopt": "3.0.x", + "q": "1.0.1", + "yauzl": "^2.10.0" + }, + "bin": { + "cldr-data-downloader": "bin/download.sh" + } + }, + "node_modules/cldr-data/node_modules/glob": { + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "license": "ISC", + "peer": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cldr-data/node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "license": "BlueOak-1.0.0", + "peer": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/cldr-data/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC", + "peer": true + }, + "node_modules/cldr-data/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "peer": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cldrjs": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/cldrjs/-/cldrjs-0.5.5.tgz", + "integrity": "sha512-KDwzwbmLIPfCgd8JERVDpQKrUUM1U4KpFJJg2IROv89rF172lLufoJnqJ/Wea6fXL5bO6WjuLMzY8V52UWPvkA==", + "peer": true + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -5887,7 +6075,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -5900,7 +6087,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/colorette": { @@ -6065,9 +6251,9 @@ } }, "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -6112,10 +6298,13 @@ } }, "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } }, "node_modules/cookiejar": { "version": "2.1.4", @@ -6156,7 +6345,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -6612,7 +6800,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, "license": "MIT" }, "node_modules/ecc-jsbn": { @@ -6630,7 +6817,6 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dev": true, "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" @@ -6691,7 +6877,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/encodeurl": { @@ -7622,49 +7807,46 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz", + "integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==", "license": "MIT", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "accepts": "^2.0.0", + "body-parser": "^2.0.1", + "content-disposition": "^1.0.0", "content-type": "~1.0.4", "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", + "cookie-signature": "^1.2.1", + "debug": "4.3.6", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", + "finalhandler": "^2.0.0", + "fresh": "2.0.0", "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", + "merge-descriptors": "^2.0.0", "methods": "~1.1.2", + "mime-types": "^3.0.0", "on-finished": "2.4.1", + "once": "1.4.0", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", + "router": "^2.0.0", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "^1.1.0", + "serve-static": "^2.1.0", "setprototypeof": "1.2.0", "statuses": "2.0.1", - "type-is": "~1.6.18", + "type-is": "^2.0.0", "utils-merge": "1.0.1", "vary": "~1.1.2" }, "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 18" } }, "node_modules/express-session": { @@ -7716,21 +7898,173 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/express/node_modules/body-parser": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.0.2.tgz", + "integrity": "sha512-SNMk0OONlQ01uk8EPeiBvTW7W4ovpL5b1O3t1sjpPgfxOQ6BqQJ6XjxinDPR79Z6HdcD5zBBwr5ssiTlgdNztQ==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "3.1.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.5.2", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "^3.0.0", + "type-is": "~1.6.18" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/express/node_modules/body-parser/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "license": "MIT", "dependencies": { "ms": "2.0.0" } }, - "node_modules/express/node_modules/ms": { + "node_modules/express/node_modules/body-parser/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/body-parser/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/express/node_modules/body-parser/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/express/node_modules/iconv-lite": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", + "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", + "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.53.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/express/node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/express/node_modules/type-is": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz", + "integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/type-is/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -7866,7 +8200,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, "license": "MIT", "dependencies": { "pend": "~1.2.0" @@ -7948,13 +8281,13 @@ } }, "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.0.0.tgz", + "integrity": "sha512-MX6Zo2adDViYh+GcxxB1dpO43eypOGUOL12rLCOTMQv/DfIbpSJUy4oQIIZhVZkH9e+bZWKMon0XHFEju16tkQ==", "license": "MIT", "dependencies": { "debug": "2.6.9", - "encodeurl": "~2.0.0", + "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -7974,6 +8307,15 @@ "ms": "2.0.0" } }, + "node_modules/finalhandler/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -8029,6 +8371,26 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -8043,7 +8405,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", - "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.0", @@ -8060,7 +8421,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -8117,12 +8477,12 @@ } }, "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/from": { @@ -9080,7 +9440,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9226,6 +9585,12 @@ "node": ">=8" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.0.tgz", @@ -9412,7 +9777,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/isstream": { @@ -10504,7 +10868,6 @@ "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", - "dev": true, "license": "MIT", "dependencies": { "jws": "^3.2.2", @@ -10550,7 +10913,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "dev": true, "license": "MIT", "dependencies": { "buffer-equal-constant-time": "1.0.1", @@ -10562,7 +10924,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "dev": true, "license": "MIT", "dependencies": { "jwa": "^1.4.1", @@ -11195,42 +11556,36 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "dev": true, "license": "MIT" }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "dev": true, "license": "MIT" }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "dev": true, "license": "MIT" }, "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "dev": true, "license": "MIT" }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true, "license": "MIT" }, "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "dev": true, "license": "MIT" }, "node_modules/lodash.memoize": { @@ -11251,7 +11606,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true, "license": "MIT" }, "node_modules/log-symbols": { @@ -11395,10 +11749,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", + "engines": { + "node": ">=18" + }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -11443,18 +11800,6 @@ "node": ">=8.6" } }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/mime-db": { "version": "1.53.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", @@ -11512,7 +11857,6 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -11538,7 +11882,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -11548,7 +11891,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" @@ -11937,16 +12279,6 @@ "@sinonjs/commons": "^3.0.1" } }, - "node_modules/nise/node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - } - }, "node_modules/nocache": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/nocache/-/nocache-4.0.0.tgz", @@ -11993,6 +12325,19 @@ "dev": true, "license": "MIT" }, + "node_modules/nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", + "license": "ISC", + "peer": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -12003,6 +12348,20 @@ "node": ">=0.10.0" } }, + "node_modules/notifications-node-client": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/notifications-node-client/-/notifications-node-client-8.2.1.tgz", + "integrity": "sha512-wyZh/NbjN8S2uQX18utYtCyC726BBaGeTc4HeUpdhZv5sYKuaQY94N31v9syh8SzVgehyMzW37y08EePmi+k3Q==", + "license": "MIT", + "dependencies": { + "axios": "^1.7.2", + "jsonwebtoken": "^9.0.2" + }, + "engines": { + "node": ">=14.17.3", + "npm": ">=6.14.13" + } + }, "node_modules/npm-java-runner": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/npm-java-runner/-/npm-java-runner-1.0.2.tgz", @@ -12403,7 +12762,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12443,10 +12801,13 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } }, "node_modules/path-type": { "version": "4.0.0", @@ -12480,7 +12841,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true, "license": "MIT" }, "node_modules/performance-now": { @@ -12816,6 +13176,18 @@ ], "license": "MIT" }, + "node_modules/q": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.0.1.tgz", + "integrity": "sha512-18MnBaCeBX9sLRUdtxz/6onlb7wLzFxCylklyO8n27y5JxJYaGLPu4ccyc5zih58SpEzY8QmfwaWqguqXU6Y+A==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -13208,6 +13580,24 @@ "node": "*" } }, + "node_modules/router": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.0.0.tgz", + "integrity": "sha512-dIM5zVoG8xhC6rnSN8uoAgFARwTE7BQs8YwHEvK0VCmfxQXMaOuA1uiR1IPwsW7JyK5iTt7Od/TC9StasS2NPQ==", + "license": "MIT", + "dependencies": { + "array-flatten": "3.0.0", + "is-promise": "4.0.0", + "methods": "~1.1.2", + "parseurl": "~1.3.3", + "path-to-regexp": "^8.0.0", + "setprototypeof": "1.2.0", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -13789,51 +14179,35 @@ } }, "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz", + "integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "debug": "^4.3.5", + "destroy": "^1.2.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^0.5.2", + "http-errors": "^2.0.0", + "mime-types": "^2.1.35", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" } }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "node_modules/send/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, "node_modules/serialize-javascript": { @@ -13848,18 +14222,18 @@ } }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.1.0.tgz", + "integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==", "license": "MIT", "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.0.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" } }, "node_modules/set-function-length": { @@ -13905,7 +14279,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -13918,7 +14291,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -14213,7 +14585,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -14229,7 +14600,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -14296,7 +14666,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -14310,7 +14679,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -15151,7 +15519,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -15285,7 +15652,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -15304,7 +15670,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -15322,7 +15687,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -15335,7 +15699,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -15348,14 +15711,12 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, "license": "MIT" }, "node_modules/wrap-ansi/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -15373,7 +15734,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -15544,7 +15904,6 @@ "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, "license": "MIT", "dependencies": { "buffer-crc32": "~0.2.3", diff --git a/package.json b/package.json index 93806e2..bd4fea1 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,9 @@ "prepare": "husky", "build": "node esbuild/esbuild.config.js --build", "build:watch": "node esbuild/esbuild.config.js --build --watch", - "start": "node esbuild/esbuild.config.js --dev-server", - "start:dev": "concurrently -k -n 'Build,Server,WireMock' 'npm run build:watch' 'npm run start' 'npm run wiremock'", + "dev-server": "node esbuild/esbuild.config.js --dev-server", + "start:dev": "concurrently -k -n 'Build,Server,WireMock' 'npm run build:watch' 'npm run dev-server' 'npm run wiremock'", + "start": "node $NODE_OPTIONS dist/server.js | bunyan -o short", "wiremock": "wiremock --port 9090 --root-dir wiremock --local-response-templating", "lint": "eslint . --cache --max-warnings 0", "lint-fix": "eslint . --cache --max-warnings 0 --fix", @@ -76,8 +77,13 @@ }, "dependencies": { "@aws-sdk/client-sqs": "^3.709.0", + "@js-joda/core": "^5.6.3", + "@js-joda/locale": "^4.14.1", + "@js-joda/locale_en": "^4.14.0", + "@json2csv/plainjs": "^7.0.6", "@ministryofjustice/frontend": "^3.2.1", "@ministryofjustice/hmpps-monitoring": "^0.0.1-beta.2", + "@types/qs": "^6.9.17", "agentkeepalive": "^4.5.0", "applicationinsights": "^2.9.6", "body-parser": "^1.20.3", @@ -87,13 +93,14 @@ "connect-flash": "^0.1.1", "connect-redis": "^8.0.1", "csrf-sync": "^4.0.3", - "express": "^4.21.2", + "express": "^5.0.1", "express-session": "^1.18.1", "govuk-frontend": "^5.7.1", "helmet": "^8.0.0", "http-errors": "^2.0.0", "jwt-decode": "^4.0.0", "nocache": "^4.0.0", + "notifications-node-client": "^8.2.1", "nunjucks": "^3.2.4", "passport": "^0.7.0", "passport-oauth2": "^1.8.0", @@ -146,5 +153,8 @@ "ts-jest": "^29.2.5", "typescript": "^5.7.2", "wiremock": "^3.10.0" + }, + "overrides": { + "cldr-data-downloader": "1.1.0" } } diff --git a/server/@types/notifications-node-client/index.d.ts b/server/@types/notifications-node-client/index.d.ts new file mode 100644 index 0000000..419c3e9 --- /dev/null +++ b/server/@types/notifications-node-client/index.d.ts @@ -0,0 +1,120 @@ +declare module 'notifications-node-client' { + export class NotifyClient { + constructor(apiKey: string) + + constructor(baseUrl: string, apiKey: string) + + sendEmail( + templateId: string, + emailAddress: string, + options: { + personalisation: unknown + reference: string + emailReplyToId?: string + }, + ): Promise + + sendSms(templateId: string, phoneNumber: string, options?: NotificationSendOptions): Promise + + sendLetter(templateId: string, options?: NotificationSendOptions): Promise + + sendPrecompiledLetter(reference: string, pdfFile: string | Buffer, postage?: string): Promise + + getNotificationById(notificationId: string): Promise<{ data: Notification }> + + getNotifications( + templateType?: string, + status?: string, + reference?: string, + olderThanId?: string, + ): Promise<{ + data: { + links: { + current: string + next: string + } + notifications: Notification[] + } + }> + + getPdfForLetterNotification(notificationId: string): Promise + + getTemplateById(templateId: string): Promise<{ + data: { + body: string + created_at: Date + created_by: string + id: string + name: string + personalisation: unknown + postage?: string + subject?: string + type: string + updated_at?: string + version: number + } + }> + + getTemplateByIdAndVersion(templateId: string, version: number): Promise + + getAllTemplates(templateType?: string): Promise + + previewTemplateById(templateId: string, personalisation?: unknown): Promise + + getReceivedTexts(olderThan?: string): Promise + + setProxy(proxyConfig: unknown): void + + prepareUpload(fileData: unknown, options?: FileUploadOptions): unknown + } + + export interface NotificationSendOptions { + personalisation?: unknown + reference?: string + emailReplyToId?: string + smsSenderId?: string + } + + export interface FileUploadOptions { + isCsv?: boolean + confirmEmailBeforeDownload?: boolean + retentionPeriod?: number + } + + export interface Notification { + body?: string + completed_at?: string + cost_details?: { + billable_sms_fragments: number + international_rate_multiplier: number + sms_rate: number + } + cost_in_pounds: number + created_at: string + created_by_name?: string + email_address?: string + id: string + is_cost_data_ready: boolean + line_1?: string + line_2?: string + line_3?: string + line_4?: string + line_5?: string + line_6?: string + one_click_unsubscribe_url?: string + phone_number?: string + postage?: string + postcode?: string + reference?: string + scheduled_for?: string + sent_at: string + status: string + subject?: string + template: { + id: string + uri: string + version: number + } + type: string + } +} diff --git a/server/config.ts b/server/config.ts index 9d652c4..a4ea81e 100755 --- a/server/config.ts +++ b/server/config.ts @@ -93,6 +93,10 @@ export default { enabled: get('TOKEN_VERIFICATION_ENABLED', 'false') === 'true', }, }, + notify: { + apiKey: get('NOTIFY_API_KEY', 'test', requiredInProduction), + customUrl: get('NOTIFY_API_KEY', false) === false ? 'http://localhost:9090/notifications-api' : undefined, + }, sqs: { audit: auditConfig(), }, diff --git a/server/middleware/asyncMiddleware.ts b/server/middleware/asyncMiddleware.ts deleted file mode 100644 index cb99dcb..0000000 --- a/server/middleware/asyncMiddleware.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Request, Response, NextFunction, RequestHandler } from 'express' - -export default function asyncMiddleware(fn: RequestHandler) { - return (req: Request, res: Response, next: NextFunction): void => { - Promise.resolve(fn(req, res, next)).catch(next) - } -} diff --git a/server/middleware/authorisationMiddleware.test.ts b/server/middleware/authorisationMiddleware.test.ts index fbf7a06..4146f62 100644 --- a/server/middleware/authorisationMiddleware.test.ts +++ b/server/middleware/authorisationMiddleware.test.ts @@ -47,25 +47,16 @@ describe('authorisationMiddleware', () => { it('should redirect when user has no authorised roles', () => { const res = createResWithToken({ authorities: [] }) - authorisationMiddleware(['SOME_REQUIRED_ROLE'])(req, res, next) + authorisationMiddleware(['ROLE_TEST'])(req, res, next) expect(next).not.toHaveBeenCalled() expect(res.redirect).toHaveBeenCalledWith('/authError') }) it('should return next when user has authorised role', () => { - const res = createResWithToken({ authorities: ['ROLE_SOME_REQUIRED_ROLE'] }) + const res = createResWithToken({ authorities: ['ROLE_TEST'] }) - authorisationMiddleware(['SOME_REQUIRED_ROLE'])(req, res, next) - - expect(next).toHaveBeenCalled() - expect(res.redirect).not.toHaveBeenCalled() - }) - - it('should return next when user has authorised role and middleware created with ROLE_ prefix', () => { - const res = createResWithToken({ authorities: ['ROLE_SOME_REQUIRED_ROLE'] }) - - authorisationMiddleware(['ROLE_SOME_REQUIRED_ROLE'])(req, res, next) + authorisationMiddleware(['ROLE_TEST'])(req, res, next) expect(next).toHaveBeenCalled() expect(res.redirect).not.toHaveBeenCalled() diff --git a/server/middleware/authorisationMiddleware.ts b/server/middleware/authorisationMiddleware.ts index b736f5a..6d34764 100644 --- a/server/middleware/authorisationMiddleware.ts +++ b/server/middleware/authorisationMiddleware.ts @@ -2,17 +2,17 @@ import { jwtDecode } from 'jwt-decode' import type { RequestHandler } from 'express' import logger from '../../logger' -import asyncMiddleware from './asyncMiddleware' -export default function authorisationMiddleware(authorisedRoles: string[] = []): RequestHandler { - return asyncMiddleware((req, res, next) => { - // authorities in the user token will always be prefixed by ROLE_. - // Convert roles that are passed into this function without the prefix so that we match correctly. - const authorisedAuthorities = authorisedRoles.map(role => (role.startsWith('ROLE_') ? role : `ROLE_${role}`)) +export default function authorisationMiddleware(requiredRoles: string[] = []): RequestHandler { + return (req, res, next) => { if (res.locals?.user?.token) { const { authorities: roles = [] } = jwtDecode(res.locals.user.token) as { authorities?: string[] } - if (authorisedAuthorities.length && !roles.some(role => authorisedAuthorities.includes(role))) { + if ( + res.locals?.user?.authSource !== 'delius' && + requiredRoles.length && + !roles.some(role => requiredRoles.includes(role)) + ) { logger.error('User is not authorised to access this') return res.redirect('/authError') } @@ -22,5 +22,5 @@ export default function authorisationMiddleware(authorisedRoles: string[] = []): req.session.returnTo = req.originalUrl return res.redirect('/sign-in') - }) + } } diff --git a/server/routes/index.test.ts b/server/routes/index.test.ts deleted file mode 100644 index d8cb5a8..0000000 --- a/server/routes/index.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { Express } from 'express' -import request from 'supertest' -import { appWithAllRoutes, user } from './testutils/appSetup' -import AuditService, { Page } from '../services/auditService' - -jest.mock('../services/auditService') - -const auditService = new AuditService(null) as jest.Mocked - -let app: Express - -beforeEach(() => { - app = appWithAllRoutes({ - services: { - auditService, - }, - userSupplier: () => user, - }) -}) - -afterEach(() => { - jest.resetAllMocks() -}) - -describe('GET /', () => { - it('should render index page', () => { - auditService.logPageView.mockResolvedValue(null) - - return request(app) - .get('/') - .expect('Content-Type', /html/) - .expect(res => { - expect(res.text).toContain('This site is under construction...') - expect(auditService.logPageView).toHaveBeenCalledWith(Page.EXAMPLE_PAGE, { - who: user.username, - correlationId: expect.any(String), - }) - }) - }) -}) diff --git a/server/routes/index.ts b/server/routes/index.ts index 2fa71df..7891c82 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -1,18 +1,181 @@ -import { type RequestHandler, Router } from 'express' +import { Request, Router } from 'express' +import { Notification, NotifyClient } from 'notifications-node-client' -import asyncMiddleware from '../middleware/asyncMiddleware' +import { DateTimeFormatter, LocalDate, ZonedDateTime } from '@js-joda/core' +import { Parser } from '@json2csv/plainjs' import type { Services } from '../services' -import { Page } from '../services/auditService' +import config from '../config' +import { convertToTitleCase, dateTimeFormatter, groupByCount } from '../utils/utils' +import { asArray, removeURLParameter } from '../utils/url' + +const notifyClient: NotifyClient = config.notify.customUrl + ? new NotifyClient(config.notify.customUrl, config.notify.apiKey) + : new NotifyClient(config.notify.apiKey) export default function routes({ auditService }: Services): Router { const router = Router() - const get = (path: string | string[], handler: RequestHandler) => router.get(path, asyncMiddleware(handler)) - get('/', async (req, res, next) => { - await auditService.logPageView(Page.EXAMPLE_PAGE, { who: res.locals.user.username, correlationId: req.id }) + router.get('/', async (req, res, next) => { + await auditService.logPageView('HOME_PAGE', { who: res.locals.user.username, correlationId: req.id }) + + const filters: Filters = { + date: req.query.date + ? LocalDate.parse(req.query.date as string, DateTimeFormatter.ofPattern('d/M/yyyy')) + : LocalDate.now(), + keywords: req.query.keywords as string, + status: asArray(req.query.status), + template: asArray(req.query.template), + provider: asArray(req.query.provider), + } + const notifications = await getAllNotifications(filters.date) + const availableFilterOptions = await getAvailableFilterOptions(notifications, filters, req) + + const headers = [{ text: 'To' }, { text: 'Message' }, { text: 'Status' }] + const results = notifications + .filter(n => filterByKeywords(n, filters.keywords)) + .filter(n => filters.status.length === 0 || filters.status.includes(n.status)) + .filter(n => filters.template.length === 0 || filters.template.includes(n.template.id)) + .map(n => [ + { + html: `${n.phone_number}
${n.reference.split(':')[0]}
`, + }, + { + html: `

${n.body}