HEX
Server: Apache/2.4.52 (Ubuntu)
System: Linux spn-python 5.15.0-89-generic #99-Ubuntu SMP Mon Oct 30 20:42:41 UTC 2023 x86_64
User: arjun (1000)
PHP: 8.1.2-1ubuntu2.20
Disabled: NONE
Upload Files
File: //proc/self/root/home/arjun/projects/buyercall/buyercall/blueprints/widgets/endpoints.py
import logging as log
import traceback
from uuid import uuid4

import redis
from flask import (
    request, Blueprint, current_app
)
from flask_cors import cross_origin
from flask_login import current_user

from buyercall.blueprints.agents.models import Agent
from buyercall.blueprints.channels.models import Channel
from buyercall.blueprints.chat.models import Chat
from buyercall.blueprints.contacts.models import Contact, ContactChannelTie
from buyercall.blueprints.notification.utilities import send_notifications
from buyercall.blueprints.partnership.models import Partnership, PartnershipAccount
from buyercall.blueprints.phonenumbers.models import Phone
from buyercall.blueprints.sms.models import Message
from buyercall.blueprints.user.decorators import api_role_required
from buyercall.blueprints.widgets.rest_api import fix_name
from buyercall.blueprints.widgets.serializers import WidgetOutSelectedResponseSchema
from buyercall.lib.util_rest import api_jsonify
from buyercall.lib.util_twilio import bw_client, subaccount_client
from .models import Widget, AgentAssignment
from .routing import (
    BandwidthRouting,
    Routing,
    add_widget_lead,
    phonecall_inprogress,
    NoAgentsException
)
from ..email.models import Email

log = log.getLogger(__name__)

widget_api = Blueprint('chat_widget_api', __name__, template_folder='templates', url_prefix='/api/widget')


@cross_origin()
def call_status(lead_sid):
    # Declare redis url
    redis_db = redis.StrictRedis(
        host=current_app.config['REDIS_CONFIG_URL'],
        port=current_app.config['REDIS_CONFIG_PORT'],
        decode_responses=True
    )
    try:
        connect = redis_db.get(f'CONNECT{str(lead_sid)}')
        if connect == '1':
            return api_jsonify({"callConnect": True}, 200, 'Success', True)
        elif connect == '-1':
            return api_jsonify({"callConnect": False, "error": True}, 200, 'Success', True)
    except Exception as e:
        print('Error : ', e)
        log.error('Cannot retrieve lead status - is Redis accessible?')

    return api_jsonify({"callConnect": False}, 200, 'Success', True)


@cross_origin()
def call(guid):
    """ The endpoint that gets called when the lead presses 'Call Now!' on the
    widget.
    Receives the widget GUID as a query string parameter, and the user's data
    in the JSON body.
    """
    from buyercall.blueprints.partnership.models import PartnershipAccount
    # Save the lead
    json = request.get_json()
    widget = Widget.query.outerjoin(
        (AgentAssignment, Widget.assignments)
    ).outerjoin(
        (Agent, AgentAssignment.agent)
    ).join(Widget.partnership_account).join(PartnershipAccount.partnership).filter(Widget.guid == str(guid)).first()

    log.info("The call widget json request: {}".format(json))

    if widget and 'firstName' in json and json['firstName'] in ['', ' '] \
            or 'phoneNumber' in json and json['phoneNumber'] in ['', ' ']:
        log.error('No lead fields provided for call widget ' + str(widget.name) + ' - ' + str(widget.guid) + '.')
        return api_jsonify([], 422, f'No lead fields provided for call widget {str(widget.name)}-{str(widget.guid)}',
                           False)

    subscription = widget.partnership_account.subscription
    if not subscription:
        subscription = widget.partnership_account.partnership.subscription

    if subscription.usage_over_limit:
        log.warning('partnership_account {} has exceeded their quota.'.format(
            widget.partnership_account_id
        ))
        return api_jsonify([], 403, f'partnership_account {widget.partnership_account_id} has exceeded their quota',
                           False)

    lead = None
    partnership = None
    try:
        lead_on_call = phonecall_inprogress(widget, **json)

        if lead_on_call:
            return api_jsonify([], 200, 'Call in progress', True)
        else:
            lead = add_widget_lead(widget, **json)

            # import partnership information to get partnership id
            from buyercall.blueprints.partnership.models import Partnership, PartnershipAccount
            partner_account = PartnershipAccount.query \
                .filter(PartnershipAccount.id == widget.partnership_account_id).first()
            partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()
            partnership = partner
            # Is it a Bandwidth or a Twilio number?
            if widget.inbound.type == 'tracking':
                client = bw_client(partner.id)
                log.info('Calling Bandwidth number...')
                BandwidthRouting(client).call_lead(widget, lead)
            else:
                log.info('Calling Twilio number...')
                subaccount_sid = subscription.twilio_subaccount_sid
                client = subaccount_client(subaccount_sid, partner.id)
                Routing(client).call_lead(widget, lead)

    except NoAgentsException:
        # Add task for agents
        all_agents_sids = []
        for agent in widget.agents:
            all_agents_sids.append(agent.sid)

        from buyercall.blueprints.partnership.models import PartnershipAccount
        from buyercall.blueprints.chat.endpoints import create_task
        partnership_account = PartnershipAccount.query.filter(
            PartnershipAccount.id == widget.partnership_account_id).first()

        task_payload = {
            "type": "MISSED_CALL_FOLLOW_UP",
            "users": all_agents_sids,
            "partnershipId": str(partnership.sid),
            'partnershipAccountId': str(partnership_account.sid)
        }
        task_resp = create_task(task_payload)
        # Send notification
        es_data = {
            'user_id': current_user.sid,
            'notify_message_type': 'MISSED_CALL',
            'user_related_entities': "You've",
            'other_user_related_entities': f'{current_user.firstname} {current_user.lastname}',
            'hyperlink': f'{partnership.partner_url}/settings'
        }
        from buyercall.blueprints.notification.utilities import send_notifications
        es_response = send_notifications(**es_data)
        return api_jsonify({'success': False, 'code': 'ERR_NO_AGENTS', 'callId': lead.sid}, 200, 'Success', True)
    return api_jsonify({'success': True, 'callId': lead.sid}, 200, 'Success', True)


@cross_origin()
def save_lead(guid):
    """ Saves the lead information in the database. Receives the widget GUID as a query string parameter,
     and the user's data in the JSON body."""

    json = request.get_json()
    widget = Widget.query.filter(Widget.guid == str(guid)).first()
    log.info("The after hours call widget json request is: {}".format(json))

    if widget and 'firstName' in json and json['firstName'] in ['', ' '] or \
            'lastName' in json and json['lastName'] in ['', ' '] or \
            'phoneNumber' in json and json['phoneNumber'] in ['', ' '] or \
            'emailAddress' in json and json['emailAddress'] in ['', ' ']:
        log.error('No lead fields provided for widget ' + str(widget.name) + ' - ' + str(widget.guid) + '.')
        return api_jsonify([], 422, "Missing parameters for widget", False)

    itype = json.get('type')
    partnership = Partnership.query.filter(Partnership.id == current_user.partnership_id).first()
    if not partnership:
        partnership = Partnership.query.get(1)
    if itype == "CALL":
        lead = add_widget_lead(widget, status='missed', **json)
        lead_id = lead.sid
        try:
            # after_call_events(lead, widget)
            es_data = {
                'user_id': current_user.sid,
                'notify_message_type': 'NEW_LEAD',
                'user_related_entities': "You've",
                'other_user_related_entities': f'{current_user.firstname} {current_user.lastname}',
                'hyperlink': f'{partnership.partner_url}/settings'
            }
            # es_response = send_notifications(**es_data)
        except Exception as e:
            log.error(traceback.format_exc())
            lead_id = None
        return api_jsonify({'leadId': lead_id}, 200, "Success", True)

    elif itype == "CHAT":
        lead_id = ''
        try:
            chat = Chat.create(**json)
            # Send notification
            es_data = {
                'user_id': current_user.sid,
                'notify_message_type': 'CHAT',
                'user_related_entities': "You've",
                'other_user_related_entities': f'{current_user.firstname} {current_user.lastname}',
                'hyperlink': f'{partnership.partner_url}/settings'
            }
            es_response = send_notifications(**es_data)
        except Exception as e:
            log.error(traceback.format_exc())
            lead_id = None
        return api_jsonify({'leadId': lead_id}, 200, "Success", True)

    elif itype == "TEXT_MESSAGE":
        lead_id = ''
        try:
            message = Message.create(**json)
            # Send notification
            es_data = {
                'user_id': current_user.sid,
                'notify_message_type': 'TEXT_MESSAGE',
                'user_related_entities': "You've",
                'other_user_related_entities': f'{current_user.firstname} {current_user.lastname}',
                'hyperlink': f'{partnership.partner_url}/settings'
            }
            es_response = send_notifications(**es_data)
        except Exception as e:
            log.error(traceback.format_exc())
            lead_id = None
        return api_jsonify({'leadId': lead_id}, 200, "Success", True)

    elif itype == "EMAIL_MESSAGE":
        lead_id = ''
        try:
            # email = Email.create(**json)
            # Send notification
            es_data = {
                'user_id': current_user.sid,
                'notify_message_type': 'EMAIL_MESSAGE',
                'user_related_entities': "You've",
                'other_user_related_entities': f'{current_user.firstname} {current_user.lastname}',
                'hyperlink': f'{partnership.partner_url}/settings'
            }
            es_response = send_notifications(**es_data)
        except Exception as e:
            log.error(traceback.format_exc())
            lead_id = None
        return api_jsonify({'leadId': lead_id}, 200, "Success", True)
    else:
        pass


@cross_origin()
@api_role_required('admin')
def create_widget():
    """Create omnichannel widget

    Args:
        name (str): widget name
        description (str): widget description
        type (str): widget type. Values: widget/adf
        enabled (bool): widget enabled or not. Default: True
        phonenumber_id (str): Phone number sid related to widget (optional)
        options (json): widget configuration
        channels (list): channel's sid

    Returns (json):
        data (json/None): contains widget guid if success else none 
        message (str): success or failure message
        status_code (int): http status code
        success (bool): Whether API was success
    """

    message = "Widget created successfully"
    success = True
    status_code = 200

    partnership_account = PartnershipAccount.query.filter(
        PartnershipAccount.id == current_user.partnership_account_id).first()
    if not partnership_account:
        partnership_account = PartnershipAccount.query.get(1)

    widget_id = None
    received = request.get_json()
    if received:
        widget_name = fix_name(received.get("name", "New widget"))
        widget_desc = received.get("description", "")
        # widget_type = received.get('type', 'widget')
        widget_enabled = received.get('enabled', True)
        widget_guid = str(uuid4())
        widget_phonenumber = received.get('phonenumber_id', None)
        channel_sids = received.get('channels', [])
        options = received.get("options", {})
        widget_options = {
            "enableNotifications": True,
            "showPoweredBCIcon": False,
            "widgetThemeColor": [72, 90, 255],
            # blue[72, 90, 255], red[224, 11, 28],
            "options": [
                {
                    "type": 'TEXT_MESSAGE'
                },
                {
                    "type": 'CALL',
                    "leadSubmitButtonName": 'SEND CALL'
                },
                {
                    "type": 'EMAIL',
                    "leadSubmitButtonName": 'SEND EMAIL'
                },
                {
                    "type": 'CHAT',
                    "leadSubmitButtonName": 'START CHAT'
                }
            ]
        }
        widget_options.update(options)
        if partnership_account.name:
            company_before_format = partnership_account.name.replace(" ", "")
            company_after_format = ''.join(e for e in company_before_format if e.isalnum())

            widget_count = Widget.query.filter(
                Widget.partnership_account_id == partnership_account.id
            ).count() + 1

            if widget_count > 0:
                widget_count_string = str(widget_count)
            else:
                widget_count_string = "0{}".format(widget_count)

            channels = [Channel.get_by_id(sid) for sid in channel_sids]

            params = {
                'enabled': widget_enabled,
                'email': f"{company_after_format}{widget_count_string}@inbound.buyercall.com",
                'partnership_account_id': partnership_account.id,
                'guid': widget_guid,
                'name': widget_name,
                'description': widget_desc,
                'options': widget_options,
                'channels': channels,
                'created_by': current_user.id
            }
            widget, error = Widget.api_create(params, widget_phonenumber)
            if widget:
                widget_id = widget.guid
                # Send notification
                partnership = Partnership.query.filter(Partnership.id == current_user.partnership_id).first()
                if not partnership:
                    partnership = Partnership.query.get(1)

                es_data = {
                    'user_id': current_user.sid,
                    'notify_message_type': 'NEW_WIDGET_CREATED',
                    'user_related_entities': "You've",
                    'other_user_related_entities': f'{current_user.firstname} {current_user.lastname}',
                    'hyperlink': f'{partnership.partner_url}/settings'
                }
                es_response = send_notifications(**es_data)
            else:
                message = f"Widget creation failed. Error: {error}"
                status_code = 500
                success = False

        else:
            message = "Partnership account id is empty."
            status_code = 401
            success = False

    return api_jsonify({'widget_id': widget_id}, status_code=status_code, message=message, success=success)


@cross_origin()
def get_widget(guid=None):
    widget = Widget.query.filter(Widget.guid == str(guid)).first()
    return api_jsonify(WidgetOutSelectedResponseSchema().dump(widget), 200, 'Widget fetch successfully', True)


@api_role_required('admin')
def get_all_widgets():
    unallocated_type = request.args.get('unallocated_type', None)

    if unallocated_type:
        widgets = Widget.get_unallocated_type(unallocated_type)
    else:
        partnership_account_id = current_user.partnership_account_id or 1
        widgets = Widget.query.filter(Widget.is_options_v3 == True,
                                      Widget.partnership_account_id == partnership_account_id).all()
    response_list = WidgetOutSelectedResponseSchema(many=True).dump(widgets)
    return api_jsonify(response_list, 200, 'Widgets fetched successfully', True)


def get_all_widget_v2():
    partnership_account_id = current_user.partnership_account_id or 1
    widgets = Widget.query.filter(Widget.is_options_v3 == False,
                                  Widget.partnership_account_id == partnership_account_id).all()
    response_list = WidgetOutSelectedResponseSchema(many=True).dump(widgets)
    return api_jsonify(response_list, 200, 'Widget fetch successfully', True)


@cross_origin()
@api_role_required('admin')
def update_widget(guid):
    """Update widget"""
    widget = Widget.query.filter(Widget.guid == str(guid)).first()
    received = request.get_json()
    if received and widget:
        result_name = received.get('name', widget.name)
        result_type = received.get('type', None)
        description = received.get('description', None)
        result_enabled = received.get('enabled', None)
        result_phonenumber = received.get('phonenumber_id', None)
        result_options = received.get('options', {})
        is_active = received.get('isActive', True)
        channel_sids = received.get('channels', [])
        channel_sids = [csid['value'] for csid in channel_sids]

        phonenumber = Phone.get_id_from_sid(result_phonenumber)
        widget.name = result_name
        widget.enabled = is_active
        widget.updated_by = current_user.id
        widget.type = result_type if result_type else widget.type
        widget.description = description
        widget.enabled = result_enabled if result_enabled else widget.enabled
        widget.inbound_id = phonenumber if phonenumber else widget.inbound_id
        existing_options = widget.options
        new_options = dict(existing_options, **result_options)
        widget.options = new_options

        if channel_sids:
            widget.channels = [Channel.get_by_sid(sid) for sid in channel_sids]
        else:
            widget.channels = []
        widget.save()

        # Send notification
        partnership = current_user.get_partnership()
        es_data = {
            'user_id': current_user.sid,
            'notify_message_type': 'WIDGET_EDITED',
            'user_related_entities': ["You've", str(guid)],
            'other_user_related_entities': [f'{current_user.firstname} {current_user.lastname}', str(guid)],
            'hyperlink': f'{partnership.partner_url}/settings'
        }
        es_response = send_notifications(**es_data)

        return api_jsonify({'widget_guid': widget.guid}, 200, "Success", True)

    return api_jsonify({'widget_guid': None}, 200, "Success", True)


@cross_origin()
def send_lead_mail(widget_sid=None):
    """ Send email using the data getting from the widget """
    if widget_sid:
        widget = Widget.query.filter(Widget.guid == str(widget_sid)).first()
        received = request.get_json()

        if not widget or 'firstName' in received and received['firstName'] in ['', ' '] or \
                'lastName' in received and received['lastName'] in ['', ' '] or \
                'phoneNumber' in received and received['phoneNumber'] in ['', ' '] or \
                'emailAddress' in received and received['emailAddress'] in ['', ' ']:
            return api_jsonify({}, 422, "Missing parameters for widget", False)
        # Add contact if not exists
        contact = Contact.find(received.get('emailAddress'), received.get('phoneNumber'))
        if contact:
            is_existing_contact = True
            if contact.phonenumber_1 != received.get('phoneNumber', None):
                contact.phonenumber_2 = received.get('phoneNumber', None)
            if not contact.lastname:
                contact.lastname = received.get('lastName', '')
            contact.status = 'new'
            contact.save()
        else:
            is_existing_contact = False
            params = {
                "firstname": received['firstName'],
                "lastname": received['lastName'],
                "phonenumber_1": received['phoneNumber'],
                "email": received['emailAddress'],
                "partnership_account_id": widget.partnership_account_id,
                "status": "new",
                "bdc_status": "lead"
            }
            contact = Contact.create(**params)

        from buyercall.blueprints.widgets.utils.interaction_manager import InteractionManager
        channel = widget.get_channel_by_type('email')
        email_params = {
            "first_name": received.get('firstName', ""),
            "last_name": received.get('lastName', ""),
            "email": received.get('emailAddress'),
            "source": channel.source,
            "channel": channel.id,
            "is_inbound": True,
            "is_forward": False,
            "contact_id": contact.id,
            "partnership_account_id": widget.partnership_account_id,
            "meta_data": received
        }
        email_object = Email.create(**email_params)
        # Create ContactChannelTie
        cc_params = {
            "contact": contact.id,
            "channel_id": channel.id,
            "email": email_object.id
        }
        contact_channel = ContactChannelTie.create(**cc_params)
        InteractionManager().run(
            'email', contact, channel, is_existing_contact, received, widget.partnership_account_id)

        # task_payload = {
        #     "type": "EMAIL_MESSAGE_FOLLOW_UP",
        #     "users": all_agents_sids,
        #     "partnershipId": str(partnership.sid),
        #     "partnershipAccountId": str(partnership_account.sid)
        # }
        # task_resp = create_task(task_payload)

        # from buyercall.blueprints.widgets.tasks import send_email
        # subject = f'New Lead from {received["firstName"]}'
        # email_data = render_template('widgets/mail/captured_lead.html', context=received)

        # send mail
        # mail_status = send_email(emails, subject, email_data)
        # if mail_status:
        return api_jsonify({}, 200, "Mail sent successfully!", True)

    return api_jsonify([], 422, "Missing parameters for widget", False)


@cross_origin()
def start_chat(widget_sid):
    message = "Chat started successfully!"
    status_code = 200
    success = True

    received = request.get_json()
    widget = Widget.query.filter(Widget.guid == str(widget_sid)).first()
    if widget:
        log.info("The after hours call widget json request is: {}".format(received))

        if widget and 'firstName' in received and received['firstName'] in ['', ' '] or \
                'lastName' in received and received['lastName'] in ['', ' '] or \
                'phoneNumber' in received and received['phoneNumber'] in ['', ' '] or \
                'emailAddress' in received and received['emailAddress'] in ['', ' ']:
            log.error('No lead fields provided for widget ' + str(widget.name) + ' - ' + str(widget.guid) + '.')
            return api_jsonify([], 422, "Missing parameters for widget", False)

        # Add contact if not exists
        contact = Contact.find(received.get('emailAddress'), received.get('phoneNumber'))
        if contact:
            is_existing_contact = True
            if contact.phonenumber_1 != received.get('phoneNumber', None):
                contact.phonenumber_2 = received.get('phoneNumber', None)
            if not contact.lastname:
                contact.lastname = received.get('lastName', '')
            contact.status = 'new'
            contact.save()

        else:
            is_existing_contact = False
            params = {
                "firstname": received['firstName'],
                "lastname": received['lastName'],
                "phonenumber_1": received['phoneNumber'],
                "email": received['emailAddress'],
                "partnership_account_id": widget.partnership_account_id,
                "status": "new",
                "bdc_status": "lead"
            }
            contact = Contact.create(**params)
        print('is_existing_contact : ', is_existing_contact)

        from buyercall.blueprints.widgets.utils.interaction_manager import InteractionManager
        channel = widget.get_channel_by_type('chat')
        if not is_existing_contact:
            chat_params = {
                "first_name": received.get('firstName', ""),
                "last_name": received.get('lastName', ""),
                "email": received['emailAddress'],
                "phone_number": received['phoneNumber'],
                "source": channel.source,
                "channel": channel.id,
                "widget_id": widget.id,
                "contact_id": contact.id,
                "partnership_account_id": widget.partnership_account_id,
            }
            chat_object = Chat.create(**chat_params)

            # Create ContactChannel
            cc_params = {
                "contact": contact.id,
                "channel_id": channel.id,
                "chat": chat_object.id
            }

            contact_channel = ContactChannelTie.create(**cc_params)

        assigned_agents_ids = InteractionManager().run(
            'chat', contact, channel, is_existing_contact, received, widget.partnership_account_id)
        data = {
            "leadId": contact.sid,
            "name": contact.name,
            "email": contact.email,
            "phone_number": contact.phonenumber_1 or contact.phonenumber_2,
            "assigned_agent_id": assigned_agents_ids
        }
        return api_jsonify(data=data, message=message)
    return api_jsonify([], 422, "Missing parameters for widget", False)