mirror of
https://github.com/GSA/notifications-api.git
synced 2025-12-20 23:41:17 -05:00
This changeset pulls in all of the notification_utils code directly into the API and removes it as an external dependency. We are doing this to cut down on operational maintenance of the project and will begin removing parts of it no longer needed for the API. Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
236 lines
8.0 KiB
Python
236 lines
8.0 KiB
Python
from flask import Blueprint, current_app, jsonify, request
|
||
|
||
from app import api_user, authenticated_service
|
||
from app.aws.s3 import get_personalisation_from_s3, get_phone_number_from_s3
|
||
from app.config import QueueNames
|
||
from app.dao import notifications_dao
|
||
from app.enums import KeyType, NotificationType, TemplateProcessType
|
||
from app.errors import InvalidRequest, register_errors
|
||
from app.notifications.process_notifications import (
|
||
persist_notification,
|
||
send_notification_to_queue,
|
||
simulated_recipient,
|
||
)
|
||
from app.notifications.validators import (
|
||
check_if_service_can_send_to_number,
|
||
check_rate_limiting,
|
||
service_has_permission,
|
||
validate_template,
|
||
)
|
||
from app.schemas import (
|
||
email_notification_schema,
|
||
notification_with_personalisation_schema,
|
||
notifications_filter_schema,
|
||
sms_template_notification_schema,
|
||
)
|
||
from app.service.utils import service_allowed_to_send_to
|
||
from app.utils import get_public_notify_type_text, pagination_links
|
||
from notifications_utils import SMS_CHAR_COUNT_LIMIT
|
||
|
||
notifications = Blueprint("notifications", __name__)
|
||
|
||
register_errors(notifications)
|
||
|
||
|
||
@notifications.route("/notifications/<uuid:notification_id>", methods=["GET"])
|
||
def get_notification_by_id(notification_id):
|
||
notification = notifications_dao.get_notification_with_personalisation(
|
||
str(authenticated_service.id), notification_id, key_type=None
|
||
)
|
||
if notification.job_id is not None:
|
||
notification.personalisation = get_personalisation_from_s3(
|
||
notification.service_id,
|
||
notification.job_id,
|
||
notification.job_row_number,
|
||
)
|
||
recipient = get_phone_number_from_s3(
|
||
notification.service_id,
|
||
notification.job_id,
|
||
notification.job_row_number,
|
||
)
|
||
notification.to = recipient
|
||
notification.normalised_to = recipient
|
||
return (
|
||
jsonify(
|
||
data={
|
||
"notification": notification_with_personalisation_schema.dump(
|
||
notification
|
||
)
|
||
}
|
||
),
|
||
200,
|
||
)
|
||
|
||
|
||
@notifications.route("/notifications", methods=["GET"])
|
||
def get_all_notifications():
|
||
data = notifications_filter_schema.load(request.args)
|
||
|
||
include_jobs = data.get("include_jobs", False)
|
||
page = data.get("page", 1)
|
||
page_size = data.get("page_size", current_app.config.get("API_PAGE_SIZE"))
|
||
limit_days = data.get("limit_days")
|
||
|
||
pagination = notifications_dao.get_notifications_for_service(
|
||
str(authenticated_service.id),
|
||
personalisation=True,
|
||
filter_dict=data,
|
||
page=page,
|
||
page_size=page_size,
|
||
limit_days=limit_days,
|
||
key_type=api_user.key_type,
|
||
include_jobs=include_jobs,
|
||
)
|
||
for notification in pagination.items:
|
||
if notification.job_id is not None:
|
||
notification.personalisation = get_personalisation_from_s3(
|
||
notification.service_id,
|
||
notification.job_id,
|
||
notification.job_row_number,
|
||
)
|
||
recipient = get_phone_number_from_s3(
|
||
notification.service_id,
|
||
notification.job_id,
|
||
notification.job_row_number,
|
||
)
|
||
notification.to = recipient
|
||
notification.normalised_to = recipient
|
||
|
||
return (
|
||
jsonify(
|
||
notifications=notification_with_personalisation_schema.dump(
|
||
pagination.items, many=True
|
||
),
|
||
page_size=page_size,
|
||
total=pagination.total,
|
||
links=pagination_links(
|
||
pagination, ".get_all_notifications", **request.args.to_dict()
|
||
),
|
||
),
|
||
200,
|
||
)
|
||
|
||
|
||
@notifications.route("/notifications/<string:notification_type>", methods=["POST"])
|
||
def send_notification(notification_type):
|
||
if notification_type not in {NotificationType.SMS, NotificationType.EMAIL}:
|
||
msg = f"{notification_type} notification type is not supported"
|
||
raise InvalidRequest(msg, 400)
|
||
|
||
notification_form = (
|
||
sms_template_notification_schema
|
||
if notification_type == NotificationType.SMS
|
||
else email_notification_schema
|
||
).load(request.get_json())
|
||
|
||
check_rate_limiting(authenticated_service, api_user)
|
||
|
||
template, template_with_content = validate_template(
|
||
template_id=notification_form["template"],
|
||
personalisation=notification_form.get("personalisation", {}),
|
||
service=authenticated_service,
|
||
notification_type=notification_type,
|
||
)
|
||
|
||
_service_allowed_to_send_to(notification_form, authenticated_service)
|
||
if not service_has_permission(notification_type, authenticated_service.permissions):
|
||
raise InvalidRequest(
|
||
{
|
||
"service": [
|
||
"Cannot send {}".format(
|
||
get_public_notify_type_text(notification_type, plural=True)
|
||
)
|
||
]
|
||
},
|
||
status_code=400,
|
||
)
|
||
|
||
if notification_type == NotificationType.SMS:
|
||
check_if_service_can_send_to_number(
|
||
authenticated_service, notification_form["to"]
|
||
)
|
||
|
||
# Do not persist or send notification to the queue if it is a simulated recipient
|
||
simulated = simulated_recipient(notification_form["to"], notification_type)
|
||
notification_model = persist_notification(
|
||
template_id=template.id,
|
||
template_version=template.version,
|
||
recipient=request.get_json()["to"],
|
||
service=authenticated_service,
|
||
personalisation=notification_form.get("personalisation", None),
|
||
notification_type=notification_type,
|
||
api_key_id=api_user.id,
|
||
key_type=api_user.key_type,
|
||
simulated=simulated,
|
||
reply_to_text=template.reply_to_text,
|
||
)
|
||
if not simulated:
|
||
queue_name = (
|
||
QueueNames.PRIORITY
|
||
if template.process_type == TemplateProcessType.PRIORITY
|
||
else None
|
||
)
|
||
send_notification_to_queue(notification=notification_model, queue=queue_name)
|
||
|
||
else:
|
||
current_app.logger.debug(
|
||
"POST simulated notification for id: {}".format(notification_model.id)
|
||
)
|
||
notification_form.update({"template_version": template.version})
|
||
|
||
return (
|
||
jsonify(
|
||
data=get_notification_return_data(
|
||
notification_model.id, notification_form, template_with_content
|
||
)
|
||
),
|
||
201,
|
||
)
|
||
|
||
|
||
def get_notification_return_data(notification_id, notification, template):
|
||
output = {
|
||
"template_version": notification["template_version"],
|
||
"notification": {"id": notification_id},
|
||
"body": template.content_with_placeholders_filled_in,
|
||
}
|
||
|
||
if hasattr(template, "subject"):
|
||
output["subject"] = template.subject
|
||
|
||
return output
|
||
|
||
|
||
def _service_allowed_to_send_to(notification, service):
|
||
if not service_allowed_to_send_to(notification["to"], service, api_user.key_type):
|
||
if api_user.key_type == KeyType.TEAM:
|
||
message = "Can’t send to this recipient using a team-only API key"
|
||
else:
|
||
message = (
|
||
"Can’t send to this recipient when service is in trial mode "
|
||
"– see https://www.notifications.service.gov.uk/trial-mode"
|
||
)
|
||
raise InvalidRequest({"to": [message]}, status_code=400)
|
||
|
||
|
||
def create_template_object_for_notification(template, personalisation):
|
||
template_object = template._as_utils_template_with_personalisation(personalisation)
|
||
|
||
if template_object.missing_data:
|
||
message = "Missing personalisation: {}".format(
|
||
", ".join(template_object.missing_data)
|
||
)
|
||
errors = {"template": [message]}
|
||
raise InvalidRequest(errors, status_code=400)
|
||
|
||
if (
|
||
template_object.template_type == NotificationType.SMS
|
||
and template_object.is_message_too_long()
|
||
):
|
||
message = "Content has a character count greater than the limit of {}".format(
|
||
SMS_CHAR_COUNT_LIMIT
|
||
)
|
||
errors = {"content": [message]}
|
||
raise InvalidRequest(errors, status_code=400)
|
||
return template_object
|