File: //home/arjun/projects/buyercall_forms/buyercall/buyercall/blueprints/mobile/models.py
import logging
from sqlalchemy.ext.hybrid import hybrid_property
from buyercall.lib.util_sqlalchemy import ResourceMixin
from buyercall.extensions import db
from buyercall.lib.util_bandwidth import bw_client
from datetime import datetime
from sqlalchemy import and_
from sqlalchemy.orm import load_only
log = logging.getLogger(__name__)
class Endpoint(ResourceMixin, db.Model):
__tablename__ = 'sip_endpoints'
id = db.Column(db.Integer, primary_key=True)
# Description of sip
description = db.Column(db.String(128), nullable=False)
# Identify the provider for the sip endpoint. Either Twilio or Bandwidth
provider = db.Column(db.String(32), nullable=False, index=True)
# The provider id for the sip endpoint
provider_id = db.Column(db.String(128), nullable=False)
# The sip endpoint url
sip_uri = db.Column(db.String(128), nullable=False)
# The SIP username. This is unique to a domain and cant be changed
sip_username = db.Column(db.String(64), nullable=False, index=True)
# The SIP password
sip_password = db.Column(db.String(256), nullable=False)
# Is the sip enabled or not
enabled = db.Column(db.Boolean, server_default='t', default=True)
# The inbound phonenumber the sips is linked to
inbound_id = db.Column(db.Integer, db.ForeignKey(
'phonenumbers.id', name='sip_inbound_fkey', onupdate='CASCADE', ondelete='SET NULL'), index=True,
nullable=False)
# The agent the sips is linked to
agent_id = db.Column(db.Integer, db.ForeignKey(
'agents.id', name='sip_agent_fkey', onupdate='CASCADE', ondelete='SET NULL'), index=True,
nullable=False)
# The partnership account id associated with the sip
partnership_account_id = db.Column(db.Integer, db.ForeignKey(
'partnership_accounts.id', name='sip_partnership_account_fkey', onupdate='CASCADE', ondelete='CASCADE'),
index=True, nullable=False)
# The partnership account id associated with the sip
domain_id = db.Column(db.Integer, db.ForeignKey(
'sip_domains.id', name='sip_domain_fkey', onupdate='CASCADE', ondelete='CASCADE'),
index=True, nullable=False)
# When a sip endpoint gets deleted it goes into deactivated state instead of removing it from DB
deactivated_on = db.Column(db.DateTime(), nullable=True)
is_deactivated = db.Column(db.Boolean(), nullable=False,
server_default='0', default=False)
# The user's device unique token, selector and app_id to be used to identify the mobile user to send
# push notifications to
device_token = db.Column(db.String(256), nullable=False, server_default='')
selector = db.Column(db.String(128), nullable=False, server_default='')
app_id = db.Column(db.String(64), nullable=False, server_default='')
install_id = db.Column(db.String(128), nullable=False, server_default='')
imei = db.Column(db.String(64), nullable=False, server_default='')
unique_id = db.Column(db.String(64), nullable=False, server_default='')
build = db.Column(db.String(64), nullable=False, server_default='')
platform = db.Column(db.String(32), nullable=False, server_default='')
platform_version = db.Column(db.String(32), nullable=False, server_default='')
version = db.Column(db.String(32), nullable=False, server_default='')
app_name = db.Column(db.String(64), nullable=False, server_default='')
locale = db.Column(db.String(32), nullable=False, server_default='')
cpu = db.Column(db.String(32), nullable=False, server_default='')
device = db.Column(db.String(32), nullable=False, server_default='')
production_build = db.Column(db.String(32), nullable=False, server_default='')
last_sms_received_id = db.Column(db.String(64), nullable=False, server_default='')
last_sms_sent_id = db.Column(db.String(64), nullable=False, server_default='')
qr_url = db.Column(db.String(256), nullable=False, server_default='')
sip_hash_1 = db.Column(db.String(64), nullable=False, server_default='')
sip_hash_1_b = db.Column(db.String(64), nullable=False, server_default='')
@hybrid_property
def source(self):
return (
self.inbound and self.inbound.friendly_name
) or ''
@source.expression
def source(cls):
from ..phonenumbers.models import Phone
return Phone.friendly_name
@hybrid_property
def source_id(self):
return (
self.inbound and str(self.inbound_id)
) or ''
@classmethod
def deactivate(cls, id_, partnership_account_id):
"""
Deactivate sip endpoint. The is_deactivated flag will be set.
:return: bool
"""
endpoint = Endpoint.query.filter(and_(Endpoint.id == id_,
Endpoint.partnership_account_id == partnership_account_id)).first()
if endpoint is not None:
endpoint.is_deactivated = True
endpoint.enabled = False
endpoint.deactivated_on = datetime.now()
db.session.commit()
return True
else:
return False
@classmethod
def create(cls, description, provider, provider_id, sip_uri, sip_username, sip_password,
enabled, inbound_id, agent_id, partnership_account_id, domain_id, is_deactivated,
hash_1=None, hash_1_b=None):
"""
Create new sip endpoint.
:return: bool
"""
new_endpoint = Endpoint(
description=description, provider=provider, provider_id=provider_id, sip_uri=sip_uri,
sip_username=sip_username, sip_password=sip_password, enabled=enabled,
inbound_id=inbound_id, agent_id=agent_id, partnership_account_id=partnership_account_id,
domain_id=domain_id, is_deactivated=is_deactivated,
sip_hash_1=hash_1, sip_hash_1_b=hash_1_b
)
db.session.add(new_endpoint)
db.session.commit()
return True
@classmethod
def update(cls, id_, partnership_account_id, sip_password,
enabled, agent_id):
"""
Update the endpoint details.
:return: bool
"""
endpoint = Endpoint.query\
.filter(and_(Endpoint.id == id_, Endpoint.partnership_account_id == partnership_account_id))\
.first()
if endpoint is not None:
endpoint.sip_password = sip_password
endpoint.enabled = enabled
endpoint.agent_id = agent_id
db.session.commit()
return True
else:
return False
@classmethod
def api_username_check(cls, sip_username):
"""
Check to see if the username exists in the domain.
:return: bool
"""
endpoint = Endpoint.query\
.filter(and_(Endpoint.sip_username == sip_username, Endpoint.is_deactivated.is_(False))).first()
if endpoint is None:
return False
else:
return True
@classmethod
def sip_agent_id(cls, sip_uri):
"""
Return the agent id associated with the sip endpoint.
:return: bool
"""
endpoint = cls.query.filter(and_(cls.sip_uri == sip_uri, cls.is_deactivated.is_(False))).first()
log.info('The sip agent id is:{}'.format(endpoint.agent_id))
if endpoint is None:
agent_id = None
return agent_id
else:
return endpoint.agent_id
@classmethod
def api_update(cls, id_, partnership_account_id, sip_password, sip_description, enabled, agent_id):
"""
Update the endpoint details.
:return: bool
"""
try:
if sip_password is None and enabled is None and agent_id is None:
return True
endpoint = Endpoint.query\
.filter(and_(Endpoint.id == id_, Endpoint.partnership_account_id == partnership_account_id))\
.first()
domain = Domain.query \
.filter(Domain.id == endpoint.domain_id).first()
# import partnership information to get partnership id
from ..partnership.models import Partnership
partner = Partnership.query.filter(Partnership.id == domain.partnership_id).first()
client = bw_client(partner.id, tn_type='mobile')
if endpoint:
# Get the old QR code before the new one is assigned
old_qr_url = endpoint.qr_url
if sip_password:
endpoint.sip_password = sip_password
# Get the partnership id from phone numbers to determine the domain to use
from ..partnership.models import PartnershipAccount
partnership_act = PartnershipAccount.query.filter(
PartnershipAccount.id == partnership_account_id).first()
from ..mobile.tasks import qr_code, delete_qr_code
qr = qr_code(endpoint.sip_username,
sip_password,
partnership_act.id,
partnership_act.name,
domain.cloud_id)
if qr:
endpoint.qr_url = qr
# Delete the old QR code image from the server
if old_qr_url:
delete_qr_code(old_qr_url)
if enabled:
endpoint.enabled = enabled
if sip_description:
endpoint.description = sip_description
if agent_id:
endpoint.agent_id = agent_id
db.session.commit()
if endpoint.provider == 'bandwidth':
client.realms.update_sip_credentials(
realm_id=domain.domain_id,
realm=domain.sip_realm,
endpoint_id=endpoint.provider_id,
sip_username=endpoint.sip_username,
sip_password=sip_password
)
return True
return False
except Exception as e:
log.error('Error updating mobile endpoint. Error: {}'.format(e))
return False
class Domain(ResourceMixin, db.Model):
__tablename__ = 'sip_domains'
id = db.Column(db.Integer, primary_key=True)
# Identifies the id of the domain provided by the provider
domain_id = db.Column(db.String(32), nullable=False)
# Identify the name of the domain
name = db.Column(db.String(16), nullable=False, index=True)
# The description for the domain
description = db.Column(db.String(128), nullable=False)
# Identify the provider for the sip domain. Either Twilio or Bandwidth
provider = db.Column(db.String(32), nullable=False, index=True)
# The partnership id associated with the sip domain
partnership_id = db.Column(db.Integer, db.ForeignKey(
'partnerships.id', name='sip_domain_partnership_fkey', onupdate='CASCADE', ondelete='CASCADE'),
index=True, nullable=False)
# When a sip domain gets deleted it goes into deactivated state instead of removing it from DB
deactivated_on = db.Column(db.DateTime(), nullable=True)
is_deactivated = db.Column(db.Boolean(), nullable=False,
server_default='0', default=False)
cloud_id = db.Column(db.String(128), nullable=False)
default_domain = db.Column(db.Boolean(), nullable=False,
server_default='0', default=False)
call_uri = db.Column(db.String(256), nullable=False, server_default='')
sip_realm = db.Column(db.String(64), nullable=False, server_default='')