docker compose up -d postgres rabbitmq nginxThis starts:
- Postgres:
localhost:5433 - RabbitMQ AMQP:
localhost:5673 - RabbitMQ Management UI:
http://localhost:15673 - Nginx reverse proxy:
http://localhost:8080-> Djangohttp://host.docker.internal:8000
Optional local fake domain (openhim.local):
echo "127.0.0.1 openhim.local" | sudo tee -a /etc/hostsThen use:
http://openhim.local:8080/http://openhim.local:8080/admin/http://openhim.local:8080/admin/mediator/tester/
API documentation:
- OpenAPI JSON:
http://127.0.0.1:8000/api/docs/openapi.json - Swagger UI:
http://127.0.0.1:8000/api/docs/swagger/ - Through nginx (example):
http://127.0.0.1:8080/api/docs/swagger/
Use the helper script:
scripts/run_openhim.shUseful modes:
# Create migration files before applying migrate
scripts/run_openhim.sh --makemigrations
# Create migration files for a single app
scripts/run_openhim.sh --makemigrations-app mediator
# Skip automatic superuser setup
scripts/run_openhim.sh --no-superuser
# Recreate services but keep DB data
scripts/run_openhim.sh --rebuild-no-db
# Full rebuild (removes DB + RabbitMQ volumes)
scripts/run_openhim.sh --rebuild-allSeed realistic demonstration records (clients, mediators, channels, transactions):
.env/bin/python manage.py seed_demo_dataReset existing demo records first:
.env/bin/python manage.py seed_demo_data --resetSeed demo data can be overridden from .env.local:
DEMO_SEED_REQUEST_BASE_URLDEMO_SEED_MEDIATOR_PREFIXDEMO_SEED_CLIENT_PREFIXDEMO_SEED_CHANNEL_PREFIXDEMO_SEED_CLIENTS_JSONDEMO_SEED_MEDIATORS_JSONDEMO_SEED_CHANNELS_JSON
Example:
DEMO_SEED_REQUEST_BASE_URL=http://openhim.local:8087
DEMO_SEED_CLIENTS_JSON=[{"client_id":"demo-env-client","name":"Demo Env Client","client_secret":"env-secret","allowed_ips":["127.0.0.1"],"is_active":true}]
DEMO_SEED_MEDIATORS_JSON=[{"urn":"urn:ttpb:demo:env","name":"Demo Env Mediator","version":"2.0.0","endpoint_url":"https://env-mediator.example.org/api","is_online":true}]
DEMO_SEED_CHANNELS_JSON=[{"name":"Demo Env Channel","description":"Channel from env configuration","path_pattern":"/demo/env/ping","methods":["get"],"requires_request_body":false,"request_content_type":"","request_body_example":"","channel_type":"https","priority":1,"is_active":true,"mediator_urn":"urn:ttpb:demo:env"}]New app: notification
Implemented SQL-derived models for:
- lookup tables (
lk_module,lk_channel,lk_status,lk_priority,lk_actor_type) event_catalog,template,user_preferenceevent_rule,event_rule_channeloutbox,delivery_log,in_app_message
Seed initial lookup/event data:
.env/bin/python manage.py seed_notification_dataNotification API endpoints:
GET /api/notification/GET /api/notification/lookups/GET /api/notification/events/?module=...&active=true|falseGET /api/notification/rules/?event_code=...&active=true|falseGET /api/notification/templates/?event_code=...&channel=...&language=...&active=true|falseGET|POST /api/notification/preferences/<actor_type_code>/<user_id>/POST /api/notification/emit/GET /api/notification/outbox/?status=...&channel=...&due=true|false&limit=100POST /api/notification/outbox/process/GET /api/notification/outbox/<outbox_id>/delivery-logs/GET /api/notification/inbox/<user_id>/?unread=true|false&limit=100POST /api/notification/inbox/<message_id>/read/
Notification transport configuration (in .env.local):
- SMTP:
NOTIFICATION_SMTP_HOSTNOTIFICATION_SMTP_PORTNOTIFICATION_SMTP_USERNAMENOTIFICATION_SMTP_PASSWORDNOTIFICATION_SMTP_USE_TLSNOTIFICATION_SMTP_USE_SSLNOTIFICATION_SMTP_TIMEOUTNOTIFICATION_FROM_EMAIL
- SMS provider:
NOTIFICATION_SMS_URLNOTIFICATION_SMS_TOKENNOTIFICATION_SMS_AUTH_HEADER(raw header, e.g.Basic ..., takes precedence over token)NOTIFICATION_SMS_TIMEOUTNOTIFICATION_SMS_SENDER_ID
Notification processing flow implemented:
- Receive event (
event_code+ payload) - Validate event is active
- Load active rules
- If no active rule, suppress event (
disposition=suppressed, reasonno_active_rule) - Resolve recipients by each rule
recipient_policy - Load user preferences
- Select channels from rule channels filtered by preferences
- Resolve templates by channel + language (with
enfallback) - Build messages from template placeholders
- Generate idempotency key per message
- Insert message to outbox as
QUEUED - Worker endpoint
/api/notification/outbox/process/picks due messages - Send via SMS/Email or create In-App message
- Write
delivery_logper attempt - On success set status
SENT(SMS/Email) orDELIVERED(In-App) - On failure increment
attempt_count:- retry path: set backoff
next_attempt_at, keepFAILED(temporary) - final path (
attempt_count >= max_attempts): setFAILED(final) - critical final failures include
escalation_required=truemetadata
- retry path: set backoff
Channel-based forwarding is active through the Django app with client auth.
Browser tester page (staff/admin login required):
http://127.0.0.1:8000/admin/mediator/tester/
Through nginx:
http://127.0.0.1:8080/admin/mediator/tester/
It is also linked inside the Mediator section of the admin sidebar (Jazzmin custom link).
Authentication supported:
- Header auth:
X-Client-IdX-Client-Secret
- HTTP Basic auth (
client_id:client_secret)
Behavior:
- In
MEDIATOR_MODE=hybrid(default):- Matches request path/method to active
Channel. - Verifies linked mediator is online.
- Matches request path/method to active
- If active
ExternalSystemRegistrationrecords are used:- Requires
X-Organizationheader (or?organization=query parameter). - Resolves the organization registration (
api_code/push_code). - Relays through National ESB using
esb_utils.
- Requires
- In
MEDIATOR_MODE=pure_esb:- Skips channel path/method matching.
- Requires
X-Organizationheader (or?organization=). - Uses global registration (
ExternalSystemRegistrationwith emptychannel) first. - Relays every request through National ESB.
- Enforces channel request rules:
requires_request_bodyrequest_content_type(if set)
- Forwards request to
channel.mediator.endpoint_url + incoming_path. - Logs full transaction state in
Transaction.
ESB mode selection (when registration exists):
- Default: uses
api_codewhen present, otherwisepush_code. - Override via
X-ESB-Mode: normal|pushor?esb_mode=normal|push.
Asynchronous proxy mode:
- Add
?async=trueorPrefer: respond-asyncto enqueue request processing. - Requests are processed by an in-process background worker thread in Django.
- Immediate response: HTTP
202 Acceptedwith:correlation_idstatus_url(/transactions/<correlation_id>/)
- Poll transaction status with client credentials:
GET /transactions/<correlation_id>/- Returns status (
pending,processing,successful,failed) and response/error fields when available.
Example request:
curl -X POST 'http://127.0.0.1:8000/integrations/teachers?sync=true' \\
-H 'Content-Type: application/json' \\
-H 'X-Client-Id: demo-integrator' \\
-H 'X-Client-Secret: demo-secret' \\
-d '{"teacher_id":"T-1"}'Async request example:
curl -X POST 'http://127.0.0.1:8000/integrations/teachers?async=true' \\
-H 'Content-Type: application/json' \\
-H 'X-Client-Id: demo-integrator' \\
-H 'X-Client-Secret: demo-secret' \\
-d '{"teacher_id":"T-1"}'Copy env template and adjust if needed:
cp .env.docker.example .env.localIf Django runs on your host machine, keep:
POSTGRES_HOST=localhostPOSTGRES_PORT=5433RABBITMQ_URL=amqp://guest:guest@localhost:5673//
If Django runs as a container in the same compose network, use:
POSTGRES_HOST=postgresRABBITMQ_URL=amqp://guest:guest@rabbitmq:5672//
Then export the variables (example):
set -a
source .env.local
set +aRun migrations and start Django:
.env/bin/python manage.py migrate
.env/bin/python manage.py runserver- Set
DB_ENGINE=postgresto enable PostgreSQL. RABBITMQ_URLshould point to your broker.NGINX_PORTcontrols published nginx proxy port.DJANGO_CSRF_TRUSTED_ORIGINSmust include the exact browser origin (scheme + host + port), for examplehttp://localhost:8087.MEDIATOR_FORWARD_TIMEOUTcontrols upstream forwarding timeout in seconds.TRANSACTION_BODY_MAX_LENGTHcontrols request/response body size persisted inTransaction.- National ESB variables:
MEDIATOR_MODE(hybridorpure_esb),GOVESB_TOKEN_URL,GOVESB_ENGINE_URL,GOVESB_GRANT_TYPE,ESB_CLIENT_ID,ESB_CLIENT_SECRET,ESB_REQUEST_DATA_FORMAT,CLIENT_PRIVATE_KEY,CLIENT_PUBLIC_KEY,GOV_ESB_PUBLIC_KEY. - Emblem sizing is context-based using:
EMBLEM_LOGO_STATIC_PATH,EMBLEM_LOGIN_STATIC_PATH,EMBLEM_ICON_STATIC_PATH,EMBLEM_FAVICON_STATIC_PATH. scripts/run_openhim.shauto-creates/updates the Django superuser from:DJANGO_SUPERUSER_USERNAME,DJANGO_SUPERUSER_EMAIL,DJANGO_SUPERUSER_PASSWORD./health/checks DB + RabbitMQ whenENABLE_HEALTH_DEPENDENCY_CHECKS=true.