From 4af919db11c5cca886c5ef5bfe16e8ea3ec00759 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 20 Sep 2023 16:43:12 +0530 Subject: [PATCH] fix: generate api_key, api_secret on update of twilio settings --- .../twilio_settings/twilio_settings.json | 11 ++- .../twilio_settings/twilio_settings.py | 83 ++++++++++++++++++- crm/twilio/api.py | 2 - frontend/src/components/CallUI.vue | 1 - 4 files changed, 88 insertions(+), 9 deletions(-) diff --git a/crm/fcrm/doctype/twilio_settings/twilio_settings.json b/crm/fcrm/doctype/twilio_settings/twilio_settings.json index 7bcb2a85..ed3aa43f 100644 --- a/crm/fcrm/doctype/twilio_settings/twilio_settings.json +++ b/crm/fcrm/doctype/twilio_settings/twilio_settings.json @@ -28,12 +28,14 @@ { "fieldname": "api_key", "fieldtype": "Data", - "label": "API Key" + "label": "API Key", + "permlevel": 1 }, { "fieldname": "api_secret", "fieldtype": "Password", - "label": "API Secret" + "label": "API Secret", + "permlevel": 1 }, { "fieldname": "column_break_idds", @@ -49,7 +51,8 @@ { "fieldname": "twiml_sid", "fieldtype": "Data", - "label": "TwiML SID" + "label": "TwiML SID", + "permlevel": 1 }, { "fieldname": "section_break_ssqj", @@ -69,7 +72,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-08-28 00:44:24.942914", + "modified": "2023-09-20 16:42:17.025651", "modified_by": "Administrator", "module": "FCRM", "name": "Twilio Settings", diff --git a/crm/fcrm/doctype/twilio_settings/twilio_settings.py b/crm/fcrm/doctype/twilio_settings/twilio_settings.py index a2b3b497..06038f1a 100644 --- a/crm/fcrm/doctype/twilio_settings/twilio_settings.py +++ b/crm/fcrm/doctype/twilio_settings/twilio_settings.py @@ -1,9 +1,88 @@ # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -# import frappe +import frappe from frappe.model.document import Document +from frappe import _ +from twilio.rest import Client class TwilioSettings(Document): - pass + friendly_resource_name = "Frappe CRM" # System creates TwiML app & API keys with this name. + + def validate(self): + self.validate_twilio_account() + + def on_update(self): + # Single doctype records are created in DB at time of installation and those field values are set as null. + # This condition make sure that we handle null. + if not self.account_sid: + return + + twilio = Client(self.account_sid, self.get_password("auth_token")) + self.set_api_credentials(twilio) + self.set_application_credentials(twilio) + self.reload() + + def validate_twilio_account(self): + try: + twilio = Client(self.account_sid, self.get_password("auth_token")) + twilio.api.accounts(self.account_sid).fetch() + return twilio + except Exception: + frappe.throw(_("Invalid Account SID or Auth Token.")) + + def set_api_credentials(self, twilio): + """Generate Twilio API credentials if not exist and update them. + """ + if self.api_key and self.api_secret: + return + new_key = self.create_api_key(twilio) + self.api_key = new_key.sid + self.api_secret = new_key.secret + frappe.db.set_value('Twilio Settings', 'Twilio Settings', { + 'api_key': self.api_key, + 'api_secret': self.api_secret + }) + + def set_application_credentials(self, twilio): + """Generate TwiML app credentials if not exist and update them. + """ + credentials = self.get_application(twilio) or self.create_application(twilio) + self.twiml_sid = credentials.sid + frappe.db.set_value('Twilio Settings', 'Twilio Settings', 'twiml_sid', self.twiml_sid) + + def create_api_key(self, twilio): + """Create API keys in twilio account. + """ + try: + return twilio.new_keys.create(friendly_name=self.friendly_resource_name) + except Exception: + frappe.log_error(title=_("Twilio API credential creation error.")) + frappe.throw(_("Twilio API credential creation error.")) + + def get_twilio_voice_url(self): + url_path = "/api/method/crm.twilio.api.voice" + return get_public_url(url_path) + + def get_application(self, twilio, friendly_name=None): + """Get TwiML App from twilio account if exists. + """ + friendly_name = friendly_name or self.friendly_resource_name + applications = twilio.applications.list(friendly_name) + return applications and applications[0] + + def create_application(self, twilio, friendly_name=None): + """Create TwilML App in twilio account. + """ + friendly_name = friendly_name or self.friendly_resource_name + application = twilio.applications.create( + voice_method='POST', + voice_url=self.get_twilio_voice_url(), + friendly_name=friendly_name + ) + return application + +def get_public_url(path: str=None): + from frappe.utils import get_url + return get_url().split(":8", 1)[0] + path \ No newline at end of file diff --git a/crm/twilio/api.py b/crm/twilio/api.py index d44db9b7..536b62ae 100644 --- a/crm/twilio/api.py +++ b/crm/twilio/api.py @@ -60,7 +60,6 @@ def twilio_incoming_call_handler(**kwargs): resp = IncomingCall(args.From, args.To).process() return Response(resp.to_xml(), mimetype='text/xml') -@frappe.whitelist() def create_call_log(call_details: TwilioCallDetails): call_log = frappe.get_doc({**call_details.to_dict(), 'doctype': 'CRM Call Log', @@ -71,7 +70,6 @@ def create_call_log(call_details: TwilioCallDetails): call_log.save() frappe.db.commit() -@frappe.whitelist() def update_call_log(call_sid, status=None): """Update call log status. """ diff --git a/frontend/src/components/CallUI.vue b/frontend/src/components/CallUI.vue index 7d69c0cf..777eb890 100644 --- a/frontend/src/components/CallUI.vue +++ b/frontend/src/components/CallUI.vue @@ -418,7 +418,6 @@ async function makeOutgoingCall(number) { title: '', content: '', } - // update_call_log(conn) }) _call.value.on('cancel', () => { log.value = `Call ended from makeOutgoing call cancel.`