2025-01-10 11:21:39 -08:00
|
|
|
import json
|
2025-01-10 14:12:45 -08:00
|
|
|
import os
|
2017-07-20 17:56:51 +01:00
|
|
|
import uuid
|
2016-11-11 14:56:33 +00:00
|
|
|
|
2016-10-27 17:34:54 +01:00
|
|
|
from flask import current_app
|
2017-04-27 12:41:10 +01:00
|
|
|
|
2024-01-24 07:55:14 -08:00
|
|
|
from app import redis_store
|
2016-10-27 17:34:54 +01:00
|
|
|
from app.celery import provider_tasks
|
2017-07-19 13:50:29 +01:00
|
|
|
from app.config import QueueNames
|
2021-03-10 13:55:06 +00:00
|
|
|
from app.dao.notifications_dao import (
|
2025-01-10 12:03:00 -08:00
|
|
|
dao_create_notification,
|
2021-03-10 13:55:06 +00:00
|
|
|
dao_delete_notifications_by_id,
|
2024-12-23 11:17:51 -05:00
|
|
|
dao_notification_exists,
|
2024-12-03 13:50:42 -08:00
|
|
|
get_notification_by_id,
|
2021-03-10 13:55:06 +00:00
|
|
|
)
|
2025-01-10 11:21:39 -08:00
|
|
|
from app.enums import NotificationStatus, NotificationType
|
2024-05-30 12:27:07 -07:00
|
|
|
from app.errors import BadRequestError
|
2024-01-18 10:28:50 -05:00
|
|
|
from app.models import Notification
|
2024-06-11 10:34:57 -07:00
|
|
|
from app.utils import hilite, utc_now
|
2024-05-16 10:17:45 -04:00
|
|
|
from notifications_utils.recipients import (
|
|
|
|
|
format_email_address,
|
|
|
|
|
get_international_phone_info,
|
|
|
|
|
validate_and_format_phone_number,
|
|
|
|
|
)
|
|
|
|
|
from notifications_utils.template import PlainTextEmailTemplate, SMSMessageTemplate
|
2016-10-27 11:46:37 +01:00
|
|
|
|
2020-05-13 11:06:27 +01:00
|
|
|
|
2016-10-27 11:46:37 +01:00
|
|
|
def create_content_for_notification(template, personalisation):
|
2024-02-28 12:40:52 -05:00
|
|
|
if template.template_type == NotificationType.EMAIL:
|
2020-06-22 10:20:52 +01:00
|
|
|
template_object = PlainTextEmailTemplate(
|
|
|
|
|
{
|
2023-08-29 14:54:30 -07:00
|
|
|
"content": template.content,
|
|
|
|
|
"subject": template.subject,
|
|
|
|
|
"template_type": template.template_type,
|
2020-06-22 10:20:52 +01:00
|
|
|
},
|
|
|
|
|
personalisation,
|
|
|
|
|
)
|
2024-02-28 12:40:52 -05:00
|
|
|
if template.template_type == NotificationType.SMS:
|
2020-06-22 10:20:52 +01:00
|
|
|
template_object = SMSMessageTemplate(
|
|
|
|
|
{
|
2023-08-29 14:54:30 -07:00
|
|
|
"content": template.content,
|
|
|
|
|
"template_type": template.template_type,
|
2020-06-22 10:20:52 +01:00
|
|
|
},
|
|
|
|
|
personalisation,
|
|
|
|
|
)
|
2020-06-22 10:20:51 +01:00
|
|
|
|
2016-10-28 17:10:00 +01:00
|
|
|
check_placeholders(template_object)
|
2016-10-27 11:46:37 +01:00
|
|
|
|
|
|
|
|
return template_object
|
|
|
|
|
|
|
|
|
|
|
2016-10-28 17:10:00 +01:00
|
|
|
def check_placeholders(template_object):
|
|
|
|
|
if template_object.missing_data:
|
2023-08-29 14:54:30 -07:00
|
|
|
message = "Missing personalisation: {}".format(
|
|
|
|
|
", ".join(template_object.missing_data)
|
|
|
|
|
)
|
|
|
|
|
raise BadRequestError(fields=[{"template": message}], message=message)
|
2016-10-28 17:10:00 +01:00
|
|
|
|
|
|
|
|
|
2024-12-03 13:50:42 -08:00
|
|
|
def get_notification(notification_id):
|
|
|
|
|
return get_notification_by_id(notification_id)
|
|
|
|
|
|
|
|
|
|
|
2017-04-27 12:41:10 +01:00
|
|
|
def persist_notification(
|
2017-07-07 17:10:25 +01:00
|
|
|
*,
|
2017-04-27 12:41:10 +01:00
|
|
|
template_id,
|
|
|
|
|
template_version,
|
|
|
|
|
recipient,
|
|
|
|
|
service,
|
|
|
|
|
personalisation,
|
|
|
|
|
notification_type,
|
|
|
|
|
api_key_id,
|
|
|
|
|
key_type,
|
2017-05-17 13:27:05 +01:00
|
|
|
created_at=None,
|
2017-04-27 12:41:10 +01:00
|
|
|
job_id=None,
|
|
|
|
|
job_row_number=None,
|
|
|
|
|
reference=None,
|
|
|
|
|
client_reference=None,
|
|
|
|
|
notification_id=None,
|
2017-06-16 16:30:03 +01:00
|
|
|
simulated=False,
|
2017-09-26 09:56:09 +01:00
|
|
|
created_by_id=None,
|
2024-01-18 10:28:15 -05:00
|
|
|
status=NotificationStatus.CREATED,
|
2018-09-20 14:47:24 +01:00
|
|
|
reply_to_text=None,
|
2019-01-09 17:49:19 +00:00
|
|
|
billable_units=None,
|
2020-06-18 08:30:19 +01:00
|
|
|
document_download_count=None,
|
2023-08-31 10:57:54 -04:00
|
|
|
updated_at=None,
|
2017-04-27 12:41:10 +01:00
|
|
|
):
|
2024-05-23 13:59:51 -07:00
|
|
|
notification_created_at = created_at or utc_now()
|
2017-07-26 15:57:30 +01:00
|
|
|
if not notification_id:
|
2017-07-20 17:56:51 +01:00
|
|
|
notification_id = uuid.uuid4()
|
2022-10-14 14:45:27 +00:00
|
|
|
|
2023-12-08 13:15:40 -05:00
|
|
|
current_app.logger.info(f"Persisting notification with id {notification_id}")
|
2022-10-14 14:45:27 +00:00
|
|
|
|
2016-11-14 14:41:32 +00:00
|
|
|
notification = Notification(
|
2016-11-25 17:32:01 +00:00
|
|
|
id=notification_id,
|
2016-11-11 14:56:33 +00:00
|
|
|
template_id=template_id,
|
|
|
|
|
template_version=template_version,
|
2016-11-14 14:41:32 +00:00
|
|
|
to=recipient,
|
2016-12-19 16:45:18 +00:00
|
|
|
service_id=service.id,
|
2016-11-11 14:56:33 +00:00
|
|
|
personalisation=personalisation,
|
|
|
|
|
notification_type=notification_type,
|
|
|
|
|
api_key_id=api_key_id,
|
|
|
|
|
key_type=key_type,
|
2017-05-17 13:25:40 +01:00
|
|
|
created_at=notification_created_at,
|
2016-11-11 14:56:33 +00:00
|
|
|
job_id=job_id,
|
2016-11-16 15:44:16 +00:00
|
|
|
job_row_number=job_row_number,
|
2017-04-12 17:56:55 +01:00
|
|
|
client_reference=client_reference,
|
2017-06-23 15:56:47 +01:00
|
|
|
reference=reference,
|
2017-09-26 09:56:09 +01:00
|
|
|
created_by_id=created_by_id,
|
2017-11-23 14:55:49 +00:00
|
|
|
status=status,
|
|
|
|
|
reply_to_text=reply_to_text,
|
2020-02-12 16:07:07 +00:00
|
|
|
billable_units=billable_units,
|
|
|
|
|
document_download_count=document_download_count,
|
2023-08-29 14:54:30 -07:00
|
|
|
updated_at=updated_at,
|
2016-11-11 14:56:33 +00:00
|
|
|
)
|
2022-10-14 14:45:27 +00:00
|
|
|
|
2024-02-28 12:40:52 -05:00
|
|
|
if notification_type == NotificationType.SMS:
|
2023-08-29 14:54:30 -07:00
|
|
|
formatted_recipient = validate_and_format_phone_number(
|
|
|
|
|
recipient, international=True
|
|
|
|
|
)
|
2024-06-11 10:34:57 -07:00
|
|
|
current_app.logger.info(
|
2024-06-06 13:18:00 -07:00
|
|
|
hilite(
|
2024-06-17 11:12:30 -07:00
|
|
|
f"Persisting notification with job_id: {job_id} row_number: {job_row_number}"
|
2024-06-06 13:18:00 -07:00
|
|
|
)
|
2024-06-11 10:34:57 -07:00
|
|
|
)
|
2017-04-27 12:41:10 +01:00
|
|
|
recipient_info = get_international_phone_info(formatted_recipient)
|
2017-05-23 14:47:55 +01:00
|
|
|
notification.normalised_to = formatted_recipient
|
2017-04-27 12:41:10 +01:00
|
|
|
notification.international = recipient_info.international
|
|
|
|
|
notification.phone_prefix = recipient_info.country_prefix
|
|
|
|
|
notification.rate_multiplier = recipient_info.billable_units
|
2024-02-01 08:06:10 -08:00
|
|
|
|
2024-02-28 12:40:52 -05:00
|
|
|
elif notification_type == NotificationType.EMAIL:
|
2024-02-28 12:41:57 -05:00
|
|
|
current_app.logger.info(
|
|
|
|
|
f"Persisting notification with type: {NotificationType.EMAIL}"
|
|
|
|
|
)
|
2024-01-24 07:55:14 -08:00
|
|
|
redis_store.set(
|
|
|
|
|
f"email-address-{notification.id}",
|
|
|
|
|
format_email_address(notification.to),
|
|
|
|
|
ex=1800,
|
|
|
|
|
)
|
2017-04-27 12:41:10 +01:00
|
|
|
|
|
|
|
|
# if simulated create a Notification model to return but do not persist the Notification to the dB
|
2017-01-17 12:08:24 +00:00
|
|
|
if not simulated:
|
2025-01-10 12:03:00 -08:00
|
|
|
if notification.notification_type == NotificationType.SMS:
|
2025-01-10 14:12:45 -08:00
|
|
|
# it's just too hard with redis and timing to test this here
|
|
|
|
|
if os.getenv("NOTIFY_ENVIRONMENT") == "test":
|
|
|
|
|
dao_create_notification(notification)
|
|
|
|
|
else:
|
|
|
|
|
redis_store.rpush(
|
|
|
|
|
"message_queue",
|
|
|
|
|
json.dumps(notification.serialize_for_redis(notification)),
|
|
|
|
|
)
|
2025-01-10 12:03:00 -08:00
|
|
|
else:
|
|
|
|
|
dao_create_notification(notification)
|
2023-09-21 13:26:27 -06:00
|
|
|
|
2016-10-27 17:34:54 +01:00
|
|
|
return notification
|
2016-10-25 18:04:03 +01:00
|
|
|
|
|
|
|
|
|
2024-12-23 11:17:51 -05:00
|
|
|
def notification_exists(notification_id):
|
|
|
|
|
return dao_notification_exists(notification_id)
|
|
|
|
|
|
|
|
|
|
|
2020-06-16 14:33:53 +01:00
|
|
|
def send_notification_to_queue_detached(
|
2023-08-25 12:09:00 -07:00
|
|
|
key_type, notification_type, notification_id, queue=None
|
2020-06-16 14:33:53 +01:00
|
|
|
):
|
2016-12-09 12:10:42 +00:00
|
|
|
|
2024-02-28 12:40:52 -05:00
|
|
|
if notification_type == NotificationType.SMS:
|
2017-07-20 16:17:04 +01:00
|
|
|
if not queue:
|
|
|
|
|
queue = QueueNames.SEND_SMS
|
2016-12-09 12:10:42 +00:00
|
|
|
deliver_task = provider_tasks.deliver_sms
|
2024-02-28 12:40:52 -05:00
|
|
|
if notification_type == NotificationType.EMAIL:
|
2017-07-20 16:17:04 +01:00
|
|
|
if not queue:
|
|
|
|
|
queue = QueueNames.SEND_EMAIL
|
2016-12-09 12:10:42 +00:00
|
|
|
deliver_task = provider_tasks.deliver_email
|
|
|
|
|
|
|
|
|
|
try:
|
2025-01-13 13:35:40 -08:00
|
|
|
deliver_task.apply_async([str(notification_id)], queue=queue, countdown=60)
|
2017-06-19 14:58:38 +01:00
|
|
|
except Exception:
|
2020-06-16 14:33:53 +01:00
|
|
|
dao_delete_notifications_by_id(notification_id)
|
2017-06-19 14:58:38 +01:00
|
|
|
raise
|
2016-10-27 17:34:54 +01:00
|
|
|
|
As Notify matures we probably need less logging, especially to report happy path events.
This PR is a proposal to reduce the average messages we see for a single notification from about 7 messages to 2.
Messaging would change to something like this:
February 2nd 2018, 15:39:05.885 Full delivery response from Firetext for notification: 8eda51d5-cd82-4569-bfc9-d5570cdf2126
{'status': ['0'], 'reference': ['8eda51d5-cd82-4569-bfc9-d5570cdf2126'], 'time': ['2018-02-02 15:39:01'], 'code': ['000']}
February 2nd 2018, 15:39:05.885 Firetext callback return status of 0 for reference: 8eda51d5-cd82-4569-bfc9-d5570cdf2126
February 2nd 2018, 15:38:57.727 SMS 8eda51d5-cd82-4569-bfc9-d5570cdf2126 sent to provider firetext at 2018-02-02 15:38:56.716814
February 2nd 2018, 15:38:56.727 Starting sending SMS 8eda51d5-cd82-4569-bfc9-d5570cdf2126 to provider at 2018-02-02 15:38:56.408181
February 2nd 2018, 15:38:56.727 Firetext request for 8eda51d5-cd82-4569-bfc9-d5570cdf2126 finished in 0.30376038211397827
February 2nd 2018, 15:38:49.449 sms 8eda51d5-cd82-4569-bfc9-d5570cdf2126 created at 2018-02-02 15:38:48.439113
February 2nd 2018, 15:38:49.449 sms 8eda51d5-cd82-4569-bfc9-d5570cdf2126 sent to the priority-tasks queue for delivery
To somthing like this:
February 2nd 2018, 15:39:05.885 Firetext callback return status of 0 for reference: 8eda51d5-cd82-4569-bfc9-d5570cdf2126
February 2nd 2018, 15:38:49.449 sms 8eda51d5-cd82-4569-bfc9-d5570cdf2126 created at 2018-02-02 15:38:48.439113
2018-02-02 15:55:25 +00:00
|
|
|
current_app.logger.debug(
|
2023-12-08 13:15:40 -05:00
|
|
|
f"{notification_type} {notification_id} sent to the {queue} queue for delivery"
|
2023-08-29 14:54:30 -07:00
|
|
|
)
|
2020-06-17 12:11:28 +01:00
|
|
|
|
|
|
|
|
|
2023-08-25 12:09:00 -07:00
|
|
|
def send_notification_to_queue(notification, queue=None):
|
2020-06-16 14:33:53 +01:00
|
|
|
send_notification_to_queue_detached(
|
2023-12-08 21:43:52 -05:00
|
|
|
notification.key_type,
|
|
|
|
|
notification.notification_type,
|
|
|
|
|
notification.id,
|
|
|
|
|
queue,
|
2020-06-16 14:33:53 +01:00
|
|
|
)
|
2017-01-17 12:08:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def simulated_recipient(to_address, notification_type):
|
2024-02-28 12:40:52 -05:00
|
|
|
if notification_type == NotificationType.SMS:
|
2017-04-26 15:56:45 +01:00
|
|
|
formatted_simulated_numbers = [
|
2023-08-29 14:54:30 -07:00
|
|
|
validate_and_format_phone_number(number)
|
|
|
|
|
for number in current_app.config["SIMULATED_SMS_NUMBERS"]
|
2017-04-26 15:56:45 +01:00
|
|
|
]
|
|
|
|
|
return to_address in formatted_simulated_numbers
|
|
|
|
|
else:
|
2023-08-29 14:54:30 -07:00
|
|
|
return to_address in current_app.config["SIMULATED_EMAIL_ADDRESSES"]
|