Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,5 @@ APPLE_TOPIC={{ app_id }}

# compose configuration - files and project name:
COMPOSE_PATH_SEPARATOR=;
COMPOSE_FILE=compose.yaml{% if use_ofelia %};compose.ofelia.yaml{% endif %}{% if db_use_adminer %};compose.adminer.yaml{% endif %}
COMPOSE_FILE=compose.yaml{% if use_ofelia %};compose.ofelia.yaml{% endif %}{% if use_orms %};compose.orms.yaml{% endif %}{% if db_use_adminer %};compose.adminer.yaml{% endif %}
# COMPOSE_PROJECT_NAME=opal_${ENVIRONMENT}
101 changes: 101 additions & 0 deletions .envs/orms.env.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# WEB SERVER SETTINGS
BASE_PATH=/var/www/orms
BASE_URL=https://${APP_HOST}:${HTTPS_PORT}/orms
IMAGE_PATH=/var/www/orms/images
IMAGE_URL=https://${APP_HOST}/orms/images
LOG_PATH=/var/www/orms/logs

# DATABASE CONNECTION SETTINGS
# ORMS database settings
ORMS_DATABASE_USER=${DB_USER}
ORMS_DATABASE_PASSWORD=${DB_PASSWORD}
ORMS_DATABASE_HOST=${DB_HOST}
ORMS_DATABASE_NAME=OrmsDatabase
ORMS_DATABASE_PORT=${DB_PORT}

# Log database settings
LOG_DATABASE_USER=${DB_USER}
LOG_DATABASE_PASSWORD=${DB_PASSWORD}
LOG_DATABASE_HOST=${DB_HOST}
LOG_DATABASE_NAME=OrmsLog
LOG_DATABASE_PORT=${DB_PORT}

# FIREBASE SETTINGS
# URL of the Firebase realtime database (NOTE! The slash is required after .com)
FIREBASE_CONFIG_PATH=/config/firebase-config.json
FIREBASE_BRANCH=/dev3/${INSTITUTION_CODE}/orms/

# OPAL INTERFACE ENGINE (OIE) SETTINGS
# Setting to enable/disable OIE communication
OIE_ENABLED=0
#OIE_URL=https://${OIE_HOST}:${OIE_PORT}
OIE_URL=
OIE_USERNAME=
OIE_PASSWORD=

# SMS SETTINGS
# Setting to enable/disable SMS messages
SMS_ENABLED=0
SMS_PROVIDER=twilio
SMS_LICENCE_KEY=
SMS_TOKEN=
SMS_REMINDER_CRON_ENABLED=0
SMS_INCOMING_SMS_CRON_ENABLED=0

# NEW OPAL ADMIN SETTINGS
NEW_OPAL_ADMIN_HOST_INTERNAL=http://admin:8000
NEW_OPAL_ADMIN_HOST_EXTERNAL=https://${APP_HOST}:${HTTPS_PORT}
NEW_OPAL_ADMIN_TOKEN={{ orms_token }}


LEGACY_OPAL_ADMIN_HOST_INTERNAL=http://admin-legacy:8080
# API settings for direct requests from orms to legacy OA endpoints
LEGACY_OPAL_ADMIN_API_USERNAME=orms
LEGACY_OPAL_ADMIN_API_PASSWORD={{ orms_password }}
LEGACY_OPAL_ADMIN_HOST_EXTERNAL=https://${APP_HOST}:${HTTPS_PORT}/opalAdmin

# SETTINGS FOR THE WEIGHTS
# Setting to enable/disable sending weights PDF to the OIE
SEND_WEIGHTS=0

# SETTINGS FOR THE VIRTUAL WAITING ROOM
# Setting for enabling cron job
VWR_CRON_ENABLED=0

# MESSAGE TRANSLATIONS
FAILED_CHECK_IN_MESSAGE_EN = "There is a problem checking in for your appointment(s). If you have an appointment today, please go to the reception to complete the check-in process."
FAILED_CHECK_IN_MESSAGE_FR = "Une erreur s'est produite lors de l'enregistrement pour votre/vos rendez-vous. Si vous avez un rendez-vous aujourd'hui, veuillez vous rendre à la réception pour terminer le processus d'enregistrement."
UNKNOWN_COMMAND_MESSAGE_EN = "You have not been checked-in. To check-in for an appointment, please reply with the word \"arrive\". No other messages are accepted."
UNKNOWN_COMMAND_MESSAGE_FR = "Vous n\'avez pas été enregistré(e). Pour vous enregister pour votre rendez-vous, svp repondez \"arrive\". Aucun autre message ne sera accepté."

# List of the email addresses (a.k.a., recipients) who should get an email when new appointment type detected
# The list should be separated by comma (e.g., ",") with no space
EMAIL=

# List of long codes
# TODO: investigate what list values we need to provide
# The list should be separated by comma (e.g., ",") with no space
# REGISTERED_LONG_CODES=

# SMTP MAILER SETTINGS
# ORMS sends emails in 2 cases:
# - An error occurs in a cron
# - A new appointment code / clinic code combination is detected.
# This was supposed to be used to notify the staff that there's
# a new appointment type that should be enabled for SMS
RECIPIENT_EMAILS=
EMAIL_HOST=
EMAIL_USER=
EMAIL_PASSWORD=
EMAIL_SENT_FROM_ADDRESS=
EMAIL_PORT=587

DATABASE_USE_SSL=${DB_USE_TLS}
{% if db_use_tls -%}
# Path to your CA public key file; used for DB connections if DATABASE_USE_SSL is enabled
SSL_CA=${DB_CERTS}
{% endif -%}

# Source System host for appointment location updates from orms (kiosk/vwr/sms patient checkins)
SOURCE_SYSTEM_SUPPORTS_CHECKIN=0
SOURCE_SYSTEM_HOST_EXTERNAL=
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Prepare a directory for these files with the following contents:

- `firebase-admin-key.json`: The private key of the service account used by the [Firebase Admin SDK](https://firebase.google.com/docs/database/admin/start#admin-sdk-authentication)
- `apn.crt` and `apn.key`: The public and private certificates for the Apple Push Notification service.
The private key cannot be password-protected
The private key cannot be password-protected.

### Details

Expand Down Expand Up @@ -226,6 +226,13 @@ Some questions are conditional based on your answer to a previous question.
It is an alternative to using the cron daemon on the host.
*Ofelia* sends emails for any failing job.

1. **Do you want to use the room management component (ORMS)?**

[ORMS](https://github.com/opalmedapps/#:~:text=opal%2Drms) provides a virtual waiting room.
Patients can check-in for an appointment via a kiosk.
Clinical staff can see checked in patients and call them to a room.
The called patients can be displayed on a TV screen.

1. **The Firebase project name**

The name of the Firebase project that is used for communication with the mobile app.
Expand Down
49 changes: 49 additions & 0 deletions compose.orms.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
services:
memcached:
image: memcached:1.6.40-alpine3.23
environment:
- TZ=${TIMEZONE}
orms:
image: ghcr.io/opalmedapps/opal-rms:main
restart: unless-stopped
depends_on:
- memcached
- db
env_file:
- $PWD/.envs/orms.env
environment:
- PHP_MEMORY_LIMIT=512M
- TZ=${TIMEZONE}
volumes:
- type: bind
source: $PWD/config/firebase/web-config.json
target: /config/firebase-config.json
read_only: true
labels:
- "ofelia.enabled=true"
- "ofelia.job-exec.vwr-appointments.schedule=@every 3s"
- "ofelia.job-exec.vwr-appointments.command=php php/cron/generateVwrAppointments.php"
- "ofelia.job-exec.vwr-appointments.no-overlap=true"
- "ofelia.job-exec.vwr-appointments.user=www-data"
# - "ofelia.job-exec.incoming-sms.schedule=@every 5s"
# - "ofelia.job-exec.incoming-sms.command=php php/cron/processIncomingSmsMessages.php"
# - "ofelia.job-exec.incoming-sms.no-overlap=true"
- "ofelia.job-exec.appointment-reminder.schedule=0 0 18 * * *"
- "ofelia.job-exec.appointment-reminder.command=php php/cron/generateAppointmentReminders.php"
- "ofelia.job-exec.checkout.schedule=0 30 23 * * *"
- "ofelia.job-exec.checkout.command=php php/cron/endOfDayCheckout.php"
admin:
environment:
- ORMS_ENABLED=True
- ORMS_HOST=https://${APP_HOST}/orms
admin-legacy:
environment:
- ORMS_ENABLED=1
- ORMS_HOST=https://${APP_HOST}/orms
listener:
environment:
- ORMS_ENABLED=1
- CHECKIN_ROOM=OPAL PHONE APP
# URL notified when a questionnaire is completed
- QUESTIONNAIRE_COMPLETED_URL=http://orms:8080/orms/php/api/public/v1/patient/notifyNewQuestionnaireResponse
- ORMS_CHECKIN_URL=http://orms:8080/orms/php/api/public/v1/patient/checkInToLocation
18 changes: 18 additions & 0 deletions config/traefik/dynamic-config/services.yaml.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ http:
tls: {}
{%- endif %}

{% if use_orms -%}
orms:
rule: {% raw %}'Host(`{{ env "APP_HOST" }}`) && PathPrefix(`/orms`)'{% endraw %}
entryPoints:
- web-secure
middlewares:
- redirect-missing-slash
service: orms
tls: {}
{%- endif %}

middlewares:
redirect-root:
redirectRegex:
Expand Down Expand Up @@ -113,3 +124,10 @@ http:
servers:
- url: http://adminer:8080
{%- endif %}

{% if use_orms -%}
orms:
loadBalancer:
servers:
- url: http://orms:8080
{%- endif %}
30 changes: 29 additions & 1 deletion copier.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ use_ofelia:
help: Do you want to use Ofelia as a job scheduler to run period jobs?
default: true

use_orms:
type: bool
help: Do you want to use the room management component (ORMS)?
default: false

firebase_project_name:
type: str
help: The Firebase project name
Expand Down Expand Up @@ -223,6 +228,12 @@ interface_engine_token:
default: "{% if run_setup %}{{ random_token(20) }}{% endif %}"
when: false

orms_token:
type: str
default: "{{ existing_secret('.envs/orms.env', 'NEW_OPAL_ADMIN_TOKEN') or random_token(20) }}"
when: false


admin_token:
type: str
default: "{{ existing_secret('.envs/admin-legacy.env', 'NEW_OPALADMIN_TOKEN') or random_token(20) }}"
Expand All @@ -238,6 +249,11 @@ labs_password:
default: "{% if run_setup %}{{ random_password(20) }}{% endif %}"
when: false

orms_password:
type: str
default: "{% if run_setup %}{{ random_password(20) }}{% endif %}"
when: false

_message_after_copy: |
Your project "{{ project_name }}" has been created successfully!

Expand Down Expand Up @@ -354,11 +370,19 @@ _tasks:
- command: |
echo "Running init_db script to initialize DB..."
ENVIRONMENT="{{ environment }}" DB_ROOT_USER="{{ db_root_user }}" DB_ROOT_PASSWORD="{{ db_root_password }}" DB_HOST="{{ db_host }}" DB_PORT="{{ db_port }}" DB_USER="{{ db_user }}" DB_PASSWORD="{{ db_password }}" DB_NAME=admin bash ./scripts/init_db.sh
- command: |
echo "Running init_orms script to initialize ORMS DB..."
ENVIRONMENT="{{ environment }}" DB_ROOT_USER="{{ db_root_user }}" DB_ROOT_PASSWORD="{{ db_root_password }}" DB_HOST="{{ db_host }}" DB_PORT="{{ db_port }}" DB_USER="{{ db_user }}" DB_PASSWORD="{{ db_password }}" DB_NAME=admin bash ./scripts/init_orms.sh
when: "{{ use_orms }}"
# run admin
- docker compose up -d admin
# Migrate schemas and initialize data
- command: |
LISTENER_TOKEN="{{ listener_token }}" LISTENER_REGISTRATION_TOKEN="{{ listener_registration_token }}" INTERFACE_ENGINE_TOKEN="{{ interface_engine_token }}" INTERFACE_ENGINE_PASSWORD="{{ labs_password }}" ADMIN_TOKEN="{{ admin_token }}" ADMIN_PASSWORD={{ admin_password }} ./scripts/initialize.sh
LISTENER_TOKEN="{{ listener_token }}" LISTENER_REGISTRATION_TOKEN="{{ listener_registration_token }}" INTERFACE_ENGINE_TOKEN="{{ interface_engine_token }}" INTERFACE_ENGINE_PASSWORD="{{ labs_password }}" ADMIN_TOKEN="{{ admin_token }}" ADMIN_PASSWORD="{{ admin_password }}" ./scripts/initialize.sh
# explicitly set a password for the orms user to allow logins on admin-legacy
- command: |
docker compose exec admin python manage.py shell -c 'user = User.objects.get(username="orms"); user.set_password("{{ orms_password }}"); user.save();'
when: "{{ use_orms }}"
# run the remaining services
- command: |
docker compose up -d
Expand Down Expand Up @@ -394,7 +418,11 @@ _exclude:
- "renovate.json5"
- "ruff.toml"
- "zizmor.yml"
- "LICENSE"
# - "README.md"
- extensions
- "{% if not db_use_adminer %}compose.adminer.yaml{% endif %}"
- "{% if not use_orms %}compose.orms.yaml{% endif %}"
- "{% if not use_ofelia %}compose.ofelia.yaml{% endif %}"
- "{% if not is_test %}scripts/cleanup_db.sh{% endif %}"
- "{% if not is_test %}tests{% endif %}"
12 changes: 11 additions & 1 deletion ctt.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ _extra_tasks = [

[output.".ctt/defaults"]
_extra_tasks = [
"! ls compose.orms.yaml",
"uv run tests/admin_login.py '{{ admin_password }}'",
"uv run tests/system_user_login.py '{{ labs_password }}'",
"uv run tests/system_user_login.py interface-engine '{{ labs_password }}'",
"uv run tests/labs_basic_auth.py '{{ labs_password }}'",
"uv run tests/validate_token.py admin_token {{ admin_token }}",
"uv run tests/validate_token.py listener_token {{ listener_token }}",
Expand Down Expand Up @@ -64,3 +65,12 @@ db_root_password = "root-password"
# TODO: add test case with TLS enabled
db_use_tls = 0
use_custom_certs = false

# orms enabled
[output.".ctt/orms_enabled"]
use_orms = true
_extra_tasks = [
"ls compose.orms.yaml",
"uv run tests/system_user_login.py orms {{ orms_password }}",
"docker compose down",
]
37 changes: 37 additions & 0 deletions scripts/init_orms.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/bash
set -euo pipefail

# renovate: datasource=docker depName=alpine
ALPINE_VERSION="3.23.2"
WAIT_FOR_IT_VERSION="latest"

echo "Waiting for DB container to be ready..."
docker run --rm --interactive --network opal-${ENVIRONMENT} chainguard/wait-for-it:${WAIT_FOR_IT_VERSION} --host="$DB_HOST" --port="$DB_PORT" --timeout=20

echo "Running container for mysql-client..."
docker run --rm --interactive \
--env DB_ROOT_USER=${DB_ROOT_USER} \
--env DB_ROOT_PASSWORD=${DB_ROOT_PASSWORD} \
--env DB_HOST=${DB_HOST} \
--env DB_USER=${DB_USER} \
--env DB_PASSWORD=${DB_PASSWORD} \
--network opal-${ENVIRONMENT} \
alpine:${ALPINE_VERSION} sh -s << EOF
set -euo pipefail
apk add --no-cache mysql-client
echo "Connecting to DB server on ${DB_HOST}:${DB_PORT}..."
echo "Creating ORMS DBs..."
MYSQL_PWD=${DB_ROOT_PASSWORD} mariadb --protocol tcp --skip-ssl --user ${DB_ROOT_USER} --host ${DB_HOST} --port ${DB_PORT} <<'EOIF'
CREATE DATABASE IF NOT EXISTS \`OrmsDatabase\` /*!40100 DEFAULT CHARACTER SET latin1 */;
CREATE DATABASE IF NOT EXISTS \`OrmsLog\` /*!40100 DEFAULT CHARACTER SET latin1 */;
EOIF
echo "Successfully created ORMS DBs"
echo "Granting privileges to DB user..."
MYSQL_PWD=${DB_ROOT_PASSWORD} mariadb --protocol tcp --skip-ssl --user ${DB_ROOT_USER} --host ${DB_HOST} --port ${DB_PORT} <<'EOIF'
GRANT ALL PRIVILEGES ON \`OrmsDatabase\`.* TO '$DB_USER'@'%';
GRANT ALL PRIVILEGES ON \`OrmsLog\`.* TO '$DB_USER'@'%';
FLUSH PRIVILEGES;
EOIF
echo "Successfully granted privileges to DB user"
echo "Done!"
EOF
44 changes: 44 additions & 0 deletions scripts/refresh_data_orms.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/bin/bash
# Description: Refresh data in a non-production environment

set -euo pipefail

echo "Beginning ORMS test data reset..."

# Check for required arguments
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <institution>"
echo "Valid institutions: OMI, OHIGPH"
exit 1
fi

institution=$1
institution_lower=$(echo $1 | tr '[:upper:]' '[:lower:]')

# Validate the institution
if [[ "$institution" != "OMI" && "$institution" != "OHIGPH" ]]; then
echo "Invalid institution: $institution"
echo "Valid institutions: OMI, OHIGPH"
exit 1
fi

set -euxo pipefail

echo "Upgrading OrmsDB..."
docker compose run --rm db-management alembic --name ormsdb upgrade head
echo "Upgrading OrmsLogDB..."
docker compose run --rm db-management alembic --name ormslogdb upgrade head

docker compose run --rm db-management python -m db_management.run_sql_scripts OrmsDB db_management/ormsdb/data/truncate/
docker compose run --rm db-management python -m db_management.run_sql_scripts OrmsLogDB db_management/ormslogdb/data/truncate/

docker compose run --rm db-management python -m db_management.run_sql_scripts OrmsDB db_management/ormsdb/data/initial/
docker compose run --rm db-management python -m db_management.run_sql_scripts OrmsDB db_management/ormsdb/data/test/
docker compose run --rm db-management python -m db_management.run_sql_scripts OrmsDB db_management/ormsdb/data/test/$institution_lower/
docker compose run --rm db-management python -m db_management.run_sql_scripts OrmsLogDB db_management/ormslogdb/data/initial/
docker compose run --rm db-management python -m db_management.run_sql_scripts OrmsLogDB db_management/ormslogdb/data/test/
docker compose run --rm db-management python -m db_management.run_sql_scripts OrmsLogDB db_management/ormslogdb/data/test/$institution_lower/

docker compose exec admin python manage.py update_orms_patients

echo "ORMS test data successfully reset."
Loading
Loading