Merge branch 'master' into split-sms-and-retry

This commit is contained in:
Martyn Inglis
2016-06-13 11:39:13 +01:00
24 changed files with 352 additions and 87 deletions

View File

@@ -19,7 +19,7 @@ def send_sms_response(provider, reference, to):
body = mmg_callback(reference, to)
headers = {"Content-type": "application/json"}
else:
headers = {"Content-type": "text/plain"}
headers = {"Content-type": "application/x-www-form-urlencoded"}
body = firetext_callback(reference, to)
make_request('sms', provider, body, headers)
@@ -92,7 +92,12 @@ def firetext_callback(notification_id, to):
status = "1"
else:
status = "0"
return 'mobile={}&status={}&time=2016-03-10 14:17:00&reference={}'.format(to, status, notification_id)
return {
'mobile': to,
'status': status,
'time': '2016-03-10 14:17:00',
'reference': notification_id
}
def ses_notification_callback(reference):

View File

@@ -1,11 +1,12 @@
import itertools
from datetime import datetime
from datetime import (datetime, timedelta)
from flask import current_app
from monotonic import monotonic
from sqlalchemy.exc import SQLAlchemyError
from app import clients, statsd_client
from app.clients import STATISTICS_FAILURE
from app.clients.email import EmailClientException
from app.clients.sms import SmsClientException
from app.dao.services_dao import dao_fetch_service_by_id
@@ -40,7 +41,9 @@ from app.dao.notifications_dao import (
dao_update_notification,
delete_notifications_created_more_than_a_week_ago,
dao_get_notification_statistics_for_service_and_day,
update_provider_stats
update_provider_stats,
get_notifications,
update_notification_status_by_id
)
from app.dao.jobs_dao import (
@@ -337,20 +340,6 @@ def send_email(service_id, notification_id, from_address, encrypted_notification
current_app.logger.exception(e)
@notify_celery.task(name='send-sms-code')
def send_sms_code(encrypted_verification):
provider = provider_to_use('sms', 'send-sms-code')
verification_message = encryption.decrypt(encrypted_verification)
try:
provider.send_sms(validate_and_format_phone_number(verification_message['to']),
"{} is your Notify authentication code".format(
verification_message['secret_code']),
'send-sms-code')
except SmsClientException as e:
current_app.logger.exception(e)
# TODO: when placeholders in templates work, this will be a real template
def invitation_template(user_name, service_name, url, expiry_date):
from string import Template
@@ -489,3 +478,23 @@ def provider_to_use(notification_type, notification_id):
raise Exception("No active {} providers".format(notification_type))
return clients.get_client_by_name_and_type(active_providers_in_order[0].identifier, notification_type)
@notify_celery.task(name='timeout-sending-notifications')
def timeout_notifications():
notifications = get_notifications(filter_dict={'status': 'sending'})
now = datetime.utcnow()
for noti in notifications:
try:
if (now - noti.created_at) > timedelta(
seconds=current_app.config.get('SENDING_NOTIFICATIONS_TIMEOUT_PERIOD')
):
update_notification_status_by_id(noti.id, 'temporary-failure', STATISTICS_FAILURE)
current_app.logger.info((
"Timeout period reached for notification ({})"
", status has been updated.").format(noti.id))
except Exception as e:
current_app.logger.exception(e)
current_app.logger.error((
"Exception raised trying to timeout notification ({})"
", skipping notification update.").format(noti.id))

View File

@@ -60,7 +60,7 @@ class FiretextClient(SmsClient):
super(SmsClient, self).__init__(*args, **kwargs)
self.current_app = current_app
self.api_key = current_app.config.get('FIRETEXT_API_KEY')
self.from_number = current_app.config.get('FIRETEXT_NUMBER')
self.from_number = current_app.config.get('FROM_NUMBER')
self.name = 'firetext'
self.statsd_client = statsd_client
@@ -93,7 +93,7 @@ class FiretextClient(SmsClient):
"POST",
"https://www.firetext.co.uk/api/sendsms",
response.status_code,
firetext_response
firetext_response.items()
)
)
except RequestException as e:

View File

@@ -60,7 +60,7 @@ class MMGClient(SmsClient):
super(SmsClient, self).__init__(*args, **kwargs)
self.current_app = current_app
self.api_key = current_app.config.get('MMG_API_KEY')
self.from_number = current_app.config.get('MMG_FROM_NUMBER')
self.from_number = current_app.config.get('FROM_NUMBER')
self.name = 'mmg'
self.statsd_client = statsd_client
@@ -91,7 +91,7 @@ class MMGClient(SmsClient):
"POST",
"https://www.mmgrp.co.uk/API/json/api.php",
response.status_code,
response.json()
response.json().items()
)
)
except RequestException as e:

View File

@@ -139,6 +139,14 @@ def dao_get_template_statistics_for_service(service_id, limit_days=None):
desc(TemplateStatistics.updated_at)).all()
def dao_get_template_statistics_for_template(template_id):
return TemplateStatistics.query.filter(
TemplateStatistics.template_id == template_id
).order_by(
desc(TemplateStatistics.updated_at)
).all()
@transactional
def dao_create_notification(notification, notification_type):
if notification.job_id:
@@ -332,6 +340,10 @@ def get_notification_by_id(notification_id):
return Notification.query.filter_by(id=notification_id).first()
def get_notifications(filter_dict=None):
return _filter_query(Notification.query, filter_dict=filter_dict)
def get_notifications_for_service(service_id,
filter_dict=None,
page=1,

View File

@@ -32,6 +32,8 @@ class PermissionDAO(DAOClass):
class Meta:
model = Permission
# TODO rework this as last filter wins, whereas what is needed is
# append to filter so that semantics are 'and'
def get_query(self, filter_by_dict=None):
if filter_by_dict is None:
filter_by_dict = MultiDict()
@@ -60,13 +62,14 @@ class PermissionDAO(DAOClass):
self.create_instance(permission, _commit=False)
def remove_user_service_permissions(self, user, service):
query = self.get_query(filter_by_dict={'user': user.id, 'service': service.id})
query = self.Meta.model.query.filter_by(user=user, service=service)
query.delete()
def set_user_service_permission(self, user, service, permissions, _commit=False):
def set_user_service_permission(self, user, service, permissions, _commit=False, replace=False):
try:
query = self.get_query(filter_by_dict={'user': user.id, 'service': service.id})
query.delete()
if replace:
query = self.Meta.model.query.filter_by(user=user, service=service)
query.delete()
for p in permissions:
p.user = user
p.service = service

View File

@@ -176,9 +176,12 @@ def process_firetext_response():
current_app.logger.info(validation_errors)
return jsonify(result='error', message=validation_errors), 400
statsd_client.incr('notifications.callback.firetext.code.{}'.format(request.form.get('code')))
response_code = request.form.get('code')
status = request.form.get('status')
statsd_client.incr('notifications.callback.firetext.code.{}'.format(response_code))
current_app.logger.info('Firetext status: {}, extended error code: {}'.format(status, response_code))
success, errors = process_sms_client_response(status=request.form.get('status'),
success, errors = process_sms_client_response(status=status,
reference=request.form.get('reference'),
client_name=client_name)
if errors:

View File

@@ -160,6 +160,7 @@ class JobSchema(BaseSchema):
class Meta:
model = models.Job
exclude = ('notifications',)
class RequestVerifyCodeSchema(ma.Schema):

View File

@@ -1,9 +1,13 @@
from datetime import (datetime, date)
from datetime import (
datetime,
date
)
from flask import Blueprint
from flask import (
jsonify,
request
request,
abort,
Blueprint
)
from sqlalchemy.orm.exc import NoResultFound
@@ -152,6 +156,8 @@ def add_user_to_service(service_id, user_id):
message='User id: {} already part of service id: {}'.format(user_id, service_id)), 400
permissions, errors = permission_schema.load(request.get_json(), many=True)
if errors:
abort(400, errors)
dao_add_user_to_service(service, user, permissions)
data, errors = service_schema.dump(service)
@@ -174,15 +180,6 @@ def remove_user_from_service(service_id, user_id):
return jsonify({}), 204
def _process_permissions(user, service, permission_groups):
from app.permissions_utils import get_permissions_by_group
permissions = get_permissions_by_group(permission_groups)
for permission in permissions:
permission.user = user
permission.service = service
return permissions
@service.route('/<uuid:service_id>/fragment/aggregate_statistics')
def get_service_provider_aggregate_statistics(service_id):
service = dao_fetch_service_by_id(service_id)

View File

@@ -5,7 +5,10 @@ from flask import (
current_app
)
from app.dao.notifications_dao import dao_get_template_statistics_for_service
from app.dao.notifications_dao import (
dao_get_template_statistics_for_service,
dao_get_template_statistics_for_template
)
from app.schemas import template_statistics_schema
@@ -34,3 +37,12 @@ def get_template_statistics_for_service(service_id):
if errors:
return jsonify(result="error", message=errors), 400
return jsonify(data=data)
@template_statistics.route('/<template_id>')
def get_template_statistics_for_template_id(service_id, template_id):
stats = dao_get_template_statistics_for_template(template_id)
data, errors = template_statistics_schema.dump(stats, many=True)
if errors:
return jsonify(result="error", message=errors), 400
return jsonify(data=data)

View File

@@ -188,7 +188,7 @@ def set_permissions(user_id, service_id):
for p in permissions:
p.user = user
p.service = service
permission_dao.set_user_service_permission(user, service, permissions, _commit=True)
permission_dao.set_user_service_permission(user, service, permissions, _commit=True, replace=True)
return jsonify({}), 204