diff --git a/.gitignore b/.gitignore index 23372bd2..4b5c5735 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ dist/ .idea/ .DS_Store tingo_db/ +env.json \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..ed4025a6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,31 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + // does not work currently + // { + // "name": "Debug REMS Backend (Docker)", + // "port": 8091, + // "request": "attach", + // "skipFiles": [ + // "/**" + // ], + // "type": "node", + // "localRoot": "${workspaceFolder}", + // "remoteRoot": "/REMS", + // "restart": true + // }, + { + "name": "Debug REMS Backend (Local)", + "port": 8091, + "request": "attach", + "skipFiles": [ + "/**" + ], + "type": "node", + "restart": true + } + ] +} \ No newline at end of file diff --git a/DeveloperSetupGuide.md b/DeveloperSetupGuide.md index b49bfc4b..ed124317 100644 --- a/DeveloperSetupGuide.md +++ b/DeveloperSetupGuide.md @@ -169,15 +169,12 @@ Reference: https://github.com/rbenv/rbenv 2. Now clone the DRLS component repositories from Github: ```bash cd - git clone https://github.com/mcode/CRD.git CRD git clone https://github.com/mcode/test-ehr.git test-ehr git clone https://github.com/mcode/crd-request-generator.git crd-request-generator git clone https://github.com/mcode/dtr.git dtr git clone https://github.com/mcode/REMS.git REMS - git clone https://github.com/mcode/pharmacy-information-system.git pharmacy-information-system + git clone https://github.com/mcode/pims.git pims - cd CRD/server - git clone https://github.com/mcode/CDS-Library.git CDS-Library ``` # Open DRLS REMS as VsCode workspace @@ -299,21 +296,23 @@ Reference: https://docker-sync.readthedocs.io/en/latest/getting-started/commands 3. Find **Jon Snow** in the list of patients and click the dropdown menu next to his name. 4. Select **2183126 - Turalio 200 MG Oral Capsule** in the dropdown menu. 5. Click anywhere in the row to select Jon Snow. -6. Click **Submit to CRD** at the bottom of the page. -7. After several seconds you should receive a response in the form of two **CDS cards**: +6. Click **Send Rx to PIMS** at the bottom of the page to send a prescription to the Pharmacist. +7. Click **Submit to REMS-Admin** at the bottom of the page. +8. After several seconds you should receive a response in the form of two **CDS cards**: - **Drug Has REMS: Documentation Required.** -8. Select **Patient Enrollment Form** on the returned CDS card with summary **Drug Has REMS: Documentation Required**. -9. If you are asked for login credentials, use **alice** for username and **alice** for password. -10. A webpage should open in a new tab, and after a few seconds, a questionnaire should appear. -11. Fill out questionnaire and hit **Submit REMS Bundle**. -12. A new UI will appear with REMS Admin Status and Pharmacy Status. -13. Go to http://localhost:4200 and play the role of a pharmacist. -14. Click on **Log in as Admin** in the top right of the page -15. Sign in with the pre-configured user Suzy: +9. Select **Patient Enrollment Form** on the returned CDS card with summary **Drug Has REMS: Documentation Required**. +10. If you are asked for login credentials, use **alice** for username and **alice** for password. +11. A webpage should open in a new tab, and after a few seconds, a questionnaire should appear. +12. Fill out questionnaire and hit **Submit REMS Bundle**. +13. A new UI will appear with REMS Admin Status and Pharmacy Status. +14. Go to http://localhost:5050 and play the role of a pharmacist. + + +15. Click **Doctor Orders** in the top hand navigation menu on the screen +16. See the Doctor Order that was sent to the pharmacist from the prescriber. +17. Repeat steps 9-12 for submitting the Prescriber Enrollment and Prescriber Knowledge Assessment Forms and check how ETASU statuses change in both the PIMS prescription UI and the Prescriber status page. Congratulations! DRLS is fully installed and ready for you to use! diff --git a/Dockerfile.tmpl b/Dockerfile.tmpl index 404ca1e6..e0ba4b9e 100644 --- a/Dockerfile.tmpl +++ b/Dockerfile.tmpl @@ -5,38 +5,7 @@ ARG BUNDLE_DIR ARG DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive -# RUN apt-get update && apt-get install -y ca-certificates && apt-get install -y curl - -# RUN curl http://pki.mitre.org/MITRE%20BA%20ROOT.crt >> /etc/ssl/certs/ca-certificates.crt && \ -# curl http://pki.mitre.org/MITRE%20BA%20NPE%20CA-3.crt >> /etc/ssl/certs/ca-certificates.crt && \ -# curl http://pki.mitre.org/MITRE%20BA%20NPE%20CA-4.crt >> /etc/ssl/certs/ca-certificates.crt && \ -# update-ca-certificates - -# WORKDIR /pki -# COPY Zscaler_Root_CA.pem . -# RUN cat Zscaler_Root_CA.pem >> /etc/ssl/certs/ca-certificates.crt - - - -# # Install Git from Source to get around TLS errors with Zscaler, -# # explicitly using openssl instead of gnutls -# # RUN cp /etc/apt/sources.list /etc/apt/sources.list~ -# RUN sed -i -- 's/# deb-src/deb-src/' /etc/apt/sources.list -# RUN apt-get update && \ -# apt-get install build-essential fakeroot dpkg-dev -y && \ -# apt-get install git-man -y && \ -# apt-get -f build-dep git -y && \ -# apt-get install libcurl4-openssl-dev -y - -# WORKDIR /sourcegit - -# RUN apt-get source git && \ -# cd git-2.*.*/ && \ -# sed -i -- 's/libcurl4-gnutls-dev/libcurl4-openssl-dev/' ./debian/control && \ -# sed -i -- '/TEST\s*=\s*test/d' ./debian/rules && \ -# dpkg-buildpackage -rfakeroot -b -uc -us && \ -# dpkg -i ../git_*ubuntu*.deb - +COPY ./mongo-init.js ${BUNDLE_DIR}/mongo-init.js COPY ./.cnab/app/porter.yaml ${BUNDLE_DIR}/porter.yaml COPY ./docker-compose-porter.yml ${BUNDLE_DIR}/docker-compose-porter.yml COPY ./.env ${BUNDLE_DIR}/.env diff --git a/REMS.code-workspace b/REMS.code-workspace index 6bee4879..7659d66d 100644 --- a/REMS.code-workspace +++ b/REMS.code-workspace @@ -3,13 +3,6 @@ { "path": "../test-ehr" }, - { - "name": "CRD-CDS-Library", - "path": "../CRD/server/CDS-Library" - }, - { - "path": "../CRD" - }, { "path": "../crd-request-generator" }, @@ -19,9 +12,6 @@ { "path": "." }, - { - "path": "../pharmacy-information-system" - }, { "path": "../pims" } @@ -42,13 +32,11 @@ "stopAll": true, "preLaunchTask": "Launch Chrome in Debug Mode", "configurations": [ - "Debug CRD (Local + Docker)", "Debug DTR Backend (Docker)", "Debug Test-EHR (Local + Docker)", "Debug DTR Frontend (Attach Local + Docker)", "Debug CRD-Request-Generator (Attach Docker)", - "Debug Pharmacy-Information-System Backend (Docker)", - "Debug Pharmacy-Information-System Frontend (Attach Local + Docker)", + // ToDO: Add in PIMS Debugging and REMS debugging in docker "Post Debug Task - Terminate Chrome (This is not a Debugger)" ], } @@ -61,13 +49,13 @@ "type": "shell", "label": "Launch Chrome in Debug Mode", "linux": { - "command": "google-chrome http://localhost:3000 http://localhost:4200 http://localhost:3005/register --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug" + "command": "google-chrome http://localhost:3000 http://localhost:5050 http://localhost:3005/register --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug" }, "osx": { - "command": "/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome http://localhost:3000 http://localhost:4200 http://localhost:3005/register --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug" + "command": "/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome http://localhost:3000 http://localhost:5050 http://localhost:3005/register --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug" }, "windows": { - "command": "for /f \"usebackq tokens=1,2,3,4,5\" %a in (`reg query HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\ /s /f \\chrome.exe ^| findstr Application`) do set CHROMEPATH=%c%d%e & set CHROMEPATH=%CHROMEPATH:ProgramFiles=Program Files% & \"%CHROMEPATH%\" http://localhost:3000 http://localhost:4200 http://localhost:3005/register --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug" + "command": "for /f \"usebackq tokens=1,2,3,4,5\" %a in (`reg query HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\ /s /f \\chrome.exe ^| findstr Application`) do set CHROMEPATH=%c%d%e & set CHROMEPATH=%CHROMEPATH:ProgramFiles=Program Files% & \"%CHROMEPATH%\" http://localhost:3000 http://localhost:5050 http://localhost:3005/register --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug" }, "presentation": { "close": true, diff --git a/SimpleSetupGuide.md b/SimpleSetupGuide.md index 490485c1..b977ac98 100644 --- a/SimpleSetupGuide.md +++ b/SimpleSetupGuide.md @@ -73,7 +73,9 @@ Your computer must have these minimum requirements: ``` #### Windows -> Note: The install on Windows requires additional steps in order to expose the WSL Docker Daemon to Porter. The way to do this is to run the porter commands inside an additional windows specific container running in interactive mode, which exposes that container's terminal instance. +> Note: The Porter Installation on Windows is currently broken, to run the REMS prototype on Windows please refer to the [Running Docker Compose without Porter](#docker-compose-without-porter) section of this guide. + + ### 4. Verify everything is working @@ -110,21 +112,23 @@ Your computer must have these minimum requirements: 3. Find **Jon Snow** in the list of patients and click the dropdown menu next to his name. 4. Select **2183126 - Turalio 200 MG Oral Capsule** in the dropdown menu. 5. Click anywhere in the row to select Jon Snow. -6. Click **Submit to CRD** at the bottom of the page. -7. After several seconds you should receive a response in the form of two **CDS cards**: +6. Click **Send Rx to PIMS** at the bottom of the page to send a prescription to the Pharmacist. +7. Click **Submit to REMS-Admin** at the bottom of the page. +8. After several seconds you should receive a response in the form of two **CDS cards**: - **Drug Has REMS: Documentation Required.** -8. Select **Patient Enrollment Form** on the returned CDS card with summary **Drug Has REMS: Documentation Required**. -9. If you are asked for login credentials, use **alice** for username and **alice** for password. -10. A webpage should open in a new tab, and after a few seconds, a questionnaire should appear. -11. Fill out questionnaire and hit **Submit REMS Bundle**. -12. A new UI will appear with REMS Admin Status and Pharmacy Status. -13. Go to http://localhost:4200 and play the role of a pharmacist. -14. Click on **Log in as Admin** in the top right of the page -15. Sign in with the pre-configured user Suzy: +9. Select **Patient Enrollment Form** on the returned CDS card with summary **Drug Has REMS: Documentation Required**. +10. If you are asked for login credentials, use **alice** for username and **alice** for password. +11. A webpage should open in a new tab, and after a few seconds, a questionnaire should appear. +12. Fill out questionnaire and hit **Submit REMS Bundle**. +13. A new UI will appear with REMS Admin Status and Pharmacy Status. +14. Go to http://localhost:5050 and play the role of a pharmacist. + + +15. Click **Doctor Orders** in the top hand navigation menu on the screen +16. See the Doctor Order that was sent to the pharmacist from the prescriber. +17. Repeat steps 9-12 for submitting the Prescriber Enrollment and Prescriber Knowledge Assessment Forms and check how ETASU statuses change in both the PIMS prescription UI and the Prescriber status page. Congratulations! DRLS is fully installed and ready for you to use! diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 53b410ed..852873e9 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -48,31 +48,6 @@ services: - pims_remsadmin_mongo:/data/db - ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js - - # Create crd container - # crd: - # # Name of our service - # build: - # context: ../CRD - # dockerfile: Dockerfile.dev - # container_name: rems_dev_crd - # ports: - # # Port binding to host from docker container - # - "8090:8090" # Bind port 3000 of host to 3000 of container - # - "8091:8091" - # environment: - # VSAC_API_KEY: ${VSAC_API_KEY} - # volumes: - # - rems_dev_crd-sync:/CRD:nocopy # nocopy is important - # - rems_dev_crd-logs:/CRD/logs - # - rems_dev_crd-gradle:/CRD/.gradle - # - rems_dev_crd-server-gradle:/CRD/server/.gradle - # - rems_dev_crd-server-build:/CRD/server/build - # - rems_dev_crd-server-bin:/CRD/server/bin - # - rems_dev_crd-server-ValueSetCache:/CRD/server/ValueSetCache - # - rems_dev_crd-operations-build:/CRD/operations/build - # - rems_dev_crd-resources-build:/CRD/resources/build - crd-request-generator: build: context: ../crd-request-generator @@ -111,30 +86,15 @@ services: container_name: rems_dev_rems-administrator ports: - "8090:8090" + - "8091:8091" + environment: + VSAC_API_KEY: ${VSAC_API_KEY} + MONGO_HOSTNAME: mongodb://rems-admin-pims-root:rems-admin-pims-password@pims_remsadmin_mongo:27017 volumes: - rems_dev_rems-sync:/REMS:nocopy # nocopy is important - rems_dev_rems-nodeModules:/REMS/node_modules - rems_dev_rems-logs:/REMS/logs - # pharmacy-information-system: # Name of our service - # build: - # context: ../pharmacy-information-system - # dockerfile: Dockerfile.dev - # container_name: rems_dev_pharmacy-information-system - # environment: - # - PORT=3010 - # - MONGODB_CONNSTRING=mongodb://pharmacy-information-root:pharmacy-information-password@pharmacy-information-system-database:27017?retryWrites=true&w=majority - # - CRD_BASE_URL=http://crd:8090/ - # ports: # Port binding to host from docker container - # - "4200:4200" - # - "3010:3010" - # - "3011:3011" - # volumes: - # - rems_dev_pharmacy-information-system-sync:/home/node/app/pharmacy-information-system:nocopy # nocopy is important - # - rems_dev_pharmacy-information-system-nodeModules:/home/node/app/pharmacy-information-system/node_modules - # - rems_dev_pharmacy-information-system-backend-nodeModules:/home/node/app/pharmacy-information-system/backend/node_modules - # - rems_dev_pharmacy-information-system-logs:/home/node/app/pharmacy-information-system/logs - pims: build: context: ../pims @@ -143,29 +103,18 @@ services: ports: - "5050:5050" - "5051:5051" + environment: + REMS_ADMIN_BASE: http://rems-administrator:8090 + MONGO_HOSTNAME: mongodb://pims_remsadmin_mongo:27017/pims volumes: - rems_dev_pims-sync:/home/node/app/pims:nocopy - rems_dev_pims-nodeModules:/home/node/app/pims/node_modules - rems_dev_pims-logs:/home/node/app/pims/logs - # pharmacy-information-system-database: # Name of our service - # image: mongo - # container_name: rems_dev_pharmacy-information-system-database - # environment: - # MONGO_INITDB_ROOT_USERNAME: pharmacy-information-root - # MONGO_INITDB_ROOT_PASSWORD: pharmacy-information-password - # expose: - # - "27017" - # ports: # Port binding to host from docker container - # - "27017:27017" - # volumes: - # - rems_dev_pharmacy-infomation-system-database:/data/db volumes: rems_dev_test-ehr-sync: external: true - # rems_dev_crd-sync: - # external: true rems_dev_crd-request-generator-sync: external: true rems_dev_dtr-sync: @@ -182,14 +131,7 @@ volumes: rems_dev_test-ehr-build: rems_dev_test-ehr-target: rems_dev_test-ehr-logs: - # rems_dev_crd-logs: - # rems_dev_crd-gradle: - # rems_dev_crd-server-gradle: - # rems_dev_crd-server-build: - # rems_dev_crd-server-bin: - # rems_dev_crd-server-ValueSetCache: - # rems_dev_crd-operations-build: - # rems_dev_crd-resources-build: + rems_dev_crd-resources-build: rems_dev_crd-request-generator-nodeModules: rems_dev_crd-request-generator-databaseData: rems_dev_crd-request-generator-build: @@ -197,8 +139,8 @@ volumes: rems_dev_dtr-nodeModules: rems_dev_dtr-databaseData: rems_dev_dtr-logs: - rems_dev_pims-logs: # rems_dev_pims-database: - rems_dev_pims-nodeModules: # rems_dev_pims-backend-nodeModules: + rems_dev_pims-logs: + rems_dev_pims-nodeModules: rems_dev_rems-nodeModules: rems_dev_rems-logs: diff --git a/docker-compose-porter.yml b/docker-compose-porter.yml index a44ccd4d..d1dd0031 100644 --- a/docker-compose-porter.yml +++ b/docker-compose-porter.yml @@ -24,18 +24,6 @@ services: extra_hosts: - "host.docker.internal:host-gateway" - - # Create crd container - crd: # Name of our service - image: codexrems/crd:REMSvCurrent - container_name: rems_porter_crd - ports: # Port binding to host from docker container - - "8090:8090" # Bind port 3000 of host to 3000 of container - environment: - VSAC_API_KEY: ${VSAC_API_KEY} - volumes: - - rems_porter_crd-server-ValueSetCache:/CRD/server/ValueSetCache - # Create crd request generator container crd-request-generator: # Name of our service image: codexrems/crd-request-generator:REMSvCurrent @@ -60,32 +48,31 @@ services: container_name: rems_porter_rems ports: # Port binding to host from docker container - "9015:9015" # Bind port 3000 of host to 3000 of container - - pharmacy-information-system: # Name of our service - image: codexrems/pharmacy-information-system:REMSvCurrent - container_name: rems_porter_pharmacy-information-system environment: - - PORT=3010 - - MONGODB_CONNSTRING=mongodb://pharmacy-information-root:pharmacy-information-password@pharmacy-information-system-database:27017?retryWrites=true&w=majority - - CRD_BASE_URL=http://crd:8090/ - ports: # Port binding to host from docker container - - "4200:4200" - - "3010:3010" + VSAC_API_KEY: ${VSAC_API_KEY} + MONGO_HOSTNAME: mongodb://rems-admin-pims-root:rems-admin-pims-password@pims_remsadmin_mongo:27017 - pharmacy-information-system-database: # Name of our service + pims_remsadmin_mongo: image: mongo - container_name: rems_porter_pharmacy-information-system-database + container_name: rems_dev_pims-remsadmin-mongo + ports: + - '27017:27017' environment: - MONGO_INITDB_ROOT_USERNAME: pharmacy-information-root - MONGO_INITDB_ROOT_PASSWORD: pharmacy-information-password - expose: - - "27017" - ports: # Port binding to host from docker container - - "27017:27017" + MONGO_INITDB_ROOT_USERNAME: rems-admin-pims-root + MONGO_INITDB_ROOT_PASSWORD: rems-admin-pims-password volumes: - - rems_porter_pharmacy-infomation-system-database:/data/db + - pims_remsadmin_mongo:/data/db + - ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js + + pims: + image: codexrems/pims:REMSvCurrent + container_name: rems_dev_pims + environment: + REMS_ADMIN_BASE: http://rems-administrator:8090 + MONGO_HOSTNAME: mongodb://pims_remsadmin_mongo:27017/pims + ports: + - "5050:5050" + - "5051:5051" volumes: rems_porter_keycloak-data: - rems_porter_pharmacy-infomation-system-database: - rems_porter_crd-server-ValueSetCache: diff --git a/docker-compose.yml b/docker-compose.yml index 4ed19355..c592b796 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,18 +25,6 @@ services: - "host.docker.internal:host-gateway" - # Create crd container - crd: # Name of our service - image: codexrems/crd:REMSvCurrent - container_name: rems_prod_crd - ports: # Port binding to host from docker container - - "8090:8090" # Bind port 3000 of host to 3000 of container - environment: - VSAC_API_KEY: ${VSAC_API_KEY} - volumes: - - rems_prod_crd-server-ValueSetCache:/CRD/server/ValueSetCache - - # Create crd request generator container crd-request-generator: # Name of our service image: codexrems/crd-request-generator:REMSvCurrent @@ -47,7 +35,6 @@ services: - "3000:3000" # Bind port 3000 of host to 3000 of container - "3001:3001" - # Create dtr container dtr: # Name of our service image: codexrems/dtr:REMSvCurrent @@ -61,33 +48,35 @@ services: container_name: rems_prod_rems ports: # Port binding to host from docker container - "9015:9015" # Bind port 3000 of host to 3000 of container - - pharmacy-information-system: # Name of our service - image: codexrems/pharmacy-information-system:REMSvCurrent - container_name: rems_prod_pharmacy-information-system environment: - - PORT=3010 - - MONGODB_CONNSTRING=mongodb://pharmacy-information-root:pharmacy-information-password@pharmacy-information-system-database:27017?retryWrites=true&w=majority - - CRD_BASE_URL=http://crd:8090/ - ports: # Port binding to host from docker container - - "4200:4200" - - "3010:3010" + VSAC_API_KEY: ${VSAC_API_KEY} + MONGO_HOSTNAME: mongodb://rems-admin-pims-root:rems-admin-pims-password@pims_remsadmin_mongo:27017 - pharmacy-information-system-database: # Name of our service + pims_remsadmin_mongo: image: mongo - container_name: rems_dev_pharmacy-information-system-database + container_name: rems_dev_pims-remsadmin-mongo + ports: + - '27017:27017' environment: - MONGO_INITDB_ROOT_USERNAME: pharmacy-information-root - MONGO_INITDB_ROOT_PASSWORD: pharmacy-information-password - expose: - - "27017" - ports: # Port binding to host from docker container - - "27017:27017" + MONGO_INITDB_ROOT_USERNAME: rems-admin-pims-root + MONGO_INITDB_ROOT_PASSWORD: rems-admin-pims-password volumes: - - rems_porter_pharmacy-infomation-system-database:/data/db + - pims_remsadmin_mongo:/data/db + - ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js + + pims: + image: codexrems/pims:REMSvCurrent + container_name: rems_dev_pims + environment: + REMS_ADMIN_BASE: http://rems-administrator:8090 + MONGO_HOSTNAME: mongodb://pims_remsadmin_mongo:27017/pims + ports: + - "5050:5050" + - "5051:5051" + + volumes: rems_prod_keycloak-data: - rems_porter_pharmacy-infomation-system-database: - rems_prod_crd-server-ValueSetCache: + diff --git a/docker-sync.yml b/docker-sync.yml index acc3d17a..26f8d4d9 100644 --- a/docker-sync.yml +++ b/docker-sync.yml @@ -6,10 +6,6 @@ syncs: src: ../test-ehr sync_excludes: ['.gradle', 'bin', 'build', 'target', 'logs'] - # rems_dev_crd-sync: - # src: '../CRD' - # sync_excludes: ['logs', '.gradle', 'server/.gradle', 'server/bin', 'server/build', 'server/ValueSetCache', 'operations/build', 'resources/build'] - rems_dev_crd-request-generator-sync: src: '../crd-request-generator' sync_excludes: ['node_modules', 'build', 'databaseData', 'logs'] @@ -21,10 +17,6 @@ syncs: rems_dev_rems-sync: src: '.' sync_excludes: ['node_modules', 'logs'] - - rems_dev_pharmacy-information-system-sync: - src: '../pharmacy-information-system' - sync_excludes: ['node_modules', 'backend/node_modules', 'logs'] rems_dev_pims-sync: src: '../pims' diff --git a/env.json b/env.json index c6c4f58b..075cf5db 100644 --- a/env.json +++ b/env.json @@ -1,7 +1,7 @@ { "MONGO_HOSTNAME": { "type": "string", - "default": "localhost", + "default": "mongodb://rems-admin-pims-root:rems-admin-pims-password@localhost:27017", "required": true }, "MONGO_DB_NAME": { diff --git a/package.json b/package.json index 3003d196..ddb7ebc0 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "scripts": { "develop": "node dist/scripts/develop.js", - "start": "ts-node-dev src/scripts/serve.ts", + "start": "ts-node-dev --inspect=8091 src/scripts/serve.ts", "test": "jest --maxWorkers=4 --coverage --detectOpenHandles", "lint": "eslint \"**/*.{js,ts}\"", "lint:fix": "eslint \"**/*.{js,ts}\" --quiet --fix", @@ -66,6 +66,7 @@ "mongodb": "^4.12.1", "morgan": "^1.9.1", "tingodb": "^0.6.1", + "uid": "^2.0.1", "uuid": "^9.0.0", "var": "^0.4.0", "winston": "^3.2.1", diff --git a/src/config.ts b/src/config.ts index 55f1f20a..8fbdf8f0 100644 --- a/src/config.ts +++ b/src/config.ts @@ -23,7 +23,7 @@ export default { }, general: { resourcePath: 'src/cds-library/CRD-DTR', - VsacApiKey: env.VSAC_KEY + VsacApiKey: env.VSAC_API_KEY }, database: { selected: 'mongo', @@ -32,7 +32,7 @@ export default { options: '' }, mongoConfig: { - location: `mongodb://${env.MONGO_HOSTNAME}`, + location: env.MONGO_HOSTNAME, db_name: env.MONGO_DB_NAME, options: { //auto_reconnect: true, diff --git a/src/fhir/utilities.ts b/src/fhir/utilities.ts index a4c60d61..4ff1b630 100644 --- a/src/fhir/utilities.ts +++ b/src/fhir/utilities.ts @@ -284,4 +284,235 @@ export class FhirUtilities { }); }); } + + static async populateDB() { + const db = Globals.database; + + // define schemas + + // leave comments in of structure in for now as they will be useful to reference during the mongoose transition + const medicationCollection = await db.collection( + 'medication-requirements' + // , { + // 'name': { 'type': 'string' }, + // 'codeSystem': { 'type': 'string' }, + // 'code': { 'type': 'string' }, + // 'requirements': { + // 'type': 'array', + // 'items': { + // 'type': 'object', + // 'properties': { + // 'name': { 'type': 'string' }, + // 'description': { 'type': 'string' }, + // 'questionnaire': { 'type': 'object' }, + // 'stakeholderType': { 'type': 'string' }, + // 'createNewCase': { 'type': 'boolean' }, + // 'resourceId': { 'type': 'string' } + // } + // } + // } + // } + ); + + await medicationCollection.createIndex({ name: 1 }, { unique: true }); + + // leave comments of structure in for now as they will be useful to reference during the mongoose transition + const metRequirementsCollection = await db.collection( + 'met-requirements' + // , { + // 'completed': { 'type': 'boolean' }, + // 'completedQuestionnaire': { 'type': 'object' }, + // 'requirementName': { 'type': 'string' }, + // 'requirementDescription': {'type': 'string'} + // 'drugName': { 'type': 'string' }, + // 'stakeholderId': { 'type': 'string' }, + // 'case_numbers': { 'type': 'array', 'items': { 'type': 'string' } } + // } + ); + + metRequirementsCollection.createIndex( + { drugName: 1, requirementName: 1, stakeholderId: 1 }, + { unique: true } + ); + + // leave comments of structure in for now as they will be useful to reference during the mongoose transition + const remsCaseCollection = await db.collection( + 'rems-case' + // , { + // 'case_number': { 'type': 'string' }, + // 'status': { 'type': 'string' }, + // 'drugName': { 'type': 'string' }, + // 'patientName': { 'type': 'string' }, + // 'metRequirements': { + // 'type': 'array', + // 'items': { + // 'type': 'object', + // 'properties': { + // 'metRequirementId': { 'type': 'number' }, + // 'completed': { 'type': 'boolean' }, + // 'stakeholderId': { 'type': 'string' }, + // 'requirementName': { 'type': 'string' }, + // 'requirementDescription': {'type': 'string'}, + // } + // } + // } + // } + ); + + // prepopulateDB + medicationCollection.insert( + [ + { + name: 'Turalio', + codeSystem: 'http://www.nlm.nih.gov/research/umls/rxnorm', + code: '2183126', + requirements: [ + { + name: 'Patient Enrollment', + description: 'Submit Patient Enrollment form to the REMS Administrator', + stakeholderType: 'patient', + createNewCase: true, + resourceId: 'TuralioRemsPatientEnrollment' + }, + { + name: 'Prescriber Enrollment', + description: 'Submit Prescriber Enrollment form to the REMS Administrator', + stakeholderType: 'prescriber', + createNewCase: false, + resourceId: 'TuralioPrescriberEnrollmentForm' + }, + { + name: 'Prescriber Knowledge Assessment', + description: 'Submit Prescriber Knowledge Assessment form to the REMS Administrator', + stakeholderType: 'prescriber', + createNewCase: false, + resourceId: 'TuralioPrescriberKnowledgeAssessment' + }, + { + name: 'Pharmacist Enrollment', + description: 'Submit Pharmacist Enrollment form to the REMS Administrator', + stakeholderType: 'pharmacist', + createNewCase: false, + resourceId: 'TuralioPharmacistEnrollment' + } + ] + }, + { + name: 'TIRF', + codeSystem: 'http://www.nlm.nih.gov/research/umls/rxnorm', + code: '1237051', + requirements: [ + { + name: 'Patient Enrollment', + description: 'Submit Patient Enrollment form to the REMS Administrator', + stakeholderType: 'patient', + createNewCase: true, + resourceId: 'TIRFRemsPatientEnrollment' + }, + { + name: 'Prescriber Enrollment', + description: 'Submit Prescriber Enrollment form to the REMS Administrator', + stakeholderType: 'prescriber', + createNewCase: false, + resourceId: 'TIRFPrescriberEnrollmentForm' + }, + { + name: 'Prescriber Knowledge Assessment', + description: 'Submit Prescriber Knowledge Assessment form to the REMS Administrator', + stakeholderType: 'prescriber', + createNewCase: false, + resourceId: 'TIRFPrescriberKnowledgeAssessment' + }, + { + name: 'Pharmacist Enrollment', + description: 'Submit Pharmacist Enrollment form to the REMS Administrator', + stakeholderType: 'pharmacist', + createNewCase: false, + resourceId: 'TIRFPharmacistEnrollmentForm' + }, + { + name: 'Pharmacist Knowledge Assessment', + description: 'Submit Pharmacist Knowledge Assessment form to the REMS Administrator', + stakeholderType: 'pharmacist', + createNewCase: false, + resourceId: 'TIRFPharmacistKnowledgeAssessment' + } + ] + }, + { + name: 'Isotretinoin', + codeSystem: 'http://www.nlm.nih.gov/research/umls/rxnorm', + code: '6064', + requirements: [ + { + name: 'Patient Enrollment', + description: 'Submit Patient Enrollment form to the REMS Administrator', + stakeholderType: 'patient', + createNewCase: true, + resourceId: 'IPledgeRemsPatientEnrollment' + }, + { + name: 'Prescriber Enrollment', + description: 'Submit Prescriber Enrollment form to the REMS Administrator', + stakeholderType: 'prescriber', + createNewCase: false, + resourceId: 'IPledgeRemsPrescriberEnrollmentForm' + }, + { + name: 'Pharmacist Enrollment', + description: 'Submit Pharmacist Enrollment form to the REMS Administrator', + stakeholderType: 'pharmacist', + createNewCase: false, + resourceId: 'IPledgeRemsPharmacistEnrollmentForm' + } + ] + } + ], + (err: any, result: any) => { + if (err) console.log(err); + console.log('Inserted Drug Information'); + } + ); + + metRequirementsCollection.insert( + [ + { + stakeholderId: 'Organization/pharm0111', + completed: true, + requirementName: 'Pharmacist Enrollment', + drugName: 'Turalio', + completedQuestionnaire: null, + case_numbers: [] + }, + { + stakeholderId: 'Organization/pharm0111', + completed: true, + requirementName: 'Pharmacist Enrollment', + drugName: 'TIRF', + completedQuestionnaire: null, + case_numbers: [] + }, + { + stakeholderId: 'Organization/pharm0111', + completed: true, + requirementName: 'Pharmacist Knowledge Assessment', + drugName: 'TIRF', + completedQuestionnaire: null, + case_numbers: [] + }, + { + stakeholderId: 'Organization/pharm0111', + completed: true, + requirementName: 'Pharmacist Enrollment', + drugName: 'Isotretinoin', + completedQuestionnaire: null, + case_numbers: [] + } + ], + (err: any, result: any) => { + if (err) console.log(err); + console.log('Inserted Pharmacist Met Requirements'); + } + ); + } } diff --git a/src/main.ts b/src/main.ts index ff0931bd..c2fb6809 100644 --- a/src/main.ts +++ b/src/main.ts @@ -44,6 +44,7 @@ export default async function main() { // load the database with the default resources FhirUtilities.loadResources(config.general.resourcePath); + FhirUtilities.populateDB(); const app = initialize(config); diff --git a/src/server.test.ts b/src/server.test.ts index 0a8724ae..bdf4fa88 100644 --- a/src/server.test.ts +++ b/src/server.test.ts @@ -1,9 +1,25 @@ import { initialize, REMSServer } from './server'; import config from './config'; - +import { Globals } from './globals'; +import { Db, MongoClient } from 'mongodb'; describe('REMSServer class', () => { let server: REMSServer; + let connection: MongoClient; + let db: Db; + + beforeAll(async () => { + if (process.env.MONGO_URL) { + connection = await MongoClient.connect(process.env.MONGO_URL, {}); + db = await connection.db(process.env.MONGO_DB_NAME); + Globals.database = db; + } + }); + + afterAll(async () => { + await connection.close(); + }); + beforeEach(() => { jest.mock('morgan', () => jest.fn()); diff --git a/src/server.ts b/src/server.ts index 6e42122f..6250eddf 100644 --- a/src/server.ts +++ b/src/server.ts @@ -5,6 +5,9 @@ import morgan from 'morgan'; import Hook from './hooks/Hook'; import remsService from './hooks/rems.hook'; import { Server } from '@projecttacoma/node-fhir-server-core'; +import { Globals } from './globals'; +import { uid } from 'uid'; +import { FhirUtilities } from './fhir/utilities'; const logger = container.get('application'); @@ -18,6 +21,7 @@ const initialize = (config: any) => { .setPublicDirectory() .setProfileRoutes() .registerCdsHooks(config.server) + .configureEtasuEndpoints() .setErrorRoutes(); }; @@ -98,6 +102,325 @@ class REMSServer extends Server { return this; } + configureEtasuEndpoints() { + const db = Globals.database; + + const medicationCollection = db.collection('medication-requirements'); + const metRequirementsCollection = db.collection('met-requirements'); + const remsCaseCollection = db.collection('rems-case'); + + // etasu endpoints + this.app.get('/etasu/:drug', (req: any, res: { send: (arg0: string) => any }) => { + medicationCollection.findOne({ name: req.params.drug }, (err: any, drug: any) => { + if (err) throw err; + res.send(drug); + }); + }); + + this.app.get('/etasu/met/:caseId', (req: any, res: { send: (arg0: string) => any }) => { + remsCaseCollection.findOne({ case_number: req.params.caseId }, (err: any, remsCase: any) => { + if (err) throw err; + res.send(remsCase); + }); + }); + + this.app.get( + '/etasu/met/patient/:patientName/drug/:drugName', + (req: any, res: { send: (arg0: string) => any }) => { + remsCaseCollection.findOne( + { patientName: req.params.patientName, drugName: req.params.drugName }, + (err: any, remsCase: any) => { + if (err) throw err; + res.send(remsCase); + } + ); + } + ); + + this.app.post('/etasu/reset', async (req: any, res: { send: (arg0: string) => any }) => { + console.log('Dropping collections'); + await medicationCollection.deleteMany({}); + await remsCaseCollection.deleteMany({}); + await metRequirementsCollection.deleteMany({}); + console.log('Resetting the database'); + await FhirUtilities.populateDB(); + res.send('reset etasu database collections'); + }); + + this.app.post('/etasu/met', async (req: any, res: { send: (arg0: string) => any }) => { + try { + let returnedRemsRequestDoc: any; + let returnedMetReqDoc: any; + let returnRemsRequest = false; + const requestBody = req.body; + + // extract params and questionnaire response identifier + const params = this.getResource( + requestBody, + requestBody.entry[0].resource.focus.parameters.reference + ); + const questionnaireResponse = this.getQuestionnaireResponse(requestBody); + const questionnaireStringArray = questionnaireResponse.questionnaire.split('/'); + const requirementId = questionnaireStringArray[questionnaireStringArray.length - 1]; + + // stakeholder and medication references + let prescriptionReference = ''; + let practitionerReference = ''; + let pharmacistReference = ''; + let patientReference = ''; + for (const param of params.parameter) { + if (param.name === 'prescription') { + prescriptionReference = param.reference; + } else if (param.name === 'prescriber') { + practitionerReference = param.reference; + } else if (param.name === 'pharmacy') { + pharmacistReference = param.reference; + } else if (param.name === 'source-patient') { + patientReference = param.reference; + } + } + + // obtain drug information from database + const presciption = this.getResource(requestBody, prescriptionReference); + const prescriptionSystem = presciption.medicationCodeableConcept.coding[0].system; + const prescriptionCode = presciption.medicationCodeableConcept.coding[0].code; + const patient = this.getResource(requestBody, patientReference); + const patientName = patient.name[0].given[0] + ' ' + patient.name[0].family; + + const drug = await medicationCollection.findOne({ + code: prescriptionCode, + codeSystem: prescriptionSystem + }); + // iterate through each requirement of the drug + for (const requirement of drug.requirements) { + // figure out which stakeholder the req corresponds to + const reqStakeholder = requirement.stakeholderType; + const reqStakeholderReference = + reqStakeholder === 'prescriber' + ? practitionerReference + : reqStakeholder === 'pharmacist' + ? pharmacistReference + : patientReference; + + // if the requirement is the one submitted continue + if (requirement.resourceId === requirementId) { + // if the req submitted is a patient enrollment form and requires creating a new case + if (requirement.createNewCase) { + returnRemsRequest = true; + const case_number = uid(); + + // create new rems request and add the created metReq to it + let remsRequestCompletedStatus = 'Approved'; + const remsRequest: any = { + case_number: case_number, + status: remsRequestCompletedStatus, + drugName: drug.name, + patientName: patientName, + metRequirements: [] + }; + returnRemsRequest = true; + + // create the metReq that was submitted + const metReq = { + completed: true, + completedQuestionnaire: questionnaireResponse, + requirementName: requirement.name, + requirementDescription: requirement.description, + drugName: drug.name, + stakeholderId: reqStakeholderReference, + case_numbers: [case_number] + }; + + await metRequirementsCollection.insertOne(metReq); + + const matchedMetReq = await metRequirementsCollection.findOne(metReq); + + remsRequest.metRequirements.push({ + stakeholderId: matchedMetReq.stakeholderId, + completed: matchedMetReq.completed, + metRequirementId: matchedMetReq._id, + requirementName: matchedMetReq.requirementName, + requirementDescription: matchedMetReq.requirementDescription + }); + + // iterate through all other reqs again to create corresponding false metReqs / assign to existing + for (const requirement2 of drug.requirements) { + // skip if the req found is the same as in the outer loop and has already been processed + if (!(requirement2.resourceId === requirementId)) { + // figure out which stakeholder the req corresponds to + const reqStakeholder2 = requirement2.stakeholderType; + const reqStakeholder2Reference = + reqStakeholder2 === 'prescriber' + ? practitionerReference + : reqStakeholder2 === 'pharmacist' + ? pharmacistReference + : patientReference; + + const matchedMetReq2 = await metRequirementsCollection.findOne({ + stakeholderId: reqStakeholder2Reference, + requirementName: requirement2.name, + drugName: drug.name + }); + if (matchedMetReq2) { + remsRequest.metRequirements.push({ + stakeholderId: matchedMetReq2.stakeholderId, + completed: matchedMetReq2.completed, + metRequirementId: matchedMetReq2._id, + requirementName: matchedMetReq2.requirementName, + requirementDescription: matchedMetReq2.requirementDescription + }); + if (!matchedMetReq2.completed) { + remsRequestCompletedStatus = 'Pending'; + } + // matchedMetReq2.case_numbers.push(case_number); + await metRequirementsCollection.updateOne(matchedMetReq2, { + $addToSet: { case_numbers: case_number } + }); + } else { + // create the metReq that was submitted + const newMetReq = { + completed: false, + completedQuestionnaire: null, + requirementName: requirement2.name, + requirementDescription: requirement2.description, + drugName: drug.name, + stakeholderId: reqStakeholder2Reference, + case_numbers: [case_number] + }; + + remsRequestCompletedStatus = 'Pending'; + + await metRequirementsCollection.insertOne(newMetReq); + + const newMetReqDoc = await metRequirementsCollection.findOne(newMetReq); + + remsRequest.metRequirements.push({ + stakeholderId: newMetReqDoc.stakeholderId, + completed: newMetReqDoc.completed, + metRequirementId: newMetReqDoc._id, + requirementName: newMetReqDoc.requirementName, + requirementDescription: newMetReqDoc.requirementDescription + }); + } + } + } + + remsRequest.status = remsRequestCompletedStatus; + await remsCaseCollection.insertOne(remsRequest); + returnedRemsRequestDoc = await remsCaseCollection.findOne(remsRequest); + } else { + const matchedMetReq3 = await metRequirementsCollection.findOne({ + stakeholderId: reqStakeholderReference, + requirementName: requirement.name, + drugName: drug.name + }); + if (matchedMetReq3) { + // matchedMetReq3.completed = true; + // matchedMetReq3.completedQuestionnaire = questionnaireResponse; + if (!matchedMetReq3.completed) { + await metRequirementsCollection.updateOne(matchedMetReq3, { + $set: { completed: true, completedQuestionnaire: questionnaireResponse } + }); + + returnedMetReqDoc = await metRequirementsCollection.findOne({ + _id: matchedMetReq3._id + }); + + // this should be an array returned via .find() - tried using $in but could not get it to work - using the first element for now as a work around since we only have one patient + const remsRequestToUpdate = await remsCaseCollection.findOne({ + case_number: returnedMetReqDoc.case_numbers[0] + }); + + // ToDO: iterate over multiple remsRequests - right now there will only be one that matches, but with multiple patients in the system there could be more + + // for (let remsRequestToUpdate of remsRequestsToUpdate) { + let foundUncompleted = false; + const metReqArray = remsRequestToUpdate.metRequirements; + for (let i = 0; i < remsRequestToUpdate.metRequirements.length; i++) { + const req4 = remsRequestToUpdate.metRequirements[i]; + // _id comparison would not work for some reason + if (req4.requirementName === matchedMetReq3.requirementName) { + metReqArray[i].completed = true; + req4.completed = true; + const update = await remsCaseCollection.updateOne( + { _id: remsRequestToUpdate._id }, + { $set: { metRequirements: metReqArray } } + ); + } + if (!req4.completed) { + foundUncompleted = true; + } + } + + if (!foundUncompleted && remsRequestToUpdate.status === 'Pending') { + await remsCaseCollection.updateOne(remsRequestToUpdate, { + $set: { status: 'Approved' } + }); + } + + // } + } + } else { + // create the metReq that was submitted + const newMetReq3 = { + completed: true, + completedQuestionnaire: questionnaireResponse, + requirementName: requirement.name, + requirementDescription: requirement.requirementDescription, + drugName: drug.name, + stakeholderId: reqStakeholderReference, + case_numbers: [] + }; + + await metRequirementsCollection.insertOne(newMetReq3); + returnedMetReqDoc = await metRequirementsCollection.findOne(newMetReq3); + } + } + break; + } + } + + // return MetReq unless a new case is created in which case return the Rems request + if (returnRemsRequest) { + res.send(returnedRemsRequestDoc); + } else { + res.send(returnedMetReqDoc); + } + } catch (error) { + console.log(error); + } + }); + + return this; + } + + getResource(bundle: { entry: any[] }, resourceReference: string) { + const temp = resourceReference.split('/'); + const _resourceType = temp[0]; + const _id = temp[1]; + + for (let i = 0; i < bundle.entry.length; i++) { + if ( + bundle.entry[i].resource.resourceType === _resourceType && + bundle.entry[i].resource.id === _id + ) { + return bundle.entry[i].resource; + } + } + return null; + } + + getQuestionnaireResponse(bundle: { entry: any[] }) { + const _resourceType = 'QuestionnaireResponse'; + + for (let i = 0; i < bundle.entry.length; i++) { + if (bundle.entry[i].resource.resourceType === _resourceType) { + return bundle.entry[i].resource; + } + } + return null; + } + /** * @method listen * @description Start listening on the configured port