From 88655935bc577cd8b15e4941fb48d3c900c6a0d2 Mon Sep 17 00:00:00 2001 From: James Reed Date: Tue, 24 Dec 2024 14:14:15 +0000 Subject: [PATCH] feat: use dps shared components for header and footer --- assets/scss/application.scss | 5 +- helm_deploy/values-dev.yaml | 3 + helm_deploy/values-preprod.yaml | 3 + helm_deploy/values-prod.yaml | 3 + package-lock.json | 321 ++- package.json | 2 + server/@types/express/index.d.ts | 14 + server/@types/keyWorker/index.d.ts | 2295 +++++++++++++++++ server/app.ts | 15 + server/config.ts | 28 + server/interfaces/caseLoad.ts | 7 + server/interfaces/hmppsUser.ts | 7 +- .../middleware/checkPopulateUserCaseloads.ts | 70 + server/middleware/setUpHealthChecks.ts | 3 +- server/routes/testutils/appSetup.ts | 13 + .../keyworkerApi/keyworkerApiClient.ts | 38 + .../keyworkerApi/keyworkerApiService.ts | 11 + server/services/prisonApi/prisonApiClient.ts | 28 + server/services/prisonApi/prisonApiService.ts | 17 + server/utils/nunjucksSetup.ts | 1 + server/views/partials/layout.njk | 21 +- server/views/partials/miniProfile/_all.scss | 53 + server/views/partials/miniProfile/macro.njk | 3 + .../views/partials/miniProfile/template.njk | 21 + 24 files changed, 2977 insertions(+), 5 deletions(-) create mode 100644 server/@types/keyWorker/index.d.ts create mode 100644 server/interfaces/caseLoad.ts create mode 100644 server/middleware/checkPopulateUserCaseloads.ts create mode 100644 server/services/keyworkerApi/keyworkerApiClient.ts create mode 100644 server/services/keyworkerApi/keyworkerApiService.ts create mode 100644 server/services/prisonApi/prisonApiClient.ts create mode 100644 server/services/prisonApi/prisonApiService.ts create mode 100644 server/views/partials/miniProfile/_all.scss create mode 100644 server/views/partials/miniProfile/macro.njk create mode 100644 server/views/partials/miniProfile/template.njk diff --git a/assets/scss/application.scss b/assets/scss/application.scss index 2050c2a..8575b91 100644 --- a/assets/scss/application.scss +++ b/assets/scss/application.scss @@ -7,5 +7,8 @@ $govuk-page-width: $moj-page-width; @import "govuk-frontend/dist/govuk/all"; @import "@ministryofjustice/frontend/moj/all"; +@import 'node_modules/@ministryofjustice/hmpps-connect-dps-components/dist/assets/footer'; +@import 'node_modules/@ministryofjustice/hmpps-connect-dps-components/dist/assets/header-bar'; + @import './components/header-bar'; -@import './local'; +@import './local'; \ No newline at end of file diff --git a/helm_deploy/values-dev.yaml b/helm_deploy/values-dev.yaml index ccc262f..cfd66bd 100644 --- a/helm_deploy/values-dev.yaml +++ b/helm_deploy/values-dev.yaml @@ -9,6 +9,9 @@ generic-service: INGRESS_URL: "https://allocate-key-workers-dev.hmpps.service.justice.gov.uk" 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" + COMPONENT_API_URL: "https://frontend-components-dev.hmpps.service.justice.gov.uk" # used by hmpps-connect-dps-components module + PRISON_API_URL: "https://prison-api-dev.prison.service.justice.gov.uk" + KEYWORKER_API_URL: https://keyworker-api-dev.prison.service.justice.gov.uk ENVIRONMENT_NAME: DEV AUDIT_ENABLED: "false" diff --git a/helm_deploy/values-preprod.yaml b/helm_deploy/values-preprod.yaml index d0fe8bd..09f1d0f 100644 --- a/helm_deploy/values-preprod.yaml +++ b/helm_deploy/values-preprod.yaml @@ -9,6 +9,9 @@ generic-service: INGRESS_URL: "https://allocate-key-workers-preprod.hmpps.service.justice.gov.uk" 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" + COMPONENT_API_URL: "https://frontend-components-preprod.hmpps.service.justice.gov.uk" # used by hmpps-connect-dps-components module + PRISON_API_URL: "https://prison-api-preprod.prison.service.justice.gov.uk" + KEYWORKER_API_URL: https://keyworker-api-preprod.prison.service.justice.gov.uk ENVIRONMENT_NAME: PRE-PRODUCTION AUDIT_ENABLED: "false" diff --git a/helm_deploy/values-prod.yaml b/helm_deploy/values-prod.yaml index 1492bc2..950b106 100644 --- a/helm_deploy/values-prod.yaml +++ b/helm_deploy/values-prod.yaml @@ -9,6 +9,9 @@ generic-service: INGRESS_URL: "https://allocate-key-workers.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" + COMPONENT_API_URL: "https://frontend-components.hmpps.service.justice.gov.uk" # used by hmpps-connect-dps-components module + KEYWORKER_API_URL: https://keyworker-api.prison.service.justice.gov.uk + PRISON_API_URL: "https://prison-api.prison.service.justice.gov.uk" AUDIT_ENABLED: "false" generic-prometheus-alerts: diff --git a/package-lock.json b/package-lock.json index 755be54..3f195c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@aws-sdk/client-sqs": "^3.714.0", "@ministryofjustice/frontend": "^3.2.1", + "@ministryofjustice/hmpps-connect-dps-components": "^1.3.1", "@ministryofjustice/hmpps-monitoring": "^0.0.1-beta.2", "agentkeepalive": "^4.5.0", "applicationinsights": "^2.9.6", @@ -81,6 +82,7 @@ "lint-staged": "^15.2.11", "mocha-junit-reporter": "^2.2.1", "nock": "^13.5.6", + "openapi-typescript": "^7.4.4", "prettier": "^3.4.2", "prettier-plugin-jinja-template": "^2.0.0", "supertest": "^7.0.0", @@ -2786,6 +2788,65 @@ "jquery": "^3.6.0" } }, + "node_modules/@ministryofjustice/hmpps-connect-dps-components": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ministryofjustice/hmpps-connect-dps-components/-/hmpps-connect-dps-components-1.3.1.tgz", + "integrity": "sha512-JSMpCH/z8XUCLik+L3SKGyDNw5zaQWW9gHRF9L9bEBCP/LQgTMf8GCx87QJ0c9SaZ4+y2VaLZ+4+BOyoqaLkbA==", + "license": "MIT", + "dependencies": { + "@types/node": "^20.17.6", + "@types/nunjucks": "^3.2.6", + "nunjucks": "^3.2.4", + "superagent": "^9.0.2" + } + }, + "node_modules/@ministryofjustice/hmpps-connect-dps-components/node_modules/@types/node": { + "version": "20.17.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.10.tgz", + "integrity": "sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@ministryofjustice/hmpps-connect-dps-components/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@ministryofjustice/hmpps-connect-dps-components/node_modules/superagent": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", + "integrity": "sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==", + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^3.5.1", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@ministryofjustice/hmpps-connect-dps-components/node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "license": "MIT" + }, "node_modules/@ministryofjustice/hmpps-monitoring": { "version": "0.0.1-beta.2", "resolved": "https://registry.npmjs.org/@ministryofjustice/hmpps-monitoring/-/hmpps-monitoring-0.0.1-beta.2.tgz", @@ -3348,6 +3409,80 @@ "@redis/client": "^1.0.0" } }, + "node_modules/@redocly/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js-replace": "^1.0.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@redocly/ajv/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@redocly/config": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.17.1.tgz", + "integrity": "sha512-CEmvaJuG7pm2ylQg53emPmtgm4nW2nxBgwXzbVEHpGas/lGnMyN8Zlkgiz6rPw0unASg6VW3wlz27SOL5XFHYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@redocly/openapi-core": { + "version": "1.26.1", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.26.1.tgz", + "integrity": "sha512-xRuVZqMVRFzqjbUCpOTra4tbnmQMWsya996omZMV3WgD084Z6OWB3FXflhAp93E/yAmbWlWZpddw758AyoaLSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@redocly/ajv": "^8.11.2", + "@redocly/config": "^0.17.0", + "colorette": "^1.2.0", + "https-proxy-agent": "^7.0.4", + "js-levenshtein": "^1.1.6", + "js-yaml": "^4.1.0", + "minimatch": "^5.0.1", + "node-fetch": "^2.6.1", + "pluralize": "^8.0.0", + "yaml-ast-parser": "0.0.43" + }, + "engines": { + "node": ">=14.19.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@redocly/openapi-core/node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@redocly/openapi-core/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -4429,7 +4564,6 @@ "version": "3.2.6", "resolved": "https://registry.npmjs.org/@types/nunjucks/-/nunjucks-3.2.6.tgz", "integrity": "sha512-pHiGtf83na1nCzliuAdq8GowYiXvH5l931xZ0YEHaLMNFgynpEqx+IPStlu7UaDkehfvl01e4x/9Tpwhy7Ue3w==", - "dev": true, "license": "MIT" }, "node_modules/@types/oauth": { @@ -5959,6 +6093,13 @@ "node": ">=8" } }, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "dev": true, + "license": "MIT" + }, "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", @@ -9291,6 +9432,19 @@ "node": ">=8" } }, + "node_modules/index-to-position": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-0.1.2.tgz", + "integrity": "sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -10871,6 +11025,16 @@ "license": "MIT", "peer": true }, + "node_modules/js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -12632,6 +12796,52 @@ "license": "MIT", "optional": true }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -12856,6 +13066,68 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openapi-typescript": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.4.4.tgz", + "integrity": "sha512-7j3nktnRzlQdlHnHsrcr6Gqz8f80/RhfA2I8s1clPI+jkY0hLNmnYVKBfuUEli5EEgK1B6M+ibdS5REasPlsUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@redocly/openapi-core": "^1.25.9", + "ansi-colors": "^4.1.3", + "change-case": "^5.4.4", + "parse-json": "^8.1.0", + "supports-color": "^9.4.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "openapi-typescript": "bin/cli.js" + }, + "peerDependencies": { + "typescript": "^5.x" + } + }, + "node_modules/openapi-typescript/node_modules/parse-json": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.1.0.tgz", + "integrity": "sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "index-to-position": "^0.1.2", + "type-fest": "^4.7.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openapi-typescript/node_modules/supports-color": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", + "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/openapi-typescript/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -13290,6 +13562,16 @@ "node": ">=8" } }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -13740,6 +14022,16 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-in-the-middle": { "version": "7.4.0", "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.4.0.tgz", @@ -15623,6 +15915,19 @@ "node": ">=4" } }, + "node_modules/type-fest": { + "version": "4.30.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.30.2.tgz", + "integrity": "sha512-UJShLPYi1aWqCdq9HycOL/gwsuqda1OISdBO3t8RlXQC4QvtuIz4b5FCfe2dQIWEpmlRExKmcTBfP1r9bhY7ig==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -15839,6 +16144,13 @@ "punycode": "^2.1.0" } }, + "node_modules/uri-js-replace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz", + "integrity": "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==", + "dev": true, + "license": "MIT" + }, "node_modules/url-parse": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", @@ -16367,6 +16679,13 @@ "node": ">= 14" } }, + "node_modules/yaml-ast-parser": { + "version": "0.0.43", + "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", + "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index ed446c1..719a38d 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "dependencies": { "@aws-sdk/client-sqs": "^3.714.0", "@ministryofjustice/frontend": "^3.2.1", + "@ministryofjustice/hmpps-connect-dps-components": "^1.3.1", "@ministryofjustice/hmpps-monitoring": "^0.0.1-beta.2", "agentkeepalive": "^4.5.0", "applicationinsights": "^2.9.6", @@ -139,6 +140,7 @@ "lint-staged": "^15.2.11", "mocha-junit-reporter": "^2.2.1", "nock": "^13.5.6", + "openapi-typescript": "^7.4.4", "prettier": "^3.4.2", "prettier-plugin-jinja-template": "^2.0.0", "supertest": "^7.0.0", diff --git a/server/@types/express/index.d.ts b/server/@types/express/index.d.ts index a69481f..6797ad4 100644 --- a/server/@types/express/index.d.ts +++ b/server/@types/express/index.d.ts @@ -20,7 +20,10 @@ export declare global { interface Request { verified?: boolean id: string + journeyData: JourneyData + logout(done: (err: unknown) => void): void + systemClientToken: string } interface Response { @@ -39,6 +42,17 @@ export declare global { applicationName: string environmentName: string environmentNameColour: string + feComponentsMeta?: { + activeCaseLoad: CaseLoad + caseLoads: CaseLoad[] + services: { + id: string + heading: string + description: string + href: string + navEnabled: boolean + }[] + } } } } diff --git a/server/@types/keyWorker/index.d.ts b/server/@types/keyWorker/index.d.ts new file mode 100644 index 0000000..00f0354 --- /dev/null +++ b/server/@types/keyWorker/index.d.ts @@ -0,0 +1,2295 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + '/queue-admin/retry-dlq/{dlqName}': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put: operations['retryDlq'] + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/queue-admin/retry-all-dlqs': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put: operations['retryAllDlqs'] + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/queue-admin/purge-queue/{queueName}': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put: operations['purgeQueue'] + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/key-worker/deallocate/{offenderNo}': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + /** + * deallocate + * @description Marks the offender with expired time on active record + */ + put: operations['deallocate'] + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/key-worker/{staffId}/prison/{prisonId}': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * getKeyworkerDetails + * @description Key worker details. + */ + get: operations['getKeyworkerDetails'] + put?: never + /** + * addOrUpdateKeyworker + * @description Add or update a key worker record + */ + post: operations['addOrUpdateKeyworker'] + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/key-worker/{prisonId}/offenders': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * getOffenderForPrison + * @description Keyworker details for specified offenders in the given prison, where the offender and details exist. + */ + get: operations['getOffenderKeyworkerDetailsList'] + put?: never + post: operations['getOffenderKeyworkerDetailsListPost'] + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/key-worker/{prisonId}/allocate/start': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + /** + * autoAllocate + * @description Initiate auto-allocation process for specified prison. + */ + post: operations['startAutoAllocation'] + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/key-worker/{prisonId}/allocate/confirm': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + /** + * confirmAutoAllocation + * @description Confirm allocations chosen by the auto-allocation process. + */ + post: operations['confirmAutoAllocation'] + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/key-worker/enable/{prisonId}/manual': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + /** + * Enable Manual Allocation and Migrate + * @description Role Required: KW_MIGRATION. This will invoke migration from NOMIS DB + */ + post: operations['addSupportedPrisonForManualAllocation'] + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/key-worker/enable/{prisonId}/auto-allocate': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + /** + * Enable Auto Allocation for specified prison and Migrate + * @description Role Required: KW_MIGRATION. This will also invoke migration from NOMIS DB + */ + post: operations['addSupportedPrisonForAutoAllocation'] + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/key-worker/allocation-history/summary': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + /** + * getKeyWorkerHistorySummaryForPrisoners + * @description Gets a summary of the offender's allocation histories + */ + post: operations['getKeyWorkerHistorySummaryForPrisoners'] + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/key-worker/allocate': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + /** + * allocate + * @description Process manual allocation of an offender to a Key worker. + */ + post: operations['allocate'] + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/subject-access-request': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** Returns all data associated to subject in JSON format */ + get: operations['subjectAccessRequest'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/queue-admin/get-dlq-messages/{dlqName}': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get: operations['getDlqMessages'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/key-worker/{staffId}/prison/{prisonId}/offenders': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * keyworkerallocations + * @description Specified key worker’s currently assigned offenders for given prison. + */ + get: operations['getAllocationsForKeyworkerWithOffenderDetails'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/key-worker/{prisonId}/offenders/unallocated': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * getUnallocatedOffenders + * @description All unallocated offenders in specified prison. + */ + get: operations['getUnallocatedOffenders'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/key-worker/{prisonId}/offender/{offenderNo}': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * getOffendersKeyworker + * @deprecated + * @description Offenders current Keyworker + */ + get: operations['deprecatedGetOffendersKeyworker'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/key-worker/{prisonId}/members': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * keyworkersearch + * @description Search for key workers within prison. + */ + get: operations['keyworkerSearch'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/key-worker/{prisonId}/available': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * getAvailableKeyworkers + * @description Key workers available for allocation at specified prison. + */ + get: operations['getAvailableKeyworkers'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/key-worker/{prisonId}/allocations': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * getAllocations + * @description Allocations in specified prison. + */ + get: operations['getKeyworkerAllocations'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/key-worker/prison/{prisonId}': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** Get Prison Migration Status */ + get: operations['getPrisonMigrationStatus'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/key-worker/offender/{offenderNo}': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * getOffendersKeyworker + * @description Offenders current Keyworker + */ + get: operations['getOffendersKeyworker'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/key-worker/allocation-history/{offenderNo}': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * getKeyWorkerHistoryForPrisoner + * @description Order by most recent first + */ + get: operations['getKeyWorkerHistoryForPrisoner'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/key-worker-stats': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * getAllPrisonStats + * @description Get Key Worker stats for any prison. + */ + get: operations['getPrisonStats'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/key-worker-stats/{staffId}/prison/{prisonId}': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * getStatsForStaff + * @description Statistic for key workers and the prisoners that they support + */ + get: operations['getStatsForStaff'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } +} +export type webhooks = Record +export interface components { + schemas: { + RetryDlqResult: { + /** Format: int32 */ + messagesFoundCount: number + } + PurgeQueueResult: { + /** Format: int32 */ + messagesFoundCount: number + } + ErrorResponse: { + /** Format: int32 */ + status?: number + /** Format: int32 */ + errorCode?: number + userMessage?: string + developerMessage?: string + moreInfo?: string + } + /** @description New keyworker details. */ + KeyworkerUpdateDto: { + /** + * Format: int32 + * @description Key worker's allocation capacity. + */ + capacity: number + /** + * @description Key worker's status. + * @enum {string} + */ + status: 'ACT' | 'UAL' | 'ULT' | 'UNP' | 'INA' + /** + * @description Determines behaviour to apply to auto-allocation + * @enum {string} + */ + behaviour?: 'KEEP_ALLOCATIONS' | 'KEEP_ALLOCATIONS_NO_AUTO' | 'REMOVE_ALLOCATIONS_NO_AUTO' + /** + * Format: date + * @description Date that the Key worker's status should be updated to Active + */ + activeDate?: string + } + OffenderKeyworkerDto: { + /** + * Format: int64 + * @description Id of offender allocation. + */ + offenderKeyworkerId: number + /** @description The offender's unique offender number (aka NOMS Number in the UK). */ + offenderNo: string + /** + * Format: int64 + * @description The offender's Key worker. + */ + staffId: number + /** @description Prison Id where allocation is effective. */ + agencyId: string + /** + * Format: date-time + * @description The date and time of the allocation. + */ + assigned: string + /** + * Format: date-time + * @description The date and time of deallocation. + */ + expired?: string + /** @description The user who created the allocation. */ + userId: string + /** @description Whether allocation is active. */ + active: string + } + Prison: { + /** + * @description Identifies prison. + * @example MDI + */ + prisonId: string + /** + * @description Indicates that Key working is supported in this prison + * @example true + */ + supported: boolean + /** + * @description Indicates that Key Worker data has been migrated to the Key Worker Service + * @example true + */ + migrated: boolean + /** + * @description Indicates this prison has high complexity prisoners + * @example true + */ + highComplexity: boolean + /** + * @description Indicates that this prison supports auto allocation of prisoner to key workers + * @example true + */ + autoAllocatedSupported: boolean + /** + * Format: int32 + * @description Default auto allocation amount for staff in this prison. + * @example 6 + */ + capacityTier1: number + /** + * Format: int32 + * @description Over allocation amount per staff member (max) + * @example 9 + */ + capacityTier2: number + /** + * Format: int32 + * @description Frequency of Key working sessions in this prison + * @example 1 + */ + kwSessionFrequencyInWeeks: number + /** + * Format: date-time + * @description Date and time migration of key workers was done for this prison + */ + migratedDateTime: string + } + OffenderKeyWorkerHistorySummary: { + /** @description Identifies prisoner. */ + offenderNo: string + /** @description Whether this prisoner has ever had a keyworker allocated. */ + hasHistory: boolean + } + /** @description New allocation details. */ + KeyworkerAllocationDto: { + /** @description Identifies offender who is subject of allocation. */ + offenderNo: string + /** + * Format: int64 + * @description Identifies Key worker who is subject of allocation. + */ + staffId: number + /** @description Prison where allocation is effective. */ + prisonId: string + /** + * @description Type of allocation - auto or manual. + * @enum {string} + */ + allocationType: 'A' | 'M' | 'P' + /** + * @description Reason for allocation. + * @enum {string} + */ + allocationReason: 'AUTO' | 'MANUAL' + /** + * @description Reason for de-allocation. + * @enum {string} + */ + deallocationReason?: + | 'OVERRIDE' + | 'RELEASED' + | 'KEYWORKER_STATUS_CHANGE' + | 'TRANSFER' + | 'MERGED' + | 'MISSING' + | 'DUPLICATE' + | 'MANUAL' + } + JsonNode: Record + /** @description Success Response */ + SuccessResponse: { + content: components['schemas']['JsonNode'] + } + DlqMessage: { + body: { + [key: string]: Record + } + messageId: string + } + GetDlqResult: { + /** Format: int32 */ + messagesFoundCount: number + /** Format: int32 */ + messagesReturnedCount: number + messages: components['schemas']['DlqMessage'][] + } + KeyworkerDto: { + /** + * Format: int64 + * @description Unique staff identifier for Key worker. + */ + staffId: number + /** @description Key worker's first name. */ + firstName: string + /** @description Key worker's last name. */ + lastName: string + /** @description Key worker's email address. */ + email?: string + /** + * Format: int64 + * @description Identifier for Key worker image. + */ + thumbnailId?: number + /** + * Format: int32 + * @description Key worker's allocation capacity. + */ + capacity: number + /** + * Format: int32 + * @description Number of offenders allocated to Key worker. + */ + numberAllocated: number + /** @description Key worker's schedule type. */ + scheduleType?: string + /** @description Key worker's agency Id. */ + agencyId?: string + /** @description Key worker's agency description. */ + agencyDescription?: string + /** + * @description Key worker's status. + * @enum {string} + */ + status?: 'ACT' | 'UAL' | 'ULT' | 'UNP' | 'INA' + /** @description Key worker is eligible for auto allocation. */ + autoAllocationAllowed?: boolean + /** + * Format: date + * @description Date keyworker status should return to active. (returning from annual leave) + */ + activeDate?: string + /** + * Format: int32 + * @description Number of KW sessions in the time period specified + */ + numKeyWorkerSessions?: number + } + KeyworkerAllocationDetailsDto: { + /** + * Format: int64 + * @description Offender Booking Id + */ + bookingId: number + /** @description Offender Unique Reference */ + offenderNo: string + /** @description First Name */ + firstName: string + /** @description Middle Name(s) */ + middleNames?: string + /** @description Last Name */ + lastName: string + /** + * Format: int64 + * @description The key worker's Staff Id + */ + staffId: number + /** + * @deprecated + * @description Agency Id - will be removed - use prisonId + */ + agencyId: string + /** @description Prison Id */ + prisonId: string + /** + * Format: date-time + * @description Date and time of the allocation + */ + assigned: string + /** + * @description A + * @enum {string} + */ + allocationType: 'A' | 'M' | 'P' + /** @description Description of the location within the prison */ + internalLocationDesc: string + /** @description Prison different to current - deallocation only allowed */ + deallocOnly: boolean + } + OffenderLocationDto: { + /** @description The offender's unique offender number (aka NOMS Number in the UK). */ + offenderNo: string + /** + * Format: int64 + * @description A unique booking id. + */ + bookingId: number + /** @description The offender's first name. */ + firstName: string + /** @description The offender's middle name(s). */ + middleName?: string + /** @description The offender's last name. */ + lastName: string + /** Format: date */ + dateOfBirth?: string + /** @description Agency Id (if known) */ + agencyId?: string + /** + * Format: int64 + * @description Internal location id (if known) + */ + assignedLivingUnitId?: number + /** @description Internal location description (if known) */ + assignedLivingUnitDesc?: string + } + BasicKeyworkerDto: { + /** + * Format: int64 + * @description Unique staff identifier for Key worker. + */ + staffId: number + /** @description Key worker's first name. */ + firstName: string + /** @description Key worker's last name. */ + lastName: string + /** @description Key worker's email address. */ + email?: string + } + KeyWorkerAllocation: { + /** + * Format: int64 + * @description Id of offender allocation. + */ + offenderKeyworkerId: number + /** + * Format: int64 + * @description The offender's Key worker staff Id. + */ + staffId: number + /** @description Key worker's first name. */ + firstName: string + /** @description Key worker's last name. */ + lastName: string + /** @description Prison Id where allocation is effective. */ + prisonId: string + /** + * Format: date-time + * @description The date and time of the allocation. + */ + assigned: string + /** + * Format: date-time + * @description The date and time of deallocation. + */ + expired?: string + userId: components['schemas']['StaffUser'] + /** @description Whether allocation is active. */ + active: boolean + /** + * @description Type of allocation - auto or manual. + * @enum {string} + */ + allocationType: 'A' | 'M' | 'P' + /** @description Reason for allocation. */ + allocationReason: string + /** @description Reason for de-allocation. */ + deallocationReason?: string + /** + * Format: date-time + * @description The date and time of creation. + */ + creationDateTime?: string + createdByUser: components['schemas']['StaffUser'] + /** + * Format: date-time + * @description Last date and time of modification. + */ + modifyDateTime?: string + lastModifiedByUser: components['schemas']['StaffUser'] + } + OffenderKeyWorkerHistory: { + offender?: components['schemas']['PrisonerDetail'] + allocationHistory?: components['schemas']['KeyWorkerAllocation'][] + } + PrisonerDetail: { + /** @description Identifies prisoner. */ + offenderNo: string + /** @description The prisoner's Title */ + title: string + /** @description The prisoner's Suffix */ + suffix: string + /** @description The prisoner's first name. */ + firstName: string + /** @description The prisoner's middle names. */ + middleNames: string + /** @description The prisoner's last name. */ + lastName: string + /** + * Format: date + * @description The prisoner's date of birth + */ + dateOfBirth: string + /** @description The prisoner's gender */ + gender: string + /** @description Indicate Y if in prison */ + currentlyInPrison: string + /** + * Format: int64 + * @description Latest booking id + */ + latestBookingId: number + /** @description Latest Location Id */ + latestLocationId: string + /** @description Latest location */ + latestLocation: string + /** @description Last Internal location */ + internalLocation: string + /** @description Current Imprisonment Status */ + imprisonmentStatus: string + /** + * Format: date + * @description Date received into prison + */ + receptionDate: string + inPrison?: boolean + } + /** @description The user who last modified the allocation. */ + StaffUser: { + /** + * Format: int64 + * @description Unique staff identifier + */ + staffId: number + /** @description Staff first name. */ + firstName: string + /** @description Staff last name. */ + lastName: string + /** @description Staff username */ + username: string + } + KeyworkerStatSummary: { + summary: components['schemas']['PrisonStatsDto'] + /** @description Individual prison stats */ + prisons: { + [key: string]: components['schemas']['PrisonStatsDto'] + } + } + /** @description Individual prison stats */ + PrisonStatsDto: { + /** + * Format: date + * @description Requested start date for data set + * @example 2018-04-01 + */ + requestedFromDate: string + /** + * Format: date + * @description Requested end date for data set + * @example 2018-05-01 + */ + requestedToDate: string + current: components['schemas']['SummaryStatistic'] + previous: components['schemas']['SummaryStatistic'] + /** @description Date and percentage compliance key value pair of up to 1 years data before requestedToDate */ + complianceTimeline?: { + [key: string]: number + } + /** + * @description Average Compliance for complianceTimeline + * @example 75.3 + */ + avgOverallCompliance?: number + /** @description Date and percentage key value pair of up to 1 years data before requestedToDate */ + keyworkerSessionsTimeline?: { + [key: string]: number + } + /** + * Format: int32 + * @description Average Key worker sessions for keyworkerSessionsTimeline + * @example 502 + */ + avgOverallKeyworkerSessions?: number + } + /** @description Summary of Prison Statistics for the previous period requested. */ + SummaryStatistic: { + /** + * Format: date + * @description Starting date for the set of summary data + * @example 2018-06-01 + */ + dataRangeFrom: string + /** + * Format: date + * @description End date for the set of summary data + * @example 2018-07-30 + */ + dataRangeTo: string + /** + * Format: int32 + * @description Average number of prisoners assigned a key worker over this time range + * @example 423 + */ + numPrisonersAssignedKeyWorker: number + /** + * Format: int32 + * @description Average total number of prisoners in the prisons over this time range + * @example 600 + */ + totalNumPrisoners: number + /** + * Format: int32 + * @description Average total number of eligible prisoners in the prisons over this time range + * @example 600 + */ + totalNumEligiblePrisoners: number + /** + * Format: int32 + * @description Average number of Key Working Sessions done over this time range + * @example 354 + */ + numberKeyWorkerSessions: number + /** + * Format: int32 + * @description Average number of Key Worker Entries made over this time range + * @example 232 + */ + numberKeyWorkerEntries: number + /** + * Format: int32 + * @description Average number of Active Key Workers over this time range + * @example 320 + */ + numberOfActiveKeyworkers: number + /** + * @description Average percentage of Prisoners who have been assigned a Key Worker over this time range + * @example 87.2 + */ + percentagePrisonersWithKeyworker: number + /** + * Format: int32 + * @description Average number of projected Key Worker sessions that could be done based on available key workers and frequency of sessions (e.g 1/week) + * @example 501 + */ + numProjectedKeyworkerSessions: number + /** + * @description Overall compliance rate for this time period + * @example 87.5 + */ + complianceRate: number + /** + * Format: int32 + * @description Average number of days between a prisoner entering this prison and being allocated a key worker. + * @example 5 + */ + avgNumDaysFromReceptionToAllocationDays: number + /** + * Format: int32 + * @description Average number of days between a prisoner entering this prison and receiving a session from key worker + * @example 10 + */ + avgNumDaysFromReceptionToKeyWorkingSession: number + } + KeyworkerStatsDto: { + /** + * Format: int64 + * @description Identifies the staff by ID. + * @example 234233 + */ + staffId: number + /** + * Format: date + * @description Start date on which statistic results are based + * @example 2018-07-01 + */ + fromDate: string + /** + * Format: date + * @description End date on which statistic results are based + * @example 2018-07-31 + */ + toDate: string + /** + * Format: int32 + * @description Number of Session done based on case note type Key worker Activity, sub type Session + * @example 24 + */ + caseNoteSessionCount: number + /** + * Format: int32 + * @description Number of key worker entry case notes done based on case note type Key worker Activity, sub type Entry + * @example 12 + */ + caseNoteEntryCount: number + /** + * Format: int32 + * @description Number of projected key worker sessions that could have been done based on number of prisoners assigned to key worker and frequency of sessions set by this prison + * @example 22 + */ + projectedKeyworkerSessions: number + /** + * @description Percentage Compliance Rate of key worker session done over this time range + * @example 87.5 + */ + complianceRate: number + } + } + responses: never + parameters: never + requestBodies: never + headers: never + pathItems: never +} +export type $defs = Record +export interface operations { + retryDlq: { + parameters: { + query?: never + header?: never + path: { + dlqName: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + '*/*': components['schemas']['RetryDlqResult'] + } + } + } + } + retryAllDlqs: { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + requestBody?: never + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + '*/*': components['schemas']['RetryDlqResult'][] + } + } + } + } + purgeQueue: { + parameters: { + query?: never + header?: never + path: { + queueName: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + '*/*': components['schemas']['PurgeQueueResult'] + } + } + } + } + deallocate: { + parameters: { + query?: never + header?: never + path: { + offenderNo: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description De allocated */ + 200: { + headers: { + [name: string]: unknown + } + content?: never + } + /** @description Invalid request. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Requested resource not found. */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unrecoverable error occurred whilst processing request. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + getKeyworkerDetails: { + parameters: { + query?: never + header?: never + path: { + staffId: number + prisonId: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['KeyworkerDto'] + } + } + /** @description Invalid request. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Requested resource not found. */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unrecoverable error occurred whilst processing request. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + addOrUpdateKeyworker: { + parameters: { + query?: never + header?: never + path: { + staffId: number + prisonId: string + } + cookie?: never + } + requestBody: { + content: { + 'application/json': components['schemas']['KeyworkerUpdateDto'] + } + } + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown + } + content?: never + } + /** @description Invalid request. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unrecoverable error occurred whilst processing request. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + getOffenderKeyworkerDetailsList: { + parameters: { + query?: { + /** @description Offenders for which details are required, or get all. */ + offenderNo?: string[] + } + header?: never + path: { + prisonId: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['OffenderKeyworkerDto'][] + } + } + /** @description Invalid request. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Requested resource not found. */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unrecoverable error occurred whilst processing request. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + getOffenderKeyworkerDetailsListPost: { + parameters: { + query?: never + header?: never + path: { + prisonId: string + } + cookie?: never + } + requestBody?: { + content: { + 'application/json': string[] + } + } + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['OffenderKeyworkerDto'][] + } + } + } + } + startAutoAllocation: { + parameters: { + query?: never + header?: never + path: { + prisonId: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description Request to initiate auto-allocation process has been successfully processed. (NOT YET IMPLEMENTED - Use returned process id to monitor process execution and outcome.) Note that until asynchronous processing is implemented, this request will execute synchronously and return total number of allocations processed.) */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': number + } + } + /** @description Prison id provided is not valid or is not accessible to user. */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Auto-allocation processing not able to proceed or halted due to state of dependent resources. */ + 409: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unrecoverable error occurred whilst processing request. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + confirmAutoAllocation: { + parameters: { + query?: never + header?: never + path: { + prisonId: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description Request to confirm allocations has been successfully processed. (NOT YET IMPLEMENTED - Use returned process id to monitor process execution and outcome.) Note that until asynchronous processing is implemented, this request will execute synchronously and return total number of allocations processed.) */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': number + } + } + /** @description Prison id provided is not valid or is not accessible to user. */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unrecoverable error occurred whilst processing request. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + addSupportedPrisonForManualAllocation: { + parameters: { + query?: { + migrate?: boolean + /** @description standard and extended default keyworker capacities for this prison, comma separated, e.g. &capacity=6,9 */ + capacity?: number[] + /** @description default KW Session Frequency in weeks (default 1) */ + frequency?: number + } + header?: never + path: { + prisonId: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['Prison'] + } + } + /** @description Invalid request. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unrecoverable error occurred whilst processing request. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + addSupportedPrisonForAutoAllocation: { + parameters: { + query?: { + migrate?: boolean + /** @description standard and extended default keyworker capacities for this prison, comma separated, e.g. &capacity=6,9 */ + capacity?: number[] + /** @description default KW Session Frequency in weeks (default 1) */ + frequency?: number + } + header?: never + path: { + prisonId: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['Prison'] + } + } + /** @description Invalid request. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unrecoverable error occurred whilst processing request. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + getKeyWorkerHistorySummaryForPrisoners: { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + requestBody?: { + content: { + 'application/json': string[] + } + } + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['OffenderKeyWorkerHistorySummary'][] + } + } + /** @description Invalid request. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unrecoverable error occurred whilst processing request. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + allocate: { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + requestBody: { + content: { + 'application/json': components['schemas']['KeyworkerAllocationDto'] + } + } + responses: { + /** @description The allocation has been created. */ + 201: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': Record + } + } + } + } + subjectAccessRequest: { + parameters: { + query?: { + prn?: string + crn?: string + fromDate?: string + toDate?: string + } + header?: never + path?: never + cookie?: never + } + requestBody?: never + responses: { + /** @description Adjudication returned */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['SuccessResponse'] + } + } + /** @description No content found */ + 204: { + headers: { + [name: string]: unknown + } + content: { + '*/*': components['schemas']['SuccessResponse'] + } + } + /** @description Subject Identifier is not recognised by this service */ + 209: { + headers: { + [name: string]: unknown + } + content: { + '*/*': components['schemas']['SuccessResponse'] + } + } + } + } + getDlqMessages: { + parameters: { + query?: { + maxMessages?: number + } + header?: never + path: { + dlqName: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + '*/*': components['schemas']['GetDlqResult'] + } + } + } + } + getAllocationsForKeyworkerWithOffenderDetails: { + parameters: { + query?: { + /** @example false */ + skipOffenderDetails?: boolean + } + header?: never + path: { + staffId: number + prisonId: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['KeyworkerAllocationDetailsDto'] + } + } + /** @description Invalid request. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Requested resource not found. */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unrecoverable error occurred whilst processing request. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + getUnallocatedOffenders: { + parameters: { + query?: never + header?: { + /** @description Comma separated list of one or more of the following fields - firstName, lastName */ + 'Sort-Fields'?: string + /** + * @description Sort order (ASC or DESC) - defaults to ASC. + * @example ASC + */ + 'Sort-Order'?: 'ASC' | 'DESC' + } + path: { + prisonId: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['OffenderLocationDto'][] + } + } + /** @description Invalid request. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Requested resource not found. */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unrecoverable error occurred whilst processing request. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + deprecatedGetOffendersKeyworker: { + parameters: { + query?: never + header?: never + path: { + prisonId: string + offenderNo: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['BasicKeyworkerDto'] + } + } + /** @description Invalid request. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Requested resource not found. */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unrecoverable error occurred whilst processing request. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + keyworkerSearch: { + parameters: { + query?: { + /** @description Filter results by first name and/or last name of key worker. Supplied filter term is matched to start of key worker's first and last name. */ + nameFilter?: string + /** @description Filter results by status of key worker. */ + statusFilter?: 'ACT' | 'UAL' | 'ULT' | 'UNP' | 'INA' + } + header?: { + /** + * @description Requested offset of first record in returned collection of allocation records. + * @example 0 + */ + 'Page-Offset'?: number + /** + * @description Requested limit to number of allocation records returned. + * @example 10 + */ + 'Page-Limit'?: number + /** @description Comma separated list of one or more of the following fields - firstName, lastName */ + 'Sort-Fields'?: string + /** + * @description Sort order (ASC or DESC) - defaults to ASC. + * @example ASC + */ + 'Sort-Order'?: 'ASC' | 'DESC' + } + path: { + prisonId: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': Record + } + } + /** @description Invalid request. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Requested resource not found. */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unrecoverable error occurred whilst processing request. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + getAvailableKeyworkers: { + parameters: { + query?: never + header?: never + path: { + prisonId: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['KeyworkerDto'][] + } + } + /** @description Invalid request. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Requested resource not found. */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unrecoverable error occurred whilst processing request. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + getKeyworkerAllocations: { + parameters: { + query?: { + /** @description Optional filter by type of allocation. A for auto allocations, M for manual allocations. */ + allocationType?: string + /** @description Returned allocations must have been assigned on or after this date (in YYYY-MM-DD format). */ + fromDate?: string + /** + * @description Returned allocations must have been assigned on or before this date (in YYYY-MM-DD format). + * @example today's date + */ + toDate?: string + } + header?: { + /** + * @description Requested offset of first record in returned collection of allocation records. + * @example 0 + */ + 'Page-Offset'?: number + /** + * @description Requested limit to number of allocation records returned. + * @example 10 + */ + 'Page-Limit'?: number + /** @description Comma separated list of one or more of the following fields - firstName, lastName, assigned */ + 'Sort-Fields'?: string + /** + * @description Sort order (ASC or DESC) - defaults to ASC. + * @example ASC + */ + 'Sort-Order'?: 'ASC' | 'DESC' + } + path: { + prisonId: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['KeyworkerAllocationDetailsDto'][] + } + } + /** @description Invalid request. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Requested resource not found. */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unrecoverable error occurred whilst processing request. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + getPrisonMigrationStatus: { + parameters: { + query?: never + header?: never + path: { + prisonId: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['Prison'] + } + } + /** @description Invalid request. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Requested resource not found. */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unrecoverable error occurred whilst processing request. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + getOffendersKeyworker: { + parameters: { + query?: never + header?: never + path: { + /** @example A1234BC */ + offenderNo: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['BasicKeyworkerDto'] + } + } + /** @description Invalid request. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Requested resource not found. */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unrecoverable error occurred whilst processing request. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + getKeyWorkerHistoryForPrisoner: { + parameters: { + query?: never + header?: never + path: { + offenderNo: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['OffenderKeyWorkerHistory'] + } + } + /** @description Invalid request. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Requested resource not found. */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unrecoverable error occurred whilst processing request. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + getPrisonStats: { + parameters: { + query?: { + /** + * @description List of prisonIds + * @example prisonId=MDI&prisonId=LEI + */ + prisonId?: string[] + /** @description Start Date of Stats, optional, will choose one month before toDate (in YYYY-MM-DD format) */ + fromDate?: string + /** @description End Date of Stats (inclusive), optional, will choose yesterday if not provided (in YYYY-MM-DD format) */ + toDate?: string + } + header?: never + path?: never + cookie?: never + } + requestBody?: never + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['KeyworkerStatSummary'] + } + } + /** @description Invalid request. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Requested resource not found. */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unrecoverable error occurred whilst processing request. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + getStatsForStaff: { + parameters: { + query?: { + /** @description Calculate stats for staff on or after this date (in YYYY-MM-DD format). */ + fromDate?: string + /** @description Calculate stats for staff on or before this date (in YYYY-MM-DD format). */ + toDate?: string + } + header?: never + path: { + staffId: number + prisonId: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['KeyworkerStatsDto'] + } + } + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unrecoverable error occurred whilst processing request. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } +} diff --git a/server/app.ts b/server/app.ts index 12a8673..f1cea2f 100755 --- a/server/app.ts +++ b/server/app.ts @@ -1,6 +1,9 @@ import express from 'express' +import dpsComponents from '@ministryofjustice/hmpps-connect-dps-components' import nunjucksSetup from './utils/nunjucksSetup' +import logger from '../logger' +import config from './config' import errorHandler from './errorHandler' import { appInsightsMiddleware } from './utils/azureAppInsights' import authorisationMiddleware from './middleware/authorisationMiddleware' @@ -35,6 +38,18 @@ export default function createApp(services: Services): express.Application { app.use(authorisationMiddleware()) app.use(setUpCsrf()) app.use(setUpCurrentUser()) + app.get( + '*', + dpsComponents.getPageComponents({ + logger, + includeMeta: true, + dpsUrl: config.serviceUrls.digitalPrison, + timeoutOptions: { + response: config.apis.componentApi.timeout.response, + deadline: config.apis.componentApi.timeout.deadline, + }, + }), + ) app.use((_req, res, next) => { res.notFound = () => res.status(404).render('pages/not-found') next() diff --git a/server/config.ts b/server/config.ts index 40cc869..536e755 100755 --- a/server/config.ts +++ b/server/config.ts @@ -68,6 +68,12 @@ export default { expiryMinutes: Number(get('WEB_SESSION_TIMEOUT_IN_MINUTES', 120)), }, apis: { + componentApi: { + timeout: { + response: Number(get('COMPONENT_API_TIMEOUT_RESPONSE', 2500)), + deadline: Number(get('COMPONENT_API_TIMEOUT_DEADLINE', 2500)), + }, + }, hmppsAuth: { url: get('HMPPS_AUTH_URL', 'http://localhost:9090/auth', requiredInProduction), healthPath: '/health/ping', @@ -82,6 +88,24 @@ export default { systemClientId: get('CLIENT_CREDS_CLIENT_ID', 'clientid', requiredInProduction), systemClientSecret: get('CLIENT_CREDS_CLIENT_SECRET', 'clientsecret', requiredInProduction), }, + keyworkerApi: { + url: get('KEYWORKER_API_URL', 'http://localhost:8082', requiredInProduction), + healthPath: '/health/ping', + timeout: { + response: Number(get('KEYWORKER_API_TIMEOUT_RESPONSE', 20000)), + deadline: Number(get('KEYWORKER_API_TIMEOUT_DEADLINE', 20000)), + }, + agent: new AgentConfig(Number(get('KEYWORKER_API_TIMEOUT_RESPONSE', 20000))), + }, + prisonApi: { + healthPath: '/health/ping', + url: get('PRISON_API_URL', 'http://127.0.0.1:8080', requiredInProduction), + timeout: { + response: Number(get('PRISON_API_TIMEOUT_RESPONSE', 10000)), + deadline: Number(get('PRISON_API_TIMEOUT_DEADLINE', 10000)), + }, + agent: new AgentConfig(Number(get('PRISON_API_TIMEOUT_RESPONSE', 10000))), + }, tokenVerification: { url: get('TOKEN_VERIFICATION_API_URL', 'http://localhost:8100', requiredInProduction), healthPath: '/health/ping', @@ -93,6 +117,10 @@ export default { enabled: get('TOKEN_VERIFICATION_ENABLED', 'false') === 'true', }, }, + serviceUrls: { + digitalPrison: get('DPS_HOME_PAGE_URL', 'http://localhost:3001', requiredInProduction), + prisonerProfile: get('PRISONER_PROFILE_URL', 'http://localhost:3001', requiredInProduction), + }, sqs: { audit: auditConfig(), }, diff --git a/server/interfaces/caseLoad.ts b/server/interfaces/caseLoad.ts new file mode 100644 index 0000000..247298f --- /dev/null +++ b/server/interfaces/caseLoad.ts @@ -0,0 +1,7 @@ +export interface CaseLoad { + caseLoadId: string + description: string + type: string + caseloadFunction: string + currentlyActive: boolean +} diff --git a/server/interfaces/hmppsUser.ts b/server/interfaces/hmppsUser.ts index 42f63d4..18fe88e 100644 --- a/server/interfaces/hmppsUser.ts +++ b/server/interfaces/hmppsUser.ts @@ -1,3 +1,5 @@ +import { CaseLoad } from './caseLoad' + export type AuthSource = 'nomis' | 'delius' | 'external' | 'azuread' /** @@ -56,4 +58,7 @@ export interface AzureADUser extends BaseUser { authSource: 'azuread' } -export type HmppsUser = PrisonUser | ProbationUser | ExternalUser | AzureADUser +export type HmppsUser = (PrisonUser | ProbationUser | ExternalUser | AzureADUser) & { + caseloads: CaseLoad[] | undefined + activeCaseLoad?: CaseLoad | undefined +} diff --git a/server/middleware/checkPopulateUserCaseloads.ts b/server/middleware/checkPopulateUserCaseloads.ts new file mode 100644 index 0000000..589f4ff --- /dev/null +++ b/server/middleware/checkPopulateUserCaseloads.ts @@ -0,0 +1,70 @@ +import { RequestHandler, Router } from 'express' +import { validate as uuidValidate } from 'uuid' +import logger from '../../logger' +import PrisonApiService from '../services/prisonApi/prisonApiService' +import KeyworkerApiService from '../services/keyworkerApi/keyworkerApiService' +import { ServiceConfigInfo } from '../services/keyworkerApi/keyworkerApiClient' +import { CaseLoad } from '../interfaces/caseLoad' + +export default function checkPopulateUserCaseloads( + prisonApiService: PrisonApiService, + keyworkerApiService: KeyworkerApiService, +): RequestHandler { + const router = Router() + + router.get('/service-not-enabled', (_req, res) => { + res.status(200) + res.render('service-not-enabled') + }) + + router.use(async (req, res, next) => { + const splitUrl = req.url.split('/').filter(Boolean) + const services = res.locals.feComponentsMeta?.services + let isEligibleForService = services + ? services.find(service => service.id === 'keyworkerUI') !== undefined + : undefined + let caseloads + try { + if (res.locals.feComponentsMeta?.caseLoads) { + res.locals.user.caseloads = res.locals.feComponentsMeta.caseLoads + } + if (res.locals.feComponentsMeta?.activeCaseLoad) { + res.locals.user.activeCaseLoad = res.locals.feComponentsMeta.activeCaseLoad + } + const refetchCaseloads = !res.locals.user.caseloads + if (refetchCaseloads) { + caseloads = caseloads ?? (await prisonApiService.getCaseLoads(req)) + res.locals.user.caseloads = caseloads as CaseLoad[] + res.locals.user.activeCaseLoad = + (caseloads as CaseLoad[])!.find(caseload => caseload.currentlyActive) ?? res.locals.user.activeCaseLoad + } + + // Check that the user's active caseload is enabled on the API side + if ( + req.method === 'GET' && + !uuidValidate(splitUrl[0] || '') && + !req.url.endsWith('/start') && + !req.url.includes('prisoner-image') && + !req.url.includes('service-not-enabled') + ) { + if (isEligibleForService === undefined) { + const configInfo = await keyworkerApiService.getServiceConfigInfo(req) + const { activeAgencies } = configInfo as ServiceConfigInfo + isEligibleForService = + activeAgencies.includes( + res.locals.user.caseloads!.find(caseload => caseload.currentlyActive)?.caseLoadId || '', + ) || activeAgencies.includes('***') + } + if (!isEligibleForService) { + res.redirect('/service-not-enabled') + return + } + } + } catch (error) { + logger.error(error, `Failed to get caseloads for: ${res.locals.user.username}`) + next(error) + } + next() + }) + return router +} diff --git a/server/middleware/setUpHealthChecks.ts b/server/middleware/setUpHealthChecks.ts index 887bdcb..c64355c 100644 --- a/server/middleware/setUpHealthChecks.ts +++ b/server/middleware/setUpHealthChecks.ts @@ -8,7 +8,8 @@ import config from '../config' export default function setUpHealthChecks(applicationInfo: ApplicationInfo): Router { const router = express.Router() - const apiConfig = Object.entries(config.apis) + const { componentApi, ...otherApis } = config.apis + const apiConfig = Object.entries(otherApis) const middleware = monitoringMiddleware({ applicationInfo, diff --git a/server/routes/testutils/appSetup.ts b/server/routes/testutils/appSetup.ts index 88cd707..4638254 100644 --- a/server/routes/testutils/appSetup.ts +++ b/server/routes/testutils/appSetup.ts @@ -1,6 +1,9 @@ import express, { Express } from 'express' +import dpsComponents from '@ministryofjustice/hmpps-connect-dps-components' import { randomUUID } from 'crypto' +import logger from '../../../logger' +import config from '../../config' import routes from '../index' import nunjucksSetup from '../../utils/nunjucksSetup' import errorHandler from '../../errorHandler' @@ -21,6 +24,7 @@ export const user: HmppsUser = { authSource: 'nomis', staffId: 1234, userRoles: [], + caseloads: [], } export const flashProvider = jest.fn() @@ -47,6 +51,15 @@ function appSetup(services: Services, production: boolean, userSupplier: () => H }) app.use(express.json()) app.use(express.urlencoded({ extended: true })) + app.get( + '*', + dpsComponents.getPageComponents({ + logger, + includeMeta: true, + dpsUrl: config.serviceUrls.digitalPrison, + timeoutOptions: { response: 50, deadline: 50 }, + }), + ) app.use((_req, res, next) => { res.notFound = () => res.status(404).render('pages/not-found') next() diff --git a/server/services/keyworkerApi/keyworkerApiClient.ts b/server/services/keyworkerApi/keyworkerApiClient.ts new file mode 100644 index 0000000..ea7e5ac --- /dev/null +++ b/server/services/keyworkerApi/keyworkerApiClient.ts @@ -0,0 +1,38 @@ +import RestClient from '../../data/restClient' +import config from '../../config' + +export interface ServiceConfigInfo { + git: { + branch: string + commit: { + id: string + time: string + } + } + build: { + operatingSystem: string + version: string + artifact: string + machine: string + by: string + name: string + time: string + group: string + } + activeAgencies: string[] + publishEvents: boolean + productId: string +} +export default class KeyworkerApiClient { + private readonly restClient: RestClient + + constructor(token: string) { + this.restClient = new RestClient('Keyworker API', config.apis.keyworkerApi, token) + } + + async getServiceConfigInfo(): Promise { + return this.restClient.get({ + path: `/info`, + }) + } +} diff --git a/server/services/keyworkerApi/keyworkerApiService.ts b/server/services/keyworkerApi/keyworkerApiService.ts new file mode 100644 index 0000000..e30929a --- /dev/null +++ b/server/services/keyworkerApi/keyworkerApiService.ts @@ -0,0 +1,11 @@ +import { Request } from 'express' +import { RestClientBuilder } from '../../data' +import KeyworkerApiClient, { ServiceConfigInfo } from './keyworkerApiClient' + +export default class KeyworkerApiService { + constructor(private readonly keyworkerApiClientBuilder: RestClientBuilder) {} + + getServiceConfigInfo(req: Request): Promise { + return this.keyworkerApiClientBuilder(req.systemClientToken).getServiceConfigInfo() + } +} diff --git a/server/services/prisonApi/prisonApiClient.ts b/server/services/prisonApi/prisonApiClient.ts new file mode 100644 index 0000000..22e4fa9 --- /dev/null +++ b/server/services/prisonApi/prisonApiClient.ts @@ -0,0 +1,28 @@ +import { Readable } from 'stream' +import RestClient from '../../data/restClient' +import config from '../../config' +import { CaseLoad } from '../../interfaces/caseLoad' + +export interface PrisonApiClient { + getPrisonerImage(prisonerNumber: string): Promise + + getUserCaseLoads(): Promise +} + +export default class PrisonApiRestClient implements PrisonApiClient { + private readonly restClient: RestClient + + constructor(token: string) { + this.restClient = new RestClient('Prison API', config.apis.prisonApi, token) + } + + async getPrisonerImage(prisonerNumber: string): Promise { + return this.restClient.stream({ + path: `/api/bookings/offenderNo/${prisonerNumber}/image/data`, + }) + } + + async getUserCaseLoads(): Promise { + return this.restClient.get({ path: '/api/users/me/caseLoads' }) + } +} diff --git a/server/services/prisonApi/prisonApiService.ts b/server/services/prisonApi/prisonApiService.ts new file mode 100644 index 0000000..9d9d40b --- /dev/null +++ b/server/services/prisonApi/prisonApiService.ts @@ -0,0 +1,17 @@ +import { Request } from 'express' +import { Readable } from 'stream' +import { RestClientBuilder } from '../../data' +import { PrisonApiClient } from './prisonApiClient' +import { CaseLoad } from '../../interfaces/caseLoad' + +export default class PrisonApiService { + constructor(private readonly prisonApiClientBuilder: RestClientBuilder) {} + + getPrisonerImage(req: Request, prisonerNumber: string): Promise { + return this.prisonApiClientBuilder(req.systemClientToken).getPrisonerImage(prisonerNumber) + } + + getCaseLoads(req: Request): Promise { + return this.prisonApiClientBuilder(req.systemClientToken).getUserCaseLoads() + } +} diff --git a/server/utils/nunjucksSetup.ts b/server/utils/nunjucksSetup.ts index 41c4441..2568756 100644 --- a/server/utils/nunjucksSetup.ts +++ b/server/utils/nunjucksSetup.ts @@ -30,6 +30,7 @@ export default function nunjucksSetup(app: express.Express): void { path.join(__dirname, '../../server/views'), 'node_modules/govuk-frontend/dist/', 'node_modules/@ministryofjustice/frontend/', + 'node_modules/@ministryofjustice/hmpps-connect-dps-components/dist/assets/', ], { autoescape: true, diff --git a/server/views/partials/layout.njk b/server/views/partials/layout.njk index 65779e3..a82b3fd 100644 --- a/server/views/partials/layout.njk +++ b/server/views/partials/layout.njk @@ -1,18 +1,37 @@ {% extends "govuk/template.njk" %} +{% from "partials/miniProfile/macro.njk" import miniProfile %} {% block head %} + + {% for js in feComponents.jsIncludes %} + + {% endfor %} + + {% for css in feComponents.cssIncludes %} + + {% endfor %} {% endblock %} {% block pageTitle %}{{pageTitle | default(applicationName)}}{% endblock %} {% block header %} - {% include "./header.njk" %} + {{ feComponents.header | safe }} +{% endblock %} + +{% block beforeContent %} + {% if prisoner %} + {{ miniProfile(prisoner, '/prisoner-image/' + prisoner.prisonerNumber, prisonerProfileUrl + '/prisoner/' + prisoner.prisonerNumber) }} + {% endif %} {% endblock %} {% block bodyStart %} {% endblock %} +{% block footer %} + {{ feComponents.footer | safe }} +{% endblock %} + {% block bodyEnd %} {% endblock %} diff --git a/server/views/partials/miniProfile/_all.scss b/server/views/partials/miniProfile/_all.scss new file mode 100644 index 0000000..7605150 --- /dev/null +++ b/server/views/partials/miniProfile/_all.scss @@ -0,0 +1,53 @@ +.mini-profile { + margin: 0 0 30px 0; + position: relative; + box-sizing: border-box; + display: flex; + align-items: center; +} + +.mini-profile-inner { + background-color: #f3f2f1 !important; + width: 100%; + display: flex; + flex-wrap: wrap; + gap: 20px; + box-sizing: border-box; + padding: 20px; +} + +.mini-profile-left { + margin-right: 15px; + box-sizing: border-box; +} + +.mini-profile-person-img { + width: 100px; + height: 130px; + box-sizing: border-box; +} + +.mini-profile-right { + flex: 1; +} + +.mini-profile-info { + padding: 0 !important; + margin: 0; + list-style: none; + box-sizing: border-box; + display: flex; + flex-wrap: wrap; +} + +.mini-profile-info li { + margin-right: 25px; + color: #0b0c0c; +} + +/* Media query for larger screens */ +@media (max-width: 768px) { + .mini-profile-info li { + margin-right: 10px; + } +} \ No newline at end of file diff --git a/server/views/partials/miniProfile/macro.njk b/server/views/partials/miniProfile/macro.njk new file mode 100644 index 0000000..7ba71d0 --- /dev/null +++ b/server/views/partials/miniProfile/macro.njk @@ -0,0 +1,3 @@ +{% macro miniProfile(person, personImgUrl, personProfileUrl) %} + {%- include "./template.njk" -%} +{% endmacro %} \ No newline at end of file diff --git a/server/views/partials/miniProfile/template.njk b/server/views/partials/miniProfile/template.njk new file mode 100644 index 0000000..7e944f1 --- /dev/null +++ b/server/views/partials/miniProfile/template.njk @@ -0,0 +1,21 @@ +
+
+
+ Image of {{ person | personProfileName }} +
+
+
    +
  • +

    + {{ person | personProfileName }} +

    + {{ person.prisonerNumber }} +
  • +
  • Date of birth{{ person.dateOfBirth | personDateOfBirth }}
  • +
  • Establishment{{ person.prisonName }}
  • +
  • Cell number{{ person.cellLocation }}
  • +
  • Status{{ person.status }}
  • +
+
+
+