From 10b851b456e998f90cb8b235f7166a1bd0c3dfd7 Mon Sep 17 00:00:00 2001 From: Leo Hemsted Date: Tue, 13 Jun 2017 17:33:04 +0100 Subject: [PATCH] add send_notification file for handling sending one off messages currently, they're made by creating a one-line job, but we want to reduce task/csv file noise so we're moving them to persist in the same vein as API usage. However, we can't just call through to that since there are some differences: * no api keys * tighter control over API format * no scheduling * no client references etc. So, re-using as much of the v2 validation stuff as possible, I've created this file that just does basic validation, and then calls through to persist_notification and schedules a task. Woo. --- app/notifications/validators.py | 25 ++++++++- app/service/rest.py | 7 +++ app/service/send_notification.py | 61 ++++++++++++++++++++++ app/v2/notifications/post_notifications.py | 36 ++++--------- tests/app/service/test_rest.py | 9 ++-- 5 files changed, 105 insertions(+), 33 deletions(-) create mode 100644 app/service/send_notification.py diff --git a/app/notifications/validators.py b/app/notifications/validators.py index 5663a6046..8d1fa5a76 100644 --- a/app/notifications/validators.py +++ b/app/notifications/validators.py @@ -1,16 +1,18 @@ +from sqlalchemy.orm.exc import NoResultFound from flask import current_app from notifications_utils.recipients import ( validate_and_format_phone_number, validate_and_format_email_address, get_international_phone_info ) +from notifications_utils.clients.redis import rate_limit_cache_key, daily_limit_cache_key -from app.dao import services_dao +from app.dao import services_dao, templates_dao from app.models import KEY_TYPE_TEST, KEY_TYPE_TEAM, SMS_TYPE, SCHEDULE_NOTIFICATIONS from app.service.utils import service_allowed_to_send_to from app.v2.errors import TooManyRequestsError, BadRequestError, RateLimitError from app import redis_store -from notifications_utils.clients.redis import rate_limit_cache_key, daily_limit_cache_key +from app.notifications.process_notifications import create_content_for_notification def check_service_over_api_rate_limit(service, api_key): @@ -96,3 +98,22 @@ def service_can_schedule_notification(service, scheduled_for): if scheduled_for: if SCHEDULE_NOTIFICATIONS not in [p.permission for p in service.permissions]: raise BadRequestError(message="Cannot schedule notifications (this feature is invite-only)") + + +def validate_template(template_id, personalisation, service, notification_type): + try: + template = templates_dao.dao_get_template_by_id_and_service_id( + template_id=template_id, + service_id=service.id + ) + except NoResultFound: + message = 'Template not found' + raise BadRequestError(message=message, + fields=[{'template': message}]) + + check_template_is_for_notification_type(notification_type, template.template_type) + check_template_is_active(template) + template_with_content = create_content_for_notification(template, personalisation) + if template.template_type == SMS_TYPE: + check_sms_content_char_count(template_with_content.content_count) + return template, template_with_content diff --git a/app/service/rest.py b/app/service/rest.py index 31d553b12..eb6ac62e3 100644 --- a/app/service/rest.py +++ b/app/service/rest.py @@ -61,6 +61,7 @@ from app.service import statistics from app.service.service_inbound_api_schema import service_inbound_api, update_service_inbound_api_schema from app.service.utils import get_whitelist_objects from app.service.sender import send_notification_to_service_users +from app.service.send_notification import send_one_off_notification from app.schemas import ( service_schema, api_key_schema, @@ -591,3 +592,9 @@ def handle_sql_errror(e): return jsonify(result='error', message="No result found"), 404 else: raise e + + +@service_blueprint.route('//send-notification', methods=['POST']) +def post_notification(service_id): + resp = send_one_off_notification(service_id, request.get_json()) + return jsonify(resp), 201 diff --git a/app/service/send_notification.py b/app/service/send_notification.py new file mode 100644 index 000000000..9d93e9656 --- /dev/null +++ b/app/service/send_notification.py @@ -0,0 +1,61 @@ +from app.config import QueueNames +from app.notifications.validators import ( + check_service_over_daily_message_limit, + check_sms_content_char_count, + validate_and_format_recipient, +) +from app.notifications.process_notifications import ( + create_content_for_notification, + persist_notification, + send_notification_to_queue, +) +from app.models import ( + KEY_TYPE_NORMAL, + PRIORITY, + SMS_TYPE, +) +from app.dao.services_dao import dao_fetch_service_by_id +from app.dao.templates_dao import dao_get_template_by_id_and_service_id + + +def send_one_off_notification(service_id, post_data): + service = dao_fetch_service_by_id(service_id) + template = dao_get_template_by_id_and_service_id( + template_id=post_data['template_id'], + service_id=service_id + ) + + personalisation = post_data.get('personalisation', None) + + if template.template_type == SMS_TYPE: + template_with_content = create_content_for_notification(template, personalisation) + check_sms_content_char_count(template_with_content.content_count) + + check_service_over_daily_message_limit(KEY_TYPE_NORMAL, service) + + validate_and_format_recipient( + send_to=post_data['to'], + key_type=KEY_TYPE_NORMAL, + service=service, + notification_type=template.template_type + ) + + notification = persist_notification( + template_id=template.id, + template_version=template.version, + recipient=post_data['to'], + service=service, + personalisation=personalisation, + notification_type=template.template_type, + api_key_id=None, + key_type=KEY_TYPE_NORMAL + ) + + queue_name = QueueNames.PRIORITY if template.process_type == PRIORITY else None + send_notification_to_queue( + notification=notification, + research_mode=service.research_mode, + queue=queue_name + ) + + return {'id': str(notification.id)} diff --git a/app/v2/notifications/post_notifications.py b/app/v2/notifications/post_notifications.py index 47a40a72e..01e25ea20 100644 --- a/app/v2/notifications/post_notifications.py +++ b/app/v2/notifications/post_notifications.py @@ -1,24 +1,20 @@ from flask import request, jsonify, current_app -from sqlalchemy.orm.exc import NoResultFound from app import api_user, authenticated_service from app.config import QueueNames -from app.dao import templates_dao from app.models import SMS_TYPE, EMAIL_TYPE, PRIORITY from app.notifications.process_notifications import ( - create_content_for_notification, persist_notification, send_notification_to_queue, simulated_recipient, persist_scheduled_notification) from app.notifications.validators import ( - check_template_is_for_notification_type, - check_template_is_active, - check_sms_content_char_count, validate_and_format_recipient, - check_rate_limiting, service_can_schedule_notification) + check_rate_limiting, + service_can_schedule_notification, + validate_template +) from app.schema_validation import validate -from app.v2.errors import BadRequestError from app.v2.notifications import v2_notification_blueprint from app.v2.notifications.notification_schemas import ( post_sms_request, @@ -45,7 +41,12 @@ def post_notification(notification_type): service=authenticated_service, notification_type=notification_type) - template, template_with_content = __validate_template(form, authenticated_service, notification_type) + template, template_with_content = validate_template( + form['template_id'], + form.get('personalisation', {}), + authenticated_service, + notification_type + ) # Do not persist or send notification to the queue if it is a simulated recipient simulated = simulated_recipient(send_to, notification_type) @@ -91,20 +92,3 @@ def post_notification(notification_type): service_id=authenticated_service.id, scheduled_for=scheduled_for) return jsonify(resp), 201 - - -def __validate_template(form, service, notification_type): - try: - template = templates_dao.dao_get_template_by_id_and_service_id(template_id=form['template_id'], - service_id=service.id) - except NoResultFound: - message = 'Template not found' - raise BadRequestError(message=message, - fields=[{'template': message}]) - - check_template_is_for_notification_type(notification_type, template.template_type) - check_template_is_active(template) - template_with_content = create_content_for_notification(template, form.get('personalisation', {})) - if template.template_type == SMS_TYPE: - check_sms_content_char_count(template_with_content.content_count) - return template, template_with_content diff --git a/tests/app/service/test_rest.py b/tests/app/service/test_rest.py index 2fa21eb51..c9bba479d 100644 --- a/tests/app/service/test_rest.py +++ b/tests/app/service/test_rest.py @@ -12,13 +12,14 @@ from app import encryption from app.dao.users_dao import save_model_user from app.dao.services_dao import dao_remove_user_from_service from app.models import ( - Organisation, Rate, Service, ServicePermission, User, + User, Organisation, Rate, Service, ServicePermission, + DVLA_ORG_LAND_REGISTRY, KEY_TYPE_NORMAL, KEY_TYPE_TEAM, KEY_TYPE_TEST, EMAIL_TYPE, SMS_TYPE, LETTER_TYPE, INTERNATIONAL_SMS_TYPE, INBOUND_SMS_TYPE, - DVLA_ORG_LAND_REGISTRY ) + from tests import create_authorization_header -from tests.app.db import create_template, create_service_inbound_api +from tests.app.db import create_template, create_service_inbound_api, create_user from tests.app.conftest import ( sample_service as create_service, sample_user_service_permission as create_user_service_permission, @@ -27,8 +28,6 @@ from tests.app.conftest import ( sample_notification_with_job ) -from tests.app.db import create_user - def test_get_service_list(client, service_factory): service_factory.get('one')