Skip to content

Commit

Permalink
Leverage aidbox as email provider
Browse files Browse the repository at this point in the history
  • Loading branch information
ir4y committed Oct 10, 2024
1 parent 7c14e92 commit 76fc0ab
Show file tree
Hide file tree
Showing 6 changed files with 14 additions and 149 deletions.
2 changes: 1 addition & 1 deletion env/aidbox
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,5 @@ TWO_FACTOR_ISSUER_NAME=MammoChat
TWO_FACTOR_VALID_PAST_TOKENS_COUNT=5
TWO_FACTOR_WEBHOOK_URL="http://devbox:8080/webhook/two-factor-confirmation"
TWO_FACTOR_WEBHOOK_AUTHORIZATION="Basic cm9vdDpzZWNyZXQ=" # root:secret

AIDBOX_CSS=http://localhost:3000/aidbox.css
SMTP_PROVIDER=console
3 changes: 2 additions & 1 deletion env/ucf-app
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ APP_SECRET=secret
APP_URL=http://ucf-app:8081
APP_PORT=8081
AIO_PORT=8081
AIO_HOST=0.0.0.0
AIO_HOST=0.0.0.0
SMTP_PROVIDER=console
136 changes: 1 addition & 135 deletions ucf-app/app/aidbox/notification.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import logging

from aidbox_python_sdk.aidboxpy import AsyncAidboxClient
from jinja2 import Environment, Undefined
from jinja2.ext import Extension
from premailer import transform

from app.config import emr as emr_config

async def send_email(
Expand All @@ -24,135 +20,5 @@ async def send_email(
)
if save:
await notification.save()
await notification.execute("$send")
return notification


class SilentUndefined(Undefined):
def _fail_with_undefined_error(self, *args, **kwargs): # type: ignore
return None


class RenderBlocksExtension(Extension):
def __init__(self, environment):
super().__init__(environment)
environment.extend(render_blocks=[])

def filter_stream(self, stream):
block_level = 0
skip_level = 0
in_endblock = False

for token in stream:
if token.type == "block_begin":
if stream.current.value == "block":
block_level += 1
if stream.look().value not in self.environment.render_blocks: # type: ignore
skip_level = block_level

if token.value == "endblock":
in_endblock = True

if skip_level == 0:
yield token

if token.type == "block_end":
if in_endblock:
in_endblock = False
block_level -= 1

if skip_level == block_level + 1:
skip_level = 0


jinja_env = Environment(undefined=SilentUndefined)

jinja_subject_env = Environment(undefined=SilentUndefined, extensions=[RenderBlocksExtension])
jinja_subject_env.render_blocks = ["subject"] # type: ignore

jinja_body_env = Environment(undefined=SilentUndefined, extensions=[RenderBlocksExtension])
jinja_body_env.render_blocks = ["body"] # type: ignore


class SendNotificationExceptionError(Exception):
pass


async def send_console(to, subject, body, attachments=None):
logging.info(
"New notification:\nTo: %s\nSubject: %s\n%s\nattachments: %s",
to,
subject,
body,
attachments or [],
)


providers = {
"console": send_console,
}


async def notification_sub(app, action, resource, _previous_resource):
sdk_settings = app["settings"]
client = app["client"]
if action == "create":
provider_data = resource["providerData"]

# Skip processing for non-app notifications
if not provider_data.get("fromApp"):
return

notification_type = provider_data.get("type")
if notification_type == "email":
payload = {
**provider_data.get("payload", {}),
"frontend_url": sdk_settings.FRONTEND_URL,
# "backend_url": sdk_settings.backend_public_url,
# "current_date": format_fhir_date(get_now()),
}

template = await provider_data["template"].to_resource()
subject_template = jinja_subject_env.from_string(template["subject"])
body_template = jinja_body_env.from_string(template["template"])
subject = subject_template.render(payload)
body = body_template.render(payload)
layout = await client.resources("NotificationTemplate").get(id="email-layout")
body = transform(
jinja_env.from_string(layout["template"]).render({"body": body, **payload})
)
props = {
"subject": subject.strip(),
"body": body.strip(),
"to": provider_data["to"],
}
if "attachments" in provider_data:
props["attachments"] = provider_data["attachments"]
elif notification_type == "sms":
props = {
"to": provider_data["to"],
"body": provider_data["body"].strip(),
}
else:
raise Exception("Notification type `%s` is not supported", notification_type)

provider = resource["provider"]

send_fn = providers.get(provider)
if not send_fn:
logging.warning("Unhandled notification for provider %s", provider)
return

try:
await send_fn(**props) # type: ignore

resource["status"] = "delivered"
await resource.save()
except SendNotificationExceptionError as exc:
logging.debug(exc)
resource["status"] = "error"
await resource.save()
except Exception as exc:
logging.exception(exc)
resource["status"] = "failure"
await resource.save()
raise
14 changes: 3 additions & 11 deletions ucf-app/app/aidbox/subscriptions.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import uuid

from app.aidbox.sdk import sdk
from app.config.emr import FRONTEND_URL
from app.aidbox.notification import notification_sub
from app.config.emr import FRONTEND_URL, EMAIL_PROVIDER

@sdk.subscription("User")
async def user_created(event, request):
Expand All @@ -16,7 +15,7 @@ async def user_created(event, request):
notification = aidbox.resource(
"Notification",
**{
"provider": "smtp-provider",
"provider": EMAIL_PROVIDER,
"providerData": {
"to": user["email"],
"subject": "Password reset",
Expand All @@ -32,11 +31,4 @@ async def user_created(event, request):
},
)
await notification.save()


@sdk.subscription("Notification")
async def notification_handler(event, request):
aidbox = request.app["client"]
notification = aidbox.resource("Notification", **event["resource"])

await notification_sub(request.app, event["action"], notification, None)
await notification.execute("$send")
2 changes: 1 addition & 1 deletion ucf-app/app/config/emr.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from os import environ

FRONTEND_URL = environ.get("FRONTEND_URL", "http://localhost:3000")
EMAIL_PROVIDER = "console"
EMAIL_PROVIDER = environ.get("SMTP_PROVIDER", "console")
6 changes: 6 additions & 0 deletions zenproject/zrc/system.edn
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
:secret "secret"}
:patient-questionnaire {:grant_types [:basic]
:secret "secret"}}
:AidboxConfig {:provider {:provider
{:console { :type "console" }
:postmark {:type "postmark"
:from "no-reply@ucfwealth.app"
:api-key #env POSTMARK_TOKEN}
:default #env SMTP_PROVIDER }}}
:AuthConfig {:app {:theme {:brand "UCF MammoChat",
:title "UCF MammoChat",
:styleUrl #env AIDBOX_CSS}
Expand Down

0 comments on commit 76fc0ab

Please sign in to comment.