Files
notifications-api/app/delivery/send_to_providers.py

250 lines
8.9 KiB
Python
Raw Normal View History

2023-08-15 14:50:41 -07:00
from datetime import datetime
2021-03-10 13:55:06 +00:00
from urllib import parse
from cachetools import TTLCache, cached
from flask import current_app
2021-03-10 13:55:06 +00:00
from notifications_utils.template import (
HTMLEmailTemplate,
PlainTextEmailTemplate,
SMSMessageTemplate,
)
2024-01-05 10:35:14 -08:00
from app import create_uuid, db, notification_provider_clients, redis_store
from app.aws.s3 import get_phone_number_from_s3
2023-08-25 12:09:00 -07:00
from app.celery.test_key_tasks import send_email_response, send_sms_response
2021-03-10 13:55:06 +00:00
from app.dao.email_branding_dao import dao_get_email_branding_by_id
from app.dao.notifications_dao import dao_update_notification
from app.dao.provider_details_dao import get_provider_details_by_notification_type
from app.exceptions import NotificationTechnicalFailureException
from app.models import (
BRANDING_BOTH,
BRANDING_ORG_BANNER,
EMAIL_TYPE,
2021-03-10 13:55:06 +00:00
KEY_TYPE_TEST,
NOTIFICATION_SENDING,
2021-03-10 13:55:06 +00:00
NOTIFICATION_STATUS_TYPES_COMPLETED,
NOTIFICATION_TECHNICAL_FAILURE,
SMS_TYPE,
)
2021-03-10 13:55:06 +00:00
from app.serialised_models import SerialisedService, SerialisedTemplate
def send_sms_to_provider(notification):
service = SerialisedService.from_id(notification.service_id)
2023-05-04 07:56:24 -07:00
message_id = None
2017-02-06 16:20:44 +00:00
if not service.active:
technical_failure(notification=notification)
return
2023-08-29 14:54:30 -07:00
if notification.status == "created":
provider = provider_to_use(SMS_TYPE, notification.international)
2022-06-25 13:05:10 -07:00
if not provider:
technical_failure(notification=notification)
return
template_model = SerialisedTemplate.from_id_and_service_id(
2023-08-29 14:54:30 -07:00
template_id=notification.template_id,
service_id=service.id,
version=notification.template_version,
)
template = SMSMessageTemplate(
2021-02-16 10:19:53 +00:00
template_model.__dict__,
values=notification.personalisation,
prefix=service.name,
show_prefix=service.prefix_sms,
)
2023-08-25 12:09:00 -07:00
if notification.key_type == KEY_TYPE_TEST:
update_notification_to_sending(notification, provider)
2023-05-22 09:12:12 -07:00
send_sms_response(provider.name, str(notification.id))
2021-02-16 10:19:53 +00:00
else:
try:
# End DB session here so that we don't have a connection stuck open waiting on the call
# to one of the SMS providers
# We don't want to tie our DB connections being open to the performance of our SMS
# providers as a slow down of our providers can cause us to run out of DB connections
# Therefore we pull all the data from our DB models into `send_sms_kwargs`now before
# closing the session (as otherwise it would be reopened immediately)
2024-01-05 10:35:14 -08:00
# We start by trying to get the phone number from a job in s3. If we fail, we assume
# the phone number is for the verification code on login, which is not a job.
my_phone = None
try:
my_phone = get_phone_number_from_s3(
notification.service_id,
notification.job_id,
notification.job_row_number,
)
except BaseException:
2024-01-09 13:36:57 -08:00
key = f"2facode{notification.id}"
key = key.replace("-", "")
key = key.replace(" ", "")
my_phone = redis_store.get(key)
2024-01-05 10:35:14 -08:00
if my_phone:
my_phone = my_phone.decode("utf-8")
if my_phone is None:
2024-01-08 14:31:28 -08:00
si = notification.service_id
ji = notification.job_id
jrn = notification.job_row_number
raise Exception(
f"The phone number for (Service ID: {si}; Job ID: {ji}; Job Row Number {jrn} was not found."
)
send_sms_kwargs = {
2024-01-05 10:35:14 -08:00
"to": my_phone,
2023-08-29 14:54:30 -07:00
"content": str(template),
"reference": str(notification.id),
"sender": notification.reply_to_text,
"international": notification.international,
}
db.session.close() # no commit needed as no changes to objects have been made above
2023-05-04 07:56:24 -07:00
message_id = provider.send_sms(**send_sms_kwargs)
except Exception as e:
notification.billable_units = template.fragment_count
dao_update_notification(notification)
raise e
else:
notification.billable_units = template.fragment_count
update_notification_to_sending(notification, provider)
2023-05-04 07:56:24 -07:00
return message_id
def send_email_to_provider(notification):
service = SerialisedService.from_id(notification.service_id)
2017-02-06 16:20:44 +00:00
if not service.active:
technical_failure(notification=notification)
return
2023-08-29 14:54:30 -07:00
if notification.status == "created":
provider = provider_to_use(EMAIL_TYPE, False)
template_dict = SerialisedTemplate.from_id_and_service_id(
2023-08-29 14:54:30 -07:00
template_id=notification.template_id,
service_id=service.id,
version=notification.template_version,
).__dict__
html_email = HTMLEmailTemplate(
template_dict,
values=notification.personalisation,
2024-01-05 10:35:14 -08:00
**get_html_email_options(service),
)
plain_text_email = PlainTextEmailTemplate(
2023-08-29 14:54:30 -07:00
template_dict, values=notification.personalisation
)
2023-08-25 12:09:00 -07:00
if notification.key_type == KEY_TYPE_TEST:
notification.reference = str(create_uuid())
update_notification_to_sending(notification, provider)
send_email_response(notification.reference, notification.to)
else:
2023-08-29 14:54:30 -07:00
from_address = '"{}" <{}@{}>'.format(
service.name,
service.email_from,
current_app.config["NOTIFY_EMAIL_DOMAIN"],
)
2021-02-16 10:19:53 +00:00
reference = provider.send_email(
from_address,
notification.normalised_to,
plain_text_email.subject,
body=str(plain_text_email),
html_body=str(html_email),
2023-08-29 14:54:30 -07:00
reply_to_address=notification.reply_to_text,
)
notification.reference = reference
update_notification_to_sending(notification, provider)
def update_notification_to_sending(notification, provider):
notification.sent_at = datetime.utcnow()
notification.sent_by = provider.name
if notification.status not in NOTIFICATION_STATUS_TYPES_COMPLETED:
2023-06-13 12:57:51 -07:00
notification.status = NOTIFICATION_SENDING
2022-10-14 14:45:27 +00:00
dao_update_notification(notification)
provider_cache = TTLCache(maxsize=8, ttl=10)
@cached(cache=provider_cache)
2022-06-17 11:16:23 -07:00
def provider_to_use(notification_type, international=True):
active_providers = [
2023-08-29 14:54:30 -07:00
p
for p in get_provider_details_by_notification_type(
2022-10-14 14:45:27 +00:00
notification_type, international
2023-08-29 14:54:30 -07:00
)
if p.active
]
if not active_providers:
current_app.logger.error(
"{} failed as no active providers".format(notification_type)
)
raise Exception("No active {} providers".format(notification_type))
2023-08-23 11:27:28 -07:00
# we only have sns
chosen_provider = active_providers[0]
2023-08-29 14:54:30 -07:00
return notification_provider_clients.get_client_by_name_and_type(
chosen_provider.identifier, notification_type
)
2017-07-21 16:06:12 +01:00
def get_logo_url(base_url, logo_file):
base_url = parse.urlparse(base_url)
netloc = base_url.netloc
2023-08-29 14:54:30 -07:00
if base_url.netloc.startswith("localhost"):
netloc = "notify.tools"
elif base_url.netloc.startswith("www"):
2017-07-21 16:06:12 +01:00
# strip "www."
netloc = base_url.netloc[4:]
logo_url = parse.ParseResult(
scheme=base_url.scheme,
2023-08-29 14:54:30 -07:00
netloc="static-logos." + netloc,
2017-07-21 16:06:12 +01:00
path=logo_file,
params=base_url.params,
query=base_url.query,
2023-08-29 14:54:30 -07:00
fragment=base_url.fragment,
)
return parse.urlunparse(logo_url)
def get_html_email_options(service):
if service.email_branding is None:
return {
2023-08-29 14:54:30 -07:00
"govuk_banner": True,
"brand_banner": False,
}
if isinstance(service, SerialisedService):
branding = dao_get_email_branding_by_id(service.email_branding)
else:
branding = service.email_branding
2023-08-29 14:54:30 -07:00
logo_url = (
get_logo_url(current_app.config["ADMIN_BASE_URL"], branding.logo)
if branding.logo
else None
)
return {
2023-08-29 14:54:30 -07:00
"govuk_banner": branding.brand_type == BRANDING_BOTH,
"brand_banner": branding.brand_type == BRANDING_ORG_BANNER,
"brand_colour": branding.colour,
"brand_logo": logo_url,
"brand_text": branding.text,
"brand_name": branding.name,
}
2017-02-03 14:27:04 +00:00
2017-02-06 16:20:44 +00:00
def technical_failure(notification):
notification.status = NOTIFICATION_TECHNICAL_FAILURE
dao_update_notification(notification)
raise NotificationTechnicalFailureException(
2017-02-06 16:20:44 +00:00
"Send {} for notification id {} to provider is not allowed: service {} is inactive".format(
2023-08-29 14:54:30 -07:00
notification.notification_type, notification.id, notification.service_id
)
)