2024-05-23 13:59:51 -07:00
|
|
|
|
from datetime import timedelta
|
2016-03-31 15:57:50 +01:00
|
|
|
|
|
2016-03-01 13:30:10 +00:00
|
|
|
|
from flask import current_app
|
2024-10-11 09:16:19 -07:00
|
|
|
|
from sqlalchemy import asc, delete, desc, func, or_, select, text, union, update
|
2016-08-09 13:07:48 +01:00
|
|
|
|
from sqlalchemy.orm import joinedload
|
2019-05-21 16:08:18 +01:00
|
|
|
|
from sqlalchemy.orm.exc import NoResultFound
|
2017-08-29 16:35:30 +01:00
|
|
|
|
from sqlalchemy.sql import functions
|
2018-12-10 16:27:59 +00:00
|
|
|
|
from sqlalchemy.sql.expression import case
|
|
|
|
|
|
from werkzeug.datastructures import MultiDict
|
2016-03-31 15:57:50 +01:00
|
|
|
|
|
2022-11-30 13:50:49 -05:00
|
|
|
|
from app import create_uuid, db
|
2021-04-14 07:11:01 +01:00
|
|
|
|
from app.dao.dao_utils import autocommit
|
2024-01-16 07:37:21 -05:00
|
|
|
|
from app.enums import KeyType, NotificationStatus, NotificationType
|
|
|
|
|
|
from app.models import FactNotificationStatus, Notification, NotificationHistory
|
2022-02-10 10:37:32 +00:00
|
|
|
|
from app.utils import (
|
|
|
|
|
|
escape_special_characters,
|
2023-05-10 08:39:50 -07:00
|
|
|
|
get_midnight_in_utc,
|
2022-02-10 10:37:32 +00:00
|
|
|
|
midnight_n_days_ago,
|
2024-05-23 13:59:51 -07:00
|
|
|
|
utc_now,
|
2022-02-10 10:37:32 +00:00
|
|
|
|
)
|
2024-05-16 10:17:45 -04:00
|
|
|
|
from notifications_utils.international_billing_rates import INTERNATIONAL_BILLING_RATES
|
|
|
|
|
|
from notifications_utils.recipients import (
|
|
|
|
|
|
InvalidEmailError,
|
|
|
|
|
|
try_validate_and_format_phone_number,
|
|
|
|
|
|
validate_and_format_email_address,
|
|
|
|
|
|
)
|
2016-04-04 12:21:38 +01:00
|
|
|
|
|
2016-02-09 12:01:17 +00:00
|
|
|
|
|
2020-02-05 16:43:17 +00:00
|
|
|
|
def dao_get_last_date_template_was_used(template_id, service_id):
|
2023-08-23 10:35:43 -07:00
|
|
|
|
last_date_from_notifications = (
|
|
|
|
|
|
db.session.query(functions.max(Notification.created_at))
|
|
|
|
|
|
.filter(
|
|
|
|
|
|
Notification.service_id == service_id,
|
|
|
|
|
|
Notification.template_id == template_id,
|
2024-01-18 10:28:50 -05:00
|
|
|
|
Notification.key_type != KeyType.TEST,
|
2023-08-23 10:35:43 -07:00
|
|
|
|
)
|
|
|
|
|
|
.scalar()
|
|
|
|
|
|
)
|
2020-02-05 13:03:54 +00:00
|
|
|
|
|
2020-02-05 16:43:17 +00:00
|
|
|
|
if last_date_from_notifications:
|
2020-02-05 13:03:54 +00:00
|
|
|
|
return last_date_from_notifications
|
2020-02-05 16:43:17 +00:00
|
|
|
|
|
2023-08-23 10:35:43 -07:00
|
|
|
|
last_date = (
|
|
|
|
|
|
db.session.query(functions.max(FactNotificationStatus.local_date))
|
|
|
|
|
|
.filter(
|
|
|
|
|
|
FactNotificationStatus.template_id == template_id,
|
2024-01-18 10:28:50 -05:00
|
|
|
|
FactNotificationStatus.key_type != KeyType.TEST,
|
2023-08-23 10:35:43 -07:00
|
|
|
|
)
|
|
|
|
|
|
.scalar()
|
|
|
|
|
|
)
|
2020-02-05 16:43:17 +00:00
|
|
|
|
|
|
|
|
|
|
return last_date
|
2020-02-05 13:03:54 +00:00
|
|
|
|
|
|
|
|
|
|
|
2021-04-14 07:11:01 +01:00
|
|
|
|
@autocommit
|
2016-08-25 11:55:38 +01:00
|
|
|
|
def dao_create_notification(notification):
|
2016-07-11 16:48:32 +01:00
|
|
|
|
if not notification.id:
|
|
|
|
|
|
# need to populate defaulted fields before we create the notification history object
|
2016-10-28 17:10:00 +01:00
|
|
|
|
notification.id = create_uuid()
|
2016-07-11 16:48:32 +01:00
|
|
|
|
if not notification.status:
|
2024-01-18 10:28:15 -05:00
|
|
|
|
notification.status = NotificationStatus.CREATED
|
2016-07-11 16:48:32 +01:00
|
|
|
|
|
2024-01-18 10:03:35 -08:00
|
|
|
|
# notify-api-749 do not write to db
|
|
|
|
|
|
# if we have a verify_code we know this is the authentication notification at login time
|
|
|
|
|
|
# and not csv (containing PII) provided by the user, so allow verify_code to continue to exist
|
|
|
|
|
|
if "verify_code" in str(notification.personalisation):
|
|
|
|
|
|
pass
|
|
|
|
|
|
else:
|
2024-01-19 08:58:24 -08:00
|
|
|
|
notification.personalisation = ""
|
2024-03-01 13:50:09 -08:00
|
|
|
|
|
2024-01-16 11:21:24 -08:00
|
|
|
|
# notify-api-742 remove phone numbers from db
|
|
|
|
|
|
notification.to = "1"
|
|
|
|
|
|
notification.normalised_to = "1"
|
2024-12-03 12:44:57 -08:00
|
|
|
|
|
2024-12-03 13:50:42 -08:00
|
|
|
|
# notify-api-1454 insert only if it doesn't exist
|
2024-12-03 12:55:52 -08:00
|
|
|
|
stmt = select(Notification).where(Notification.id == notification.id)
|
|
|
|
|
|
result = db.session.execute(stmt).scalar()
|
|
|
|
|
|
if result is None:
|
|
|
|
|
|
db.session.add(notification)
|
2016-12-19 13:57:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
2017-05-12 14:59:14 +01:00
|
|
|
|
def country_records_delivery(phone_prefix):
|
2023-08-23 10:35:43 -07:00
|
|
|
|
dlr = INTERNATIONAL_BILLING_RATES[phone_prefix]["attributes"]["dlr"]
|
|
|
|
|
|
return dlr and dlr.lower() == "yes"
|
2017-05-12 14:59:14 +01:00
|
|
|
|
|
2022-10-14 14:45:27 +00:00
|
|
|
|
|
2022-09-15 14:59:13 -07:00
|
|
|
|
def _decide_permanent_temporary_failure(current_status, status):
|
|
|
|
|
|
# If we go from pending to delivered we need to set failure type as temporary-failure
|
2023-08-23 10:35:43 -07:00
|
|
|
|
if (
|
2024-01-18 10:28:15 -05:00
|
|
|
|
current_status == NotificationStatus.PENDING
|
|
|
|
|
|
and status == NotificationStatus.PERMANENT_FAILURE
|
2023-08-23 10:35:43 -07:00
|
|
|
|
):
|
2024-01-18 10:28:15 -05:00
|
|
|
|
status = NotificationStatus.TEMPORARY_FAILURE
|
2022-09-15 14:59:13 -07:00
|
|
|
|
return status
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-10-24 11:35:00 -07:00
|
|
|
|
def _update_notification_status(
|
|
|
|
|
|
notification, status, provider_response=None, carrier=None
|
|
|
|
|
|
):
|
2023-08-23 10:35:43 -07:00
|
|
|
|
status = _decide_permanent_temporary_failure(
|
|
|
|
|
|
current_status=notification.status, status=status
|
|
|
|
|
|
)
|
2022-09-15 14:59:13 -07:00
|
|
|
|
notification.status = status
|
2024-05-23 13:59:51 -07:00
|
|
|
|
notification.sent_at = utc_now()
|
2022-09-15 14:59:13 -07:00
|
|
|
|
if provider_response:
|
|
|
|
|
|
notification.provider_response = provider_response
|
2023-10-24 11:35:00 -07:00
|
|
|
|
if carrier:
|
|
|
|
|
|
notification.carrier = carrier
|
2022-09-15 14:59:13 -07:00
|
|
|
|
dao_update_notification(notification)
|
2016-09-13 12:29:40 +01:00
|
|
|
|
return notification
|
2016-03-10 17:29:17 +00:00
|
|
|
|
|
2022-10-14 14:45:27 +00:00
|
|
|
|
|
2024-12-13 07:30:41 -08:00
|
|
|
|
def update_notification_message_id(notification_id, message_id):
|
|
|
|
|
|
stmt = (
|
|
|
|
|
|
update(Notification)
|
|
|
|
|
|
.where(Notification.id == notification_id)
|
2024-12-13 09:48:48 -08:00
|
|
|
|
.values(message_id=message_id)
|
2024-12-13 07:30:41 -08:00
|
|
|
|
)
|
|
|
|
|
|
db.session.execute(stmt)
|
2024-12-13 08:43:23 -08:00
|
|
|
|
db.session.commit()
|
2024-12-13 07:30:41 -08:00
|
|
|
|
|
|
|
|
|
|
|
2021-04-14 07:11:01 +01:00
|
|
|
|
@autocommit
|
2023-08-23 10:35:43 -07:00
|
|
|
|
def update_notification_status_by_id(
|
2023-10-24 11:35:00 -07:00
|
|
|
|
notification_id, status, sent_by=None, provider_response=None, carrier=None
|
2023-08-23 10:35:43 -07:00
|
|
|
|
):
|
2024-10-11 09:16:19 -07:00
|
|
|
|
stmt = (
|
|
|
|
|
|
select(Notification)
|
|
|
|
|
|
.with_for_update()
|
2023-08-23 10:35:43 -07:00
|
|
|
|
.filter(Notification.id == notification_id)
|
|
|
|
|
|
)
|
2024-10-11 09:16:19 -07:00
|
|
|
|
notification = db.session.execute(stmt).scalars().first()
|
2016-05-27 12:09:36 +01:00
|
|
|
|
|
2017-05-12 14:59:14 +01:00
|
|
|
|
if not notification:
|
2023-08-23 10:35:43 -07:00
|
|
|
|
current_app.logger.info(
|
|
|
|
|
|
"notification not found for id {} (update to status {})".format(
|
|
|
|
|
|
notification_id, status
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
2018-12-20 16:01:39 +00:00
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
if notification.status not in {
|
2024-01-18 10:28:15 -05:00
|
|
|
|
NotificationStatus.CREATED,
|
|
|
|
|
|
NotificationStatus.SENDING,
|
|
|
|
|
|
NotificationStatus.PENDING,
|
|
|
|
|
|
NotificationStatus.SENT,
|
|
|
|
|
|
NotificationStatus.PENDING_VIRUS_CHECK,
|
2018-12-20 16:01:39 +00:00
|
|
|
|
}:
|
|
|
|
|
|
_duplicate_update_warning(notification, status)
|
2017-05-12 14:59:14 +01:00
|
|
|
|
return None
|
|
|
|
|
|
|
2020-09-09 10:55:55 +01:00
|
|
|
|
if (
|
2024-02-28 12:40:52 -05:00
|
|
|
|
notification.notification_type == NotificationType.SMS
|
2020-09-09 10:55:55 +01:00
|
|
|
|
and notification.international
|
|
|
|
|
|
and not country_records_delivery(notification.phone_prefix)
|
|
|
|
|
|
):
|
2016-09-13 12:29:40 +01:00
|
|
|
|
return None
|
2023-05-04 07:56:24 -07:00
|
|
|
|
if provider_response:
|
|
|
|
|
|
notification.provider_response = provider_response
|
2023-10-24 11:35:00 -07:00
|
|
|
|
if carrier:
|
|
|
|
|
|
notification.carrier = carrier
|
2018-10-24 11:24:53 +01:00
|
|
|
|
if not notification.sent_by and sent_by:
|
|
|
|
|
|
notification.sent_by = sent_by
|
2023-09-29 13:39:10 -07:00
|
|
|
|
return _update_notification_status(
|
|
|
|
|
|
notification=notification,
|
|
|
|
|
|
status=status,
|
|
|
|
|
|
provider_response=notification.provider_response,
|
2023-10-24 11:35:00 -07:00
|
|
|
|
carrier=notification.carrier,
|
2023-09-29 13:39:10 -07:00
|
|
|
|
)
|
2016-03-21 13:24:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
2021-04-14 07:11:01 +01:00
|
|
|
|
@autocommit
|
2016-08-25 11:55:38 +01:00
|
|
|
|
def update_notification_status_by_reference(reference, status):
|
2023-03-02 20:20:31 -05:00
|
|
|
|
# this is used to update emails
|
2024-10-11 09:16:19 -07:00
|
|
|
|
stmt = select(Notification).filter(Notification.reference == reference)
|
|
|
|
|
|
notification = db.session.execute(stmt).scalars().first()
|
2018-12-20 16:01:39 +00:00
|
|
|
|
|
|
|
|
|
|
if not notification:
|
2023-08-23 10:35:43 -07:00
|
|
|
|
current_app.logger.error(
|
|
|
|
|
|
"notification not found for reference {} (update to {})".format(
|
|
|
|
|
|
reference, status
|
2024-08-15 10:31:02 -07:00
|
|
|
|
),
|
2023-08-23 10:35:43 -07:00
|
|
|
|
)
|
2018-12-20 16:01:39 +00:00
|
|
|
|
return None
|
|
|
|
|
|
|
2024-01-16 07:37:21 -05:00
|
|
|
|
if notification.status not in {
|
|
|
|
|
|
NotificationStatus.SENDING,
|
|
|
|
|
|
NotificationStatus.PENDING,
|
|
|
|
|
|
}:
|
2018-12-20 16:01:39 +00:00
|
|
|
|
_duplicate_update_warning(notification, status)
|
2016-09-13 12:29:40 +01:00
|
|
|
|
return None
|
2016-05-27 12:09:36 +01:00
|
|
|
|
|
2023-08-23 10:35:43 -07:00
|
|
|
|
return _update_notification_status(notification=notification, status=status)
|
2016-03-11 09:40:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
2021-04-14 07:11:01 +01:00
|
|
|
|
@autocommit
|
2016-05-26 16:46:00 +01:00
|
|
|
|
def dao_update_notification(notification):
|
2024-05-23 13:59:51 -07:00
|
|
|
|
notification.updated_at = utc_now()
|
2024-01-16 11:21:24 -08:00
|
|
|
|
# notify-api-742 remove phone numbers from db
|
|
|
|
|
|
notification.to = "1"
|
|
|
|
|
|
notification.normalised_to = "1"
|
2016-05-26 16:46:00 +01:00
|
|
|
|
db.session.add(notification)
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-08-23 10:35:43 -07:00
|
|
|
|
def get_notifications_for_job(
|
|
|
|
|
|
service_id, job_id, filter_dict=None, page=1, page_size=None
|
|
|
|
|
|
):
|
2016-04-19 10:52:52 +01:00
|
|
|
|
if page_size is None:
|
2023-08-23 10:35:43 -07:00
|
|
|
|
page_size = current_app.config["PAGE_SIZE"]
|
2024-10-31 09:17:49 -07:00
|
|
|
|
|
2023-06-15 08:23:00 -07:00
|
|
|
|
query = Notification.query.filter_by(service_id=service_id, job_id=job_id)
|
2016-05-24 11:31:44 +01:00
|
|
|
|
query = _filter_query(query, filter_dict)
|
2023-06-15 08:23:00 -07:00
|
|
|
|
return query.order_by(asc(Notification.job_row_number)).paginate(
|
2023-08-23 10:35:43 -07:00
|
|
|
|
page=page, per_page=page_size
|
2016-03-04 14:25:28 +00:00
|
|
|
|
)
|
2016-02-16 11:22:44 +00:00
|
|
|
|
|
|
|
|
|
|
|
2019-10-03 14:58:49 +01:00
|
|
|
|
def dao_get_notification_count_for_job_id(*, job_id):
|
2024-10-11 09:16:19 -07:00
|
|
|
|
stmt = select(func.count(Notification.id)).filter_by(job_id=job_id)
|
|
|
|
|
|
return db.session.execute(stmt).scalar()
|
2019-09-24 16:52:18 +01:00
|
|
|
|
|
|
|
|
|
|
|
2023-10-13 14:07:10 -06:00
|
|
|
|
def dao_get_notification_count_for_service(*, service_id):
|
2024-10-11 09:16:19 -07:00
|
|
|
|
stmt = select(func.count(Notification.id)).filter_by(service_id=service_id)
|
|
|
|
|
|
return db.session.execute(stmt).scalar()
|
2023-09-21 13:26:27 -06:00
|
|
|
|
|
|
|
|
|
|
|
2023-11-28 12:30:53 -08:00
|
|
|
|
def dao_get_failed_notification_count():
|
2024-10-11 09:16:19 -07:00
|
|
|
|
stmt = select(func.count(Notification.id)).filter_by(
|
2024-01-16 07:37:21 -05:00
|
|
|
|
status=NotificationStatus.FAILED
|
2024-10-11 09:16:19 -07:00
|
|
|
|
)
|
|
|
|
|
|
return db.session.execute(stmt).scalar()
|
2023-11-28 12:30:53 -08:00
|
|
|
|
|
|
|
|
|
|
|
2016-08-09 13:07:48 +01:00
|
|
|
|
def get_notification_with_personalisation(service_id, notification_id, key_type):
|
2023-08-23 10:35:43 -07:00
|
|
|
|
filter_dict = {"service_id": service_id, "id": notification_id}
|
2016-06-30 18:43:15 +01:00
|
|
|
|
if key_type:
|
2023-08-23 10:35:43 -07:00
|
|
|
|
filter_dict["key_type"] = key_type
|
2016-06-30 18:43:15 +01:00
|
|
|
|
|
2024-10-11 09:16:19 -07:00
|
|
|
|
stmt = (
|
|
|
|
|
|
select(Notification)
|
|
|
|
|
|
.filter_by(**filter_dict)
|
2024-03-25 10:55:14 -04:00
|
|
|
|
.options(joinedload(Notification.template))
|
2023-08-23 10:35:43 -07:00
|
|
|
|
)
|
2024-10-11 09:16:19 -07:00
|
|
|
|
return db.session.execute(stmt).scalars().one()
|
2016-03-01 13:30:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
2018-11-15 10:55:29 +00:00
|
|
|
|
def get_notification_by_id(notification_id, service_id=None, _raise=False):
|
|
|
|
|
|
filters = [Notification.id == notification_id]
|
|
|
|
|
|
|
|
|
|
|
|
if service_id:
|
|
|
|
|
|
filters.append(Notification.service_id == service_id)
|
|
|
|
|
|
|
2024-10-11 09:16:19 -07:00
|
|
|
|
stmt = select(Notification).filter(*filters)
|
2018-11-15 10:55:29 +00:00
|
|
|
|
|
2024-10-11 09:16:19 -07:00
|
|
|
|
return (
|
|
|
|
|
|
db.session.execute(stmt).scalars().one()
|
|
|
|
|
|
if _raise
|
|
|
|
|
|
else db.session.execute(stmt).scalars().first()
|
|
|
|
|
|
)
|
2016-03-10 15:40:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
2016-09-23 09:43:25 +01:00
|
|
|
|
def get_notifications_for_service(
|
2023-08-23 10:35:43 -07:00
|
|
|
|
service_id,
|
|
|
|
|
|
filter_dict=None,
|
|
|
|
|
|
page=1,
|
|
|
|
|
|
page_size=None,
|
|
|
|
|
|
count_pages=True,
|
|
|
|
|
|
limit_days=None,
|
|
|
|
|
|
key_type=None,
|
|
|
|
|
|
personalisation=False,
|
|
|
|
|
|
include_jobs=False,
|
|
|
|
|
|
include_from_test_key=False,
|
|
|
|
|
|
older_than=None,
|
|
|
|
|
|
client_reference=None,
|
|
|
|
|
|
include_one_off=True,
|
|
|
|
|
|
error_out=True,
|
2016-09-23 09:43:25 +01:00
|
|
|
|
):
|
2016-04-19 10:52:52 +01:00
|
|
|
|
if page_size is None:
|
2023-08-23 10:35:43 -07:00
|
|
|
|
page_size = current_app.config["PAGE_SIZE"]
|
2016-09-15 15:59:02 +01:00
|
|
|
|
|
2016-04-28 16:10:35 +01:00
|
|
|
|
filters = [Notification.service_id == service_id]
|
|
|
|
|
|
|
|
|
|
|
|
if limit_days is not None:
|
2018-04-30 11:50:56 +01:00
|
|
|
|
filters.append(Notification.created_at >= midnight_n_days_ago(limit_days))
|
2016-04-28 16:10:35 +01:00
|
|
|
|
|
2016-11-23 11:44:38 +00:00
|
|
|
|
if older_than is not None:
|
2023-08-23 10:35:43 -07:00
|
|
|
|
older_than_created_at = (
|
|
|
|
|
|
db.session.query(Notification.created_at)
|
|
|
|
|
|
.filter(Notification.id == older_than)
|
|
|
|
|
|
.as_scalar()
|
|
|
|
|
|
)
|
2016-11-23 11:44:38 +00:00
|
|
|
|
filters.append(Notification.created_at < older_than_created_at)
|
|
|
|
|
|
|
2018-07-18 10:54:20 +01:00
|
|
|
|
if not include_jobs:
|
|
|
|
|
|
filters.append(Notification.job_id == None) # noqa
|
|
|
|
|
|
|
|
|
|
|
|
if not include_one_off:
|
|
|
|
|
|
filters.append(Notification.created_by_id == None) # noqa
|
2016-09-15 15:59:02 +01:00
|
|
|
|
|
2016-06-30 18:43:15 +01:00
|
|
|
|
if key_type is not None:
|
|
|
|
|
|
filters.append(Notification.key_type == key_type)
|
2016-09-23 10:27:10 +01:00
|
|
|
|
elif not include_from_test_key:
|
2024-01-18 10:28:50 -05:00
|
|
|
|
filters.append(Notification.key_type != KeyType.TEST)
|
2016-09-16 13:47:09 +01:00
|
|
|
|
|
2016-12-12 18:04:20 +00:00
|
|
|
|
if client_reference is not None:
|
|
|
|
|
|
filters.append(Notification.client_reference == client_reference)
|
|
|
|
|
|
|
2016-04-28 16:10:35 +01:00
|
|
|
|
query = Notification.query.filter(*filters)
|
2016-05-24 11:31:44 +01:00
|
|
|
|
query = _filter_query(query, filter_dict)
|
2016-08-09 13:07:48 +01:00
|
|
|
|
if personalisation:
|
2024-03-25 10:55:14 -04:00
|
|
|
|
query = query.options(joinedload(Notification.template))
|
2016-09-15 15:59:02 +01:00
|
|
|
|
|
2023-06-15 08:23:00 -07:00
|
|
|
|
return query.order_by(desc(Notification.created_at)).paginate(
|
2016-03-01 13:30:10 +00:00
|
|
|
|
page=page,
|
2019-01-07 17:12:00 +00:00
|
|
|
|
per_page=page_size,
|
2021-12-03 17:07:03 +00:00
|
|
|
|
count=count_pages,
|
|
|
|
|
|
error_out=error_out,
|
2016-03-01 13:30:10 +00:00
|
|
|
|
)
|
2016-03-21 12:37:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
2016-05-24 11:31:44 +01:00
|
|
|
|
def _filter_query(query, filter_dict=None):
|
2016-04-04 13:13:29 +01:00
|
|
|
|
if filter_dict is None:
|
2016-11-22 17:30:03 +00:00
|
|
|
|
return query
|
|
|
|
|
|
|
|
|
|
|
|
multidict = MultiDict(filter_dict)
|
|
|
|
|
|
|
|
|
|
|
|
# filter by status
|
2023-08-23 10:35:43 -07:00
|
|
|
|
statuses = multidict.getlist("status")
|
2023-06-14 07:37:38 -07:00
|
|
|
|
|
2016-04-04 13:13:29 +01:00
|
|
|
|
if statuses:
|
|
|
|
|
|
query = query.filter(Notification.status.in_(statuses))
|
2016-11-22 17:30:03 +00:00
|
|
|
|
|
|
|
|
|
|
# filter by template
|
2023-08-23 10:35:43 -07:00
|
|
|
|
template_types = multidict.getlist("template_type")
|
2016-04-04 13:13:29 +01:00
|
|
|
|
if template_types:
|
2018-12-11 14:57:10 +00:00
|
|
|
|
query = query.filter(Notification.notification_type.in_(template_types))
|
2016-11-22 17:30:03 +00:00
|
|
|
|
|
2016-03-01 13:30:10 +00:00
|
|
|
|
return query
|
2016-03-09 17:46:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
2023-11-03 08:28:21 -07:00
|
|
|
|
def sanitize_successful_notification_by_id(notification_id, carrier, provider_response):
|
2023-06-28 06:59:28 -07:00
|
|
|
|
update_query = """
|
2023-11-03 08:28:21 -07:00
|
|
|
|
update notifications set provider_response=:response, carrier=:carrier,
|
2024-02-02 12:51:11 -08:00
|
|
|
|
notification_status='delivered', sent_at=:sent_at, "to"='1', normalised_to='1'
|
2023-06-28 06:59:28 -07:00
|
|
|
|
where id=:notification_id
|
|
|
|
|
|
"""
|
2024-02-02 12:51:11 -08:00
|
|
|
|
|
2023-11-03 08:28:21 -07:00
|
|
|
|
input_params = {
|
|
|
|
|
|
"notification_id": notification_id,
|
|
|
|
|
|
"carrier": carrier,
|
|
|
|
|
|
"response": provider_response,
|
2024-05-23 13:59:51 -07:00
|
|
|
|
"sent_at": utc_now(),
|
2023-11-03 08:28:21 -07:00
|
|
|
|
}
|
2023-06-28 06:59:28 -07:00
|
|
|
|
|
2024-05-28 14:01:37 -07:00
|
|
|
|
db.session.execute(text(update_query), input_params)
|
2023-09-29 13:39:10 -07:00
|
|
|
|
db.session.commit()
|
2023-04-18 12:42:23 -07:00
|
|
|
|
|
|
|
|
|
|
|
2021-04-14 07:11:01 +01:00
|
|
|
|
@autocommit
|
2020-03-23 15:53:53 +00:00
|
|
|
|
def insert_notification_history_delete_notifications(
|
2020-03-24 12:21:28 +00:00
|
|
|
|
notification_type, service_id, timestamp_to_delete_backwards_from, qry_limit=50000
|
2020-03-23 15:53:53 +00:00
|
|
|
|
):
|
2021-12-01 14:28:08 +00:00
|
|
|
|
"""
|
|
|
|
|
|
Delete up to 50,000 notifications that are past retention for a notification type and service.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Steps are as follows:
|
|
|
|
|
|
|
|
|
|
|
|
Create a temporary notifications table
|
|
|
|
|
|
Populate that table with up to 50k notifications that are to be deleted. (Note: no specified order)
|
|
|
|
|
|
Insert everything in the temp table into notification history
|
|
|
|
|
|
Delete from notifications if notification id is in the temp table
|
|
|
|
|
|
Drop the temp table (automatically when the transaction commits)
|
|
|
|
|
|
|
|
|
|
|
|
Temporary tables are in a separate postgres schema, and only visible to the current session (db connection,
|
|
|
|
|
|
in a celery task there's one connection per thread.)
|
|
|
|
|
|
"""
|
2020-03-24 12:21:28 +00:00
|
|
|
|
# Setting default query limit to 50,000 which take about 48 seconds on current table size
|
|
|
|
|
|
# 10, 000 took 11s and 100,000 took 1 min 30 seconds.
|
2020-03-23 15:53:53 +00:00
|
|
|
|
select_into_temp_table = """
|
2021-12-01 14:28:08 +00:00
|
|
|
|
CREATE TEMP TABLE NOTIFICATION_ARCHIVE ON COMMIT DROP AS
|
2020-03-24 12:21:28 +00:00
|
|
|
|
SELECT id, job_id, job_row_number, service_id, template_id, template_version, api_key_id,
|
|
|
|
|
|
key_type, notification_type, created_at, sent_at, sent_by, updated_at, reference, billable_units,
|
|
|
|
|
|
client_reference, international, phone_prefix, rate_multiplier, notification_status,
|
2023-03-02 20:20:31 -05:00
|
|
|
|
created_by_id, document_download_count
|
2020-12-16 10:50:11 +00:00
|
|
|
|
FROM notifications
|
|
|
|
|
|
WHERE service_id = :service_id
|
|
|
|
|
|
AND notification_type = :notification_type
|
|
|
|
|
|
AND created_at < :timestamp_to_delete_backwards_from
|
|
|
|
|
|
AND key_type in ('normal', 'team')
|
|
|
|
|
|
limit :qry_limit
|
|
|
|
|
|
"""
|
2020-03-24 12:21:28 +00:00
|
|
|
|
# Insert into NotificationHistory if the row already exists do nothing.
|
2020-03-23 15:53:53 +00:00
|
|
|
|
insert_query = """
|
|
|
|
|
|
insert into notification_history
|
2020-03-24 12:21:28 +00:00
|
|
|
|
SELECT * from NOTIFICATION_ARCHIVE
|
|
|
|
|
|
ON CONFLICT ON CONSTRAINT notification_history_pkey
|
|
|
|
|
|
DO NOTHING
|
2020-03-23 15:53:53 +00:00
|
|
|
|
"""
|
|
|
|
|
|
delete_query = """
|
|
|
|
|
|
DELETE FROM notifications
|
|
|
|
|
|
where id in (select id from NOTIFICATION_ARCHIVE)
|
|
|
|
|
|
"""
|
|
|
|
|
|
input_params = {
|
|
|
|
|
|
"service_id": service_id,
|
|
|
|
|
|
"notification_type": notification_type,
|
2020-03-24 12:21:28 +00:00
|
|
|
|
"timestamp_to_delete_backwards_from": timestamp_to_delete_backwards_from,
|
2023-08-23 10:35:43 -07:00
|
|
|
|
"qry_limit": qry_limit,
|
2020-03-23 15:53:53 +00:00
|
|
|
|
}
|
2020-03-25 08:08:33 +00:00
|
|
|
|
|
2024-04-24 16:27:20 -04:00
|
|
|
|
db.session.execute(text(select_into_temp_table), input_params)
|
2020-03-23 15:53:53 +00:00
|
|
|
|
|
2024-04-24 16:27:20 -04:00
|
|
|
|
result = db.session.execute(
|
|
|
|
|
|
text("select count(*) from NOTIFICATION_ARCHIVE")
|
|
|
|
|
|
).fetchone()[0]
|
2020-03-23 15:53:53 +00:00
|
|
|
|
|
2024-04-24 16:27:20 -04:00
|
|
|
|
db.session.execute(text(insert_query))
|
2020-03-23 15:53:53 +00:00
|
|
|
|
|
2024-04-24 16:27:20 -04:00
|
|
|
|
db.session.execute(text(delete_query))
|
2020-03-23 15:53:53 +00:00
|
|
|
|
|
2021-11-17 14:46:52 +00:00
|
|
|
|
return result
|
2020-03-23 15:53:53 +00:00
|
|
|
|
|
|
|
|
|
|
|
2021-12-06 09:30:48 +00:00
|
|
|
|
def move_notifications_to_notification_history(
|
2023-08-23 10:35:43 -07:00
|
|
|
|
notification_type, service_id, timestamp_to_delete_backwards_from, qry_limit=50000
|
2021-12-06 09:30:48 +00:00
|
|
|
|
):
|
2020-03-20 19:07:08 +00:00
|
|
|
|
deleted = 0
|
2020-03-24 14:44:42 +00:00
|
|
|
|
delete_count_per_call = 1
|
|
|
|
|
|
while delete_count_per_call > 0:
|
|
|
|
|
|
delete_count_per_call = insert_notification_history_delete_notifications(
|
2020-03-24 12:21:28 +00:00
|
|
|
|
notification_type=notification_type,
|
|
|
|
|
|
service_id=service_id,
|
2021-12-06 09:30:48 +00:00
|
|
|
|
timestamp_to_delete_backwards_from=timestamp_to_delete_backwards_from,
|
2023-08-23 10:35:43 -07:00
|
|
|
|
qry_limit=qry_limit,
|
2020-03-20 19:07:08 +00:00
|
|
|
|
)
|
2020-03-24 14:44:42 +00:00
|
|
|
|
deleted += delete_count_per_call
|
2020-03-20 19:07:08 +00:00
|
|
|
|
|
2020-03-24 14:09:13 +00:00
|
|
|
|
# Deleting test Notifications, test notifications are not persisted to NotificationHistory
|
2024-10-11 09:16:19 -07:00
|
|
|
|
stmt = delete(Notification).filter(
|
2018-12-27 14:00:53 +00:00
|
|
|
|
Notification.notification_type == notification_type,
|
|
|
|
|
|
Notification.service_id == service_id,
|
2021-12-06 09:30:48 +00:00
|
|
|
|
Notification.created_at < timestamp_to_delete_backwards_from,
|
2024-01-18 10:28:50 -05:00
|
|
|
|
Notification.key_type == KeyType.TEST,
|
2024-10-11 09:16:19 -07:00
|
|
|
|
)
|
|
|
|
|
|
db.session.execute(stmt)
|
2018-12-21 13:57:35 +00:00
|
|
|
|
db.session.commit()
|
2019-10-14 16:43:37 +01:00
|
|
|
|
|
2020-03-24 12:21:28 +00:00
|
|
|
|
return deleted
|
2019-04-29 15:44:42 +01:00
|
|
|
|
|
|
|
|
|
|
|
2021-04-14 07:11:01 +01:00
|
|
|
|
@autocommit
|
2019-05-30 10:37:57 +01:00
|
|
|
|
def dao_delete_notifications_by_id(notification_id):
|
2023-08-23 10:35:43 -07:00
|
|
|
|
db.session.query(Notification).filter(Notification.id == notification_id).delete(
|
|
|
|
|
|
synchronize_session="fetch"
|
|
|
|
|
|
)
|
2016-09-13 16:42:53 +01:00
|
|
|
|
|
|
|
|
|
|
|
2021-12-13 16:59:25 +00:00
|
|
|
|
def dao_timeout_notifications(cutoff_time, limit=100000):
|
2017-04-19 11:34:00 +01:00
|
|
|
|
"""
|
2021-12-13 16:56:21 +00:00
|
|
|
|
Set email and SMS notifications (only) to "temporary-failure" status
|
|
|
|
|
|
if they're still sending from before the specified cutoff_time.
|
2017-04-19 11:34:00 +01:00
|
|
|
|
"""
|
2024-05-23 13:59:51 -07:00
|
|
|
|
updated_at = utc_now()
|
2024-01-18 10:28:15 -05:00
|
|
|
|
current_statuses = [NotificationStatus.SENDING, NotificationStatus.PENDING]
|
|
|
|
|
|
new_status = NotificationStatus.TEMPORARY_FAILURE
|
2017-07-06 11:55:56 +01:00
|
|
|
|
|
2024-10-11 09:16:19 -07:00
|
|
|
|
stmt = (
|
|
|
|
|
|
select(Notification)
|
|
|
|
|
|
.filter(
|
2023-08-23 10:35:43 -07:00
|
|
|
|
Notification.created_at < cutoff_time,
|
|
|
|
|
|
Notification.status.in_(current_statuses),
|
2024-02-28 12:41:57 -05:00
|
|
|
|
Notification.notification_type.in_(
|
|
|
|
|
|
[NotificationType.SMS, NotificationType.EMAIL]
|
|
|
|
|
|
),
|
2023-08-23 10:35:43 -07:00
|
|
|
|
)
|
|
|
|
|
|
.limit(limit)
|
|
|
|
|
|
)
|
2024-10-11 09:16:19 -07:00
|
|
|
|
notifications = db.session.execute(stmt).scalars().all()
|
2021-11-08 14:18:21 +00:00
|
|
|
|
|
2024-10-11 09:16:19 -07:00
|
|
|
|
stmt = (
|
|
|
|
|
|
update(Notification)
|
|
|
|
|
|
.filter(Notification.id.in_([n.id for n in notifications]))
|
2024-10-11 10:13:11 -07:00
|
|
|
|
.values({"status": new_status, "updated_at": updated_at})
|
2019-05-30 10:37:57 +01:00
|
|
|
|
)
|
2024-10-11 09:16:19 -07:00
|
|
|
|
db.session.execute(stmt)
|
2017-04-19 11:34:00 +01:00
|
|
|
|
|
2016-09-13 16:42:53 +01:00
|
|
|
|
db.session.commit()
|
2021-11-25 17:52:16 +00:00
|
|
|
|
return notifications
|
2016-09-30 17:17:28 +01:00
|
|
|
|
|
|
|
|
|
|
|
2021-04-14 07:11:01 +01:00
|
|
|
|
@autocommit
|
2017-09-20 11:12:37 +01:00
|
|
|
|
def dao_update_notifications_by_reference(references, update_dict):
|
2024-10-11 09:16:19 -07:00
|
|
|
|
stmt = (
|
|
|
|
|
|
update(Notification)
|
|
|
|
|
|
.filter(Notification.reference.in_(references))
|
2024-10-11 09:31:47 -07:00
|
|
|
|
.values(update_dict)
|
2024-10-11 09:16:19 -07:00
|
|
|
|
)
|
2024-10-11 10:13:11 -07:00
|
|
|
|
result = db.session.execute(stmt)
|
|
|
|
|
|
updated_count = result.rowcount
|
2017-04-07 11:22:03 +01:00
|
|
|
|
|
2019-05-08 17:31:27 +01:00
|
|
|
|
updated_history_count = 0
|
2019-05-15 15:30:15 +01:00
|
|
|
|
if updated_count != len(references):
|
2024-10-11 09:16:19 -07:00
|
|
|
|
stmt = (
|
2024-10-11 09:47:00 -07:00
|
|
|
|
update(NotificationHistory)
|
2024-10-11 09:16:19 -07:00
|
|
|
|
.filter(NotificationHistory.reference.in_(references))
|
2024-10-11 09:38:12 -07:00
|
|
|
|
.values(update_dict)
|
2024-10-11 09:16:19 -07:00
|
|
|
|
)
|
2024-10-11 10:13:11 -07:00
|
|
|
|
result = db.session.execute(stmt)
|
|
|
|
|
|
updated_history_count = result.rowcount
|
2017-04-07 10:59:12 +01:00
|
|
|
|
|
2018-08-30 14:27:57 +01:00
|
|
|
|
return updated_count, updated_history_count
|
2017-05-05 14:12:50 +01:00
|
|
|
|
|
|
|
|
|
|
|
2020-05-01 11:18:33 +01:00
|
|
|
|
def dao_get_notifications_by_recipient_or_reference(
|
|
|
|
|
|
service_id,
|
|
|
|
|
|
search_term,
|
|
|
|
|
|
notification_type=None,
|
|
|
|
|
|
statuses=None,
|
|
|
|
|
|
page=1,
|
|
|
|
|
|
page_size=None,
|
2021-12-10 12:06:55 +00:00
|
|
|
|
error_out=True,
|
2020-05-01 11:18:33 +01:00
|
|
|
|
):
|
2024-02-28 12:40:52 -05:00
|
|
|
|
if notification_type == NotificationType.SMS:
|
2018-03-07 18:13:40 +00:00
|
|
|
|
normalised = try_validate_and_format_phone_number(search_term)
|
|
|
|
|
|
|
2023-08-23 10:35:43 -07:00
|
|
|
|
for character in {"(", ")", " ", "-"}:
|
|
|
|
|
|
normalised = normalised.replace(character, "")
|
2018-03-07 18:13:40 +00:00
|
|
|
|
|
2023-08-23 10:35:43 -07:00
|
|
|
|
normalised = normalised.lstrip("+0")
|
2018-03-07 18:13:40 +00:00
|
|
|
|
|
2024-02-28 12:40:52 -05:00
|
|
|
|
elif notification_type == NotificationType.EMAIL:
|
2017-05-30 14:40:27 +01:00
|
|
|
|
try:
|
|
|
|
|
|
normalised = validate_and_format_email_address(search_term)
|
|
|
|
|
|
except InvalidEmailError:
|
2018-03-07 18:13:40 +00:00
|
|
|
|
normalised = search_term.lower()
|
2018-06-13 16:04:49 +01:00
|
|
|
|
|
2023-03-02 20:20:31 -05:00
|
|
|
|
elif notification_type is None:
|
|
|
|
|
|
# This happens when a notification type isn’t provided (this will
|
2020-04-23 16:06:34 +01:00
|
|
|
|
# happen if a user doesn’t have permission to see the dashboard)
|
|
|
|
|
|
# because email addresses and phone numbers will never be stored
|
|
|
|
|
|
# with spaces either.
|
2023-08-23 10:35:43 -07:00
|
|
|
|
normalised = "".join(search_term.split()).lower()
|
2018-03-06 12:39:58 +00:00
|
|
|
|
|
2019-12-16 10:27:55 +00:00
|
|
|
|
else:
|
2024-02-28 12:41:57 -05:00
|
|
|
|
raise TypeError(
|
|
|
|
|
|
f"Notification type must be {NotificationType.EMAIL}, {NotificationType.SMS}, or None"
|
|
|
|
|
|
)
|
2019-12-16 10:27:55 +00:00
|
|
|
|
|
2018-07-13 15:26:42 +01:00
|
|
|
|
normalised = escape_special_characters(normalised)
|
2019-12-12 16:01:22 +00:00
|
|
|
|
search_term = escape_special_characters(search_term)
|
2018-03-14 10:34:45 +00:00
|
|
|
|
|
2017-05-24 14:24:57 +01:00
|
|
|
|
filters = [
|
2017-05-05 14:12:50 +01:00
|
|
|
|
Notification.service_id == service_id,
|
2019-12-12 16:01:22 +00:00
|
|
|
|
or_(
|
|
|
|
|
|
Notification.normalised_to.like("%{}%".format(normalised)),
|
|
|
|
|
|
Notification.client_reference.ilike("%{}%".format(search_term)),
|
|
|
|
|
|
),
|
2024-01-18 10:28:50 -05:00
|
|
|
|
Notification.key_type != KeyType.TEST,
|
2017-05-24 14:24:57 +01:00
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
if statuses:
|
|
|
|
|
|
filters.append(Notification.status.in_(statuses))
|
2018-03-07 17:11:29 +00:00
|
|
|
|
if notification_type:
|
|
|
|
|
|
filters.append(Notification.notification_type == notification_type)
|
2017-05-24 14:24:57 +01:00
|
|
|
|
|
2023-08-23 10:35:43 -07:00
|
|
|
|
results = (
|
|
|
|
|
|
db.session.query(Notification)
|
|
|
|
|
|
.filter(*filters)
|
|
|
|
|
|
.order_by(desc(Notification.created_at))
|
2021-12-10 12:06:55 +00:00
|
|
|
|
.paginate(page=page, per_page=page_size, count=False, error_out=error_out)
|
2023-08-23 10:35:43 -07:00
|
|
|
|
)
|
2017-05-24 14:24:57 +01:00
|
|
|
|
return results
|
2017-05-15 17:27:38 +01:00
|
|
|
|
|
|
|
|
|
|
|
2018-01-17 09:52:13 +00:00
|
|
|
|
def dao_get_notification_by_reference(reference):
|
2024-10-11 09:16:19 -07:00
|
|
|
|
stmt = select(Notification).filter(Notification.reference == reference)
|
|
|
|
|
|
return db.session.execute(stmt).scalars().one()
|
2018-01-17 09:52:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
2022-09-15 14:59:13 -07:00
|
|
|
|
def dao_get_notification_history_by_reference(reference):
|
|
|
|
|
|
try:
|
|
|
|
|
|
# This try except is necessary because in test keys and research mode does not create notification history.
|
|
|
|
|
|
# Otherwise we could just search for the NotificationHistory object
|
2024-10-11 09:16:19 -07:00
|
|
|
|
stmt = select(Notification).filter(Notification.reference == reference)
|
|
|
|
|
|
return db.session.execute(stmt).scalars().one()
|
2022-09-15 14:59:13 -07:00
|
|
|
|
except NoResultFound:
|
2024-10-11 09:16:19 -07:00
|
|
|
|
stmt = select(NotificationHistory).filter(
|
2023-08-23 10:35:43 -07:00
|
|
|
|
NotificationHistory.reference == reference
|
2024-10-11 09:16:19 -07:00
|
|
|
|
)
|
|
|
|
|
|
return db.session.execute(stmt).scalars().one()
|
2022-09-15 14:59:13 -07:00
|
|
|
|
|
2018-03-01 15:39:51 +00:00
|
|
|
|
|
2021-03-11 18:53:43 +00:00
|
|
|
|
def dao_get_notifications_processing_time_stats(start_date, end_date):
|
2017-08-29 16:35:30 +01:00
|
|
|
|
"""
|
2021-03-11 18:53:43 +00:00
|
|
|
|
For a given time range, returns the number of notifications sent and the number of
|
|
|
|
|
|
those notifications that we processed within 10 seconds
|
|
|
|
|
|
|
2017-08-29 16:35:30 +01:00
|
|
|
|
SELECT
|
2021-02-22 15:42:29 +00:00
|
|
|
|
count(notifications),
|
2017-08-30 16:02:30 +01:00
|
|
|
|
coalesce(sum(CASE WHEN sent_at - created_at <= interval '10 seconds' THEN 1 ELSE 0 END), 0)
|
2021-02-22 15:42:29 +00:00
|
|
|
|
FROM notifications
|
2017-08-29 16:35:30 +01:00
|
|
|
|
WHERE
|
|
|
|
|
|
created_at > 'START DATE' AND
|
|
|
|
|
|
created_at < 'END DATE' AND
|
|
|
|
|
|
api_key_id IS NOT NULL AND
|
2023-03-02 20:20:31 -05:00
|
|
|
|
key_type != 'test';
|
2017-08-29 16:35:30 +01:00
|
|
|
|
"""
|
2023-08-23 10:35:43 -07:00
|
|
|
|
under_10_secs = Notification.sent_at - Notification.created_at <= timedelta(
|
|
|
|
|
|
seconds=10
|
|
|
|
|
|
)
|
2024-03-25 10:55:14 -04:00
|
|
|
|
sum_column = functions.coalesce(functions.sum(case((under_10_secs, 1), else_=0)), 0)
|
|
|
|
|
|
|
|
|
|
|
|
stmt = select(
|
|
|
|
|
|
functions.count(Notification.id).label("messages_total"),
|
|
|
|
|
|
sum_column.label("messages_within_10_secs"),
|
|
|
|
|
|
).where(
|
|
|
|
|
|
Notification.created_at >= start_date,
|
|
|
|
|
|
Notification.created_at < end_date,
|
|
|
|
|
|
Notification.api_key_id.isnot(None),
|
|
|
|
|
|
Notification.key_type != KeyType.TEST,
|
2023-08-23 10:35:43 -07:00
|
|
|
|
)
|
2017-08-31 12:44:06 +01:00
|
|
|
|
|
2024-03-25 10:55:14 -04:00
|
|
|
|
result = db.session.execute(stmt)
|
2024-04-24 16:27:20 -04:00
|
|
|
|
return result.one()
|
2017-09-15 17:46:08 +01:00
|
|
|
|
|
|
|
|
|
|
|
2017-10-17 11:07:36 +01:00
|
|
|
|
def dao_get_last_notification_added_for_job_id(job_id):
|
2024-10-11 09:16:19 -07:00
|
|
|
|
stmt = (
|
|
|
|
|
|
select(Notification)
|
|
|
|
|
|
.filter(Notification.job_id == job_id)
|
2023-08-23 10:35:43 -07:00
|
|
|
|
.order_by(Notification.job_row_number.desc())
|
|
|
|
|
|
)
|
2024-10-11 09:16:19 -07:00
|
|
|
|
last_notification_added = db.session.execute(stmt).scalars().first()
|
2017-10-17 11:07:36 +01:00
|
|
|
|
|
|
|
|
|
|
return last_notification_added
|
2017-12-18 16:12:17 +00:00
|
|
|
|
|
|
|
|
|
|
|
2018-03-23 15:38:35 +00:00
|
|
|
|
def notifications_not_yet_sent(should_be_sending_after_seconds, notification_type):
|
2024-05-23 13:59:51 -07:00
|
|
|
|
older_than_date = utc_now() - timedelta(seconds=should_be_sending_after_seconds)
|
2018-03-23 15:38:35 +00:00
|
|
|
|
|
2024-10-11 09:16:19 -07:00
|
|
|
|
stmt = select(Notification).filter(
|
2018-03-23 15:38:35 +00:00
|
|
|
|
Notification.created_at <= older_than_date,
|
|
|
|
|
|
Notification.notification_type == notification_type,
|
2024-01-18 10:28:15 -05:00
|
|
|
|
Notification.status == NotificationStatus.CREATED,
|
2024-10-11 09:16:19 -07:00
|
|
|
|
)
|
2024-10-11 10:38:28 -07:00
|
|
|
|
notifications = db.session.execute(stmt).scalars().all()
|
2018-03-23 15:38:35 +00:00
|
|
|
|
return notifications
|
2018-06-13 16:04:49 +01:00
|
|
|
|
|
|
|
|
|
|
|
2018-12-20 16:01:39 +00:00
|
|
|
|
def _duplicate_update_warning(notification, status):
|
|
|
|
|
|
current_app.logger.info(
|
|
|
|
|
|
(
|
2023-08-23 10:35:43 -07:00
|
|
|
|
"Duplicate callback received for service {service_id}. "
|
|
|
|
|
|
"Notification ID {id} with type {type} sent by {sent_by}. "
|
|
|
|
|
|
"New status was {new_status}, current status is {old_status}. "
|
|
|
|
|
|
"This happened {time_diff} after being first set."
|
2018-12-20 16:01:39 +00:00
|
|
|
|
).format(
|
|
|
|
|
|
id=notification.id,
|
|
|
|
|
|
old_status=notification.status,
|
|
|
|
|
|
new_status=status,
|
2024-05-23 13:59:51 -07:00
|
|
|
|
time_diff=utc_now() - (notification.updated_at or notification.created_at),
|
2018-12-20 16:01:39 +00:00
|
|
|
|
type=notification.notification_type,
|
2022-03-21 15:41:59 +00:00
|
|
|
|
sent_by=notification.sent_by,
|
2023-08-23 10:35:43 -07:00
|
|
|
|
service_id=notification.service_id,
|
2018-12-20 16:01:39 +00:00
|
|
|
|
)
|
|
|
|
|
|
)
|
2021-12-06 09:30:48 +00:00
|
|
|
|
|
|
|
|
|
|
|
2022-01-24 15:54:37 +00:00
|
|
|
|
def get_service_ids_with_notifications_before(notification_type, timestamp):
|
2021-12-06 09:30:48 +00:00
|
|
|
|
return {
|
|
|
|
|
|
row.service_id
|
2023-08-23 10:35:43 -07:00
|
|
|
|
for row in db.session.query(Notification.service_id)
|
|
|
|
|
|
.filter(
|
2021-12-06 09:30:48 +00:00
|
|
|
|
Notification.notification_type == notification_type,
|
2023-08-23 10:35:43 -07:00
|
|
|
|
Notification.created_at < timestamp,
|
|
|
|
|
|
)
|
|
|
|
|
|
.distinct()
|
2021-12-06 09:30:48 +00:00
|
|
|
|
}
|
2022-01-25 11:29:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_service_ids_with_notifications_on_date(notification_type, date):
|
2023-05-10 08:39:50 -07:00
|
|
|
|
start_date = get_midnight_in_utc(date)
|
|
|
|
|
|
end_date = get_midnight_in_utc(date + timedelta(days=1))
|
2022-02-10 10:37:32 +00:00
|
|
|
|
|
2022-05-10 11:14:59 +01:00
|
|
|
|
notification_table_query = db.session.query(
|
2023-08-23 10:35:43 -07:00
|
|
|
|
Notification.service_id.label("service_id")
|
2022-05-10 11:14:59 +01:00
|
|
|
|
).filter(
|
|
|
|
|
|
Notification.notification_type == notification_type,
|
|
|
|
|
|
# using >= + < is much more efficient than date(created_at)
|
|
|
|
|
|
Notification.created_at >= start_date,
|
|
|
|
|
|
Notification.created_at < end_date,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# Looking at this table is more efficient for historical notifications,
|
|
|
|
|
|
# provided the task to populate it has run before they were archived.
|
|
|
|
|
|
ft_status_table_query = db.session.query(
|
2023-08-23 10:35:43 -07:00
|
|
|
|
FactNotificationStatus.service_id.label("service_id")
|
2022-05-10 11:14:59 +01:00
|
|
|
|
).filter(
|
|
|
|
|
|
FactNotificationStatus.notification_type == notification_type,
|
2022-11-21 11:49:59 -05:00
|
|
|
|
FactNotificationStatus.local_date == date,
|
2022-05-10 11:14:59 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
2022-01-25 11:29:57 +00:00
|
|
|
|
return {
|
2023-08-23 10:35:43 -07:00
|
|
|
|
row.service_id
|
|
|
|
|
|
for row in db.session.query(
|
|
|
|
|
|
union(notification_table_query, ft_status_table_query).subquery()
|
|
|
|
|
|
).distinct()
|
2022-01-25 11:29:57 +00:00
|
|
|
|
}
|
2024-12-13 14:40:37 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def dao_update_delivery_receipts(receipts):
|
|
|
|
|
|
id_to_status = {r["notification.messageId"]: r["status"] for r in receipts}
|
|
|
|
|
|
id_to_carrier = {
|
|
|
|
|
|
r["notification.messageId"]: r["delivery.phoneCarrier"] for r in receipts
|
|
|
|
|
|
}
|
|
|
|
|
|
id_to_provider_response = {
|
|
|
|
|
|
r["notification.messageId"]: r["delivery.providerResponse"] for r in receipts
|
|
|
|
|
|
}
|
|
|
|
|
|
id_to_timestamp = {r["notification.messageId"]: r["@timestamp"] for r in receipts}
|
|
|
|
|
|
|
|
|
|
|
|
stmt = (
|
|
|
|
|
|
update(Notification)
|
|
|
|
|
|
.where(Notification.c.message_id.in_(id_to_carrier.keys()))
|
|
|
|
|
|
.values(
|
|
|
|
|
|
carrier=case(id_to_carrier),
|
|
|
|
|
|
status=case(id_to_status),
|
|
|
|
|
|
notification_status=case(id_to_status),
|
|
|
|
|
|
sent_at=case(id_to_timestamp),
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
db.session.execute(stmt)
|
|
|
|
|
|
db.session.commit()
|