diff --git a/.env b/.env index 3673098..cfe7782 100644 --- a/.env +++ b/.env @@ -11,3 +11,11 @@ USE_HTTPS = false VSAC_API_KEY = changeMe WHITELIST = http://localhost, http://localhost:3005 SERVER_NAME = CodeX REMS Administrator Prototype +FULL_RESOURCE_IN_APP_CONTEXT = false + +#Frontend Vars +FRONTEND_PORT=9090 +VITE_REALM = ClientFhirServer +VITE_AUTH = http://localhost:8180 +VITE_CLIENT = app-login +VITE_SCOPE_ID = rems-admin diff --git a/Dockerfile b/Dockerfile index 594e2cf..a50d5b2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,10 +4,21 @@ WORKDIR /rems-admin ARG PORT=8090 ENV PORT=${PORT} +ARG FRONTEND_PORT=9090 +ENV FRONTEND_PORT=${FRONTEND_PORT} + COPY --chown=node:node . . RUN npm install -EXPOSE 8090 -HEALTHCHECK --interval=30s --start-period=15s --timeout=10m --retries=10 CMD wget --no-verbose --tries=1 --spider http://localhost:${PORT} || exit 1 +WORKDIR /rems-admin/frontend +RUN npm install + +WORKDIR /rems-admin + +EXPOSE 8090 EXPOSE 8095 -CMD npm run start \ No newline at end of file +EXPOSE 9090 +EXPOSE 9095 + +HEALTHCHECK --interval=45s --start-period=60s --timeout=10m --retries=10 CMD (wget --no-verbose --tries=1 --spider http://localhost:${PORT} && wget --no-verbose --tries=1 --spider http://localhost:${FRONTEND_PORT}) || exit 1 +CMD ./dockerRunnerProd.sh \ No newline at end of file diff --git a/Dockerfile.dev b/Dockerfile.dev index 051e83b..8bbff68 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -4,13 +4,31 @@ WORKDIR /rems-admin ARG PORT=8090 ENV PORT=${PORT} +ARG FRONTEND_PORT=9090 +ENV FRONTEND_PORT=${FRONTEND_PORT} + COPY --chown=node:node . . RUN npm install + +WORKDIR /rems-admin/frontend +RUN npm install + +WORKDIR /rems-admin + + EXPOSE 8090 EXPOSE 8091 -HEALTHCHECK --interval=30s --start-period=15s --timeout=10m --retries=10 CMD wget --no-verbose --tries=1 --spider http://localhost:${PORT} || exit 1 - EXPOSE 8095 EXPOSE 8096 + +EXPOSE 9090 +EXPOSE 9091 + +EXPOSE 9095 +EXPOSE 9096 + +HEALTHCHECK --interval=45s --start-period=60s --timeout=10m --retries=10 CMD (wget --no-verbose --tries=1 --spider http://localhost:${PORT} && wget --no-verbose --tries=1 --spider http://localhost:${FRONTEND_PORT}) || exit 1 + + CMD ./dockerRunnerDev.sh \ No newline at end of file diff --git a/README.md b/README.md index c68f548..84cdb6b 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Next, start the frontend with the following: ### `npm start` -Go to the UI running on http://localhost:5173/ (or whichever port it was run on) +Go to the UI running on http://localhost:9090/ (or whichever port it was run on) Still need to update docker to start the UI automatically. @@ -117,3 +117,5 @@ Following are a list of modifiable paths: | VSAC_API_KEY | `changeMe` | Replace with VSAC API key for pulling down ValueSets. Request an API Key from the [VSAC website](https://vsac.nlm.nih.gov/) | | WHITELIST | `http://localhost, http://localhost:3005` | List of valid URLs for CORS. Should include any URLs the server accesses for resources. | | SERVER_NAME | `CodeX REMS Administrator Prototype` | Name of the server that is returned in the card source. | +| FULL_RESOURCE_IN_APP_CONTEXT | 'false' | If true, the entire order resource will be included in the appContext, otherwise only a reference will be. | +| FRONTEND_PORT | `9080` | Port that the frontend server should run on, change if there are conflicts with port usage. | \ No newline at end of file diff --git a/dockerRunnerDev.sh b/dockerRunnerDev.sh index c59845c..7395bf6 100755 --- a/dockerRunnerDev.sh +++ b/dockerRunnerDev.sh @@ -1,44 +1,71 @@ #!/bin/sh # Handle closing application on signal interrupt (ctrl + c) -trap 'kill $CONTINUOUS_INSTALL_PID $SERVER_PID; gradle --stop; exit' INT +trap 'kill $CONTINUOUS_INSTALL_PID $SERVER_PID $BACKEND_SERVER_PID; exit' INT mkdir logs +touch ./logs/frontend_installer.log +touch ./logs/frontend_runner.log +touch ./logs/backend_installer.log +touch ./logs/backend_runner.log + # Reset log file content for new application boot -echo "*** Logs for continuous installer ***" > ./logs/installer.log -echo "*** Logs for 'npm run start' ***" > ./logs/runner.log +echo "*** Logs for continuous frontend installer ***" > ./logs/frontend_installer.log +echo "*** Logs for frontend 'npm run start' ***" > ./logs/frontend_runner.log + +echo "*** Logs for continuous backend installer ***" > ./logs/backend_installer.log +echo "*** Logs for backend 'npm run start' ***" > ./logs/backend_runner.log # Print that the application is starting in watch mode echo "starting application in watch mode..." # Start the continious build listener process -echo "starting continuous installer..." -npm install +echo "starting continuous installers..." + +cd frontend +npm install | tee ./logs/frontend_installer.log +cd .. +npm install | tee ./logs/backend_installer.log -( package_modify_time=$(stat -c %Y package.json) -package_lock_modify_time=$(stat -c %Y package-lock.json) +( package_modify_time=$(stat -c %Y frontend/package.json) +package_lock_modify_time=$(stat -c %Y frontend/package-lock.json) +backend_modify_time=$(stat -c %Y package.json) +backend_lock_modify_time=$(stat -c %Y package-lock.json) while sleep 1 do - new_package_modify_time=$(stat -c %Y package.json) - new_package_lock_modify_time=$(stat -c %Y package-lock.json) + new_package_modify_time=$(stat -c %Y frontend/package.json) + new_package_lock_modify_time=$(stat -c %Y frontend/package-lock.json) + new_backend_modify_time=$(stat -c %Y package.json) + new_backend_lock_modify_time=$(stat -c %Y package-lock.json) - if [[ "$package_modify_time" != "$new_package_modify_time" ]] || [[ "$package_lock_modify_time" != "$new_package_lock_modify_time" ]] + if [[ "$package_modify_time" != "$new_package_modify_time" ]] || [[ "$package_lock_modify_time" != "$new_package_lock_modify_time" ]] || [[ "$backend_lock_modify_time" != "$new_backend_lock_modify_time" ]]|| [[ "$backend_modify_time" != "$new_backend_modify_time" ]] then - echo "running npm install..." - npm install | tee ./logs/installer.log + echo "running frontent npm install..." + cd frontend + npm install | tee ./logs/frontend_installer.log + cd .. + elif [[ "$backend_lock_modify_time" != "$new_backend_lock_modify_time" ]]|| [[ "$backend_modify_time" != "$new_backend_modify_time" ]] + then + echo "running backend npm install..." + npm install | tee ./logs/backend_installer.log fi package_modify_time=$new_package_modify_time package_lock_modify_time=$new_package_lock_modify_time + backend_modify_time=$new_backend_modify_time + backend_lock_modify_time=$new_backend_lock_modify_time done ) & CONTINUOUS_INSTALL_PID=$! # Start server process once initial build finishes -( npm run start | tee ./logs/runner.log ) & SERVER_PID=$! +cd frontend +( npm run start | tee ./logs/frontend_runner.log ) & SERVER_PID=$! + +cd .. +( npm run start | tee ./logs/backend_runner.log ) & BACKEND_SERVER_PID=$! # Handle application background process exiting -wait $CONTINUOUS_INSTALL_PID $SERVER_PID +wait $CONTINUOUS_INSTALL_PID $SERVER_PID $BACKEND_SERVER_PID EXIT_CODE=$? echo "application exited with exit code $EXIT_CODE..." - diff --git a/dockerRunnerProd.sh b/dockerRunnerProd.sh new file mode 100755 index 0000000..bb91319 --- /dev/null +++ b/dockerRunnerProd.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +cd frontend +( npm run start ) & SERVER_PID=$! + +cd .. +( npm run start ) & BACKEND_SERVER_PID=$! + +# Handle application background process exiting +wait $SERVER_PID $BACKEND_SERVER_PID +EXIT_CODE=$? +echo "application exited with exit code $EXIT_CODE..." \ No newline at end of file diff --git a/frontend/config.json b/frontend/config.json deleted file mode 100644 index 2f2a382..0000000 --- a/frontend/config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "realm": "ClientFhirServer", - "client": "app-login", - "auth": "http://localhost:8180/", - "scopeId": "pims" -} diff --git a/frontend/package.json b/frontend/package.json index 7f69441..b5b2e34 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,6 +15,7 @@ "@mui/icons-material": "^6.1.0", "@mui/material": "^6.1.0", "axios": "^1.7.7", + "dotenv": "^16.4.5", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.26.2" diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 895151d..2d8b36b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -29,7 +29,7 @@ function App() { const resetDB = async () => { setOpen(false); await axios - .post('http://localhost:8090/etasu/reset') + .post(process.env.RESOURCE_SERVER + '/etasu/reset') .then(function (response: any) { console.log(response); setForceRefresh(true); diff --git a/frontend/src/views/DataViews/CaseCollection.tsx b/frontend/src/views/DataViews/CaseCollection.tsx index 96bdd42..2e2339d 100644 --- a/frontend/src/views/DataViews/CaseCollection.tsx +++ b/frontend/src/views/DataViews/CaseCollection.tsx @@ -56,7 +56,7 @@ const CaseCollection = (props: { refresh: boolean }) => { }, []); const getAllRemsCase = async () => { - const url = 'http://localhost:8090/api/all/remscase'; + const url = process.env.RESOURCE_SERVER + '/api/all/remscase'; await axios .get(url) .then(function (response: { data: SetStateAction }) { @@ -70,7 +70,7 @@ const CaseCollection = (props: { refresh: boolean }) => { }; const deleteSingleRow = async (event: any, row: RemsCase) => { - const url = 'http://localhost:8090/api/remsCase/deleteOne'; + const url = process.env.RESOURCE_SERVER + '/api/remsCase/deleteOne'; await axios .post(url, { data: { params: row } }) .then(function (response: { data: any; status: number }) { diff --git a/frontend/src/views/DataViews/Medications.tsx b/frontend/src/views/DataViews/Medications.tsx index 373a734..aa37011 100644 --- a/frontend/src/views/DataViews/Medications.tsx +++ b/frontend/src/views/DataViews/Medications.tsx @@ -46,7 +46,7 @@ const Medications = (props: { refresh: boolean }) => { }, []); const getAllMedications = async () => { - const url = 'http://localhost:8090/api/all/medications'; + const url = process.env.RESOURCE_SERVER + '/api/all/medications'; await axios .get(url) .then(function (response: { data: SetStateAction }) { diff --git a/frontend/src/views/DataViews/MetRequirements.tsx b/frontend/src/views/DataViews/MetRequirements.tsx index 98e2d5f..d544fcf 100644 --- a/frontend/src/views/DataViews/MetRequirements.tsx +++ b/frontend/src/views/DataViews/MetRequirements.tsx @@ -44,7 +44,7 @@ const MetRequirements = (props: { refresh: boolean }) => { }, []); const getAllMetReqs = async () => { - const url = 'http://localhost:8090/api/all/metreqs'; + const url = process.env.RESOURCE_SERVER + '/api/all/metreqs'; await axios .get(url) .then(function (response: { data: any }) { @@ -58,7 +58,7 @@ const MetRequirements = (props: { refresh: boolean }) => { }; const deleteSingleRow = async (event: any, row: MetRequirements) => { - const url = 'http://localhost:8090/api/metreqs/deleteOne'; + const url = process.env.RESOURCE_SERVER + '/api/metreqs/deleteOne'; await axios .post(url, { data: { params: row } }) .then(function (response: { data: any; status: number }) { @@ -73,7 +73,7 @@ const MetRequirements = (props: { refresh: boolean }) => { }; const formattedQuestionnaire = (row: MetRequirements) => { - return row?.completedQuestionnaire?.questionnaire.split('http://localhost:8090/4_0_0/')[1]; + return row?.completedQuestionnaire?.questionnaire.split(process.env.RESOURCE_SERVER + '/4_0_0/')[1]; }; const formattedCompleted = (row: MetRequirements) => { return row?.completed === true ? 'Yes' : 'No'; diff --git a/frontend/src/views/Login.tsx b/frontend/src/views/Login.tsx index 15b7df6..66a114f 100644 --- a/frontend/src/views/Login.tsx +++ b/frontend/src/views/Login.tsx @@ -2,7 +2,6 @@ import { SetStateAction, useState } from 'react'; import axios from 'axios'; import { Avatar, Box, Button, Container, CssBaseline, TextField, Typography } from '@mui/material'; import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; -import config from '../../config.json'; const Login = props => { const [showMessage, setShowMessage] = useState(false); @@ -20,9 +19,9 @@ const Login = props => { params.append('username', user); params.append('password', pass); params.append('grant_type', 'password'); - params.append('client_id', config.client); + params.append('client_id', process.env.VITE_CLIENT!); axios - .post(`${config.auth}/realms/${config.realm}/protocol/openid-connect/token`, params, { + .post(`${process.env.VITE_AUTH}/realms/${process.env.VITE_REALM}/protocol/openid-connect/token`, params, { withCredentials: true }) .then( diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 6549182..5aa4da6 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,7 +1,19 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; -// https://vitejs.dev/config/ +import dotenv from 'dotenv'; + +dotenv.config({ path: '../.env' }); // load env vars from .env export default defineConfig({ - plugins: [react()] + // depending on your application, base can also be "/" + base: '', + plugins: [react()], + define: { + 'process.env': process.env + }, + server: { + port: parseInt(process.env.FRONTEND_PORT!), + open: false, + host: true + } }); diff --git a/src/config.ts b/src/config.ts index 69fa1a4..ad1680a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -23,7 +23,8 @@ export default { }, general: { resourcePath: 'src/cds-library/CRD-DTR', - VsacApiKey: env.get('VSAC_API_KEY').required().asString() + VsacApiKey: env.get('VSAC_API_KEY').required().asString(), + fullResourceInAppContext: env.get('FULL_RESOURCE_IN_APP_CONTEXT').required().asBool() }, database: { selected: 'mongo', @@ -40,7 +41,7 @@ export default { fhirServerConfig: { auth: { // This server's URI - resourceServer: env.get('RESOURCE_SERVER').required().asUrlString() + resourceServer: env.get('RESOURCE_SERVER').required().asUrlString() + '/' // // if you use this strategy, you need to add the corresponding env vars to docker-compose // diff --git a/src/fhir/utilities.ts b/src/fhir/utilities.ts index 72d7967..de0cde9 100644 --- a/src/fhir/utilities.ts +++ b/src/fhir/utilities.ts @@ -13,6 +13,7 @@ import ValueSetModel from '../lib/schemas/resources/ValueSet'; import { Model } from 'mongoose'; import { medicationCollection, metRequirementsCollection } from './models'; import { glob } from 'glob'; +import config from '../config'; class ResourceExistsException extends Error {} @@ -135,7 +136,9 @@ export class FhirUtilities { resourceId: 'TuralioRemsPatientEnrollment', requiredToDispense: true, appContext: - 'questionnaire=http://localhost:8090/4_0_0/Questionnaire/TuralioRemsPatientEnrollment', + 'questionnaire=' + + config.fhirServerConfig.auth.resourceServer + + '4_0_0/Questionnaire/TuralioRemsPatientEnrollment', questionnaire: null }, { @@ -146,7 +149,9 @@ export class FhirUtilities { resourceId: 'TuralioPrescriberEnrollmentForm', requiredToDispense: true, appContext: - 'questionnaire=http://localhost:8090/4_0_0/Questionnaire/TuralioPrescriberEnrollmentForm', + 'questionnaire=' + + config.fhirServerConfig.auth.resourceServer + + '4_0_0/Questionnaire/TuralioPrescriberEnrollmentForm', questionnaire: null }, { @@ -157,7 +162,9 @@ export class FhirUtilities { resourceId: 'TuralioPrescriberKnowledgeAssessment', requiredToDispense: true, appContext: - 'questionnaire=http://localhost:8090/4_0_0/Questionnaire/TuralioPrescriberKnowledgeAssessment', + 'questionnaire=' + + config.fhirServerConfig.auth.resourceServer + + '4_0_0/Questionnaire/TuralioPrescriberKnowledgeAssessment', questionnaire: null }, { @@ -178,7 +185,9 @@ export class FhirUtilities { resourceId: 'TuralioRemsPatientStatus', requiredToDispense: false, appContext: - 'questionnaire=http://localhost:8090/4_0_0/Questionnaire/TuralioRemsPatientStatus', + 'questionnaire=' + + config.fhirServerConfig.auth.resourceServer + + '4_0_0/Questionnaire/TuralioRemsPatientStatus', questionnaire: null } ] @@ -196,7 +205,9 @@ export class FhirUtilities { resourceId: 'TIRFRemsPatientEnrollment', requiredToDispense: true, appContext: - 'questionnaire=http://localhost:8090/4_0_0/Questionnaire/TIRFRemsPatientEnrollment', + 'questionnaire=' + + config.fhirServerConfig.auth.resourceServer + + '4_0_0/Questionnaire/TIRFRemsPatientEnrollment', questionnaire: null }, { @@ -207,7 +218,9 @@ export class FhirUtilities { resourceId: 'TIRFPrescriberEnrollmentForm', requiredToDispense: true, appContext: - 'questionnaire=http://localhost:8090/4_0_0/Questionnaire/TIRFPrescriberEnrollmentForm', + 'questionnaire=' + + config.fhirServerConfig.auth.resourceServer + + '4_0_0/Questionnaire/TIRFPrescriberEnrollmentForm', questionnaire: null }, { @@ -218,7 +231,9 @@ export class FhirUtilities { resourceId: 'TIRFPrescriberKnowledgeAssessment', requiredToDispense: true, appContext: - 'questionnaire=http://localhost:8090/4_0_0/Questionnaire/TIRFPrescriberKnowledgeAssessment', + 'questionnaire=' + + config.fhirServerConfig.auth.resourceServer + + '4_0_0/Questionnaire/TIRFPrescriberKnowledgeAssessment', questionnaire: null }, { @@ -256,7 +271,9 @@ export class FhirUtilities { resourceId: 'IPledgeRemsPatientEnrollment', requiredToDispense: true, appContext: - 'questionnaire=http://localhost:8090/4_0_0/Questionnaire/IPledgeRemsPatientEnrollment', + 'questionnaire=' + + config.fhirServerConfig.auth.resourceServer + + '4_0_0/Questionnaire/IPledgeRemsPatientEnrollment', questionnaire: null }, { @@ -267,7 +284,9 @@ export class FhirUtilities { resourceId: 'IPledgeRemsPrescriberEnrollmentForm', requiredToDispense: true, appContext: - 'questionnaire=http://localhost:8090/4_0_0/Questionnaire/IPledgeRemsPrescriberEnrollmentForm', + 'questionnaire=' + + config.fhirServerConfig.auth.resourceServer + + '4_0_0/Questionnaire/IPledgeRemsPrescriberEnrollmentForm', questionnaire: null }, { diff --git a/src/hooks/hookResources.ts b/src/hooks/hookResources.ts index b7a3503..05321ff 100644 --- a/src/hooks/hookResources.ts +++ b/src/hooks/hookResources.ts @@ -297,13 +297,18 @@ export function createSmartLink( appContext: string | null, request: MedicationRequest | undefined ) { + let order; + if (config.general.fullResourceInAppContext) { + order = JSON.stringify(request); + } else { + order = request?.resourceType + '/' + request?.id; + } + const newLink: Link = { label: requirementName + ' Form', url: new URL(config.smart.endpoint), type: 'smart', - appContext: `${appContext}&order=${JSON.stringify(request)}&coverage=${ - request?.insurance?.[0].reference - }` + appContext: `${appContext}&order=${order}&coverage=${request?.insurance?.[0].reference}` }; return newLink; } diff --git a/src/server.ts b/src/server.ts index bfb47ef..ff5fd52 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,4 +1,4 @@ -import cors from 'cors'; +import cors, { CorsOptions } from 'cors'; import bodyParser from 'body-parser'; import container from './lib/winston'; import morgan from 'morgan'; @@ -18,10 +18,11 @@ import bodyParserXml from 'body-parser-xml'; const logger = container.get('application'); + const initialize = (config: any) => { //const logLevel = _.get(config, 'logging.level'); return new REMSServer(config.fhirServerConfig) - .configureMiddleware() + .configureMiddleware(config.fhirServerConfig.server.corsOptions) .configureSession() .configureHelmet() .configurePassport() @@ -60,15 +61,15 @@ class REMSServer extends Server { * @method configureMiddleware * @description Enable all the standard middleware */ - configureMiddleware() { + configureMiddleware(corsOptions: CorsOptions) { super.configureMiddleware(); this.app.set('showStackError', true); this.app.set('jsonp callback', true); this.app.use(bodyParser.urlencoded({ limit: '50mb', extended: true })); this.app.use(bodyParser.json({ limit: '50mb' })); - this.app.use(cors()); - this.app.options('*', cors()); + this.app.use(cors(corsOptions)); + this.app.options('*', cors(corsOptions)); return this; }