diff --git a/files/emr-3-demo/docker-compose.yml b/files/emr-3-demo/docker-compose.yml
index effae5c..57cf978 100644
--- a/files/emr-3-demo/docker-compose.yml
+++ b/files/emr-3-demo/docker-compose.yml
@@ -43,6 +43,7 @@ services:
timeout: 5s
volumes:
- openmrs-data:/openmrs/data
+ - ./liquibase:/openmrs/data/openmrs_config/liquibase/
# MariaDB
db:
diff --git a/files/emr-3-demo/liquibase/fix-admin_core-demo.xml b/files/emr-3-demo/liquibase/fix-admin_core-demo.xml
new file mode 100644
index 0000000..ca89750
--- /dev/null
+++ b/files/emr-3-demo/liquibase/fix-admin_core-demo.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ DELIMITER $$;
+ CREATE TRIGGER users_update BEFORE UPDATE ON users FOR EACH ROW
+ IF OLD.user_id = 1 THEN
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'admin user account is locked';
+ END IF; $$
+ CREATE TRIGGER users_delete BEFORE DELETE ON users FOR EACH ROW
+ IF OLD.user_id = 1 THEN
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'admin user account is locked';
+ END IF; $$
+ DELIMITER ;
+
+
+
diff --git a/files/emr-3-demo/liquibase/liquibase.xml b/files/emr-3-demo/liquibase/liquibase.xml
new file mode 100644
index 0000000..6484c21
--- /dev/null
+++ b/files/emr-3-demo/liquibase/liquibase.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/files/emr-3-qa/.env b/files/emr-3-qa/.env
new file mode 100644
index 0000000..16e366f
--- /dev/null
+++ b/files/emr-3-qa/.env
@@ -0,0 +1,14 @@
+$ANSIBLE_VAULT;1.1;AES256
+63663666336639303062653137653231306535303635663364393033346235613535626235663634
+6238363162663262363435656436303230666361363063330a373939353331336135333135313236
+63373738326633366639613362636431383931653131373062373337363538333439373235323534
+6330633532613164380a616531643163383739343166353235366334316236643662666237373837
+32643763336637333566653933376133396362373161323039626333356566306438663337623665
+66633964613831376133366364623363623332303533656266643432353032316238343965626237
+31393235393731346536383335393534653330303230313162373164616564343235366534393061
+66393166353934663332643430383765663565636430623933366634333664393939643962623261
+65633230613335313632316365653431333333386566633564376366633032353737386330636334
+62663633633331666239366166376434333466646138393563666638383533633333356630393237
+31643363383463346363323036326137626465396466323434303331623033666266383334653234
+38653864646134356562336266646237646361626234393032653065653031383134376636383135
+61356363646537643138373863343939636134386136326665633466356330313861
diff --git a/files/emr-3-qa/deploy.env b/files/emr-3-qa/deploy.env
new file mode 100644
index 0000000..6c6b9f0
--- /dev/null
+++ b/files/emr-3-qa/deploy.env
@@ -0,0 +1 @@
+DESTROY_VOLUMES=false
diff --git a/files/emr-3-qa/docker-compose.yml b/files/emr-3-qa/docker-compose.yml
new file mode 100644
index 0000000..b1bdcff
--- /dev/null
+++ b/files/emr-3-qa/docker-compose.yml
@@ -0,0 +1,62 @@
+version: "3.7"
+
+services:
+ # Proxy
+ proxy:
+ image: openmrs/openmrs-reference-application-3-gateway:qa
+ depends_on:
+ - frontend
+ - openmrs
+ ports:
+ - "8086:80"
+ healthcheck:
+ test: exit 0
+ volumes:
+ - "./proxy.conf:/etc/nginx/nginx.conf"
+
+ # Frontend
+ frontend:
+ image: openmrs/openmrs-reference-application-3-frontend:qa
+ environment:
+ SPA_PATH: /openmrs/spa
+ API_URL: /openmrs
+ SPA_CONFIG_URLS: "https://spa-modules.nyc3.digitaloceanspaces.com/@openmrs/config/config.json"
+ healthcheck:
+ test: ["CMD", "curl", "-fsSL", "http://localhost/ui/index.html"]
+ timeout: 5s
+
+ # OpenMRS:
+ openmrs:
+ image: openmrs/openmrs-reference-application-3-backend:qa
+ depends_on:
+ - db
+ environment:
+ OMRS_CONFIG_MODULE_WEB_ADMIN: "false"
+ OMRS_CONFIG_AUTO_UPDATE_DATABASE: "true"
+ OMRS_CONFIG_CREATE_TABLES: "true"
+ OMRS_CONFIG_CONNECTION_SERVER: db
+ OMRS_CONFIG_CONNECTION_DATABASE: ${OPENMRS_DB:-openmrs}
+ OMRS_CONFIG_CONNECTION_USERNAME: ${OPENMRS_DB_USER:-openmrs}
+ OMRS_CONFIG_CONNECTION_PASSWORD: ${OPENMRS_DB_PASSWORD:-Admin123}
+ healthcheck:
+ test: ["CMD", "curl", "-fsSL", "http://localhost:8080/openmrs"]
+ timeout: 5s
+ volumes:
+ - openmrs-data:/openmrs/data
+ - ./liquibase:/openmrs/data/openmrs_config/liquibase/
+
+ # MariaDB
+ db:
+ image: mariadb:10.8.2
+ command: "mysqld --character-set-server=utf8 --collation-server=utf8_general_ci"
+ environment:
+ MYSQL_DATABASE: ${OPENMRS_DB:-openmrs}
+ MYSQL_USER: ${OPENMRS_DB_USER:-openmrs}
+ MYSQL_PASSWORD: ${OPENMRS_DB_PASSWORD:-Admin123}
+ MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-Admin123}
+ volumes:
+ - db-data:/var/lib/mysql
+
+volumes:
+ openmrs-data: ~
+ db-data: ~
diff --git a/files/emr-3-qa/liquibase/fix-admin_core-demo.xml b/files/emr-3-qa/liquibase/fix-admin_core-demo.xml
new file mode 100644
index 0000000..ca89750
--- /dev/null
+++ b/files/emr-3-qa/liquibase/fix-admin_core-demo.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ DELIMITER $$;
+ CREATE TRIGGER users_update BEFORE UPDATE ON users FOR EACH ROW
+ IF OLD.user_id = 1 THEN
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'admin user account is locked';
+ END IF; $$
+ CREATE TRIGGER users_delete BEFORE DELETE ON users FOR EACH ROW
+ IF OLD.user_id = 1 THEN
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'admin user account is locked';
+ END IF; $$
+ DELIMITER ;
+
+
+
diff --git a/files/emr-3-qa/liquibase/liquibase.xml b/files/emr-3-qa/liquibase/liquibase.xml
new file mode 100644
index 0000000..6484c21
--- /dev/null
+++ b/files/emr-3-qa/liquibase/liquibase.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/files/emr-3-qa/proxy.conf b/files/emr-3-qa/proxy.conf
new file mode 100644
index 0000000..e211c5d
--- /dev/null
+++ b/files/emr-3-qa/proxy.conf
@@ -0,0 +1,116 @@
+worker_processes 1;
+user nobody;
+
+events {
+ worker_connections 1024;
+ multi_accept off;
+}
+
+http {
+ include mime.types;
+ default_type application/octet-stream;
+ sendfile on;
+ tcp_nopush on;
+ tcp_nodelay on;
+
+ keepalive_timeout 65;
+
+ # allow CORS requests from selected hosts
+ map $http_origin $cors_origin_header {
+ default "";
+ "https://formbuilder.o3.openmrs.org" "$http_origin";
+ }
+
+ map $http_origin $cors_cred {
+ default "false";
+ "https://formbuilder.o3.openmrs.org" "true";
+ }
+
+ map $request_uri $csp_header {
+ default "default-src 'self' 'unsafe-inline' https://fonts.gstatic.com/ https://spa-modules.nyc3.digitaloceanspaces.com/ https://spa-modules.nyc3.cdn.digitaloceanspaces.com/; base-uri 'self'; font-src 'self' https://fonts.gstatic.com/; img-src 'self' data:; frame-ancestors 'self';";
+ "~^/openmrs/(?:admin|dictionary|module|patientDashboard.form)/" "default-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; base-uri 'self'; font-src 'self'; frame-ancestors 'self';";
+ "~^/openmrs/owa" "default-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; base-uri 'self'; font-src 'self' data:; img-src 'self' data:; frame-ancestors 'self';";
+ }
+
+ server {
+ listen 80;
+
+ add_header X-Frame-Options "SAMEORIGIN";
+ add_header X-XSS-Protection "1; mode=block";
+ add_header Content-Security-Policy $csp_header;
+
+ proxy_cookie_flags JSESSIONID secure samesite=strict;
+ proxy_http_version 1.1;
+
+ gzip on;
+ gzip_vary on;
+ gzip_comp_level 4;
+ # 1 KiB
+ gzip_min_length 1024;
+ gzip_proxied any;
+ gzip_http_version 1.0;
+ gzip_types font/eot
+ font/otf
+ font/ttf
+ image/svg+xml
+ text/css
+ text/javascript
+ text/plain
+ text/xml
+ application/atom+xml
+ application/geo+json
+ application/importmap+json
+ application/javascript
+ application/x-javascript
+ application/json
+ application/ld+json
+ application/fhir+json
+ application/fhir+xml
+ application/manifest+json
+ application/rdf+xml
+ application/rss+xml
+ application/xhtml+xml
+ application/xml;
+
+ location /openmrs/spa {
+ proxy_pass http://frontend/;
+ }
+
+ location /openmrs {
+ location /openmrs/ws/rest/v1 {
+ add_header X-Frame-Options "SAMEORIGIN";
+ add_header X-XSS-Protection "1; mode=block";
+ add_header Content-Security-Policy $csp_header;
+ add_header "Access-Control-Allow-Origin" $cors_origin_header always;
+ add_header "Access-Control-Allow-Credentials" $cors_cred;
+ add_header "Access-Control-Allow-Methods" "GET, POST, PUT, DELETE, OPTIONS";
+ add_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Type, Accept";
+ add_header "Access-Control-Expose-Headers" "Authorization" always;
+
+ if ($request_method = 'OPTIONS') {
+ add_header X-Frame-Options "SAMEORIGIN";
+ add_header X-XSS-Protection "1; mode=block";
+ add_header Content-Security-Policy $csp_header;
+ add_header "Access-Control-Allow-Origin" $cors_origin_header always;
+ add_header "Access-Control-Allow-Credentials" $cors_cred;
+ add_header "Access-Control-Allow-Methods" "GET, POST, PUT, DELETE, OPTIONS";
+ add_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Type, Accept";
+ add_header "Access-Control-Expose-Headers" "Authorization" always;
+ # Tell client that this pre-flight info is valid for 20 days
+ add_header 'Access-Control-Max-Age' 1728000;
+ add_header 'Content-Type' 'text/plain;charset=UTF-8';
+ add_header 'Content-Length' 0;
+ return 204;
+ }
+
+ proxy_pass http://openmrs:8080/openmrs/ws/rest/v1;
+ }
+
+ proxy_pass http://openmrs:8080;
+ }
+
+ location = / {
+ return 301 /openmrs/spa;
+ }
+ }
+}