mirror of
https://github.com/GSA/notifications-api.git
synced 2026-02-01 23:55:58 -05:00
Merge pull request #91 from alphagov/send-email-from-templates
Send Email via the API
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import os
|
||||
|
||||
import re
|
||||
from flask import request, url_for
|
||||
from flask import Flask, _request_ctx_stack
|
||||
from flask.ext.sqlalchemy import SQLAlchemy
|
||||
@@ -92,3 +92,10 @@ def get_db_version():
|
||||
return full_name.split('_')[0]
|
||||
except:
|
||||
return 'n/a'
|
||||
|
||||
|
||||
def email_safe(string):
|
||||
return "".join([
|
||||
character.lower() if character.isalnum() or character == "." else ""
|
||||
for character in re.sub("\s+", ".", string.strip())
|
||||
])
|
||||
|
||||
@@ -33,6 +33,36 @@ def send_sms(service_id, notification_id, encrypted_notification):
|
||||
current_app.logger.debug(e)
|
||||
|
||||
|
||||
@notify_celery.task(name="send-email")
|
||||
def send_email(service_id, notification_id, subject, from_address, encrypted_notification):
|
||||
notification = encryption.decrypt(encrypted_notification)
|
||||
template = get_model_templates(notification['template'])
|
||||
|
||||
try:
|
||||
notification_db_object = Notification(
|
||||
id=notification_id,
|
||||
template_id=notification['template'],
|
||||
to=notification['to'],
|
||||
service_id=service_id,
|
||||
status='sent'
|
||||
)
|
||||
save_notification(notification_db_object)
|
||||
|
||||
try:
|
||||
aws_ses_client.send_email(
|
||||
from_address,
|
||||
notification['to'],
|
||||
subject,
|
||||
template.content
|
||||
)
|
||||
except AwsSesClientException as e:
|
||||
current_app.logger.debug(e)
|
||||
save_notification(notification_db_object, {"status": "failed"})
|
||||
|
||||
except SQLAlchemyError as e:
|
||||
current_app.logger.debug(e)
|
||||
|
||||
|
||||
@notify_celery.task(name='send-sms-code')
|
||||
def send_sms_code(encrypted_verification):
|
||||
verification_message = encryption.decrypt(encrypted_verification)
|
||||
|
||||
@@ -3,23 +3,30 @@ import uuid
|
||||
from flask import (
|
||||
Blueprint,
|
||||
jsonify,
|
||||
request
|
||||
request,
|
||||
current_app
|
||||
)
|
||||
|
||||
from app import api_user, encryption
|
||||
from app.aws_sqs import add_notification_to_queue
|
||||
from app.dao import (templates_dao, notifications_dao)
|
||||
from app.dao import (
|
||||
templates_dao,
|
||||
users_dao,
|
||||
services_dao,
|
||||
notifications_dao
|
||||
)
|
||||
from app.schemas import (
|
||||
email_notification_schema,
|
||||
sms_template_notification_schema,
|
||||
notification_status_schema
|
||||
)
|
||||
from app.celery.tasks import send_sms
|
||||
from app.celery.tasks import send_sms, send_email
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
|
||||
notifications = Blueprint('notifications', __name__)
|
||||
|
||||
from app.errors import register_errors
|
||||
|
||||
register_errors(notifications)
|
||||
|
||||
|
||||
@@ -42,11 +49,20 @@ def create_sms_notification():
|
||||
if errors:
|
||||
return jsonify(result="error", message=errors), 400
|
||||
|
||||
try:
|
||||
templates_dao.get_model_templates(template_id=notification['template'], service_id=api_user['client'])
|
||||
except NoResultFound:
|
||||
template = templates_dao.dao_get_template_by_id_and_service_id(
|
||||
template_id=notification['template'],
|
||||
service_id=api_user['client']
|
||||
)
|
||||
|
||||
if not template:
|
||||
return jsonify(result="error", message={'template': ['Template not found']}), 400
|
||||
|
||||
service = services_dao.dao_fetch_service_by_id(api_user['client'])
|
||||
|
||||
if service.restricted:
|
||||
if notification['to'] not in [user.email_address for user in service.users]:
|
||||
return jsonify(result="error", message={'to': ['Invalid phone number for restricted service']}), 400
|
||||
|
||||
notification_id = create_notification_id()
|
||||
|
||||
send_sms.apply_async((
|
||||
@@ -59,17 +75,38 @@ def create_sms_notification():
|
||||
|
||||
@notifications.route('/email', methods=['POST'])
|
||||
def create_email_notification():
|
||||
resp_json = request.get_json()
|
||||
notification, errors = email_notification_schema.load(resp_json)
|
||||
notification, errors = email_notification_schema.load(request.get_json())
|
||||
if errors:
|
||||
return jsonify(result="error", message=errors), 400
|
||||
notification_id = add_notification_to_queue(api_user['client'], "admin", 'email', notification)
|
||||
|
||||
template = templates_dao.dao_get_template_by_id_and_service_id(
|
||||
template_id=notification['template'],
|
||||
service_id=api_user['client']
|
||||
)
|
||||
|
||||
if not template:
|
||||
return jsonify(result="error", message={'template': ['Template not found']}), 400
|
||||
|
||||
service = services_dao.dao_fetch_service_by_id(api_user['client'])
|
||||
|
||||
if service.restricted:
|
||||
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_notification_id()
|
||||
|
||||
send_email.apply_async((
|
||||
api_user['client'],
|
||||
notification_id,
|
||||
template.subject,
|
||||
"{}@{}".format(service.email_from, current_app.config['NOTIFY_EMAIL_DOMAIN']),
|
||||
encryption.encrypt(notification)),
|
||||
queue='email')
|
||||
return jsonify({'notification_id': notification_id}), 201
|
||||
|
||||
|
||||
@notifications.route('/sms/service/<service_id>', methods=['POST'])
|
||||
def create_sms_for_service(service_id):
|
||||
|
||||
resp_json = request.get_json()
|
||||
|
||||
notification, errors = sms_template_notification_schema.load(resp_json)
|
||||
|
||||
@@ -7,6 +7,8 @@ from marshmallow import (post_load, ValidationError, validates, validates_schema
|
||||
|
||||
mobile_regex = re.compile("^\\+44[\\d]{10}$")
|
||||
|
||||
email_regex = re.compile("(^[^@^\\s]+@[^@^\\.^\\s]+(\\.[^@^\\.^\\s]*)*\.(.+))")
|
||||
|
||||
|
||||
# TODO I think marshmallow provides a better integration and error handling.
|
||||
# Would be better to replace functionality in dao with the marshmallow supported
|
||||
@@ -78,9 +80,6 @@ class RequestVerifyCodeSchema(ma.Schema):
|
||||
to = fields.Str(required=False)
|
||||
|
||||
|
||||
# TODO main purpose to be added later
|
||||
# when processing templates, template will be
|
||||
# common for all notifications.
|
||||
class NotificationSchema(ma.Schema):
|
||||
pass
|
||||
|
||||
@@ -94,50 +93,25 @@ class SmsNotificationSchema(NotificationSchema):
|
||||
raise ValidationError('Invalid phone number, must be of format +441234123123')
|
||||
|
||||
|
||||
class EmailNotificationSchema(NotificationSchema):
|
||||
to = fields.Str(required=True)
|
||||
template = fields.Int(required=True)
|
||||
|
||||
@validates('to')
|
||||
def validate_to(self, value):
|
||||
if not email_regex.match(value):
|
||||
raise ValidationError('Invalid email')
|
||||
|
||||
|
||||
class SmsTemplateNotificationSchema(SmsNotificationSchema):
|
||||
template = fields.Int(required=True)
|
||||
job = fields.String()
|
||||
|
||||
@validates_schema
|
||||
def validate_schema(self, data):
|
||||
"""
|
||||
Validate the to field is valid for this notification
|
||||
"""
|
||||
from app import api_user
|
||||
template_id = data.get('template', None)
|
||||
template = models.Template.query.filter_by(id=template_id).first()
|
||||
if template:
|
||||
service = template.service
|
||||
# Validate restricted service,
|
||||
# restricted services can only send to one of its users.
|
||||
if service.restricted:
|
||||
valid = False
|
||||
for usr in service.users:
|
||||
if data['to'] == usr.mobile_number:
|
||||
valid = True
|
||||
break
|
||||
if not valid:
|
||||
raise ValidationError('Invalid phone number for restricted service', 'restricted')
|
||||
# Assert the template is valid for the service which made the request.
|
||||
service = api_user['client']
|
||||
admin_users = [current_app.config.get('ADMIN_CLIENT_USER_NAME'),
|
||||
current_app.config.get('DELIVERY_CLIENT_USER_NAME')]
|
||||
if (service not in admin_users and
|
||||
template.service != models.Service.query.filter_by(id=service).first()):
|
||||
raise ValidationError('Invalid template', 'restricted')
|
||||
|
||||
|
||||
class SmsAdminNotificationSchema(SmsNotificationSchema):
|
||||
content = fields.Str(required=True)
|
||||
|
||||
|
||||
class EmailNotificationSchema(NotificationSchema):
|
||||
to_address = fields.Str(load_from="to", dump_to='to', required=True)
|
||||
from_address = fields.Str(load_from="from", dump_to='from', required=True)
|
||||
subject = fields.Str(required=True)
|
||||
body = fields.Str(load_from="message", dump_to='message', required=True)
|
||||
|
||||
|
||||
class NotificationStatusSchema(BaseSchema):
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
from flask import (
|
||||
@@ -30,6 +29,7 @@ from app.schemas import (
|
||||
service_schema,
|
||||
api_keys_schema
|
||||
)
|
||||
from app import email_safe
|
||||
|
||||
from flask import Blueprint
|
||||
|
||||
@@ -76,7 +76,7 @@ def create_service():
|
||||
|
||||
data.pop('user_id', None)
|
||||
if 'name' in data:
|
||||
data['email_from'] = _email_safe(data.get('name', None))
|
||||
data['email_from'] = email_safe(data.get('name', None))
|
||||
|
||||
valid_service, errors = service_schema.load(request.get_json())
|
||||
|
||||
@@ -87,13 +87,6 @@ def create_service():
|
||||
return jsonify(data=service_schema.dump(valid_service).data), 201
|
||||
|
||||
|
||||
def _email_safe(string):
|
||||
return "".join([
|
||||
character.lower() if character.isalnum() or character == "." else ""
|
||||
for character in re.sub("\s+", ".", string.strip())
|
||||
])
|
||||
|
||||
|
||||
@service.route('/<service_id>', methods=['POST'])
|
||||
def update_service(service_id):
|
||||
fetched_service = dao_fetch_service_by_id(service_id)
|
||||
|
||||
Reference in New Issue
Block a user