remove broadcast-related code, except migrations

This commit is contained in:
stvnrlly
2022-10-04 15:28:27 +00:00
parent 36ce2f7830
commit 57f4df8ed1
58 changed files with 14 additions and 6600 deletions

View File

@@ -16,7 +16,6 @@ from notifications_utils.recipients import (
validate_phone_number,
)
from notifications_utils.template import (
BroadcastMessageTemplate,
LetterPrintTemplate,
PlainTextEmailTemplate,
SMSMessageTemplate,
@@ -50,10 +49,9 @@ from app.utils import (
SMS_TYPE = 'sms'
EMAIL_TYPE = 'email'
LETTER_TYPE = 'letter'
BROADCAST_TYPE = 'broadcast'
TEMPLATE_TYPES = [SMS_TYPE, EMAIL_TYPE, LETTER_TYPE, BROADCAST_TYPE]
NOTIFICATION_TYPES = [SMS_TYPE, EMAIL_TYPE, LETTER_TYPE] # not broadcast
TEMPLATE_TYPES = [SMS_TYPE, EMAIL_TYPE, LETTER_TYPE]
NOTIFICATION_TYPES = [SMS_TYPE, EMAIL_TYPE, LETTER_TYPE]
template_types = db.Enum(*TEMPLATE_TYPES, name='template_type')
@@ -152,7 +150,6 @@ class User(db.Model):
return True
return any(
str(service.organisation_id) == current_app.config['BROADCAST_ORGANISATION_ID'] or
str(service.id) == current_app.config['NOTIFY_SERVICE_ID']
for service in self.services
)
@@ -326,7 +323,6 @@ SERVICE_PERMISSION_TYPES = [
EMAIL_TYPE,
SMS_TYPE,
LETTER_TYPE,
BROADCAST_TYPE,
INTERNATIONAL_SMS_TYPE,
INBOUND_SMS_TYPE,
SCHEDULE_NOTIFICATIONS,
@@ -531,9 +527,6 @@ class Service(db.Model, Versioned):
uselist=False,
backref=db.backref('services', lazy='dynamic'))
allowed_broadcast_provider = association_proxy('service_broadcast_settings', 'provider')
broadcast_channel = association_proxy('service_broadcast_settings', 'channel')
@classmethod
def from_json(cls, data):
"""
@@ -577,13 +570,6 @@ class Service(db.Model, Versioned):
'research_mode': self.research_mode
}
def get_available_broadcast_providers(self):
# There may be future checks here if we add, for example, platform admin level provider killswitches.
if self.allowed_broadcast_provider != ALL_BROADCAST_PROVIDERS:
return [x for x in current_app.config['ENABLED_CBCS'] if x == self.allowed_broadcast_provider]
else:
return current_app.config['ENABLED_CBCS']
class AnnualBilling(db.Model):
__tablename__ = "annual_billing"
@@ -953,7 +939,6 @@ class TemplateBase(db.Model):
hidden = db.Column(db.Boolean, nullable=False, default=False)
subject = db.Column(db.Text)
postage = db.Column(db.String, nullable=True)
broadcast_data = db.Column(JSONB(none_as_null=True), nullable=True)
@declared_attr
def service_id(cls):
@@ -1026,8 +1011,6 @@ class TemplateBase(db.Model):
return PlainTextEmailTemplate(self.__dict__)
if self.template_type == SMS_TYPE:
return SMSMessageTemplate(self.__dict__)
if self.template_type == BROADCAST_TYPE:
return BroadcastMessageTemplate(self.__dict__)
if self.template_type == LETTER_TYPE:
return LetterPrintTemplate(
self.__dict__,
@@ -1891,10 +1874,6 @@ SEND_LETTERS = 'send_letters'
MANAGE_API_KEYS = 'manage_api_keys'
PLATFORM_ADMIN = 'platform_admin'
VIEW_ACTIVITY = 'view_activity'
CREATE_BROADCASTS = 'create_broadcasts'
APPROVE_BROADCASTS = 'approve_broadcasts'
CANCEL_BROADCASTS = 'cancel_broadcasts'
REJECT_BROADCASTS = 'reject_broadcasts'
# List of permissions
PERMISSION_LIST = [
@@ -1907,10 +1886,6 @@ PERMISSION_LIST = [
MANAGE_API_KEYS,
PLATFORM_ADMIN,
VIEW_ACTIVITY,
CREATE_BROADCASTS,
APPROVE_BROADCASTS,
CANCEL_BROADCASTS,
REJECT_BROADCASTS,
]
@@ -2263,400 +2238,6 @@ class ServiceContactList(db.Model):
return contact_list
class BroadcastStatusType(db.Model):
__tablename__ = 'broadcast_status_type'
DRAFT = 'draft'
PENDING_APPROVAL = 'pending-approval'
REJECTED = 'rejected'
BROADCASTING = 'broadcasting'
COMPLETED = 'completed'
CANCELLED = 'cancelled'
TECHNICAL_FAILURE = 'technical-failure'
STATUSES = [DRAFT, PENDING_APPROVAL, REJECTED, BROADCASTING, COMPLETED, CANCELLED, TECHNICAL_FAILURE]
# a broadcast message can be edited while in one of these states
PRE_BROADCAST_STATUSES = [DRAFT, PENDING_APPROVAL, REJECTED]
LIVE_STATUSES = [BROADCASTING, COMPLETED, CANCELLED]
# these are only the transitions we expect to administer via the API code.
ALLOWED_STATUS_TRANSITIONS = {
DRAFT: {PENDING_APPROVAL},
PENDING_APPROVAL: {REJECTED, DRAFT, BROADCASTING},
REJECTED: {DRAFT, PENDING_APPROVAL},
BROADCASTING: {COMPLETED, CANCELLED},
COMPLETED: {},
CANCELLED: {},
TECHNICAL_FAILURE: {},
}
name = db.Column(db.String, primary_key=True)
class BroadcastMessage(db.Model):
"""
This is for creating a message, viewing it in notify, adding areas, approvals, drafts, etc. Notify logic before
hitting send.
"""
__tablename__ = 'broadcast_message'
__table_args__ = (
db.ForeignKeyConstraint(
['template_id', 'template_version'],
['templates_history.id', 'templates_history.version'],
),
{}
)
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'))
service = db.relationship('Service', backref='broadcast_messages')
template_id = db.Column(UUID(as_uuid=True), nullable=True)
template_version = db.Column(db.Integer, nullable=True)
template = db.relationship('TemplateHistory', backref='broadcast_messages')
_personalisation = db.Column(db.String, nullable=True)
content = db.Column(db.String, nullable=False)
# defaults to empty list
areas = db.Column(JSONB(none_as_null=True), nullable=False, default=list)
status = db.Column(
db.String,
db.ForeignKey('broadcast_status_type.name'),
nullable=False,
default=BroadcastStatusType.DRAFT
)
# these times are related to the actual broadcast, rather than auditing purposes
starts_at = db.Column(db.DateTime, nullable=True)
finishes_at = db.Column(db.DateTime, nullable=True) # isn't updated if user cancels
# these times correspond to when
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
approved_at = db.Column(db.DateTime, nullable=True)
cancelled_at = db.Column(db.DateTime, nullable=True)
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
created_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), nullable=True)
approved_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), nullable=True)
cancelled_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), nullable=True)
created_by = db.relationship('User', foreign_keys=[created_by_id])
approved_by = db.relationship('User', foreign_keys=[approved_by_id])
cancelled_by = db.relationship('User', foreign_keys=[cancelled_by_id])
created_by_api_key_id = db.Column(UUID(as_uuid=True), db.ForeignKey('api_keys.id'), nullable=True)
cancelled_by_api_key_id = db.Column(UUID(as_uuid=True), db.ForeignKey('api_keys.id'), nullable=True)
created_by_api_key = db.relationship('ApiKey', foreign_keys=[created_by_api_key_id])
cancelled_by_api_key = db.relationship('ApiKey', foreign_keys=[cancelled_by_api_key_id])
reference = db.Column(db.String(255), nullable=True)
cap_event = db.Column(db.String(255), nullable=True)
stubbed = db.Column(db.Boolean, nullable=False)
CheckConstraint("created_by_id is not null or created_by_api_key_id is not null")
@property
def personalisation(self):
if self._personalisation:
return encryption.decrypt(self._personalisation)
return {}
@personalisation.setter
def personalisation(self, personalisation):
self._personalisation = encryption.encrypt(personalisation or {})
def serialize(self):
return {
'id': str(self.id),
'reference': self.reference,
'cap_event': self.cap_event,
'service_id': str(self.service_id),
'template_id': str(self.template_id) if self.template else None,
'template_version': self.template_version,
'template_name': self.template.name if self.template else None,
'personalisation': self.personalisation if self.template else None,
'content': self.content,
'areas': self.areas,
'status': self.status,
'starts_at': get_dt_string_or_none(self.starts_at),
'finishes_at': get_dt_string_or_none(self.finishes_at),
'created_at': get_dt_string_or_none(self.created_at),
'approved_at': get_dt_string_or_none(self.approved_at),
'cancelled_at': get_dt_string_or_none(self.cancelled_at),
'updated_at': get_dt_string_or_none(self.updated_at),
'created_by_id': get_uuid_string_or_none(self.created_by_id),
'approved_by_id': get_uuid_string_or_none(self.approved_by_id),
'cancelled_by_id': get_uuid_string_or_none(self.cancelled_by_id),
}
class BroadcastEventMessageType:
ALERT = 'alert'
UPDATE = 'update'
CANCEL = 'cancel'
MESSAGE_TYPES = [ALERT, UPDATE, CANCEL]
class BroadcastEvent(db.Model):
"""
This table represents an instruction that we will send to the broadcast providers. It directly correlates with an
instruction from the admin - to broadcast a message, to cancel an existing message, or to update an existing one.
We should be able to create the complete CAP message without joining from this to any other tables, eg
template, service, or broadcast_message.
The only exception to this is that we will have to join to itself to find other broadcast_events with the
same broadcast_message_id when building up the `<references>` xml field for updating/cancelling an existing message.
As such, this shouldn't have foreign keys to things that can change or be deleted.
"""
__tablename__ = 'broadcast_event'
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'))
service = db.relationship('Service')
broadcast_message_id = db.Column(UUID(as_uuid=True), db.ForeignKey('broadcast_message.id'), nullable=False)
broadcast_message = db.relationship('BroadcastMessage', backref='events')
# this is used for <sent> in the cap xml
sent_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
# msgType. alert, cancel, or update. (other options in the spec are "ack" and "error")
message_type = db.Column(db.String, nullable=False)
# this will be json containing anything that isnt hardcoded in utils/cbc proxy. for now just body but may grow to
# include, eg, title, headline, instructions.
transmitted_content = db.Column(
JSONB(none_as_null=True),
nullable=True
)
# unsubstantiated reckon: even if we're sending a cancel, we'll still need to provide areas
transmitted_areas = db.Column(JSONB(none_as_null=True), nullable=False, default=list)
transmitted_sender = db.Column(db.String(), nullable=False)
# we may only need this starts_at if this is scheduled for the future. Interested to see how this affects
# updates/cancels (ie: can you schedule an update for the future?)
transmitted_starts_at = db.Column(db.DateTime, nullable=True)
transmitted_finishes_at = db.Column(db.DateTime, nullable=True)
@property
def reference(self):
notify_email_domain = current_app.config['NOTIFY_EMAIL_DOMAIN']
return (
f'https://www.{notify_email_domain}/,'
f'{self.id},'
f'{self.sent_at_as_cap_datetime_string}'
)
@property
def sent_at_as_cap_datetime_string(self):
return self.formatted_datetime_for('sent_at')
@property
def transmitted_finishes_at_as_cap_datetime_string(self):
return self.formatted_datetime_for('transmitted_finishes_at')
def formatted_datetime_for(self, property_name):
return self.convert_naive_utc_datetime_to_cap_standard_string(
getattr(self, property_name)
)
@staticmethod
def convert_naive_utc_datetime_to_cap_standard_string(dt):
"""
As defined in section 3.3.2 of
http://docs.oasis-open.org/emergency/cap/v1.2/CAP-v1.2-os.html
They define the standard "YYYY-MM-DDThh:mm:ssXzh:zm", where X is
`+` if the timezone is > UTC, otherwise `-`
"""
return f"{dt.strftime('%Y-%m-%dT%H:%M:%S')}-00:00"
def get_provider_message(self, provider):
return next(
(
provider_message
for provider_message in self.provider_messages
if provider_message.provider == provider
),
None
)
def get_earlier_provider_messages(self, provider):
"""
Get the previous message for a provider. These are different per provider, as the identifiers are different.
Return the full provider_message object rather than just an identifier, since the different providers expect
reference to contain different things - let the cbc_proxy work out what information is relevant.
"""
from app.dao.broadcast_message_dao import (
get_earlier_events_for_broadcast_event,
)
earlier_events = [
event for event in get_earlier_events_for_broadcast_event(self.id)
]
ret = []
for event in earlier_events:
provider_message = event.get_provider_message(provider)
if provider_message is None:
# TODO: We should figure out what to do if a previous message hasn't been sent out yet.
# We don't want to not cancel a message just because it's stuck in a queue somewhere.
# This exception should probably be named, and then should be caught further up and handled
# appropriately.
raise Exception(
f'Cannot get earlier message references for event {self.id}, previous event {event.id} has not ' +
f' been sent to provider "{provider}" yet'
)
ret.append(provider_message)
return ret
def serialize(self):
return {
'id': str(self.id),
'service_id': str(self.service_id),
'broadcast_message_id': str(self.broadcast_message_id),
# sent_at is required by BroadcastMessageTemplate.from_broadcast_event
'sent_at': self.sent_at.strftime(DATETIME_FORMAT),
'message_type': self.message_type,
'transmitted_content': self.transmitted_content,
'transmitted_areas': self.transmitted_areas,
'transmitted_sender': self.transmitted_sender,
'transmitted_starts_at': get_dt_string_or_none(self.transmitted_starts_at),
# transmitted_finishes_at is required by BroadcastMessageTemplate.from_broadcast_event
'transmitted_finishes_at': self.transmitted_finishes_at.strftime(DATETIME_FORMAT),
}
class BroadcastProvider:
EE = 'ee'
VODAFONE = 'vodafone'
THREE = 'three'
O2 = 'o2'
PROVIDERS = [EE, VODAFONE, THREE, O2]
ALL_BROADCAST_PROVIDERS = 'all'
class BroadcastProviderMessageStatus:
TECHNICAL_FAILURE = 'technical-failure' # Couldnt send (cbc proxy 5xx/4xx)
SENDING = 'sending' # Sent to cbc, awaiting response
ACK = 'returned-ack' # Received ack response
ERR = 'returned-error' # Received error response
STATES = [TECHNICAL_FAILURE, SENDING, ACK, ERR]
class BroadcastProviderMessage(db.Model):
"""
A row in this table represents the XML blob sent to a single provider.
"""
__tablename__ = 'broadcast_provider_message'
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
broadcast_event_id = db.Column(UUID(as_uuid=True), db.ForeignKey('broadcast_event.id'))
broadcast_event = db.relationship('BroadcastEvent', backref='provider_messages')
# 'ee', 'three', 'vodafone', etc
provider = db.Column(db.String)
status = db.Column(db.String)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
UniqueConstraint(broadcast_event_id, provider)
message_number = association_proxy('broadcast_provider_message_number', 'broadcast_provider_message_number')
class BroadcastProviderMessageNumber(db.Model):
"""
To send IBAG messages via the CBC proxy to Nokia CBC appliances, Notify must generate and store a numeric
message_number alongside the message ID (GUID).
Subsequent messages (Update, Cancel) in IBAG format must reference the original message_number & message_id.
This model relates broadcast_provider_message_id to that numeric message_number.
"""
__tablename__ = 'broadcast_provider_message_number'
sequence = Sequence('broadcast_provider_message_number_seq')
broadcast_provider_message_number = db.Column(
db.Integer, sequence, server_default=sequence.next_value(), primary_key=True
)
broadcast_provider_message_id = db.Column(
UUID(as_uuid=True), db.ForeignKey('broadcast_provider_message.id'), nullable=False
)
broadcast_provider_message = db.relationship(
'BroadcastProviderMessage', backref=db.backref("broadcast_provider_message_number", uselist=False)
)
class ServiceBroadcastSettings(db.Model):
"""
Every broadcast service should have one and only one row in this table.
"""
__tablename__ = "service_broadcast_settings"
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), primary_key=True, nullable=False)
service = db.relationship(Service, backref=db.backref("service_broadcast_settings", uselist=False))
channel = db.Column(
db.String(255), db.ForeignKey('broadcast_channel_types.name'), nullable=False
)
provider = db.Column(db.String, db.ForeignKey('broadcast_provider_types.name'), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
class BroadcastChannelTypes(db.Model):
__tablename__ = 'broadcast_channel_types'
name = db.Column(db.String(255), primary_key=True)
class BroadcastProviderTypes(db.Model):
__tablename__ = 'broadcast_provider_types'
name = db.Column(db.String(255), primary_key=True)
class ServiceBroadcastProviderRestriction(db.Model):
"""
TODO: Drop this table as no longer used
Most services don't send broadcasts. Of those that do, most send to all broadcast providers.
However, some services don't send to all providers. These services are test services that we or the providers
themselves use.
This table links those services. There should only be one row per service in this table, and this is enforced by
the service_id being a primary key.
"""
__tablename__ = "service_broadcast_provider_restriction"
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), primary_key=True, nullable=False)
service = db.relationship(Service, backref=db.backref("service_broadcast_provider_restriction", uselist=False))
provider = db.Column(db.String, nullable=False)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
class WebauthnCredential(db.Model):
"""
A table that stores data for registered webauthn credentials.