mirror of
https://github.com/GSA/notifications-api.git
synced 2026-02-01 07:35:34 -05:00
Merge branch 'master' into revert-queue-config
This commit is contained in:
@@ -2,6 +2,7 @@ from app import create_uuid
|
||||
from app import notify_celery, encryption, firetext_client, aws_ses_client
|
||||
from app.clients.email.aws_ses import AwsSesClientException
|
||||
from app.clients.sms.firetext import FiretextClientException
|
||||
from app.dao.services_dao import dao_fetch_service_by_id
|
||||
from app.dao.templates_dao import dao_get_template_by_id
|
||||
from app.dao.notifications_dao import dao_create_notification, dao_update_notification
|
||||
from app.dao.jobs_dao import dao_update_job, dao_get_job_by_id
|
||||
@@ -9,8 +10,9 @@ from app.models import Notification
|
||||
from flask import current_app
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from app.aws import s3
|
||||
from app.csv import get_recipient_from_csv
|
||||
from datetime import datetime
|
||||
from utils.template import Template
|
||||
from utils.process_csv import get_rows_from_csv, get_recipient_from_row, first_column_heading
|
||||
|
||||
|
||||
@notify_celery.task(name="process-job")
|
||||
@@ -21,13 +23,14 @@ def process_job(job_id):
|
||||
dao_update_job(job)
|
||||
|
||||
file = s3.get_job_from_s3(job.bucket_name, job_id)
|
||||
recipients = get_recipient_from_csv(file)
|
||||
|
||||
for recipient in recipients:
|
||||
for row in get_rows_from_csv(file):
|
||||
|
||||
encrypted = encryption.encrypt({
|
||||
'template': job.template_id,
|
||||
'job': str(job.id),
|
||||
'to': recipient
|
||||
'to': get_recipient_from_row(row, job.template.template_type),
|
||||
'personalisation': row
|
||||
})
|
||||
|
||||
if job.template.template_type == 'sms':
|
||||
@@ -62,7 +65,13 @@ def process_job(job_id):
|
||||
@notify_celery.task(name="send-sms")
|
||||
def send_sms(service_id, notification_id, encrypted_notification, created_at):
|
||||
notification = encryption.decrypt(encrypted_notification)
|
||||
template = dao_get_template_by_id(notification['template'])
|
||||
service = dao_fetch_service_by_id(service_id)
|
||||
template = Template(
|
||||
dao_get_template_by_id(notification['template']).__dict__,
|
||||
values=notification.get('personalisation', {}),
|
||||
prefix=service.name,
|
||||
drop_values={first_column_heading['sms']}
|
||||
)
|
||||
|
||||
client = firetext_client
|
||||
|
||||
@@ -78,14 +87,16 @@ def send_sms(service_id, notification_id, encrypted_notification, created_at):
|
||||
created_at=created_at,
|
||||
sent_at=sent_at,
|
||||
sent_by=client.get_name()
|
||||
|
||||
)
|
||||
dao_create_notification(notification_db_object)
|
||||
|
||||
try:
|
||||
client.send_sms(notification['to'], template.content)
|
||||
client.send_sms(
|
||||
notification['to'],
|
||||
template.replaced
|
||||
)
|
||||
except FiretextClientException as e:
|
||||
current_app.logger.debug(e)
|
||||
current_app.logger.exception(e)
|
||||
notification_db_object.status = 'failed'
|
||||
dao_update_notification(notification_db_object)
|
||||
|
||||
@@ -99,7 +110,11 @@ def send_sms(service_id, notification_id, encrypted_notification, created_at):
|
||||
@notify_celery.task(name="send-email")
|
||||
def send_email(service_id, notification_id, subject, from_address, encrypted_notification, created_at):
|
||||
notification = encryption.decrypt(encrypted_notification)
|
||||
template = dao_get_template_by_id(notification['template'])
|
||||
template = Template(
|
||||
dao_get_template_by_id(notification['template']).__dict__,
|
||||
values=notification.get('personalisation', {}),
|
||||
drop_values={first_column_heading['email']}
|
||||
)
|
||||
|
||||
client = aws_ses_client
|
||||
|
||||
@@ -123,7 +138,7 @@ def send_email(service_id, notification_id, subject, from_address, encrypted_not
|
||||
from_address,
|
||||
notification['to'],
|
||||
subject,
|
||||
template.content
|
||||
template.replaced
|
||||
)
|
||||
except AwsSesClientException as e:
|
||||
current_app.logger.debug(e)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import boto3
|
||||
|
||||
from flask import current_app
|
||||
from monotonic import monotonic
|
||||
from app.clients.email import (EmailClientException, EmailClient)
|
||||
|
||||
|
||||
@@ -34,6 +35,7 @@ class AwsSesClient(EmailClient):
|
||||
elif reply_to_addresses is None:
|
||||
reply_to_addresses = []
|
||||
|
||||
start_time = monotonic()
|
||||
response = self._client.send_email(
|
||||
Source=source,
|
||||
Destination={
|
||||
@@ -50,6 +52,8 @@ class AwsSesClient(EmailClient):
|
||||
'Data': body}}
|
||||
},
|
||||
ReplyToAddresses=reply_to_addresses)
|
||||
elapsed_time = monotonic() - start_time
|
||||
current_app.logger.info("AWS SES request finished in {}".format(elapsed_time))
|
||||
return response['MessageId']
|
||||
except Exception as e:
|
||||
# TODO logging exceptions
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import logging
|
||||
from monotonic import monotonic
|
||||
from app.clients.sms import (
|
||||
SmsClient,
|
||||
SmsClientException
|
||||
)
|
||||
from flask import current_app
|
||||
from requests import request, RequestException, HTTPError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -36,6 +38,7 @@ class FiretextClient(SmsClient):
|
||||
}
|
||||
|
||||
try:
|
||||
start_time = monotonic()
|
||||
response = request(
|
||||
"POST",
|
||||
"https://www.firetext.co.uk/api/sendsms",
|
||||
@@ -53,4 +56,7 @@ class FiretextClient(SmsClient):
|
||||
)
|
||||
)
|
||||
raise api_error
|
||||
finally:
|
||||
elapsed_time = monotonic() - start_time
|
||||
current_app.logger.info("Firetext request finished in {}".format(elapsed_time))
|
||||
return response
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import logging
|
||||
from monotonic import monotonic
|
||||
from app.clients.sms import (
|
||||
SmsClient, SmsClientException)
|
||||
from twilio.rest import TwilioRestClient
|
||||
from twilio import TwilioRestException
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
from flask import current_app
|
||||
|
||||
|
||||
class TwilioClientException(SmsClientException):
|
||||
@@ -28,6 +26,7 @@ class TwilioClient(SmsClient):
|
||||
return self.name
|
||||
|
||||
def send_sms(self, to, content):
|
||||
start_time = monotonic()
|
||||
try:
|
||||
response = self.client.messages.create(
|
||||
body=content,
|
||||
@@ -36,8 +35,11 @@ class TwilioClient(SmsClient):
|
||||
)
|
||||
return response.sid
|
||||
except TwilioRestException as e:
|
||||
logger.exception(e)
|
||||
current_app.logger.exception(e)
|
||||
raise TwilioClientException(e)
|
||||
finally:
|
||||
elapsed_time = monotonic() - start_time
|
||||
current_app.logger.info("Twilio request finished in {}".format(elapsed_time))
|
||||
|
||||
def status(self, message_id):
|
||||
try:
|
||||
@@ -46,5 +48,5 @@ class TwilioClient(SmsClient):
|
||||
return response.status
|
||||
return None
|
||||
except TwilioRestException as e:
|
||||
logger.exception(e)
|
||||
current_app.logger.exception(e)
|
||||
raise TwilioClientException(e)
|
||||
|
||||
12
app/csv.py
12
app/csv.py
@@ -1,12 +0,0 @@
|
||||
import csv
|
||||
|
||||
|
||||
def get_recipient_from_csv(file_data):
|
||||
numbers = []
|
||||
reader = csv.DictReader(
|
||||
file_data.splitlines(),
|
||||
lineterminator='\n',
|
||||
quoting=csv.QUOTE_NONE)
|
||||
for i, row in enumerate(reader):
|
||||
numbers.append(row['to'].replace(' ', ''))
|
||||
return numbers
|
||||
@@ -8,6 +8,8 @@ from flask import (
|
||||
url_for
|
||||
)
|
||||
|
||||
from utils.template import Template, NeededByTemplateError, NoPlaceholderForDataError
|
||||
|
||||
from app import api_user, encryption, create_uuid
|
||||
from app.authentication.auth import require_admin
|
||||
from app.dao import (
|
||||
@@ -29,9 +31,6 @@ from app.errors import register_errors
|
||||
|
||||
register_errors(notifications)
|
||||
|
||||
SMS_NOTIFICATION = 'sms'
|
||||
EMAIL_NOTIFICATION = 'email'
|
||||
|
||||
|
||||
@notifications.route('/notifications/<string:notification_id>', methods=['GET'])
|
||||
def get_notifications(notification_id):
|
||||
@@ -122,24 +121,17 @@ def pagination_links(pagination, endpoint, args):
|
||||
return links
|
||||
|
||||
|
||||
@notifications.route('/notifications/sms', methods=['POST'])
|
||||
def create_sms_notification():
|
||||
return send_notification(notification_type=SMS_NOTIFICATION)
|
||||
|
||||
|
||||
@notifications.route('/notifications/email', methods=['POST'])
|
||||
def create_email_notification():
|
||||
return send_notification(notification_type=EMAIL_NOTIFICATION)
|
||||
|
||||
|
||||
@notifications.route('/notifications/<string:notification_type>', methods=['POST'])
|
||||
def send_notification(notification_type):
|
||||
assert notification_type
|
||||
if notification_type not in ['sms', 'email']:
|
||||
assert False
|
||||
|
||||
service_id = api_user['client']
|
||||
|
||||
schema = sms_template_notification_schema if notification_type is SMS_NOTIFICATION else email_notification_schema
|
||||
notification, errors = (
|
||||
sms_template_notification_schema if notification_type == 'sms' else email_notification_schema
|
||||
).load(request.get_json())
|
||||
|
||||
notification, errors = schema.load(request.get_json())
|
||||
if errors:
|
||||
return jsonify(result="error", message=errors), 400
|
||||
|
||||
@@ -147,7 +139,6 @@ def send_notification(notification_type):
|
||||
template_id=notification['template'],
|
||||
service_id=service_id
|
||||
)
|
||||
|
||||
if not template:
|
||||
return jsonify(
|
||||
result="error",
|
||||
@@ -156,34 +147,49 @@ def send_notification(notification_type):
|
||||
}
|
||||
), 404
|
||||
|
||||
template_object = Template(template.__dict__, notification.get('personalisation', {}))
|
||||
if template_object.missing_data:
|
||||
return jsonify(
|
||||
result="error",
|
||||
message={
|
||||
'template': ['Missing personalisation: {}'.format(
|
||||
", ".join(template_object.missing_data)
|
||||
)]
|
||||
}
|
||||
), 400
|
||||
if template_object.additional_data:
|
||||
return jsonify(
|
||||
result="error",
|
||||
message={
|
||||
'template': ['Personalisation not needed for template: {}'.format(
|
||||
", ".join(template_object.additional_data)
|
||||
)]
|
||||
}
|
||||
), 400
|
||||
|
||||
service = services_dao.dao_fetch_service_by_id(api_user['client'])
|
||||
|
||||
if service.restricted:
|
||||
if notification_type is SMS_NOTIFICATION:
|
||||
if notification['to'] not in [user.mobile_number for user in service.users]:
|
||||
return jsonify(
|
||||
result="error", message={'to': ['Invalid phone number for restricted service']}), 400
|
||||
else:
|
||||
if notification['to'] not in [user.email_address for user in service.users]:
|
||||
return jsonify(
|
||||
result="error", message={'to': ['Email address not permitted for restricted service']}), 400
|
||||
|
||||
notification_id = create_uuid()
|
||||
|
||||
if notification_type is SMS_NOTIFICATION:
|
||||
if notification_type == 'sms':
|
||||
if service.restricted and notification['to'] not in [user.mobile_number for user in service.users]:
|
||||
return jsonify(
|
||||
result="error", message={'to': ['Invalid phone number for restricted service']}), 400
|
||||
send_sms.apply_async((
|
||||
service_id,
|
||||
notification_id,
|
||||
encryption.encrypt(notification),
|
||||
str(datetime.utcnow())),
|
||||
queue='sms')
|
||||
str(datetime.utcnow())
|
||||
), queue='sms')
|
||||
else:
|
||||
if service.restricted and notification['to'] not in [user.email_address for user in service.users]:
|
||||
return jsonify(
|
||||
result="error", message={'to': ['Email address not permitted for restricted service']}), 400
|
||||
send_email.apply_async((
|
||||
service_id,
|
||||
notification_id,
|
||||
template.subject,
|
||||
"{}@{}".format(service.email_from, current_app.config['NOTIFY_EMAIL_DOMAIN']),
|
||||
encryption.encrypt(notification),
|
||||
str(datetime.utcnow())),
|
||||
queue='email')
|
||||
str(datetime.utcnow())
|
||||
), queue='email')
|
||||
return jsonify({'notification_id': notification_id}), 201
|
||||
|
||||
@@ -118,6 +118,7 @@ class RequestVerifyCodeSchema(ma.Schema):
|
||||
|
||||
|
||||
class NotificationSchema(ma.Schema):
|
||||
personalisation = fields.Dict(required=False)
|
||||
pass
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user