From 654e6fc657171ea62e178542ed2d64ec7d99885f Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Thu, 12 Mar 2020 13:53:57 +0000 Subject: [PATCH] New table and endpoints for service contact lists. - Table to store meta data for the emergency contact list for a service. - Endpoint for fetching contact lists for service - Endpoint for saving contact list for service. The list will be stored in S3. The service will then be able to send emergency announcements to staff. --- app/dao/service_contact_list_dao.py | 16 ++++ app/models.py | 29 +++++++ app/service/rest.py | 25 +++++- app/service/service_contact_list_schema.py | 16 ++++ .../versions/0318_service_contact_list.py | 42 ++++++++++ .../app/dao/test_service_contact_list_dao.py | 10 +++ tests/app/db.py | 26 +++++- .../service/test_service_contact_list_rest.py | 81 +++++++++++++++++++ 8 files changed, 243 insertions(+), 2 deletions(-) create mode 100644 app/dao/service_contact_list_dao.py create mode 100644 app/service/service_contact_list_schema.py create mode 100644 migrations/versions/0318_service_contact_list.py create mode 100644 tests/app/dao/test_service_contact_list_dao.py create mode 100644 tests/app/service/test_service_contact_list_rest.py diff --git a/app/dao/service_contact_list_dao.py b/app/dao/service_contact_list_dao.py new file mode 100644 index 000000000..04dc0148a --- /dev/null +++ b/app/dao/service_contact_list_dao.py @@ -0,0 +1,16 @@ +from app import db +from app.models import ServiceContactList + + +def dao_get_contact_lists(service_id): + contact_lists = ServiceContactList.query.filter_by( + service_id=service_id + ).order_by( + ServiceContactList.created_at.desc() + ) + return contact_lists.all() + + +def save_service_contact_list(service_contact_list): + db.session.add(service_contact_list) + db.session.commit() diff --git a/app/models.py b/app/models.py index 1c6621b57..802567046 100644 --- a/app/models.py +++ b/app/models.py @@ -1214,6 +1214,7 @@ class Job(db.Model): db.String(255), db.ForeignKey('job_status.name'), index=True, nullable=False, default='pending' ) archived = db.Column(db.Boolean, nullable=False, default=False) + contact_list_id = db.Column(UUID(as_uuid=True), db.ForeignKey('service_contact_list.id'), nullable=True) VERIFY_CODE_TYPES = [EMAIL_TYPE, SMS_TYPE] @@ -2119,3 +2120,31 @@ class ReturnedLetter(db.Model): notification_id = db.Column(UUID(as_uuid=True), unique=True, nullable=False) created_at = db.Column(db.DateTime, nullable=False) updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow) + + +class ServiceContactList(db.Model): + __tablename__ = 'service_contact_list' + + id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + original_file_name = db.Column(db.String, nullable=False) + row_count = db.Column(db.Integer, nullable=False) + template_type = db.Column(template_types, nullable=False) + service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), unique=False, index=True, nullable=False) + service = db.relationship(Service, backref=db.backref('contact_list')) + created_by = db.relationship('User') + created_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), index=True, nullable=True) + created_at = db.Column(db.DateTime, nullable=False) + updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow) + + def serialize(self): + created_at_in_bst = convert_utc_to_bst(self.created_at) + contact_list = { + "id": str(self.id), + "original_file_name": self.original_file_name, + "row_count": self.row_count, + "template_type": self.template_type, + "service_id": str(self.service_id), + "created_by": self.created_by.name, + "created_at": created_at_in_bst.strftime("%Y-%m-%d %H:%M:%S"), + } + return contact_list diff --git a/app/service/rest.py b/app/service/rest.py index b542e2fd0..f60f5f6dc 100644 --- a/app/service/rest.py +++ b/app/service/rest.py @@ -36,6 +36,7 @@ from app.dao.returned_letters_dao import ( fetch_returned_letter_summary, fetch_returned_letters, ) +from app.dao.service_contact_list_dao import dao_get_contact_lists, save_service_contact_list from app.dao.service_data_retention_dao import ( fetch_service_data_retention, fetch_service_data_retention_by_id, @@ -95,12 +96,14 @@ from app.errors import ( from app.letters.utils import letter_print_day from app.models import ( KEY_TYPE_NORMAL, LETTER_TYPE, NOTIFICATION_CANCELLED, Permission, Service, - EmailBranding, LetterBranding + EmailBranding, LetterBranding, + ServiceContactList ) from app.notifications.process_notifications import persist_notification, send_notification_to_queue from app.schema_validation import validate from app.service import statistics from app.service.send_pdf_letter_schema import send_pdf_letter_request +from app.service.service_contact_list_schema import create_service_contact_list_schema from app.service.service_data_retention_schema import ( add_service_data_retention_request, update_service_data_retention_request @@ -1011,3 +1014,23 @@ def get_returned_letters(service_id): } for x in results] return jsonify(sorted(json_results, key=lambda i: i['created_at'], reverse=True)) + + +@service_blueprint.route('//contact-list', methods=['GET']) +def get_contact_list(service_id): + contact_lists = dao_get_contact_lists(service_id) + + return jsonify([x.serialize() for x in contact_lists]) + + +@service_blueprint.route('//contact-list', methods=['POST']) +def create_contact_list(service_id): + service_contact_list = validate(request.get_json(), create_service_contact_list_schema) + service_contact_list['created_by_id'] = service_contact_list.pop('created_by') + service_contact_list['created_at'] = datetime.utcnow() + service_contact_list['service_id'] = str(service_id) + list_to_save = ServiceContactList(**service_contact_list) + + save_service_contact_list(list_to_save) + + return jsonify(list_to_save.serialize()), 201 diff --git a/app/service/service_contact_list_schema.py b/app/service/service_contact_list_schema.py new file mode 100644 index 000000000..2549292c6 --- /dev/null +++ b/app/service/service_contact_list_schema.py @@ -0,0 +1,16 @@ +from app.schema_validation.definitions import uuid + +create_service_contact_list_schema = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "POST create service contact list schema", + "type": "object", + "title": "Create service contact list", + "properties": { + "id": uuid, + "original_file_name": {"type": "string"}, + "row_count": {"type": "integer"}, + "template_type": {"enum": ['email', 'sms']}, + "created_by": uuid + }, + "required": ["id", "original_file_name", "row_count", "template_type", "created_by"] +} diff --git a/migrations/versions/0318_service_contact_list.py b/migrations/versions/0318_service_contact_list.py new file mode 100644 index 000000000..3c7efae05 --- /dev/null +++ b/migrations/versions/0318_service_contact_list.py @@ -0,0 +1,42 @@ +""" + +Revision ID: 0318_service_contact_list +Revises: 0317_uploads_for_all +Create Date: 2020-03-12 15:44:30.784031 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +revision = '0318_service_contact_list' +down_revision = '0317_uploads_for_all' + + +def upgrade(): + op.create_table( + 'service_contact_list', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('original_file_name', sa.String(), nullable=False), + sa.Column('row_count', sa.Integer(), nullable=False), + sa.Column('template_type', postgresql.ENUM(name='template_type', create_type=False), nullable=False), + sa.Column('service_id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('created_by_id', postgresql.UUID(as_uuid=True), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(['created_by_id'], ['users.id'], ), + sa.ForeignKeyConstraint(['service_id'], ['services.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_service_contact_list_created_by_id'), 'service_contact_list', ['created_by_id'], unique=False) + op.create_index(op.f('ix_service_contact_list_service_id'), 'service_contact_list', ['service_id'], unique=False) + op.add_column('jobs', sa.Column('contact_list_id', postgresql.UUID(as_uuid=True), nullable=True)) + op.create_foreign_key('jobs_contact_list_id_fkey', 'jobs', 'service_contact_list', ['contact_list_id'], ['id']) + + +def downgrade(): + op.drop_constraint('jobs_contact_list_id_fkey', 'jobs', type_='foreignkey') + op.drop_column('jobs', 'contact_list_id') + op.drop_index(op.f('ix_service_contact_list_service_id'), table_name='service_contact_list') + op.drop_index(op.f('ix_service_contact_list_created_by_id'), table_name='service_contact_list') + op.drop_table('service_contact_list') diff --git a/tests/app/dao/test_service_contact_list_dao.py b/tests/app/dao/test_service_contact_list_dao.py new file mode 100644 index 000000000..df09fe0a4 --- /dev/null +++ b/tests/app/dao/test_service_contact_list_dao.py @@ -0,0 +1,10 @@ +from app.dao.service_contact_list_dao import dao_get_contact_lists +from tests.app.db import create_service_contact_list + + +def test_dao_get_contact_lists(notify_db_session): + contact_list = create_service_contact_list() + fetched_list = dao_get_contact_lists(contact_list.service_id) + + assert len(fetched_list) == 1 + assert fetched_list[0] == contact_list diff --git a/tests/app/db.py b/tests/app/db.py index 0c847b920..1b870924b 100644 --- a/tests/app/db.py +++ b/tests/app/db.py @@ -60,7 +60,8 @@ from app.models import ( LetterBranding, Domain, NotificationHistory, - ReturnedLetter + ReturnedLetter, + ServiceContactList ) @@ -965,3 +966,26 @@ def create_returned_letter(service=None, reported_at=None, notification_id=None) db.session.add(returned_letter) db.session.commit() return returned_letter + + +def create_service_contact_list( + service=None, + original_file_name='EmergencyContactList.xls', + row_count=100, + template_type='email', + created_by_id=None +): + if not service: + service = create_service(service_name='service for contact list', user=create_user()) + + contact_list = ServiceContactList( + service_id=service.id, + original_file_name=original_file_name, + row_count=row_count, + template_type=template_type, + created_by_id=created_by_id or service.users[0].id, + created_at=datetime.utcnow(), + ) + db.session.add(contact_list) + db.session.commit() + return contact_list diff --git a/tests/app/service/test_service_contact_list_rest.py b/tests/app/service/test_service_contact_list_rest.py new file mode 100644 index 000000000..c7e741e77 --- /dev/null +++ b/tests/app/service/test_service_contact_list_rest.py @@ -0,0 +1,81 @@ +import uuid + +from app.models import ServiceContactList +from tests.app.db import create_service_contact_list, create_service + + +def test_create_service_contact_list(sample_service, admin_request): + data = { + "id": str(uuid.uuid4()), + "row_count": 100, + "original_file_name": "staff_emergency_list.xls", + "template_type": 'email', + "created_by": str(sample_service.users[0].id) + } + + response = admin_request.post( + 'service.create_contact_list', + _data=data, + service_id=sample_service.id, + _expected_status=201 + ) + + assert response['id'] == data['id'] + assert response['original_file_name'] == 'staff_emergency_list.xls' + assert response['row_count'] == 100 + assert response['template_type'] == 'email' + assert response['service_id'] == str(sample_service.id) + assert response['created_at'] + + db_results = ServiceContactList.query.all() + assert len(db_results) == 1 + assert str(db_results[0].id) == data['id'] + + +def test_create_service_contact_list_cannot_save_type_letter(sample_service, admin_request): + data = { + "id": str(uuid.uuid4()), + "row_count": 100, + "original_file_name": "staff_emergency_list.xls", + "template_type": 'letter', + "created_by": str(sample_service.users[0].id) + } + + response = admin_request.post( + 'service.create_contact_list', + _data=data, + service_id=sample_service.id, + _expected_status=400 + ) + assert response['errors'][0]['message'] == "template_type letter is not one of [email, sms]" + + +def test_get_contact_list(admin_request, notify_db_session): + contact_list = create_service_contact_list() + + response = admin_request.get( + 'service.get_contact_list', + service_id=contact_list.service_id + ) + + assert len(response) == 1 + assert response[0] == contact_list.serialize() + + +def test_get_contact_list_returns_for_service(admin_request, notify_db_session): + service_1 = create_service(service_name='Service under test') + service_2 = create_service(service_name='Service should return results') + + expected_list_1 = create_service_contact_list(service=service_1) + expected_list_2 = create_service_contact_list(service=service_1) + # not included in results + create_service_contact_list(service=service_2) + + response = admin_request.get( + 'service.get_contact_list', + service_id=service_1.id + ) + + assert len(response) == 2 + assert response[0] == expected_list_2.serialize() + assert response[1] == expected_list_1.serialize()